Lucas Berger and Claude Opus 4.8
a986c74963
docs(phase-17): mark phase complete (pending human verification)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-18 13:11:35 -04:00
Lucas Berger and Claude Opus 4.8
fcc02e3833
docs(17): add phase verification report (10/10 decisions delivered; human_needed for device-only checks)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-18 13:11:23 -04:00
Lucas Berger and Claude Opus 4.8
9730d3dcdb
docs(17): add code review report
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-18 13:06:20 -04:00
Lucas Berger and Claude Opus 4.8
ea49a4dc83
docs(phase-17): update tracking after wave 2
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-18 13:03:12 -04:00
Lucas Berger and Claude Opus 4.8
b5fcd1d172
fix(17): resolve post-merge wave 2 integration issues
...
- SettingsSheet: void the navigate('/login') promise (react-router v7 returns Promise) — eslint no-floating-promises
- InstructionSheet.test: wrap SettingsSheet render in MemoryRouter — 17-05 added useNavigate() which needs Router context
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-18 13:01:39 -04:00
Lucas Berger
4887ba1b5f
chore: merge executor worktree (worktree-agent-a12887546a61a8053)
2026-06-18 12:58:59 -04:00
Lucas Berger
aaf754d938
chore: merge executor worktree (worktree-agent-a65077bbd9885bf7e)
2026-06-18 12:58:58 -04:00
Lucas Berger
9e39a5c492
chore: merge executor worktree (worktree-agent-ad448992e936c77aa)
2026-06-18 12:58:57 -04:00
Lucas Berger
29f3d62312
chore: merge executor worktree (worktree-agent-a1f3ad1ab5d67e70c)
2026-06-18 12:58:56 -04:00
Lucas Berger
f4c65d478a
docs(17-06): complete admin polish plan — toasts + two-tab ARIA nav + e2e assertions
2026-06-18 12:58:06 -04:00
Lucas Berger
944045cd7e
test(17-06): add admin two-tab ARIA and keyboard assertions to admin.spec.ts
...
- Add 'Admin two-tab ARIA strip (D-10)' describe block: tablist visible,
both named tabs visible, ArrowRight/ArrowLeft keyboard switching,
panel aria-labelledby, phone overflow check
- Add 'Admin success toast structure (D-08)' describe: role=status absent on load
- Satisfies Wave 0 admin-ARIA CI requirement — 12/12 tests pass on pixel profile
2026-06-18 12:56:34 -04:00
Lucas Berger
caff8b4b0a
docs(17-05): complete logout control + sheet centering plan
...
- Sign out control (D-07): LogOut icon, handleSignOut fire-and-best-effort, navigate /login
- Desktop centering (D-09): phone/desktop style branch on SettingsSheet, ChangePasswordSheet, LinkOidcSheet, CredentialSheet
- playwright-cli verified: centered modal @1280x720, bottom-sheet @390x844, Sign out redirects to /login
2026-06-18 12:54:43 -04:00
Lucas Berger
28e9ca9840
docs(17-03): complete phone layout overlap fix plan — SUMMARY.md
...
FAB lifted above BottomTabBar, phone content padding added, D-01
regression guard committed to layout.spec.ts; 3-profile e2e sweep green.
2026-06-18 12:54:30 -04:00
Lucas Berger
620d64138a
feat(17-06): add success toasts to admin create-member and reset-password
...
- Add toast state + 3000ms auto-dismiss useEffect to AdminPage
- Set toast 'Member added.' in createMemberMutation.onSuccess
- Propagate 'Password reset.' up from ResetPasswordSheet via onSuccess callback
- Render role=status/aria-live=polite toast with CheckCircle icon
- Phone toast offset uses calc(var(--bottom-chrome-h) + var(--space-4)) to clear BottomTabBar
2026-06-18 12:53:12 -04:00
Lucas Berger
b71238634f
feat(17-05): add phone/desktop style branch to CredentialSheet (D-09)
...
- Add window.matchMedia('(max-width: 767px)') phone check
- Desktop branch: position fixed, top/left 50%, translate(-50%, -50%), maxWidth 480px, borderRadius 12px
- Phone branch: unchanged bottom-sheet (bottom 0, left 0, right 0, borderRadius 12px 12px 0 0)
- role=dialog, aria-modal, aria-label unchanged — only position CSS branched
- SettingsSheet, ChangePasswordSheet, LinkOidcSheet centering in prior commit (Task 1)
2026-06-18 12:52:59 -04:00
Lucas Berger
132a5e4eae
feat(17-05): add Sign out control with handleSignOut to SettingsSheet (D-07)
...
- Add LogOut icon (lucide-react) and fetchLocalLogout (api/client) imports
- Add useNavigate (react-router) for post-logout redirect
- Implement handleSignOut: fire-and-best-effort try/catch, navigate to /login in both branches
- Add Sign out button row at bottom of sheet (44px tap target, var(--color-destructive), divider separator)
- Always navigates to /login even on API failure (server cookie cleared or expired)
2026-06-18 12:52:48 -04:00
Lucas Berger
4cb16f8271
docs(17-04): complete brand wiring plan — BrandSlot logo, favicons, maskable icon fix, accent
...
- BrandSlot: placeholder div replaced with decorative logo img (ce95aa3 )
- tokens.css: --brand-logo-border-radius 0, --color-member-0 #e8915a (df578fd )
- index.html: favicon.svg + favicon.ico links, theme-color #e8915a (df578fd )
- vite.config.ts: maskable icon fixed to /icon-maskable-512.png, theme_color #e8915a (df578fd )
- Build: green; manifest.webmanifest verified correct
2026-06-18 12:51:58 -04:00
Lucas Berger
85a803fba6
test(17-03): add FAB↔BottomTabBar overlap regression guard to layout.spec.ts
...
- New test: "New Event FAB does not overlap BottomTabBar (A — phone only)"
- Skips on desktop profile; runs iphone + pixel (the profiles that exposed D-01)
- Asserts fabBox.y + fabBox.height <= navBox.y (FAB bottom ≤ bar top)
- Confirms Task 1 fix resolves the seed defect — passes green on both profiles
2026-06-18 12:51:32 -04:00
Lucas Berger
df578fd7b7
feat(17-04): wire favicons + theme-color in index.html, fix maskable icon + accent in manifest
...
- index.html: add favicon.svg (SVG, modern browsers first) + favicon.ico (legacy) links
- index.html: update theme-color from #4A90D9 to approved #e8915a (warm amber)
- vite.config.ts: fix maskable icon — reference /icon-maskable-512.png (separate file) not /icon-512.png
- vite.config.ts: update manifest theme_color to #e8915a to match index.html
- tokens.css: update --color-member-0 to #e8915a (warm amber, Variant B, operator-approved 17-02)
- --sx-color-primary follows automatically via var(--color-member-0)
2026-06-18 12:50:50 -04:00
Lucas Berger
ce95aa3e6b
feat(17-04): wire logo img into BrandSlot, set --brand-logo-border-radius 0
...
- Replace placeholder FS div with decorative <img src="/logo.svg" alt="" aria-hidden>
- Apply Surface B-1 style (width/height brand-logo-size, borderRadius token, contain/block)
- Update --brand-logo-border-radius from 50% to 0 (SVG draws its own rx=104 shape)
- h1 FamilySync and tagline p unchanged; LoginPage.tsx untouched (seam contract honored)
- No dangerouslySetInnerHTML (T-05-24 invariant maintained)
2026-06-18 12:48:55 -04:00
Lucas Berger
5e1c714894
fix(17-03): lift FAB above BottomTabBar and reserve phone content padding
...
- CalendarShell.tsx: FAB bottom changed from var(--space-6) to
calc(var(--bottom-chrome-h) + var(--space-6)) — sits 24px above bar
- App.tsx: contentStyle gains phone-only paddingBottom: var(--bottom-chrome-h)
via spread pattern ...(phone ? {...} : {}) — desktop unchanged
2026-06-18 12:48:35 -04:00
Lucas Berger
9e080fb22a
docs(phase-17): update tracking after wave 1
2026-06-18 12:45:49 -04:00
Lucas Berger
65e6222944
chore: merge executor worktree (worktree-agent-af89750bf86857964)
2026-06-18 12:44:24 -04:00
Lucas Berger
58d11caee0
chore: merge executor worktree (worktree-agent-a1fb88aa75fd00c8f)
2026-06-18 12:44:23 -04:00
Lucas Berger
d917157c63
docs(17-02): complete branding assets plan summary
...
Records approved brand decisions for plan 17-04:
- logo art approved (family-house redesign)
- brand accent #e8915a (warm amber)
- --brand-logo-border-radius: 0
2026-06-18 12:43:45 -04:00
Lucas Berger
4c99470c1a
feat(17-02): apply approved family-house logo + regenerate icon set
...
- Overwrite logo.svg with approved higher-contrast family-house SVG
(warm peach gradient bg, amber roof, bold white walls, heart finial,
three clearly-spaced family figures in rose/gold/blue)
- Regenerate full icon/favicon set from new art:
favicon.svg, favicon.ico (967 B), icon-192.png (192x192),
icon-512.png (512x512), icon-maskable-512.png (512x512 safe-zone),
apple-touch-icon.png (180x180)
- Approved brand decisions recorded for plan 17-04:
accent #e8915a, --brand-logo-border-radius: 0
2026-06-18 12:42:43 -04:00
Lucas Berger
d01ec2388c
docs(17-01): complete token groundwork plan — SUMMARY
...
tokens.css restructured to :root,[data-theme=light] combined selector;
--bottom-chrome-h added; all --sx-color-* overrides intact; build green;
layout.spec.ts pixel profile 15/15 pass; Schedule-X colors verified.
2026-06-18 12:14:45 -04:00
Lucas Berger
b364573285
feat(17-02): generate full icon/favicon set from logo.svg (Task 2)
...
- Run @vite-pwa/assets-generator minimal2023Preset against public/logo.svg
- Produce: favicon.ico (861 B, non-trivial), favicon.svg, icon-192.png (1617 B),
icon-512.png (4221 B), icon-maskable-512.png (3353 B, 512x512 distinct maskable
with safe-zone padding), apple-touch-icon.png (1110 B, 180x180)
- Replace 3 placeholder stubs with real generated assets
- Update pwa:icons script to include post-generation canonical rename step
- Add generator intermediate output filenames to .gitignore
2026-06-18 12:13:50 -04:00
Lucas Berger
c2f89bd55f
feat(17-01): restructure tokens.css to combined :root,[data-theme=light] selector + add --bottom-chrome-h
...
- Change :root { to :root, [data-theme="light"] { (D-06 combined selector)
- Add --bottom-chrome-h: calc(56px + env(safe-area-inset-bottom, 0px)) to spacing scale
- Keep all 12 --sx-color-* overrides inside the combined block (cascade unchanged)
- Keep --brand-logo-* tokens inside the combined block (verbatim, no value changes)
- Add dark-theme stub as comment-only (Phase 999.20 fills values; no live rule)
- All existing values unchanged; PWA build passes; Schedule-X colors verified via playwright-cli
2026-06-18 12:12:42 -04:00
Lucas Berger
7db9005645
feat(17-02): install @vite-pwa/assets-generator, author logo.svg, add pwa-assets.config.ts
...
- Add @vite-pwa/assets-generator@1.0.2 as devDependency in apps/pwa
- Add pwa:icons script (pwa-assets-generator generate) to apps/pwa/package.json
- Approve sharp build scripts in pnpm-workspace.yaml (required for @vite-pwa/assets-generator)
- Hand-author apps/pwa/public/logo.svg: warm/rounded/at-home family scene with house, two adults, child, heart — square 512x512 viewBox, self-contained, text-free
- Create apps/pwa/pwa-assets.config.ts with minimal2023Preset, images: ['public/logo.svg'], no overrideManifestIcons
2026-06-18 12:12:17 -04:00
Lucas Berger
eb0db8beef
docs(17): create phase plan
2026-06-18 11:36:21 -04:00
Lucas Berger
280438b2d3
docs(17): create phase plan (6 plans, 2 waves) — UI optimization & polish
2026-06-18 11:34:04 -04:00
Lucas Berger
28cf79754a
docs(17): add validation strategy
2026-06-18 11:19:24 -04:00
Lucas Berger
f789a67f95
docs(17): research phase domain
2026-06-18 11:17:44 -04:00
Lucas Berger
a1457a5b30
docs(17): UI design contract
2026-06-18 10:59:25 -04:00
Lucas Berger
efeee02a36
Merge remote-tracking branch 'origin/main' into gsd/phase-17-ui-optimization-polish
...
# Conflicts:
# .planning/STATE.md
2026-06-18 10:08:45 -04:00
Lucas Berger and Claude Opus 4.8
cfe84715d5
docs: add developer profile section to CLAUDE.md
...
Publish / publish (push) Successful in 11s
Generated by /gsd-profile-user from session analysis (8 dimensions).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-18 10:07:03 -04:00
Lucas Berger
1e2cc52659
docs: refresh project documentation against current codebase
Publish / publish (push) Successful in 26s
2026-06-18 06:44:29 -04:00
Lucas Berger and Claude Opus 4.8
b6490feff4
fix(19): satisfy CI fast-checks + secret scan
...
CI / changes (pull_request) Successful in 9s
CI / api (pull_request) Successful in 3m2s
CI / fast-checks (pull_request) Successful in 4m20s
CI / security (pull_request) Successful in 1m14s
CI / harness (pull_request) Successful in 6m56s
CI / gate (pull_request) Successful in 2s
Lint (eslint --max-warnings 0):
- index.ts: disable no-unsafe-argument on the type-only Context mismatch when
delegating to the OIDC handler inside the local-session skip wrapper
- localAuth.ts: handleLogout is sync (no await) — drop async (require-await)
- devBypass.ts: disable detect-possible-timing-attacks on the public well-known
dev-placeholder string compare (not a secret comparison)
- remove dead code / unused bindings flagged by no-unused-vars: makeTestApp
(localSession.test), makeUnauthContext + BrowserContext import (login.spec),
unused memberId (admin.test), unused txSelectCount counter (me.test)
- localAuthMiddleware.test / me.test: fix unused + reflow-detached
eslint-disable directives
Format: prettier --write across the 20 Phase-19 files that were never formatted.
Secret scan (gitleaks): allowlist two false positives — the synthetic >=32-char
TEST_SECRET in localSession.test.ts, and .planning/ design prose (a generic-api-key
regex hit on "credential atomically, 409-equivalent"). Neither is a real secret.
Verified locally: format:check, lint, typecheck, md:lint, gitleaks (no leaks),
PWA 266/266, API 452/452.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-17 23:05:15 -04:00
Lucas Berger
91ab9d1f78
docs(19): ship phase 19 — PR #23
2026-06-17 22:49:45 -04:00
Lucas Berger
abf7be782a
docs(phase-19): add security threat verification (28/28 closed, threats_open: 0)
CI / changes (pull_request) Successful in 10s
CI / fast-checks (pull_request) Failing after 2m17s
CI / api (pull_request) Successful in 2m51s
CI / security (pull_request) Failing after 12s
CI / harness (pull_request) Successful in 5m9s
CI / gate (pull_request) Failing after 2s
2026-06-17 22:47:45 -04:00
Lucas Berger
f3af130e9e
docs(17): add workstream D — 4 UI findings from Phase 19 UAT (logout button, admin feedback, popup centering, admin nav)
2026-06-17 22:34:18 -04:00
Lucas Berger
af0a70ccec
test(19): complete UAT — Tests 1-3 pass live, 4 resolved-by-fix, 5→ship; 4 UI findings routed to Phase 17
2026-06-17 22:32:52 -04:00
Lucas Berger
73dd6a2383
docs(19): re-review after fixes — status clean (0 critical/warning, 2 info)
2026-06-17 20:50:25 -04:00
Lucas Berger
9cccf17ef9
chore: enable MemPalace discuss-time recall + KG mirror
2026-06-17 20:45:39 -04:00
Lucas Berger
5f74ae965d
docs(19): add code review fix report
2026-06-17 20:42:51 -04:00
Lucas Berger
cef2c66de5
merge(19): land code-review fixes (CR-01..04, BL-01..04, WR-01..07, IN-01..04)
2026-06-17 20:39:49 -04:00
Lucas Berger
2691dd0f95
fix(19): IN-04 enforce single-use OIDC-link nonce to prevent state replay
2026-06-17 20:37:09 -04:00
Lucas Berger
83e23d760d
docs(state): record phase 17 context session
2026-06-17 20:37:02 -04:00
Lucas Berger
3784762817
docs(17): capture phase context (layout polish + branding + theme groundwork; dark mode/styling deferred)
2026-06-17 20:37:02 -04:00
Lucas Berger
f02521dd02
fix(19): IN-03 evict stale loginAttempts entries to bound in-memory map growth
2026-06-17 20:35:43 -04:00
Lucas Berger
e392bf2eb7
fix(19): IN-02 add lockstep test pinning inlined scrypt params against canonical verifyPassword
2026-06-17 20:34:58 -04:00
Lucas Berger
f2fc1404d4
fix(19): IN-01 validate LOCAL_SESSION_EXPIRES coercion (fallback on NaN/non-positive)
2026-06-17 20:34:14 -04:00
Lucas Berger
916fb34f17
docs: add backlog items 999.20 (dark mode/theming) + 999.21 (styling refresh)
2026-06-17 20:34:02 -04:00
Lucas Berger
4bd6b2c057
fix(19): WR-05 add no-echo tests for admin create-member and me password hook sites
2026-06-17 20:33:46 -04:00
Lucas Berger
32bdd1e92d
fix(19): WR-07 reject route ids with trailing garbage via strict integer parse
2026-06-17 20:32:26 -04:00
Lucas Berger
4cf2ad4bff
fix(19): WR-06 stop sliding the rate-limit cooldown window on rejected attempts
2026-06-17 20:31:07 -04:00
Lucas Berger
30ad25c026
fix(19): WR-03 make scrypt hashing async (threadpool) to avoid event-loop starvation DoS
2026-06-17 20:30:28 -04:00
Lucas Berger
322929aebe
fix(19): WR-02+WR-04 centralize OIDC config (env-or-app_config) and discover auth endpoint
2026-06-17 20:26:47 -04:00
Lucas Berger
c4d8d76a4c
fix(19): WR-01 parse reset-admin flags explicitly and stop echoing username
2026-06-17 20:25:18 -04:00
Lucas Berger
40666e1cc5
fix(19): BL-04 keep context oidcIss/oidcSub null for local users (no fabricated identity sentinels)
2026-06-17 20:24:01 -04:00
Lucas Berger
71537601ce
fix(19): BL-03 reject OIDC-link bind on empty iss/sub or mismatched local session (takeover guard)
2026-06-17 20:22:37 -04:00
Lucas Berger
cd095e5b67
fix(19): BL-02 match Secure attribute on logout cookie deletion to issue-time logic
2026-06-17 20:21:51 -04:00
Lucas Berger
3674b255b2
fix(19): BL-01 enforce LOCAL_SESSION_SECRET length floor in devSessionCookieMiddleware
2026-06-17 20:21:17 -04:00
Lucas Berger
b083cb7193
fix(19): CR-04 scope login lockout to username, add TTL auto-expiry + admin-reset unlock
2026-06-17 20:20:43 -04:00
Lucas Berger
6ef8e03f8c
fix(19): CR-03 return 403 for wrong current password so change-password does not log user out
2026-06-17 20:17:32 -04:00
Lucas Berger
93c47b38aa
fix(19): CR-02 send initialPassword + map 409 conflict in fetchCreateMember
2026-06-17 20:15:58 -04:00
Lucas Berger
1688f229e0
fix(19): CR-01 align OIDC-link client contract with server (authorizationUrl)
2026-06-17 20:15:23 -04:00
Lucas Berger and Claude Opus 4.8
46eaf070ea
chore: enable MemPalace for project and ignore per-project files
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-17 20:12:50 -04:00
Lucas Berger
4b635784e4
docs(19): add code review report
2026-06-17 20:08:26 -04:00
Lucas Berger
37fd896ce9
docs(phase-19): mark phase complete (verification passed, 5/5 plans)
2026-06-17 20:00:21 -04:00
Lucas Berger
0aca22f743
docs(19): verification passed (21/21) after blocker fix; UAT for live/CI items
2026-06-17 20:00:00 -04:00
Lucas Berger and Claude Opus 4.8
53da4be62b
fix(19): correct admin reset-password client URL (AUTH-LOCAL-08 blocker)
...
VERIFICATION.md found a cross-layer URL mismatch: fetchAdminResetPassword
POSTed to /api/admin/members/:id/reset-password but the API registers the
route as /api/admin/members/:id/password (admin.ts), so the Admin reset sheet
404'd on every submit. Confirmed live: old path -> 404, correct path -> 400
(route reached). Unit tests missed it because API tests hit the real path
directly and PWA tests mock the fetcher — no test crossed both layers.
Fix the client URL and add a URL-contract regression test that pins the exact
path (asserts fetch is called with /api/admin/members/:id/password).
PWA 266/266 (+1), typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-17 19:58:17 -04:00
Lucas Berger
9b10d875d4
docs(phase-19): update tracking after wave 4
2026-06-17 17:44:10 -04:00
Lucas Berger and Claude Opus 4.8
17a531550a
fix(19): resolve post-merge wave-4 harness integration gaps
...
Two issues surfaced only when plans 19-04 (login UI) and 19-05 (Option C
bypass + login.spec) were merged together and run against the real stack —
neither executor could catch them in isolation:
1. LOCAL_SESSION_SECRET was added to the CI harness (ci.yml) but not to the
local dev stack (docker-compose.dev.yml). Without it the real-login success
path (POST /api/auth/local/login) 503s when signing the session cookie, so
the e2e round-trip failed. Add the same fixed dev-only value to the dev
compose override (dev-only target; never a production secret).
2. login.spec test 1 assumed clearing the local-session cookie yields a
logged-out state, but under the always-on DEV_AUTH_BYPASS devAuthBypass()
injects DEV_USER into /api/me regardless of any cookie — a logged-out state
is architecturally unreachable in this bypass-only harness. Reframe the test
to drive /login directly (validating the real-browser render of all brand +
form surfaces) and move the unauthenticated root->/login redirect-gate
coverage to a unit test in App.test.tsx where meQuery.isError is controllable.
Result: API 446/446, PWA 265/265 (+2 gate tests), e2e desktop 42 passed / 3
skipped (all login specs green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-17 17:43:11 -04:00
Lucas Berger
11977fddf4
chore: merge executor worktree (worktree-agent-ad5628ea37541e256)
2026-06-17 17:28:49 -04:00
Lucas Berger
c7142b5fe5
chore: merge executor worktree (worktree-agent-a71adf90ed21b044c)
2026-06-17 17:28:48 -04:00
Lucas Berger
63beb74650
docs(19-04): complete PWA login UI + account management surfaces plan
2026-06-17 17:26:07 -04:00
Lucas Berger
eba0bb095d
docs(19-05): complete dev-bypass rework + harness + CI plan (checkpoint)
2026-06-17 17:22:39 -04:00
Lucas Berger
19c45eb069
feat(19-04): AdminPage LOCAL ACCOUNTS + SettingsSheet change-password / link-OIDC
...
- Add hasLocalCredential to AdminMember type (mirrors API extension from plan 19-02)
- Add createMember mutation + Surface 11A inline add-member form in AdminPage
- Add Surface 11B Reset-password button in MemberRow (hasLocalCredential gate)
- Add ResetPasswordSheet component (bottom-sheet, role=dialog, focus-managed, Escape closes)
- Add Surface 12 Change-password row in SettingsSheet (hasLocalCredential gate)
- Add Surface 13 Link-OIDC identity row in SettingsSheet (hasLocalCredential + oidcEnabled gate)
- Add ChangePasswordSheet component (current/new/confirm fields, change-password mutation)
- Add LinkOidcSheet component (confirmation dialog; uses generic OIDC copy per D-06, no provider branding)
- Fix: update InstructionSheet.test.tsx to wrap with QueryClientProvider (Rule 1 - now uses useQuery)
- Fix: remove stale eslint-disable in App.test.tsx (lint --max-warnings 0 would fail)
- All 263 tests pass; typecheck clean; lint clean
2026-06-17 17:21:20 -04:00
Lucas Berger
1f94dc5eb7
feat(19-05): global-setup local_credentials seed + login.spec.ts + CI harness env
...
- global-setup.ts: TRUNCATE local_credentials + seed devuser/devpass (PHC scrypt inline)
- Create login.spec.ts: real-login-form e2e (gate redirect, wrong-password error, correct login)
- ci.yml: add LOCAL_SESSION_SECRET dev value + local_credentials seed step in harness job
- Fix all test mocks: add devSessionCookieMiddleware no-op to vi.mock(devBypass.js) blocks
in admin/setup/push/lists/localAuth/authMode/requireAdmin tests (Rule 1 - Bug: missing export)
- Full API suite: 446/446 tests pass; pnpm typecheck: exit 0
2026-06-17 17:21:05 -04:00
Lucas Berger
32d0408774
feat(19-04): LoginPage (Surfaces 1-10) + App.tsx authModeQuery gate + /login route
...
- Create LoginPage with BrandSlot, username/password form, show/hide toggle
- Four error states: invalid credentials, rate-limit, locked, server (all per UI-SPEC)
- OIDC method divider + 'Login with OIDC' button rendered only when oidcEnabled
- Accessibility: role=main, h1 in BrandSlot, h2 Sign in, aria-live error banner, 44px targets
- Focus management: username autofocus, Enter navigates username→password→submit
- App.tsx: add authModeQuery (queryKey ['authMode'], staleTime 60s)
- App.tsx: add /login standalone route (sibling of /setup, no AppNav/BottomTabBar)
- App.tsx: login gate after setup gate — meQuery error + localEnabled → Navigate /login
- App.tsx: OidcRedirect helper for OIDC-only mode (meQuery error + !localEnabled + oidcEnabled)
- Fix App.test.tsx to include fetchAuthMode mock and hasLocalCredential in user fixture
2026-06-17 17:14:18 -04:00
Lucas Berger
82391874ee
feat(19-05): add break-glass reset-admin CLI (dev-only, .dockerignore'd)
...
- Create apps/api/scripts/reset-admin.ts (AUTH-LOCAL-11, D-13, D-15)
- Dev-only guard as FIRST executable statement (NODE_ENV=production throws)
- Inline scrypt PHC hashPassword (cannot import compiled TS, Pitfall 11)
- Parse --username/--password from argv; never log password value (T-19-26)
- --dry-run validates args + DB connection without writing
- Upserts users (is_admin=true) + local_credentials rows idempotently
- Script excluded from prod image via .dockerignore apps/api/scripts/ (IMG-02)
- dry-run: exit=0, no password in output verified
2026-06-17 17:13:04 -04:00
Lucas Berger
3094df84c8
feat(19-05): Option C — devSessionCookieMiddleware issues real local-session cookie under bypass
...
- Add devSessionCookieMiddleware() to devBypass.ts (production hard-guard FIRST)
- Issues local-session JWT cookie for DEV_USER when no cookie present under bypass
- Pure no-op when NODE_ENV=production, DEV_AUTH_BYPASS!=true, or secret not set
- Mount devSessionCookieMiddleware() after devAuthBypass() in index.ts
- Existing devBypass tests: 3/3 pass; typecheck: exit 0
2026-06-17 17:11:31 -04:00
Lucas Berger
869cdc26c8
feat(19-04): add LoginError, fetchAuthMode/login/logout + auth fetchers, BrandSlot, brand-seam tokens
...
- Add LoginError class (4 codes: invalid/rate-limit/locked/server) mirroring SessionExpiredError shape
- Add hasLocalCredential to MeUser interface
- Add fetchAuthMode, fetchLocalLogin, fetchLocalLogout (pre-auth endpoints)
- Add fetchChangePassword, fetchCreateMember, fetchAdminResetPassword, fetchLinkOidc
- Create BrandSlot component with Phase-17-ready placeholder (48px circle, FS initials, h1, tagline)
- Add --brand-logo-* CSS custom properties to tokens.css (Phase 17 seam)
2026-06-17 17:10:30 -04:00
Lucas Berger
1cf572a2b9
docs(phase-19): update tracking after wave 3
2026-06-17 17:06:30 -04:00
Lucas Berger
b2f3182ef4
chore: merge executor worktree (worktree-agent-a11c9f6d35e8d8721)
2026-06-17 17:01:05 -04:00
Lucas Berger
e0d471a5d5
docs(19-03): complete local-auth middleware wiring plan
2026-06-17 17:00:42 -04:00
Lucas Berger
9b569efeab
feat(19-03): wire /callback link branch, OIDC-guard skip, de-Authelia comments
...
- /callback: reads signed state, extracts linkUserId, calls linkOidcToUser after OIDC session set; OidcLinkConflictError redirects to /?error=oidc-link-conflict
- OIDC guard: oidcAuthMiddleware() factory called once at construction, handler invoked per-request inside skip-when-user-set wrapper (D-03)
- middleware.ts: de-Authelia-ize comments — generic OIDC identity provider language (D-06, AUTH-LOCAL-18)
- localAuthMiddleware.ts: cast to typeof DEV_USER for ContextVariableMap type compatibility
- All 446 tests pass; typecheck clean
2026-06-17 16:59:06 -04:00
Lucas Berger
c437f408bb
feat(19-03): implement POST /api/auth/local/login (rate-limit + lockout) + logout
...
- Rate-limit: per-IP in-memory Map; 5 failures → 429, 10 → 423 (lockedOut)
- Counter increments even on 429 so brute-force accumulates toward lockout
- Timing-safe: DUMMY_HASH ensures verifyPassword runs for unknown usernames (T-19-12)
- noEchoHook: Zod errors never echo submitted values (T-19-14)
- Same 401 body for wrong-password and unknown-username (no enumeration)
- POST+GET /local/logout clear the local-session cookie
2026-06-17 16:49:59 -04:00
Lucas Berger
db66295920
test(19-03): add failing tests for POST /api/auth/local/login + logout
...
- RED: 8 tests for login success, wrong-password 401, unknown-username 401 (no enumeration), rate-limit 429, lockout 423, logout cookie clear, no-echo 400
2026-06-17 16:49:51 -04:00
Lucas Berger
be7a0aec90
feat(19-03): implement localAuthMiddleware, GET /api/auth/mode, and pre-auth route mounts
...
- localAuthMiddleware: cookie→c.set('user') with Pitfall-1 guard (no-set on no-cookie path)
- authMode: GET /api/auth/mode pre-auth endpoint (localEnabled:true, oidcEnabled from env+config)
- localAuth: POST /api/auth/local/login (rate-limit + timing-safe), logout routes
- index.ts: mount authModeRouter + localAuthRouter pre-auth; localAuthMiddleware after devAuthBypass; OIDC guard wrapped skip-when-user-set
2026-06-17 16:48:17 -04:00
Lucas Berger
ac32bd405f
test(19-03): add failing tests for localAuthMiddleware and GET /api/auth/mode
...
- RED: 8 tests failing (modules not yet created)
- localAuthMiddleware: 4 tests for cookie→user shape, no-cookie passthrough, missing user row, devAuthBypass coexistence
- authMode: 3 tests for mode response with no oidc, env oidc, app_config oidc
2026-06-17 16:48:07 -04:00
Lucas Berger
eb090bb57e
docs(phase-19): update tracking after wave 2
2026-06-17 16:42:17 -04:00
Lucas Berger
55cd5cf698
chore: merge executor worktree (worktree-agent-a177cd3aa5422e109)
2026-06-17 16:41:18 -04:00
Lucas Berger
f167031292
docs(19-02): complete admin+me account management plan summary
2026-06-17 16:40:50 -04:00
Lucas Berger
efb80c8c1a
feat(19-02): linkOidcToUser helper + POST /api/me/link-oidc initiation
...
apps/api/src/auth/linkOidc.ts (new):
- OidcLinkConflictError: thrown when iss+sub already belongs to a different user
- linkOidcToUser(userId, iss, sub): preflight SELECT for conflict, then db.transaction
(UPDATE users SET oidc_iss/sub/claimed + DELETE local_credentials); atomic, no email (D-10)
- 88 lines; no email in source (D-10/T-19-08 assertion passes)
apps/api/src/routes/me.ts:
- POST /api/me/link-oidc: resolveUserId (401 if null), sign state JWT
({ linkUserId, nonce, iat, exp } HS256 with LOCAL_SESSION_SECRET, 10-min window)
- Returns { signedState, authorizationUrl } — 19-03 /callback reads linkUserId from state
- authorizationUrl constructed from OIDC env vars when configured, null otherwise
- T-19-09: per-request nonce in state prevents CSRF/replay
2026-06-17 16:38:14 -04:00
Lucas Berger
8ced2d0a20
test(19-02): add failing tests for linkOidcToUser and POST /api/me/link-oidc
...
RED phase for Task 3:
- Test 1: linkOidcToUser updates users.oidc_iss/sub and deletes local_credentials
- Test 2: linkOidcToUser throws OidcLinkConflictError on conflict, no local_cred deletion
- Test 3: POST /api/me/link-oidc returns initiation payload (state / authorizationUrl)
2026-06-17 16:35:19 -04:00
Lucas Berger
c88f7d41e5
feat(19-02): self-change password and hasLocalCredential on GET /api/me
...
- POST /api/me/password: verifyPassword(current) gate before hashPassword(new) update
- 401 on wrong current password, 404 if no local_credentials row, 200 on success
- meNoEchoHook on /password route (T-19-06, never echo submitted password)
- resolveAdminAndSetupStatus extended with hasLocalCredential (AUTH-LOCAL-17)
- GET /api/me response includes hasLocalCredential alongside isAdmin/needsProviderSetup
2026-06-17 16:34:10 -04:00
Lucas Berger
80b5906bb8
test(19-02): add failing tests for self-change password and hasLocalCredential on /api/me
...
RED phase for Task 2:
- Test 1: POST /api/me/password correct current → 200, new hash verifies newPassword
- Test 2: wrong currentPassword → 401, UPDATE not called (hash unchanged)
- Test 3: no local_credentials row → 404
- Test 4 (GET /api/me): hasLocalCredential:true/false based on local_credentials existence
2026-06-17 16:32:48 -04:00