Commit Graph
824 Commits
Author SHA1 Message Date
Lucas Berger 69e5ae8726 fix(06): set hasRrule on EventDetailPopover test fixtures
Plan 06-05 added hasRrule as a required field on the PWA CalendarOccurrence
type (mirroring 06-03's server-side field), but pre-existing fixtures in
EventDetailPopover.test.tsx did not set it — breaking tsc --noEmit / vite build
(vitest passed because esbuild strips types). Both fixtures are non-recurring
single events, so hasRrule: false is correct. Post-merge integration fix.
2026-06-10 11:44:37 -04:00
Lucas Berger 883b00b92b feat(06-06): distinct all-day event pill styling
- Add .sx__all-day-event override to Schedule-X section of index.css
- Full-width rounded pill: border-radius 4px, white (#FFFFFF) label, font-weight 600,
  font-size var(--text-label-size) — UI-SPEC Surface 3 contract
- Color source: calendarId color config (buildCalendarConfig) — no per-event override
- Timed event chip styling unchanged; existing .sx__* layout rules intact
2026-06-10 11:41:01 -04:00
Lucas Berger 96ef0b45b9 feat(06-06): add whole-series edit confirmation prompt
- Create SeriesEditPrompt.tsx: bottom-sheet (phone) / dialog (desktop) matching
  DeleteConfirmationDialog pattern; focus trap, Escape=cancel, role=dialog/aria-modal
- UI-SPEC Surface 6 copy: 'Edit recurring series' heading, 'This will update all
  occurrences of this event.' body, 'Update series' accent-filled CTA, 'Cancel' ghost
- Import SeriesEditPrompt + add seriesEditPromptOpen state to EventForm
- handleSubmit gates on occurrence.hasRrule: opens prompt for recurring edits;
  executeSubmit() fires the existing whole-series PATCH on confirmation
- Save CTA label becomes 'Update series' for recurring edits (UI-SPEC primary CTAs)
- Non-recurring and create-mode Save behavior unchanged (no prompt)
2026-06-10 11:40:33 -04:00
Lucas Berger cbf5f98eb9 feat(06-06): wire end-tracking and recurrence-bound control into EventForm
- Import computeNewTimedEnd/computeNewAllDayEnd from eventDateTime.ts (D-04)
- Start date onChange: calls computeNewAllDayEnd (all-day) or computeNewTimedEnd (timed)
- Start time onChange: calls computeNewTimedEnd preserving duration (timed only)
- Add recurrenceBound/recurrenceUntil/recurrenceCount state (D-06)
- Reset useEffect extended to reset bound state on form open
- Add 'Ends' control (Never/On date/After N times) shown when recurrence != none
- Inline validation: count < 1 and until < start
- Payload conditionally includes recurrenceUntil/recurrenceCount (create mode only)
- Error state type extended for recurrenceBound validation
2026-06-10 11:38:24 -04:00
Lucas Berger 9aa15c484b test(06-06): add failing tests for end-tracking wiring and recurrence-bound control
- Add hasRrule to EDIT_OCCURRENCE, RECURRING_OCCURRENCE, LATE_OCCURRENCE fixtures
- Add ALL_DAY_OCCURRENCE fixture for D-05 round-trip test
- Add test cases: D-04 timed/all-day end-tracking on start change
- Add test cases: D-05 all-day edit pre-fills inclusive end (no drift)
- Add test cases: D-06 Ends control visibility, On date/After N times reveals
- Add test cases: D-06 validation (count < 1), payload emission (count, never)
2026-06-10 11:36:49 -04:00
Lucas Berger 139ef00ed4 feat(06-05): global session-expiry interstitial via QueryCache/MutationCache onError
- Add sessionExpired boolean + setSessionExpired action to Zustand calendarStore
- main.tsx: construct QueryClient with QueryCache+MutationCache onError (v5 pattern, not defaultOptions.onError)
- onGlobalError: checks instanceof SessionExpiredError, calls setSessionExpired(true) via store.getState()
- CalendarShell: read sessionExpired from store; render AuthSplash(state=redirecting, 'Session expired', 'Signing you back in…')
- CalendarShell: useEffect fires clearLoginRedirect + maybeRedirectToLogin after 1.5s when sessionExpired (one-shot guard re-armed)
- Context7 /tanstack/query confirmed v5 QueryCache/MutationCache constructor + onError signature
2026-06-10 11:32:45 -04:00
Lucas Berger e7b34a5ce2 feat(06-05): gate app render behind AuthSplash (no pre-auth flash)
- Create AuthSplash component: full-screen centered column, loading/redirecting/dead-end states
- loading state: Loader2 spinner + 'Signing you in' heading + 'Taking you to the sign-in page…' body
- redirecting state: spinner + customisable heading/body (defaults to cold-load copy)
- dead-end state: tap-to-retry button (clearLoginRedirect + maybeRedirectToLogin)
- role=status, aria-label, inline styles matching SkeletonCalendar layout approach
- CalendarShell: early-return AuthSplash on meQuery.isLoading (no skeleton before auth)
- CalendarShell: replace 'Sign-in required' role=alert block with AuthSplash state=redirecting
- CalendarShell: isInitialLoading no longer includes meQuery.isLoading (handled by early return)
2026-06-10 11:31:17 -04:00
Lucas Berger d7d4023cf9 feat(06-05): centralize session-expiry detection and extend client types
- Add SessionExpiredError class with Object.setPrototypeOf for correct instanceof
- Add handleAuthResponse helper: throws SessionExpiredError on 401/opaqueredirect, generic Error on other non-ok
- Add redirect:'manual' + handleAuthResponse to all six fetch wrappers (fetchEvents, createEvent, updateEvent, deleteEvent, fetchSyncStatus, fetchWritableCalendars)
- Unify fetchMe: now throws SessionExpiredError instead of generic Error
- Add recurrenceUntil? and recurrenceCount? to CreateEventPayload (D-06)
- Add hasRrule: boolean to CalendarOccurrence client mirror (D-08, Pitfall 4)
2026-06-10 11:29:37 -04:00
Lucas Berger e5072ff663 test(06-05): add failing SessionExpiredError detection tests 2026-06-10 11:26:59 -04:00
Lucas Berger 81f2678987 fix(06-04): add global pulse keyframe and drop redundant spin redefinition
- Add @keyframes pulse (0%,100% opacity:1; 50% opacity:0.4) to tokens.css after @keyframes spin
- Remove redundant local <style> block redefining @keyframes spin from PushPermissionPrompt.tsx
- LiveSyncIndicator reconnecting dot now resolves its pulse animation from the global stylesheet
- PushPermissionPrompt spinner continues to work via the existing global spin keyframe
2026-06-10 11:23:01 -04:00
Lucas Berger 0c2c26c375 docs(06-03): complete hasRrule server-side exposure plan 2026-06-10 11:20:17 -04:00
Lucas Berger 44d336c01b feat(06-03): expose hasRrule on expanded occurrences
- Add hasRrule: boolean to CalendarOccurrence interface
- Capture isRecurring = event.isRecurring() once before the branch
- Set hasRrule: isRecurring in non-recurring push (always false)
- Set hasRrule: isRecurring in recurring push (always true)
- All 10 expand.test.ts tests pass (RED→GREEN)
2026-06-10 11:17:13 -04:00
Lucas Berger 593302ee41 test(06-03): add failing tests for hasRrule + bounded expansion
- Add hasRrule===true assertion for recurring events (weekly-dst.ics)
- Add hasRrule===false assertion for non-recurring events (single-duration.ics)
- Add weekly-count3.ics fixture (FREQ=WEEKLY;COUNT=3, 1-hour events)
- Add bounded RRULE test: expects exactly 3 occurrences in wide window
- Add per-occurrence duration test: each occurrence is 1 hour (not recurrence span)
- Tests are RED: hasRrule field absent from CalendarOccurrence interface
2026-06-10 11:11:08 -04:00
Lucas Berger b869fe0a93 docs(06-02): complete RRULE UNTIL/COUNT serialization plan 2026-06-10 11:04:04 -04:00
Lucas Berger d2abb91bd2 feat(06-02): serialize RRULE UNTIL/COUNT and lock FREQ persistence
- Add assembleRruleString() helper (exported) to outboxWorker.ts (D-06)
- Wire UNTIL/COUNT bound assembly into create + update dispatch branches
- Add recurrenceUntil (max 10) + recurrenceCount (int min 1) to outboxPayloadSchema
- Add recurrenceUntil + recurrenceCount to eventFieldsSchema in events.ts
- Series-edit bound change strips existing UNTIL/COUNT via regex before re-apply (Pitfall 3)
- All 39 broker tests pass (RED->GREEN); existing CR-01 none-wins test preserved
2026-06-10 11:00:24 -04:00
Lucas Berger a59455a727 test(06-02): add failing tests for RRULE UNTIL/COUNT + FREQ persistence
- vevent.test.ts: add COUNT, UNTIL-DATE, UNTIL-DATETIME serialization assertions
- outboxWorker.test.ts: add assembleRruleString (D-06) describe block (not yet exported)
- outboxWorker.test.ts: add FREQ persistence (D-07 regression) describe block
- RED: assembleRruleString not yet exported; FREQ-persistence cases fail on missing helper
2026-06-10 10:56:22 -04:00
Lucas Berger 78a8cb5ccd docs(06-01): complete end-tracking math plan (D-04) 2026-06-10 10:51:16 -04:00
Lucas Berger 605f543f81 feat(06-01): implement duration-preserving end-tracking helpers
- computeNewTimedEnd: preserves timed duration; 1h floor for stale state
- computeNewAllDayEnd: preserves all-day day-span; same-day floor for stale state
- Private helpers: dateDiffDays, addDaysISO, localDateISO, localTimeHHMM (local accessors only — WR-05)
- All 6 RED tests now GREEN; pre-existing suite unaffected (166/166 pass)
2026-06-10 10:49:10 -04:00
Lucas Berger 16cdbf3d7c test(06-01): add failing tests for end-tracking duration math
- computeNewTimedEnd: 1h delta, 26h multi-day delta, floor-to-1h rule
- computeNewAllDayEnd: 0-day span, 3-day span, floor-to-same-day rule
- RED: all 6 new cases fail with missing-export TypeError
2026-06-10 10:48:09 -04:00
Lucas Berger 7de1f2482e docs: add backlog 999.10 (admin Settings section) + 999.11 (initial setup wizard)
999.10: in-app admin UI to manage app passwords + designate the shared
calendar (replaces manual is_shared DB write, D-16).
999.11: first-run setup wizard for env vars/VAPID/DB/app-password with
validation (motivated by the truncated-VAPID-key setup friction).
2026-06-10 10:38:36 -04:00
Lucas Berger 53913bb7fd docs(06): record planning complete + annotate roadmap 2026-06-10 10:37:02 -04:00
Lucas Berger f6b2322012 docs(06): fix 06-06 depends_on format; populate validation map 2026-06-10 10:34:44 -04:00
Lucas Berger f1a2de2cdc docs: resolve D-16 — FamilySync shared calendar synced (id 10) + marked is_shared=1
Operator created the shared 'FamilySync' calendar on the primary Fastmail
account; poller synced it; ran UPDATE calendars SET is_shared=1 WHERE id=10.
Shared color lane now populated; Phase 5 reminders fire on its events.
2026-06-10 10:34:41 -04:00
Lucas BergerandClaude Opus 4.8 456121969f docs(06): create ux-polish phase plan (6 plans, 2 waves)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:25:41 -04:00
Lucas Berger d52acad54c docs(06): add validation strategy 2026-06-10 10:04:34 -04:00
Lucas Berger 3d0ec986a2 docs(06): research phase for ux-polish
Code-verified findings for all six D-01..D-13 fix areas: end-tracking
gap in EventForm, @keyframes pulse absent from tokens.css, hasRrule
missing from CalendarOccurrence type, and ical.js UNTIL/COUNT verified
against project node_modules. Includes validation architecture for TDD
and playwright-cli verification scopes.
2026-06-10 10:02:29 -04:00
Lucas Berger 4b77ec0254 docs(06): add brand assets & iconography contract to UI-SPEC
Appends a new "Brand Assets & Iconography" section covering the
glyph+wordmark mark concept, SVG source-of-truth files, raster export
pipeline, full favicon set (SVG + ICO + index.html gaps), maskable
safe-zone spec, in-app logo usage surfaces, and a complete asset
manifest table. All existing UI-SPEC sections preserved verbatim.
2026-06-10 09:45:00 -04:00
Lucas BergerandClaude Sonnet 4.6 9707fd0d85 docs(06): UI design contract for ux-polish phase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 09:38:54 -04:00
Lucas Berger cdbe94deb2 docs(state): record phase 6 context session 2026-06-10 09:33:43 -04:00
Lucas Berger afdc8d124d docs(06): capture phase context 2026-06-10 09:33:36 -04:00
Lucas Berger 497daf6add docs: add deferred REQ-IDs to REQUIREMENTS traceability table
CAL-09..CAL-12 (v1.x) and DISP-01/DISP-02 (v2) were in the body but
absent from the Traceability table. v1 coverage stays 20/20; deferred
IDs listed separately. Resolves the REQUIREMENTS traceability todo.
2026-06-10 09:26:45 -04:00
Lucas Berger 7369c9f1d1 docs(quick-260610-czd): plan/summary + STATE quick-task row, resolve deployment.md dev-cmd todo 2026-06-10 09:25:24 -04:00
Lucas Berger 39e2ee067e docs(quick-260610-czd-01): add host-side local dev run instructions to deployment.md
- Add 'Running locally (host-side, no Docker)' subsection after dev-auth bypass section
- Explain why plain pnpm dev fails: dev script has no dotenv, root .env sets DB_HOST=mariadb
- Document exact command: build first, then set -a; source .env; set +a && DEV_AUTH_BYPASS=true DB_HOST=localhost pnpm --filter @familysync/api dev
- Add Terminal 2 (PWA) command: pnpm --filter @familysync/pwa dev
- Explain why --env-file is intentionally absent from the dev script
2026-06-10 09:24:22 -04:00
Lucas Berger b745515753 docs(quick-260610-cr8): plan/summary/verification + STATE quick-task row 2026-06-10 09:20:22 -04:00
Lucas Berger 736adf7c58 docs(quick-260610-cr8): adopt drizzle generate+migrate, retire db:push
Plan/summary/verification artifacts; STATE quick-task row (Verified);
archive source todo to completed/.
2026-06-10 09:20:13 -04:00
Lucas Berger 1a95d81a3f docs(quick-260610-cr8-01): repoint deployment.md to generate+migrate, warn against push
- Step 3: replace drizzle-kit push command with drizzle-kit migrate
- Step 3: rewrite description from 'push schema once' to 'apply committed migrations'
- Step 3: add warning callout explaining MariaDB false-truncate foot-gun
- Step 3: add note on db:generate workflow for future schema changes
- Step 6: change push reference to drizzle-kit migrate (Step 3)
2026-06-10 09:16:30 -04:00
Lucas Berger f452400517 chore(quick-260610-cr8-01): remove db:push script from apps/api/package.json
- Delete the 'db:push': 'drizzle-kit push' entry from scripts
- Retain db:generate and db:migrate as the canonical workflow
- JSON remains valid; no other changes
2026-06-10 09:15:30 -04:00
Lucas Berger 68ff72d195 docs(phase-05): add security threat verification (26/26 closed) 2026-06-10 09:09:45 -04:00
Lucas Berger 3b54ea2f12 wip: phase 05 web-push paused — code complete + verified, awaiting iOS/Android device UAT 2026-06-09 22:51:04 -04:00
Lucas Berger 17dfaac5f2 docs(05): mark phase pending iOS/Android device UAT (verification human_needed) 2026-06-09 22:48:07 -04:00
Lucas Berger ecb576eb8a test(05): verification (12/12 code) + persist 5 human UAT items 2026-06-09 22:47:14 -04:00
Lucas Berger 02526d07eb docs(05): code review clean after --fix --all --auto (3 iterations) 2026-06-09 22:41:28 -04:00
Lucas Berger 17756fc523 fix(05-review): NEW-WR-01 emit delete changes when server returns zero events (whole-cache clear)
Pre-capture all currently-cached rows into pendingDeleteRows before the whole-cache
db.delete() when seenUids.length === 0. The existing >0 branch behavior is unchanged.
Adds a regression test verifying onChanges receives one delete change per cached row
on a full-calendar clear.
2026-06-09 22:39:11 -04:00
Lucas Berger c7ef5811d1 fix(05-review): NEW-CR-01 pre-resolve SW registration to eliminate await before pushManager.subscribe
Pre-fetch ServiceWorkerRegistration into component state via useEffect in both
PushPermissionPrompt and SettingsSheet. Gate the subscribe tap target as disabled
until both vapidKey AND swRegistration are ready. The tap handler now has zero
awaits between the user gesture and registration.pushManager.subscribe(), fully
satisfying the iOS user-gesture requirement.
2026-06-09 22:39:05 -04:00
Lucas Berger dc8516beb8 docs(05): add code review report 2026-06-09 22:33:09 -04:00
Lucas Berger 44fbb2bb3a fix(05-review): commit 0004 migration meta (journal + snapshot) 2026-06-09 22:33:09 -04:00
Lucas Berger 1044de57ae test(05-review): add CR-01/WR-01 reminder pruning tests and IN-01 actor-name tests 2026-06-09 22:30:03 -04:00
Lucas Berger 50da9b3bca fix(05-review): IN-01 resolve actor display name in eventChangeDispatcher for D-02/D-03 2026-06-09 22:25:32 -04:00
Lucas Berger 8cecbab7ab fix(05-review): CR-02 change endpoint/p256dh to varchar to prevent InnoDB prefix-index truncation 2026-06-09 22:24:23 -04:00
Lucas Berger 82eccc9017 fix(05-review): CR-04 pre-fetch VAPID key into state; no await before pushManager.subscribe 2026-06-09 22:24:00 -04:00