EnableNotifications subscribes the owner's device with the public VAPID
key and stores it; sw.js receives pushes and opens /defects on click;
`pnpm vapid:keys` mints the key pair.
isOwner compares the session email to the configured owner; the body
matches the browser PushSubscription.toJSON() shape, reporter resolved
server-side.
Gate the app behind Google sign-in restricted to verified @theodo.com
identities, and record the real reporter on filed defects (F9).
- nuxt-auth-utils for sealed cookie sessions + the Google OAuth handler,
mounted at /auth/google/callback to match the registered redirect URI.
- isAllowedGoogleUser re-derives the domain from Google's verified email;
the spoofable `hd` claim is deliberately ignored (DESIGN T9).
- Default-deny: server middleware 401s unauthenticated /api calls (health
and the session endpoint excepted); a global route middleware redirects
unauthenticated page navigations to /login.
- getReporter() now reads the session email instead of the dev stub.
- Env contract moves to nuxt-auth-utils names (NUXT_SESSION_PASSWORD,
NUXT_OAUTH_GOOGLE_*); .env.example, compose and README updated.
Unit-tested: domain check (incl. hd-spoof + look-alike) and public-path
matching. Live OAuth round-trip pending manual verification.
Anchor each defect's date label at the dot's bottom-right (splaying
diagonally down-right) and move the "+N more" marker to the section's
bottom-left so it no longer collides with the A2/A3/A4 size label.
Use the proper navbar-start/navbar-end structure with a horizontal menu
for the Report / View defects links, a red-dot brand mark echoing the
weak-point map, and a sticky shadowed bar. Rename the brand to the less
terse "Project Board Andon".
Overlay each filed defect as a red dot inside its section box with a
diagonal date label; cap visible dots and collapse the remainder to a
"+N more" using the authoritative per-section counts (T6).
Add an `overlay` scoped slot to BoardView (C2) so the dashboard can layer
dots without forking the board definition (F1); the reporting view leaves
it empty. DotMap groups the bounded feed by section and the /defects page
wires both endpoints.
Drop the two-subdomain split (ADR 0004): report at /, view defects at
/defects, with a shared layout + header nav. Renames the dashboard page to
defects.vue and updates plan/README. Transparency over separation now that
auth gates everyone, and it removes the Coolify two-domain routing question.
GET /api/defects returns a newest-first feed joined to the project name,
bounded by limit; ?section=ID narrows to one section's history. GET
/api/defects/counts returns { sectionId: n } for the weak-point map. Adds a
(section_id, created_at) index and a db:seed script; ~2k rows query in ~20ms.
Specs truncate the test DB between tests; with multiple DB-backed spec files
running in parallel workers they clobbered each other. Suffix the database
name with VITEST_POOL_ID so each worker is isolated.
The dialog autofocuses the project input on open; opening the dropdown on
focus made it spring open with no user interaction. Open on click or typing
instead, so the field is type-ready on open without showing the list.
DaisyUI's modal drives the native <dialog> via showModal/close, so Escape,
focus-trapping and the backdrop work without custom handling. The parent's
sectionId controls open/close and the dialog's native close event maps to the
component's close emit.
POST /api/defects validates the body with arktype (known section, uuid
project, non-empty trimmed verbatim) and resolves the reporter through a dev
seam (getReporter, ADR 0002) until OAuth lands. DefectForm is a DaisyUI modal
opened by a section click — pick a project, describe the problem, submit.
Tailwind v4 integrates as a Vite plugin and configures daisyui via the
CSS @plugin directive; the light theme is selected with data-theme on
<html>. Heading font sizes are re-asserted because Tailwind's preflight
resets them, which would otherwise shrink the hand-crafted headings.
Source is copied before install because the root postinstall (nuxt prepare)
needs it, so the dependency layer can't be cached by ordering alone. A
BuildKit cache mount on pnpm's store keeps packages across builds instead.
- GET /api/projects returns the shared global list; POST /api/projects
creates a project, deduped case-insensitively (F3).
- Repository find-or-create trims and matches on lower(name); a unique index
on lower(name) enforces the same rule at the DB (replacing the case-
sensitive column unique constraint) — migration 0001.
- ProjectAutocomplete: creatable autocomplete that lists projects, offers to
create an unknown name inline, and selects it without reload.
- Integration test (dedupe/list against Postgres) + component test.
Component/runtime specs (*.nuxt.spec.ts) run in the Nuxt environment; all
other specs run in a plain node environment where node_modules stay external,
so native CJS deps like `pg` load correctly (the Nuxt runtime inlines them and
breaks pg's internal `class … extends Pool`). Adds tests/helpers/db.ts, which
spins up and migrates a dedicated test database for repository integration
tests, and renames the board spec to the .nuxt suffix.
- Add Nitro plugin to apply Drizzle migrations on server boot (skips when
DATABASE_URL is unset, e.g. during build).
- Split compose: docker-compose.dev.yml (hot-reload + Postgres) vs
docker-compose.yml (production/self-host, env-driven, Coolify-deployable).
- Add .dockerignore; parameterise compose env; document the autodeploy
decision (Coolify watches main, no CI workflow needed) in ADR 0003 and
plan.md T12.
Drizzle schema for projects, defects, push_subscriptions (C8); generated
migration; a defects repository (createDefect / listRecentDefects); a
programmatic migrator; and a db:verify script. section_id is a plain string
referencing in-code board ids, no FK (ADR 0001). Verified against real
Postgres: migration applies, a defect round-trips, /api/health reports db up.
Render A-series sheets in landscape (√2:1), size them off a single
--sheet-base variable so A2/A3/A4 keep exact ISO ratios while leaving room
for labels (which now wrap). Load Playfair Display + Cutive Mono via the
coollabs fonts API: Playfair Display for headings, Cutive Mono for the board.
Encode the board as blocks of stacked-section columns (C1) and render it as
the ASCII-styled DOM grid (C2) with hover highlight and a select emit. Layout
mirrors the physical board (Client Satisfaction above Product Architecture;
Macroplan above Defect Visualisation, both left of Feature Kanban) and boxes
use true ISO 216 proportions (1:√2, each step up scaled by √2). Both views
render from the one definition; tests pin section presence, click, hover,
layout order, and a snapshot.
Nuxt 4 (app/ structure) + TypeScript, ESLint, Vitest + @nuxt/test-utils,
Drizzle + pg, a /api/health route with a DB ping, and a dev docker-compose
(app + Postgres). Verified: pnpm test, lint, and build pass; the built
server answers /api/health.