Files
familysync/.planning/STATE.md
T

39 KiB
Raw Blame History

gsd_state_version, milestone, milestone_name, status, stopped_at, last_updated, last_activity, progress
gsd_state_version milestone milestone_name status stopped_at last_updated last_activity progress
1.0 v1.1 Operability & Polish verifying Completed Phase 10 Plan 02 (admin role primitives: requireAdmin, upsertUser is_admin, /api/me isAdmin+needsProviderSetup) 2026-06-13T19:45:55.920Z 2026-06-13
total_phases completed_phases total_plans completed_plans percent
20 8 27 27 40

Project State

Project Reference

See: .planning/PROJECT.md (updated 2026-06-10)

Core value: One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store Current focus: Phase 10 — admin-role-settings

Current Position

Phase: 13 Plan: Not started Status: Phase complete — ready for verification Last activity: 2026-06-13

Resolved Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action)

Done 2026-06-12. Gitea branch protection on main now requires EXACTLY CI / fast-checks (pull_request) + CI / gate (pull_request); CI / api + CI / harness removed from required checks (verified via tea api repos/luckberg/familysync/branch_protections).

Performance Metrics

Velocity:

  • Total plans completed: 43
  • Average duration: -
  • Total execution time: 0 hours

By Phase:

Phase Plans Total Avg/Plan
02 5 - -
03 12 - -
07 4 - -
13 3 - -
14 1 - -
15 3 - -
09 2 - -
16 6 - -
10 4 - -

Recent Trend:

  • Last 5 plans: -
  • Trend: -

Updated after each plan completion | Phase 03 P01 | 45 | 5 tasks | 8 files | | Phase 03 P03-02 | 4 | 2 tasks | 2 files | | Phase 03 P03-03 | 7 | 3 tasks | 2 files | | Phase 03 P03-07 | 5 | 2 tasks | 7 files | | Phase 03 P03-04 | 15 | 2 tasks | 3 files | | Phase 03 P03-05 | 6 | 3 tasks | 6 files | | Phase 04 P01 | 65 | 4 tasks | 17 files | | Phase 04 P03 | 12 | 2 tasks | 9 files | | Phase 04 P04 | 11 | 2 tasks | 10 files | | Phase 04 P05 | 10 | 2 tasks | 4 files | | Phase 04 P06 | 11 | 2 tasks | 7 files | | Phase 04 P07 | 6 | 2 tasks | 4 files | | Phase 05 P01 | 20 | 4 tasks | 15 files | | Phase 05 P02 | 5 | 1 tasks | 1 files | | Phase 05 P03 | 5 | - tasks | - files | | Phase 05 P04 | 11 | 3 tasks | 9 files | | Phase 05 P05 | 8 | 2 tasks | 4 files | | Phase 05 P06 | 6 | 1 tasks | 2 files | | Phase 05 P08 | 9 | 3 tasks | 7 files | | Phase 05 P07 | 8 | 1 tasks | 4 files | | Phase 06-ux-polish P01 | 2 | 2 tasks | 2 files | | Phase 06-ux-polish P02 | 8 | 2 tasks | 4 files | | Phase 06-ux-polish P03 | 11 | 2 tasks | 3 files | | Phase 06-ux-polish P04 | 5 | 2 tasks | 2 files | | Phase 06-ux-polish P05 | 35 | 4 tasks | 6 files | | Phase 06-ux-polish P06 | 45 | 4 tasks | 5 files | | Phase 07 P01 | 310 | 3 tasks | 7 files | | Phase 07 P02 | 196 | 2 tasks | 4 files | | Phase 07-mobile-test-harness P03 | 480 | 2 tasks | 2 files | | Phase 07-mobile-test-harness P04 | 22 | 2 tasks | 2 files | | Phase 13 P01 | 8 | 2 tasks | 7 files | | Phase 13-real-lint-gate-eslint P02 | 90 | 2 tasks | 31 files | | Phase 13-real-lint-gate-eslint P03 | 10 | 3 tasks | 399 files | | Phase 09-faster-write-back P01 | 341 | 3 tasks | 3 files | | Phase 16 P01 | 188 | 3 tasks | 4 files | | Phase 16-ci-dependency-audit-and-security-checks P02 | 25 | 3 tasks | 5 files | | Phase 16-ci-dependency-audit-and-security-checks P03 | 2 | 2 tasks | 5 files | | Phase 16 P04 | 45 | 4 tasks | 3 files | | Phase 16 P05 | 7 | 2 tasks | 1 files | | Phase 10-admin-role-settings P01 | 265 | - tasks | - files | | Phase 10-admin-role-settings P02 | 700 | 3 tasks | 6 files | | Phase 10-admin-role-settings P03 | 720 | 3 tasks | 6 files | | Phase 10-admin-role-settings P04 | 1315 | 3 tasks | 8 files |

Accumulated Context

Decisions

Decisions are logged in PROJECT.md Key Decisions table. Recent decisions affecting current work:

  • D-13-ESLint-PIN (2026-06-11, 13-01): eslint pinned to 9.39.4 — ESLint 10 breaks eslint-plugin-react@7.37.5 at runtime ("getFilename is not a function", jsx-eslint#3977). Unpin when plugin releases ESLint 10 support.
  • D-13-JSX-SCOPE (2026-06-11, 13-01): react/react-in-jsx-scope disabled explicitly — flat.recommended enables it at error; PWA uses jsx:react-jsx (React 19 automatic transform), React import not required in JSX files.
  • D-PROBE-01 (2026-06-11, 08-01): runs-on must be ubuntu-latest — runner has no self-hosted label; all downstream ci.yml workflows use ubuntu-latest.
  • D-PROBE-02 (2026-06-11, 08-01): Docker-executor confirmed (/.dockerenv present); services: works; DB_HOST=mariadb in all downstream jobs.
  • D-PROBE-03 (2026-06-11, 08-01): No mysql CLI in runner image — DB readiness uses healthcheck.sh --connect --innodb_initialized or Node mysql2 wait; no mysql shell-out.
  • D-PROBE-04 (2026-06-11, 08-01): actions/cache@v4 timed out — skip cache in Plans 02/03 critical path; best-effort with continue-on-error if used.
  • D-PROBE-05 (2026-06-11, 08-01): Playwright WebKit + Chromium deps install cleanly (exit 0); Phase-7 harness CI-feasible.
  • D-PROBE-06 (2026-06-11, 08-01): ChristopherHX/gitea-upload-artifact@v4 works — MUST use this fork; actions/upload-artifact@v4 broken on Gitea.
  • D-PROBE-07 (2026-06-11, 08-01): ${GITHUB_SHA:0:7} produces 7 chars — D-04 publish tag expression valid.
  • D-PROBE-08 (2026-06-11, 08-01): GITEA_REGISTRY_PAT deferred to Plan 04; PAT not exercised in probe.
  • D-PAT-NAMING (2026-06-11, 08-04): Gitea runner silently drops secrets with the GITEA_ prefix (reserved namespace). Secret renamed from GITEA_REGISTRY_PAT → REGISTRY_PAT in both repo secret and ci.yml (commit 73eecf7). Use REGISTRY_PAT in any future registry operations.
  • CAL-08 RESOLVED → GO (Phase 1): per-member Fastmail app password reaches all of that account's calendars; no cross-account ACL needed. Unified view stands; no shared-only fallback. See CAL-08-DECISION.md.
  • D-14 (2026-06-04): Phase 1 Gate 2 (live Authelia/Pangolin) deferred. SSE-over-Pangolin smoke = hard gate before Phase 4; live AUTH smoke incl. iOS standalone-PWA folded into Phase 3. Phases 23 build behind a dev-auth bypass. Tracked in 01-HUMAN-UAT.md + docs/deployment.md.
  • D-15 (2026-06-04): Validate real topology via local Newt connector + test subdomain through Pangolin (Mode A), not an Unraid deploy; Unraid reserved for go-live.
  • CalDAV locked: Fastmail does not expose calendars over JMAP. CalDAV via tsdav is the only protocol. No reconsideration.
  • Identity: Use oidc_iss + oidc_sub as stable composite key. Never email.
  • Real-time transport: Prefer SSE over WebSocket (proxy-resilient through Pangolin). SSE pass-through STILL UNVERIFIED — gate before Phase 4 (D-14).
  • Recurring events: Create + display only in v1 (CALDAV:expand on server side). Single-occurrence edit deferred to v1.x.
  • [Phase ?]: D-Task5-DDL: drizzle-kit push unsafe on MariaDB 11 (mysql dialect misreads metadata, schedules truncate). Additive DDL hand-applied; adopt drizzle-kit generate+migrate before next schema change.
  • [Phase ?]: D-02-RRULE: use ICAL.Recur.fromString + ICAL.Property for RRULE serialization — addPropertyWithValue produces char-split output for RECUR types
  • [Phase ?]: resolveUserId helper uses any type to avoid Hono context generic complexity — internal helper only
  • [Phase ?]: Write endpoints enqueue-only (calendarOutbox) with 202 optimistic-accept; no Fastmail call in routes (D-12/D-05)
  • [Phase ?]: GET /writable-calendars is authoritative D-03 writable-set endpoint: WHERE userId=currentUser.id OR isShared=1
  • [Phase ?]: D-03-07-icons: PWA icons generated via pure Node.js (zlib/Buffer) when ImageMagick/sharp unavailable; placeholder art; operator can replace in public/ without code changes
  • [Phase ?]: D-03-07-layout: InstallPrompt desktop layout wraps CalendarContent in flex-column div so banner appears above calendar grid
  • [Phase ?]: D-03-04-hoisting: vi.hoisted() required for vi.mock() factory variables when test file has static import of module under test (TDZ issue)
  • [Phase ?]: D-03-04-where: Drizzle types disallow chained .where().where(); use and() in single .where() call
  • [Phase ?]: D-01 calendar default: last-used URL from localStorage (eventForm.lastCalendarUrl), first writable calendar as fallback
  • [Phase ?]: D-02 calendar picker: hidden when writableCalendars.length === 1, shown when >1 — authoritative from GET /api/events/writable-calendars
  • [Phase ?]: T-03-15 XSS: EventForm renders all values as plain-text JSX children; no dangerouslySetInnerHTML in code
  • [Phase ?]: Phase 4 Plan 1
  • [Phase ?]: D-04 GET scoped: two-select + Set union (owner + list_shares); ListDeleteDialog props-driven to preserve calendarStore dialog; zValidator returns 400 not 422 per existing convention
  • [Phase 04-04]: listItemsRouter separate from listsRouter, mounted at /api/list-items for PATCH/DELETE item routes per RESEARCH architecture diagram
  • [Phase 04-04]: Uncheck rank recomputed to active-bottom (generateKeyBetween(lastActiveRank, null)) in same DB write (Open Question 2 resolved)
  • [Phase 04-04]: Delete-wins no-rollback: deleteMutation has no onError handler; item removal from cache is final (D-09)
  • [Phase ?]: LIST-04: SSE connection lives in ListDetail (not hoisted to Lists route); Phase 5 push will own session lifecycle
  • [Phase 04-07]: D-04-07-collation: Drizzle 0.45.x has no first-class collation option on varchar; used customType to emit varchar(255) COLLATE utf8mb4_bin for list_items.rank — keeps schema-as-code + generate+migrate workflow
  • [Phase 04-07]: D-04-07-guard: isShared owner-only guard placed after access check, before updateValues construction; mirrors DELETE handler idiom (if !access.isOwner → 403)
  • [Phase ?]: VAPID config is env-injected at runtime via docker-compose.yml environment block; no key baked into image (Phase 5 D-transposability)
  • [Phase ?]: dispatchPush uses sub.id (not a separate dbRowId argument) — 2-arg signature matches existing test
  • [Phase ?]: coalesceListPush dispatch signature is (listId, actorId, count) — test scaffold canonical; richer payload deferred to Plan 05-05 caller
  • [Phase ?]: notifyListChange fires for all list/item mutations except reorder (position) and list-create per D-01
  • [Phase ?]: D-05-06-crossjoin: Drizzle cross-join in reminderScheduler pairs shared events with all pushSubscriptions; grouping by uid post-join ensures full fan-out per deduped event (reminderScheduler.ts)
  • [Phase ?]: D-03 actor exclusion: ne() at DB level + filter() in application code (defence-in-depth for eventChangeDispatcher tests)
  • [Phase ?]: D-08: hasRrule derived from event.isRecurring() in expand.ts — no DB query change needed; captured once before branch
  • [Phase 06-04]: @keyframes pulse added globally to tokens.css; redundant local spin redefinition removed from PushPermissionPrompt.tsx — all sync-animation consumers now resolve from the global stylesheet (D-13)
  • [Phase 06-05]: TanStack Query v5 global error handler: QueryCache({onError})/MutationCache({onError}) constructor pattern; defaultOptions.onError removed in v5 (NOT used); confirmed via Context7 /tanstack/query
  • [Phase 06-05]: AuthSplash state machine: loading/redirecting/dead-end; CalendarContent renders only on meQuery.isSuccess (D-10); sessionExpired flag via Zustand + global QueryCache/MutationCache onError (D-11); one-shot redirect guard re-armed only on explicit user tap
  • [Phase 06-06]: Schedule-X all-day CSS: .sxall-day-event does not exist in v4.6.0; real selectors are .sxdate-grid-event (week/day) + .sxmonth-grid-event:not(:has(.sxmonth-grid-event-time)) (month); --sx-color-primary-container remapped as fallback
  • [Phase 06]: Phase-level UX fixes (surfaced during UAT, not in any single plan): AppNav made persistent across routes — nav no longer disappears on /lists (commits 6070437 RED + 051874b fix); BottomTabBar hidden on desktop — no longer overlaps sidebar Settings affordance (commits 740e342 RED + 089b53d fix)
  • [Phase ?]: D-04-SCHEDULE-X-LOCATOR: Used .sx-react-calendar-wrapper CSS class to assert Schedule-X grid — no semantic role on outer wrapper div
  • [Phase ?]: D-04-EMPTY-NETWORK-SIM: Lists empty state simulated via page.route to 200 empty array — preserves seeded DB for parallel workers (D-06 / T-07-11)
  • [Phase ?]: D-13-08: Prettier reformat committed as isolated mechanical diff; CI Format check step added to fast-checks job
  • [Phase ?]: D-07 (16-01): ENV NODE_ENV=production baked into production Dockerfile stage — engages devBypass.ts hard guard so DEV_AUTH_BYPASS never injects in production
  • [Phase ?]: D-08 (16-01): assertNotDevBypassInProduction() boot guard — first statement in isMainModule(), exits non-zero when NODE_ENV=production AND DEV_AUTH_BYPASS=true; unit-tested
  • [Phase ?]: D-03-SEC-VERSION: Pinned eslint-plugin-security@3.0.1 over 4.0.1 — stable, flat-config compatible with ESLint 9.39.4, more bake time
  • [Phase ?]: D-03-OBJ-INJECT: detect-object-injection disabled globally in eslint-plugin-security block — all hits were numeric loop indices / schema-derived keys; zod guards real API input; 14 of 15 rules remain at error
  • [Phase ?]: D-04-ALLOWLIST: crypto.test.ts TEST_KEY allowlisted by path — human-verified Vitest beforeAll synthetic AES-256-GCM fixture; 4th allowlists block in .gitleaks.toml
  • [Phase ?]: D-04-BASELINE: gitleaks full-history baseline is empty [] after allowlisting — 613 commits / 23 MB scanned clean; PR-diff scans in 16-05 start from provably clean state
  • [Phase ?]: D-12-security-job: gitleaks runs unconditionally, pnpm audit/outdated code-gated at step level
  • [Phase ?]: D-14-gate-security: security wired into gate with individual needs.security.result==success check (not success-or-skipped, Gitea #31007)
  • [Phase ?]: D-MIGRATION-10-01: v1.1 DB migration applied via direct mysql2 DDL (drizzle-kit migrate silently failed due to journal hash mismatch with legacy tracking; hash recorded in __drizzle_migrations for forward compatibility)
  • [Phase ?]: D-10-02-aggregate-limit1: Drizzle COUNT aggregate uses .limit(1) for mock-chain compatibility
  • [Phase ?]: isAdmin drives nav visibility; real boundary is server-side
  • [Phase ?]: Single bottom sheet component handles all credential entry flows
  • [Phase ?]: No X button on SetupBanner; cleared by needsProviderSetup=false from /api/me refetch

Roadmap Evolution

  • Phase 6 added (2026-06-07): UX Polish — all-day visual distinction, event-form date/recurrence behavior, recurring-series edit, auth-flow smoothing. Candidate scope pulls from backlog 999.2/999.3/999.6/999.7/999.8/999.9.
  • Phase 6 complete (2026-06-10): all 6 plans executed + 2 phase-level UX fixes (AppNav persistence + BottomTabBar desktop hide). Residual device-only checkpoints documented above.
  • Backlog reviewed (2026-06-10, /gsd-review-backlog): removed 6 stale duplicates (999.2/3/6/7/8/9 — already promoted into Phase 6) from the Backlog section + deleted the 999.2 dir; kept 999.1/4/5/10/11/12/13; added 999.14 (Gitea CI, promoted from STATE pending todo); archived stale kickoff-new-project todo.
  • v1.1 roadmap created (2026-06-10): 6 phases (712), continuing v1.0 numbering. 17/17 requirements mapped, no orphans. Promotions: 999.13→Faster Write-Back (CAL-15), 999.10→Admin Role & Settings (ADMIN-01/02/03), 999.11→Setup Wizard (SETUP-01/02/03/04), 999.4→Per-Event Reminders (CAL-13/14 + NOTIF-04/05/06), 999.14→Gitea CI (CI-01/02), 999.12→Mobile Test Harness (TEST-01/02). The v1.1 DB migration (users.is_admin, calendar_events.reminder_lead_minutes, app_config) is folded into the Admin phase (per ARCHITECTURE.md ordering), not a standalone migration phase; the reminders + wizard phases consume it. Backlog 999.5 (self-service onboarding) and 999.1 (provider abstraction) stay deferred — ADMIN-01 covers the admin-managed credential gap in the interim.
  • v1.1 roadmap reordered (2026-06-10): same 6 phases / 17 mappings, new order/numbering. Phase 7 = Mobile Test Harness (was 12), Phase 8 = Gitea CI (was 11), Phase 9 = Faster Write-Back (was 7), Phase 10 = Admin Role & Settings (was 8, carries the DB migration), Phase 11 = Per-Event Reminders (was 10), Phase 12 = Initial Setup Wizard (was 9). Phase 8 (CI) scope extended: the PR regression now also runs the Phase 7 mobile harness as a UI-regression step, bringing up the dev stack (API + PWA dev servers + MariaDB service container + DEV_AUTH_BYPASS) in the runner — so Phase 8 now depends on Phase 7. New critical path: 7 → 8 (CI consumes the harness); 9 independent; 10 → 11 and 10 → 12 (admin migration precedes reminders + wizard). Parallelizable once prerequisites met: 9 anytime; 11 and 12 in parallel after 10; 7 then 8.
  • Phase 16 added (2026-06-12, /gsd-phase): CI Dependency Audit, Security Checks & Image Hygiene — extend the Phase 8 Gitea CI workflow with outdated-dependency reporting (pnpm outdated), a vulnerability audit (pnpm audit at a chosen severity), and a baseline of additional security checks (secret scan / image scan). User requested a 16 integer phase (not a decimal insert) — they've been running independent/CI phases ahead of the admin chain. Depends on Phase 8; independent of the admin chain (1012). Scope still needs definition — run /gsd-discuss-phase 16. Milestone window now Phases 716.
  • Backlog 999.17 folded into Phase 16 + removed (2026-06-12, /gsd-phase): the dev/prod image-boundary item (confine DEV_AUTH_BYPASS to dev via build/boot-time enforcement; ensure no .env/secrets/encryption keys/DEV_USER seed/family data ship in published images; CI assertion in the publish job) was pulled into Phase 16 — shared CI surface and overlapping secret/image scanning made a separate phase redundant. The 999.17 backlog entry + its phase dir were deleted (not retained-for-history) since the scope now lives in an active phase; this also clears the recycled-number collision with Phase 15's historical "promoted from 999.17" provenance (the markdown-lint item that became Phase 15 had reused 999.17 first).

Pending Todos

  • Fix docs/deployment.md local-dev command DONE 2026-06-10 (quick 260610-czd) — added a "Running locally (host-side, no Docker)" subsection with the correct two-terminal command (set -a; source .env; set +a && DEV_AUTH_BYPASS=true DB_HOST=localhost pnpm --filter @familysync/api dev + pnpm --filter @familysync/pwa dev). --env-file deliberately NOT baked into the dev script (root .env sets DB_HOST=mariadb; auto-load would break host-side dev).
  • REQUIREMENTS.md traceability gap DONE 2026-06-10 (gsd-fast) — added the 6 deferred REQ-IDs to the Traceability table: CAL-09…CAL-12 (v1.x, Deferred), DISP-01/DISP-02 (v2, Deferred). v1 coverage stays 20/20; deferred IDs tracked separately.
  • DST spring-forward spot-check (Phase 2) — recurring/DST is implemented and code-verified (VTIMEZONE before expansion + local display TZ), and operator approved general times; navigating to March 2026 to eyeball the spring-forward transition is a recommended future spot-check.
  • Gitea CI — regression on PR to main + Docker build/publish PROMOTED TO BACKLOG 999.14 → now v1.1 Phase 8 (Gitea CI, CI-01/CI-02). Detail retained in pending todo 2026-06-10-gitea-ci-regression-and-docker-publish.md. Scope extended in the reorder: CI also runs the Phase 7 mobile harness as a UI-regression step.

Blockers/Concerns

  • Phase 1: Personal-calendar CalDAV ACL RESOLVED → CAL-08 GO (per-member app password; no cross-account ACL).
  • Phase 4 ENTRY GATE: Pangolin SSE pass-through (issue #1034) unverified CLEARED 2026-06-08 — SSE smoke PASS over familysync-dev.bergerhouse.net (~6 min, 35 heartbeats, buffering off, no cut). Live sync unblocked. Caveat: untested for a max total connection-duration cap; residual risk covered by Phase 4 design (D-10/D-11/D-12). See quick 260607-u8o + 03-GATE2-RESULTS.md Part C.
  • Phase 3: iOS standalone-PWA + Authelia login is load-bearing for the wife and is the first real external auth test (carried Gate 2 item, D-14). Also: iOS install guide is load-bearing — she gets no push notifications if she does not install the PWA.
  • Phase 2/3 dev: build behind a documented dev-auth bypass until Gate 2 deploy (D-14).
  • Phase 5: iOS push subscriptions silently revoked after 3 silent pushes. Subscription health-check and event.waitUntil() are mandatory from day one.
  • Phase 06 residual device-only items (not drivable in desktop Chromium): (1) PushPermissionPrompt spinner visible only in an installed iOS/standalone PWA — code-confirmed uses global @keyframes spin; spot-check at go-live. (2) iOS-Safari standalone cold-load and Authelia redirect — per 06-VALIDATION.md Manual-Only table; not yet verified. (3) Dev-bypass user (id 1) has no CalDAV credential/calendars; live event-create via the form requires user 2 or a dev-seed fix before go-live testing.
  • v1.1 Phase 7 (Mobile Test Harness): DEV_AUTH_BYPASS user 1 has no CalDAV credential/calendars — the harness verifies layout/flows, not live event-create. Confirm SW-block + bypass strategy before the first test (PITFALLS 14/15). These specs become Phase 8's CI UI-regression step, so structure them to run headlessly against a runner-hosted dev stack.
  • v1.1 Phase 8 (Gitea CI): Unraid Gitea runner Docker-socket/Node/pnpm state unknown — runner-probe is the first CI task (PITFALLS 12); MariaDB 11 readiness uses healthcheck.sh --connect, not mysqladmin ping (removed in MariaDB 11). NEW: the harness step brings up the API + PWA dev servers in the runner — added startup/readiness races on top of the MariaDB race; the step must wait for both dev servers to be ready before launching Playwright. Depends on Phase 7.

Quick Tasks Completed

# Description Date Commit Status Directory
260606-tv8 Fix missing sign-in redirect in the PWA (Phase 03 auth-entry gap from Gate 2): guarded /api/login → / + full-page redirect on unauthenticated fetchMe 2026-06-07 7c6531f 260606-tv8-fix-missing-sign-in-redirect-in-the-pwa-
260607-l6l Batch-fix Phase 03 write-path bugs: events.ts edit/delete missing calendars innerJoin (503, BLOCKING) + handler-coupled regression test; shared deriveDisplayName helper (me.ts + resolveUserId, corrects blank rows); GET /api/events userId/isShared ownership filter 2026-06-07 2870413 260607-l6l-fix-phase-03-write-path-correctness-bugs
260607-u8o Record SSE-over-Pangolin smoke test PASS (Phase 4 entry gate, D-14 / issue #1034) — updated 01-HUMAN-UAT item 4 + 03-GATE2-RESULTS Part C to PASS with live evidence 2026-06-08 26655cf 260607-u8o-record-sse-over-pangolin-smoke-test-pass
260610-cr8 Adopt drizzle generate+migrate workflow, retire db:push on MariaDB — removed db:push script + repointed deployment.md to migrate with anti-push warning; dry-verified no destructive diff 2026-06-10 1a95d81 Verified 260610-cr8-adopt-drizzle-generate-migrate-workflow-
260610-czd Fix docs/deployment.md local-dev command — added "Running locally (host-side, no Docker)" subsection with correct env-sourced two-terminal run command (Phase 2 UAT gap) 2026-06-10 39e2ee0 260610-czd-fix-docs-deployment-md-local-dev-command
260610-hbu Phase 5 reminder scheduler resilience (UAT Test 1 gap) — catch-up scan (now, now+16min] + per-uid exactly-once dedup so a missed/late cron tick no longer drops a reminder; lead-accurate body; also fixes pre-existing cross-tick double-fire. 10/10 reminder tests pass 2026-06-10 19d92c6 Verified 260610-hbu-make-phase-5-reminder-scheduler-resilien
260610-i4x Replace node-cron with setInterval in all 3 broker workers (poller/outbox/reminder) — node-cron 4.2.1 skipped EVERY scheduled execution in the long-running API process ("missed execution" each tick), so reminders/poll/outbox never fired on schedule. setInterval fires reliably (verified). 91 broker tests pass 2026-06-10 d9efbc1 Verified 260610-i4x-replace-node-cron-with-setinterval-in-ba
260610-jlp Fix broken "How to enable" link in notifications-blocked UI (Phase 5 UAT Test 4) — extracted InstructionSheet into a shared component; SettingsSheet "How to enable" now opens the OS-step instructions instead of just closing the sheet. 187 pwa tests pass, build green 2026-06-10 f82837c Verified 260610-jlp-fix-broken-how-to-enable-link-in-notific
260610-k1z Persist OIDC session cookie (AUTH-02) — @hono/oidc-auth 1.8.3 sets a session-scoped oidc-auth cookie (no maxAge) so it died on PWA/browser close → re-login almost every return (both devices). Added persistSessionCookie middleware re-issuing the cookie with maxAge(=OIDC_AUTH_EXPIRES)+SameSite=Lax, ONLY when a valid session exists (no resurrection guard). NOT an Authelia/refresh issue. 14 auth tests pass 2026-06-10 8343fad Verified 260610-k1z-persist-oidc-session-cookie-with-maxage-
260610-ka9 Fix silent Android push (Phase 5 UAT Test 4) — SW showNotification had only {body,tag,data} → Android Chromium/Edge showed them silently. Added icon/badge/renotify:true/vibrate; generalized re-enable instructions to Chrome-or-Edge. iOS unaffected. Build emits sw.js with renotify; 187 pwa tests pass 2026-06-10 c864fc4 Verified 260610-ka9-fix-silent-android-push-notifications-en
260611-ozt Split publish job into standalone .gitea/workflows/publish.yml (on: push→main only, no redundant event-guard if:; MILESTONE env moved with it) and strip it + the push trigger from ci.yml — kills the orphaned CI / publish (pull_request) pending status (phase-8 code-review WR-01). name:CI + fast-checks/api/harness job ids held stable so the required branch-protection contexts stay valid. Documented the release model in README "Publishing / Releases" + publish.yml header. Both YAML validated (yq) 2026-06-11 92353e1 260611-ozt-split-publish-job-into-standalone-gitea-
260611-tfc Fix WR-01 (13-REVIEW): apps/pwa/src/sw.ts notificationclick openWindow fallback was unreachable when client.focus() rejects (window closed between matchAll/focus) or client.navigate() resolves null — chained a navigate-result check + a .catch, both falling through to self.clients.openWindow(url). lint/format:check/typecheck green, build emits sw.js, 191/191 pwa tests 2026-06-12 af78ccc Verified 260611-tfc-fix-wr-01-sw-ts-notificationclick-openwi
260613-dmw Exclude .gitea/** from the CI changes code paths-filter so workflow-only PRs skip the heavy api/harness jobs (treated like docs) while fast-checks + gate still run. Single - '!.gitea/**' negation appended after the yml/yaml globs (index 11 vs 5). Rides along on the Phase 16 branch / PR #15. 2026-06-13 2d329a9 260613-dmw-exclude-gitea-workflow-config-changes-fr
260613-fp9 .gitea/.planning-only pushes to main no longer trigger the Docker publish — added paths-ignore: ['.gitea/**', '.planning/**'] under on.push in .gitea/workflows/publish.yml (skips only when EVERY changed file matches; mixed code+docs pushes still publish). .dockerignore already excludes .planning so the image is byte-identical. Done in isolated worktree (phase-10 agent held main tree). 2026-06-13 cd5a88c 260613-fp9-gitea-and-planning-pushes-should-not-tri

Deferred Items

Category Item Status Deferred At
Calendar Single-occurrence recurring edit (RECURRENCE-ID) v1.x Roadmap
Calendar "This and following" recurring edit v1.x Roadmap
Calendar Apple Calendar native subscribe URL docs v1.x Roadmap
Calendar Secondary timezone display toggle v1.x Roadmap
Display Wall-display / kiosk dashboard v2 PROJECT.md
Reminders Multiple reminders per event (2× VALARM) v1.2 v1.1 REQUIREMENTS.md
Onboarding Self-service member app-password setup (999.5) — v1.1 covers admin-managed (ADMIN-01) Backlog v1.1 roadmap
Calendar Provider abstraction (999.1) — Fastmail as one of several backends Backlog v1.1 roadmap
Setup Wizard re-run / reconfigure flow after first setup Backlog v1.1 REQUIREMENTS.md
Notifications Android event-change push delivery (Phase 5 UAT Test 4) — confirm member B's Android device receives a non-silent "A updated an event" push after member A edits a shared event. Blocking bugs already fixed + deployed (quick 260610-jlp how-to-enable link, 260610-ka9 silent-notification options); server-side FCM delivery proven (FCM 201). Remaining: on-device confirmation + operator raises the Edge/Android notification-channel importance. See 05-UAT.md Test 4. Phase 6 verification 2026-06-10
Calendar Mark shared-family calendar is_shared=1 RESOLVED 2026-06-10 — operator created the "FamilySync" calendar on the primary Fastmail account; poller synced it as calendars.id=10 (user 2); ran UPDATE calendars SET is_shared=1 WHERE id=10. Shared color lane now populated; Phase 5 reminders now fire on its events. Poller upsert does not touch is_shared, so the flag persists. (ADMIN-02 in v1.1 Phase 10 replaces this manual step with a UI toggle.) Phase 2 (deferred, D-16) DONE 2026-06-05 → 2026-06-10

Session Continuity

Last session: 2026-06-13T19:27:40.437Z Stopped at: Completed Phase 10 Plan 02 (admin role primitives: requireAdmin, upsertUser is_admin, /api/me isAdmin+needsProviderSetup) Resume file: None

Operator Next Steps

  • Phase 8 is complete. CI pipeline is fully operational on the self-hosted Gitea runner.
  • Next: /gsd-plan-phase 9 (Faster Write-Back — fully independent, lowest risk) or /gsd-plan-phase 10 (Admin Role & Settings — carries the v1.1 DB migration that Phases 11 & 12 depend on). These can run in parallel once planned.