- 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
5.9 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics, status
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | status | |||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17-ui-optimization-polish | 05 | pwa-ui |
|
|
|
|
|
|
complete |
Phase 17 Plan 05: Logout Control + Sheet Centering Summary
Wired a reachable Sign out control into SettingsSheet (D-07) and fixed dialog centering so all four settings sheets render as centered modals on desktop and unchanged bottom-sheets on phone (D-09).
Tasks Completed
| Task | Name | Commit | Files |
|---|---|---|---|
| 1 | Add Sign out control and handleSignOut to SettingsSheet | 132a5e4 |
SettingsSheet.tsx |
| 2 | Add phone/desktop centering branch to all sheets | b712386 |
CredentialSheet.tsx |
What Was Built
Task 1 — Sign out control (D-07)
Added to SettingsSheet.tsx:
LogOuticon imported from lucide-react (added to existing icon import)fetchLocalLogoutimported from../api/client.js(added to existing import)useNavigatefromreact-routerfor post-logout routinghandleSignOutasync function: wrapsfetchLocalLogout()in try/catch, callsonClose()thennavigate('/login')in both success and catch branches — fire-and-best-effort per D-07 spec (server cookie is cleared or already expired; navigation must always proceed)- Sign out button row: 44px minHeight,
var(--color-destructive)color, 16px LogOut icon, "Sign out" text, full-width,aria-label="Sign out", separated from prior sections by the project's 1pxvar(--color-border-subtle)divider
The button renders unconditionally at the bottom of the sheet — visible regardless of hasLocalCredential or oidcEnabled gating.
Task 2 — Phone/desktop centering branch (D-09)
Applied phone = window.matchMedia('(max-width: 767px)').matches + ternary style branch to four dialog wrappers:
| Sheet | File | zIndex |
|---|---|---|
| SettingsSheet | SettingsSheet.tsx | 301 |
| ChangePasswordSheet | SettingsSheet.tsx | 303 |
| LinkOidcSheet | SettingsSheet.tsx | 303 |
| CredentialSheet | CredentialSheet.tsx | 301 |
Phone branch (unchanged): position: fixed; bottom: 0; left: 0; right: 0; borderRadius: 12px 12px 0 0
Desktop branch (new): position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); maxWidth: 480px; width: calc(100% - 32px); maxHeight: calc(100dvh - 32px); overflowY: auto; borderRadius: 12px; boxShadow: 0 8px 32px rgba(0,0,0,0.18)
role=dialog, aria-modal, aria-label, Escape-closes, and focus-return invariants unchanged on all wrappers.
Note: AdminPage.ResetPasswordSheet centering is handled by plan 17-06 (single-file-owner constraint).
Playwright Validation
Desktop @1280x720: SettingsSheet opened centered on the page as a modal dialog. Sign out button visible with destructive red styling and LogOut icon at the bottom of the sheet. Clicking Sign out navigated to /login (confirmed URL change from http://localhost:5175/calendar to http://localhost:5175/login).
Phone @390x844: SettingsSheet rendered as a bottom-sheet anchored to the bottom of viewport with rounded top corners. Sign out button visible at bottom. Bottom-sheet behavior unchanged.
Both screenshots confirmed correct behavior for D-07 and D-09.
Verification Checks
grep handleSignOutin SettingsSheet.tsx: PASSgrep fetchLocalLogoutin SettingsSheet.tsx: PASSgrep 'Sign out'in SettingsSheet.tsx: PASSgrep LogOutin SettingsSheet.tsx: PASSgrep 'translate(-50%, -50%)'in SettingsSheet.tsx: PASS (3 occurrences — SettingsSheet, ChangePasswordSheet, LinkOidcSheet)grep 'translate(-50%, -50%)'in CredentialSheet.tsx: PASSgrep 'max-width: 767px'in CredentialSheet.tsx: PASSpnpm --filter @familysync/pwa build: PASS (exits 0, 1858 modules)
Threat Model Compliance
| Threat ID | Mitigation Applied |
|---|---|
| T-17-05-01 | handleSignOut calls fetchLocalLogout() (clears server-side cookie) then always navigates to /login — no stale-cookie-with-logged-out-UI state possible |
| T-17-05-02 | Position-only CSS branch — no new input or executable content |
Deviations from Plan
None — plan executed exactly as written.
Known Stubs
None — both D-07 and D-09 are fully wired. Sign out calls the real fetchLocalLogout() endpoint. Centering is CSS-only with no data source.
Threat Flags
None — no new network endpoints, auth paths, or schema changes introduced. All changes are UI-layer only.
Self-Check: PASSED
/home/luc/projects/familysync/.claude/worktrees/agent-a65077bbd9885bf7e/apps/pwa/src/components/SettingsSheet.tsx— FOUND, contains handleSignOut, fetchLocalLogout, Sign out, LogOut, translate(-50%, -50%)/home/luc/projects/familysync/.claude/worktrees/agent-a65077bbd9885bf7e/apps/pwa/src/components/CredentialSheet.tsx— FOUND, contains translate(-50%, -50%), max-width: 767px- Commit
132a5e4— FOUND (Task 1) - Commit
b712386— FOUND (Task 2) - Build:
pnpm --filter @familysync/pwa buildexits 0