Playwright transpiles specs without typechecking (esbuild), so layout.spec.ts ran
green while `tsc -p tsconfig.e2e.json` failed: page.evaluate(() => document...)
callbacks need the DOM lib, and styleHandle.evaluate((el) => el.remove()) typed el as
Node (no .remove()). Phase 8 CI runs the typecheck gate, so this would have broken CI.
Adds DOM/DOM.Iterable to the e2e tsconfig (also covers 07-04 specs) and casts el to Element.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace ISO 8601 'T' separator with space in dtstart_utc value
- MariaDB TIMESTAMP requires 'YYYY-MM-DD HH:MM:SS', not 'YYYY-MM-DDTHH:MM:SSZ'
- Was causing 'Incorrect datetime value' error blocking all e2e harness runs
- Documents pnpm test:e2e run commands and single-profile / headed variants
- Documents DEV_AUTH_BYPASS=true must be set before API starts (Pitfall 5)
- States production compose MUST NOT set DEV_AUTH_BYPASS (Elevation of Privilege)
- Lists PLAYWRIGHT_BASE_URL and DB_* env vars (all credentials env-only, never hardcoded)
- States no storageState file is used (D-01 — no expiring session cookie)
- Describes globalSetup readiness gate + seed anchors (Milk/Eggs/Seeded Test Event)
- Notes Phase 8 CI scope and --with-deps WebKit requirement
- Add exclude: ['e2e/**', 'node_modules/**'] to vitest.config.ts test block
to prevent Playwright specs from being picked up by Vitest jsdom runner
- Add tsconfig.e2e.json extending main tsconfig with node types for
playwright.config.ts and e2e/**/* typecheck coverage
- Add @types/node to pwa devDependencies (required by playwright.config.ts)
- Update typecheck script to run both src and e2e tsc passes
- 191 unit tests still pass; no e2e import errors in vitest run
- Two projects: iphone/WebKit (iPhone 14) + pixel/Chromium (Pixel 7)
- serviceWorkers: 'block' on both profiles per D-02/Pitfall 15
- env-driven baseURL via PLAYWRIGHT_BASE_URL (D-08/Rule 8)
- globalSetup ref to e2e/global-setup.ts (Plan 02 implements)
- webServer manages vite only with reuseExistingServer (D-10)
- no storageState, no toHaveScreenshot per D-01/UI-SPEC Rule 6
- add e2e/global-setup.ts placeholder (stub) so config path resolves
The per-family remap (shared, member-1..4) only fills all-day pills whose
Schedule-X colorName is registered. Member calendars absent from the current
/api/me members list fall back to Schedule-X's built-in primary family, which
was not remapped — so those all-day events degraded to the light tint. Remap
--sx-color-primary-container as well so all-day pills stay solid in the
fallback case too (production member-N calendars already covered).
- Add isPhone() helper using window.matchMedia('(max-width: 767px)') consistent with AppNav
- Return null when isPhone() is false (desktop ≥768px) — BottomTabBar is phone-only
- Prevents the position:fixed bottom bar from overlaying AppNav sidebar avatar/Settings on desktop
- RED test committed in prior commit (740e342)
- Lift AppNav from CalendarShell to App.tsx as a sibling of <Routes>
- App.tsx fetches /api/me (same query key as CalendarShell — deduplicated by TanStack Query)
- App.tsx provides the outer layout (phone: column, desktop: row) with AppNav always rendered
- CalendarShell simplified: no longer manages AppNav, outer flex layout stays in App.tsx
- AuthSplash gains overlay prop (position:fixed inset:0 z-index:999) so it covers AppNav when needed
- CalendarShell uses AuthSplash with overlay=true so auth splashes cover full viewport
- Remove onOpenSettings prop from CalendarShell (wired directly in App.tsx to SettingsSheet)
- Desktop sidebar nav (FamilySync brand, Calendar/Lists links) now persists on /lists route
- CalendarShell now captures maybeRedirectToLogin() return value in meQuery.isError effect
- When the one-shot guard is exhausted (returns false), arm loginRedirectExhausted state
- Render AuthSplash state=dead-end (tap-to-retry) when guard is exhausted, not indefinite redirecting spinner
- Reset loginRedirectExhausted on successful auth (meQuery.isSuccess) for session recovery
- Add sessionStorage.clear() to beforeEach so CalendarShell tests are isolated
- RED test committed in prior commit (36ef7a0)
- Replace non-existent .sx__all-day-event selector with confirmed class names
- .sx__date-grid-event: exclusively all-day in week/day view (timed go to .sx__time-grid-event)
- .sx__month-grid-event:not(:has(.sx__month-grid-event-time)): all-day in month view
- Use CSS custom property inheritance to remap -container vars to solid main colors in date-grid and month-grid-day__events scopes
- Cover shared + member-1..4 calendar colorNames (N-member design)
- Source-confirmed from @schedule-x/calendar@4.6.0 dist/core.js
- Add icon, badge, renotify:true, vibrate to showNotification options so
reused-tag updates produce heads-up + sound/vibration on Android Chromium
- Narrow cast (as NotificationOptions) to handle renotify/vibrate absent
from this lib.dom version without suppressing other errors
- Generalize ANDROID_STEPS first step to browser-agnostic (Chrome or Edge)
- Re-issues oidc-auth cookie with maxAge so PWA sessions survive close/reopen
- Guards on c.get('oidcAuthJwt'): only runs when @hono/oidc-auth set a valid session
- Falsy oidcAuthJwt falls straight through — no resurrection of deleted/absent cookies
- Cookie attrs mirror the library: httpOnly, secure, sameSite=Lax, conditional domain
- maxAge reads OIDC_AUTH_EXPIRES (default 86400s)
- Move isIOS, IOS_STEPS, ANDROID_STEPS, InstructionSheetProps, InstructionSheet from PermissionDeniedBanner verbatim
- Export InstructionSheet as named export from InstructionSheet.tsx
- PermissionDeniedBanner now imports from ./InstructionSheet.js (behaviour identical)
- Replace [now+14min, now+16min] window with (now, now+16min] catch-up
- Replace minuteBucket-keyed Set with uid-keyed Map for exactly-once dedup
- Lead-accurate body: 'Starts in N min' (Math.max(1, round(lead/60000)))
- CR-01 pruning: drop entries whose dtstart <= now (event started)
- WR-01 preserved: mark uid sent after all dispatches complete
- Drop gte import; add gt import from drizzle-orm
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.
- 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
- 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)
- 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)
- 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