45 Commits

Author SHA1 Message Date
Julien Calixte
4b03f0e885 chore(docker): remove container healthcheck
Coolify performs its own liveness probe, so the in-image HEALTHCHECK is
redundant.
2026-05-28 17:26:50 +02:00
Julien Calixte
9aecbfbbf9 docs(tasks): mark T11 done 2026-05-28 01:51:46 +02:00
Julien Calixte
7c755740b9 feat(push): notify on defect filed without delaying the response
Send fire-and-forget via event.waitUntil after the row is created, so
filing latency is unaffected. (T11/F7)
2026-05-28 01:51:41 +02:00
Julien Calixte
b56577ddc2 feat(push): fan out web push to subscribed devices on defect filed
Prune endpoints the push service rejects with 404/410; log and skip
transient failures so one bad device never blocks the rest. (T11/F7)
2026-05-28 01:51:36 +02:00
Julien Calixte
53349ca530 docs(deploy): add Coolify secret-handling and stale-deploy troubleshooting 2026-05-28 01:44:04 +02:00
Julien Calixte
d32c108f23 docs(tasks): mark T10 done; record owner-only push decision 2026-05-28 01:23:22 +02:00
Julien Calixte
0745ad14f0 feat(push): owner enrolment UI, service worker and VAPID keygen (T10)
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.
2026-05-28 01:23:10 +02:00
Julien Calixte
ea7384591c feat(push): owner-only subscribe/unsubscribe endpoints + VAPID config (T10)
POST/DELETE /api/subscriptions gated to the configured owner; runtime
config carries the public VAPID key, owner email and the signing secret.
2026-05-28 01:20:20 +02:00
Julien Calixte
5016c5cd47 feat(push): owner-only gate and subscription body validation (T10)
isOwner compares the session email to the configured owner; the body
matches the browser PushSubscription.toJSON() shape, reporter resolved
server-side.
2026-05-28 01:19:19 +02:00
Julien Calixte
9caebca20d feat(push): subscription store keyed by endpoint (T10)
Idempotent upsert per device endpoint, list for fan-out, and
delete-by-endpoint for the 410 prune path.
2026-05-28 01:17:58 +02:00
Julien Calixte
82ac540909 feat(auth): Google OAuth, sealed session and default-deny gating (T9)
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.
2026-05-28 01:09:48 +02:00
Julien Calixte
73e4c56a9e feat(dashboard): verbatim modal + defect feed on /defects (T8) 2026-05-28 00:54:51 +02:00
Julien Calixte
406a4d5ec5 style(dashboard): use fixed units for dot scatter spacing 2026-05-28 00:50:14 +02:00
Julien Calixte
421903aa89 style(dashboard): add vertical breathing room between dot rows
Widen the vertical step between the two dot rows so they're less cramped.
2026-05-28 00:48:12 +02:00
Julien Calixte
268769c26e style(dashboard): tighten dot spacing and align date labels
Cluster each section's dots closer (narrower horizontal step) and shift
the date label right so it starts under the dot rather than off to its
left.
2026-05-28 00:45:04 +02:00
Julien Calixte
743ddfa899 style(dashboard): reposition dot map labels
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.
2026-05-28 00:43:05 +02:00
Julien Calixte
bfb0485598 feat(ui): redesign header with a DaisyUI navbar
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".
2026-05-28 00:43:05 +02:00
Julien Calixte
3e5f7d3643 feat(dashboard): red-dot weak-point map on /defects (T7)
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.
2026-05-28 00:31:17 +02:00
Julien Calixte
c6b991646a docs: tighten F6 dashboard render budget to 350ms
1s was too soft; reads already return in ~20ms at 2k rows. Set the target to
350ms across DESIGN and the checkpoints.
2026-05-28 00:24:02 +02:00
Julien Calixte
e5c002547f refactor(app): serve one domain — /defects route and header nav
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.
2026-05-28 00:24:02 +02:00
Julien Calixte
999593b426 feat(defects): read API — feed, per-section counts and history (T6)
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.
2026-05-28 00:18:18 +02:00
Julien Calixte
fc958ba59f test: give each vitest worker its own Postgres database
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.
2026-05-28 00:18:18 +02:00
Julien Calixte
7f655009ac fix(projects): open autocomplete on click/type, not focus
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.
2026-05-28 00:07:34 +02:00
Julien Calixte
a894e3715d refactor(defects): use the native <dialog> for the defect modal
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.
2026-05-28 00:00:42 +02:00
Julien Calixte
cb19b2df55 feat(defects): file a defect via a board-section modal (T5)
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.
2026-05-27 23:53:49 +02:00
Julien Calixte
bd427ab171 style(board): reformat and center block labels 2026-05-27 23:46:54 +02:00
Julien Calixte
704b521b7d feat(defects): add shared arktype defect input schema
Shared validation source for the DefectForm and /api/defects route
(Task 5): trimmed non-empty sectionId/verbatim and a uuid projectId.
2026-05-27 23:46:18 +02:00
Julien Calixte
762d912243 feat(ui): wire tailwind v4 and daisyui theme into nuxt
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.
2026-05-27 23:46:12 +02:00
Julien Calixte
e2e6b95e7c chore(deps): add tailwind v4, daisyui, and arktype 2026-05-27 23:45:44 +02:00
Julien Calixte
230d523891 chore(docker): cache pnpm store to avoid re-downloading deps
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.
2026-05-27 23:31:32 +02:00
Julien Calixte
1194edcf75 feat(projects): list + create API and creatable ProjectAutocomplete (T4)
- 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.
2026-05-27 23:28:03 +02:00
Julien Calixte
63f83d316a test: split vitest into node + nuxt projects and add a Postgres harness
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.
2026-05-27 23:27:55 +02:00
Julien Calixte
405b929f66 ci: Coolify autodeploy from main; migrate-on-boot and split dev/prod compose
- 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.
2026-05-27 23:13:06 +02:00
Julien Calixte
c71718ccbc chore: clean install 2026-05-27 22:46:55 +02:00
Julien Calixte
02b89410f0 style(board): center board and size columns to content 2026-05-27 22:42:48 +02:00
Julien Calixte
60ee7b6277 feat(db): Postgres schema, migrations and defects repository (T3)
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.
2026-05-27 22:39:32 +02:00
Julien Calixte
7a4860e111 style(board): landscape sheets, larger sizing, and web fonts
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.
2026-05-27 22:29:04 +02:00
Julien Calixte
ae85d212db feat(board): canonical board definition and BoardView (T2)
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.
2026-05-27 22:23:54 +02:00
Julien Calixte
d8ff2c75c9 chore: add typescript dev dependency for eslint TS parsing 2026-05-27 22:23:44 +02:00
Julien Calixte
c13f3deeff chore: scaffold Nuxt 4 app with Postgres, tooling and health route
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.
2026-05-27 22:16:46 +02:00
Julien Calixte
e81ee53f7b docs: add phased implementation plan and task checklist 2026-05-27 22:16:39 +02:00
Julien Calixte
200103ab4d docs: adopt Coolify-managed Postgres over SQLite 2026-05-27 22:16:39 +02:00
Julien Calixte
2892d12775 docs: add README with documentation index 2026-05-27 21:59:00 +02:00
Julien Calixte
6606524fc7 docs: add House of Quality diagram
TikZ rendering of the DESIGN.md QFD matrices (goals, functions,
relations, roof, basement weights) for review and slides.
2026-05-27 21:58:55 +02:00
Julien Calixte
a304973af6 init 2026-05-27 21:54:20 +02:00