LW-W5-R1: Web product UX completion + visual system - #7
Merged
Conversation
Evidence-based walk of every current screen (desktop/375/360, EN+VI) against docs/UX_CONTRACT.md and the new W5 spec. Classifies findings P0-P3: unlabeled Home /preview CTA, unlabeled VI address-term summary, Remember-in-story reload state (client-only, server already idempotent), replay affordance density, missing hover/focus states, incomplete color roles, unimplemented motion contract. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Visual foundation (Part 2): add warning/success/focus color roles and motion duration tokens (src/theme/tokens.ts); add a shared hover/focus-visible/press style helper (src/theme/interactive.ts) used across the reading, setup, and composer Pressables so desktop finally shows interactive affordance and keyboard focus is visible (P1-2/P1-3); add useReducedMotion (react-native-web's AccessibilityInfo is already matchMedia-backed, so one hook covers native + web). Fixes three P0 findings from handoff/LW-W5-R1/ux-audit.md: - Home no longer gives /preview equal weight to "Start a Story" — it's now a small internal QA link, not a second primary CTA. - Vietnamese address-term summary in Advanced Setup now renders as four labeled rows (character calls you / calls themselves, you call character / call yourself) instead of an unlabeled "tôi · cậu · tôi · cậu" join of the same preset data. - "Remember in story" now reflects server truth: the character-chat loadThread read now joins canon_facts on (source_chat_message_id, source_chat_candidate_index) — the same key lw_promote_chat_memory already uses for its existing idempotency check — and reports `promoted` per candidate, so a reload shows a stable "Remembered" state instead of re-offering promotion. No schema change, no client-only fake persistence. Also thins the per-scene "Replay from here" control (P1-1): historical scenes get a small secondary text-weight ReplayLink instead of a full-size bordered button repeated after every scene in a long story; the current scene's own forward action keeps full ActionButton weight. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Story reader and Character Chat error/quota states all rendered in the same default text color before this (P1-3) — now warning (a boundary the player can act on or wait out: allowance exhausted, beta capacity, offline, safety rejection) is visually distinct from danger (a genuine provider/transport failure), using the tokens added in the previous commit. Copy is unchanged; only color role assignment changes. Character directory: drop the redundant top border on the first character card (a section heading already separates it) and add hover/focus to "Talk to character". Character Chat: the promoted "Remembered in story" pill now renders in the success color with a matching border instead of looking identical to the pre-promotion state once disabled. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every screen's header bar (back link + language switcher) spanned full width edge-to-edge while its body content sat in a centered readingWidth.maxContentWidth column below it — on desktop this is what made the app read as "a mobile page stretched wide" rather than a considered layout (P1-6). New shared ScreenHeaderBar/ScreenHeaderRow/ BackLink (src/components/screen-header-bar.tsx) keep the bar's border/background full-bleed but center its content to the same column width, and consolidate six near-identical header blocks (run, new-story, characters, character-chat, account, preview) into one component with hover/focus already wired in. Layout-only change, verified at 1280px (header now left-aligns at x=320, matching the content column) with no new horizontal overflow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
new-story.tsx and advanced-setup-form.tsx each defined an identical selectable-pill Pressable (path tabs, language, genre, tone, POV) with no hover/focus treatment. Extracted to src/components/choice-pill.tsx (built on the theme/interactive helpers from the visual-foundation commit) and reused from both call sites — same visual result, no duplicated implementation, and pills now get the same hover/focus affordance as every other interactive control in this pass. Also wires hover/focus into the Quick/Advanced Setup "Start" button, the one primary Pressable on that screen that still only branched on `pressed`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…itcher ChoicePill and LanguageSwitcher's radios rendered around 28px tall on mobile, short of the ~44px tap-target guidance (Part 19). Added hitSlop instead of enlarging the visible pill, so restrained chrome (Part 8) is unaffected but the touch target grows. LanguageSwitcher previously had no hover/focus treatment at all, unlike every other Pressable touched in this pass — brought it onto the same theme/interactive helpers. Verified live via keyboard Tab: focused radios now show the custom colors.focus outline (rgb(61,110,133) / #3D6E85, solid, 2px) instead of the browser's default focus ring. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Home, Account, and Quick/Advanced Setup's top-level titles rendered as plain text with no accessibilityRole="header", unlike Story/Characters/ Character Chat which already had it — meant screen-reader heading navigation skipped three of the six main screens. Also verified the new warning/success/focus color tokens against their surfaces: all pairs are >=4.5:1 contrast (WCAG AA), light warning/background 4.74 being the tightest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the end-to-end proof the spec asked for (Part 26): the mock character-chat route now tracks a promotedCandidates Set keyed by (message_id, candidate_index) — the same idempotency key lw_promote_chat_memory uses — and the lw_promote_chat_memory mock populates it, so "open" responses report `promoted` from that server-truth mock instead of a hardcoded value. New test promotes a candidate, asserts the button flips to "Remembered in story", reloads the page, and asserts it's still "Remembered in story" rather than reverting to the pre-promotion label. Runs on both desktop-chromium and mobile-chromium (Pixel 7). Complements the existing chat-memory-promotion.test.ts unit coverage of the pure mapping logic. Full local CI verified green: typecheck, lint, 164 vitest unit tests, web export, 10/10 Playwright e2e (both projects). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real bug found only by actually rendering the page: Home's "START A
STORY" button was wrapped in <Link href="/play" asChild><Pressable
style={fn}>. Link's child-cloning does not compose correctly with an
array-valued style function — the button painted at near-zero opacity
(confirmed via screenshot, both desktop and mobile). Every other
primary button in the app already uses a plain Pressable +
router.push(), which composes fine (verified: "Talk to character" and
the Advanced Setup "Start" button both render correctly with the same
pattern) — switched Home to match. Also stopped washing out the
"Remembered in story" pill with disabledOpacity: it's a confirmed
state, not a low-emphasis disabled one, so it now stays at full
opacity and the success color reads clearly.
Adds scripts/capture-w5-screenshots.mjs: a standalone Playwright driver
(reusing the same deterministic route-mocking as
tests/e2e/roleplay-chat.spec.ts) that captures the Part 24 screenshot
set — desktop Home/Advanced-Setup/Story/Characters/Character-Chat,
mobile (Pixel 7) Home/Advanced-Setup-VI/Story/Character-Chat, plus an
allowance-exhausted state showing the new warning color — into
handoff/LW-W5-R1/screenshots/. Used because the interactive Browser
pane could not composite frames in this session (screenshot/click both
timed out); Playwright's own headless capture is unaffected and is
what actually caught the Home CTA bug above.
Full local CI re-verified green after both fixes: typecheck, lint, 164
unit tests, web export, 10/10 Playwright e2e (both projects).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Documents the evidence gathered during implementation: before/after usability rationale per surface (visual-review.md, including the Home CTA bug found only by actual screenshot capture), keyboard-focus and contrast verification (accessibility-review.md), desktop chrome alignment and no-overflow checks at 1280/412/375/360px (responsive-review.md), and the automated + manual browser QA record including the interactive-pane compositing limitation encountered mid-session (browser-e2e.md). Raw typecheck/lint/vitest and web-export/ Playwright output captured in test-results.txt and ci-results.txt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Master handoff summary (A-Z per the task template), production-smoke.md recording that deploy is deliberately deferred pending the user's separate go-ahead (asked directly; declined for now), and the git status/log/diff snapshots as of this point in the branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live closeout per independent review: character-chat was still v6 on sfarcofvqfeobtcizxyv (pre-W5), so the P0-3 fix was not actually live despite being merged and tested. This commit records the resolution, no source code changed: - character-chat redeployed v6 -> v7 (only function depending on the changed shared files, per dependency-graph check; submit-turn and replay-branch untouched). verify_jwt unchanged. Downloaded deployed source verified byte-identical to committed source. - Actual Cloudflare Pages production deployed from HEAD 9108b02, confirmed via `wrangler pages deployment list` (Environment: Production, Branch: main, not inferred from URL). - Remember-in-story fix proven end-to-end against real backend + real DeepSeek generation: real guest, real VI story respecting the configured address register, real Character Chat, real promotion, server truth confirmed via direct API call, and confirmed to survive an actual browser reload. Also verified: no duplicate canon_facts row on repeat promotion, cross-guest access denied, unauthenticated access denied. - Full 14-item production smoke checklist recorded, 5 real provider calls used (within the 8-call budget). Updated: production-smoke.md (full live-verification record), HANDOFF.md (corrected verdict, IMPLEMENTATION_CODE_HEAD vs FINAL_PR_HEAD distinction), ci-results.txt (exact-head CI run appended), git-status.txt, git-log.txt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LW-W5-R1-R1 blocker 2. Character Chat's open()/send() threw the raw
Postgres exception text ("chat thread: run not owned" etc.) on a
cross-owner access attempt, which matched neither the "unauthenticated"
nor "forbidden" literal strings the edge function's catch block checks
for, so it fell through to a generic 500 internal_error. Access was
already correctly denied either way (confirmed live in the prior
closeout: no data crossed the guest boundary) — only the error shape
was wrong.
Every cross-owner RPC guard in the character-chat migrations already
raises SQLSTATE 42501 (insufficient_privilege) consistently. Added
ownership-error.ts (Deno-import-free, unit tested) mapping that code to
the literal "forbidden" message loadThread()'s own inline check already
used — reused at both open()'s and send()'s RPC call sites. No schema
change, no ownership check weakened, just a more precise error
classification using the SQLSTATE Postgres already provides instead of
matching against exception message text.
Also adds a regression test for LW-W5-R1-R1 blocker 1 (the reported
React hydration console error): after exhaustive independent
reproduction across 5 methods (local dev server, exact new-story submit
flow, and three passes against real production — including the one
sequence not previously tried, switching UI language mid-session before
a real submission) the error could not be reproduced. Current
client-side navigation (Home<->Setup, EN/VI switch+persistence, real
browser back/forward) is verified console-clean; a permanent Playwright
assertion now locks this in as a regression guard.
168/168 unit tests, 12/12 e2e (both projects), typecheck/lint clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records the second live-closeout pass: character-chat redeployed v7->v8 with the cross-guest 42501->403 mapping fix, Cloudflare production redeployed from 5bfcf75 and reconfirmed via deployment metadata, hydration investigation findings (not reproducible under 5 independent methods), and the corrected verdict distinguishing IMPLEMENTATION_CODE_HEAD (5bfcf75, last source change) from FINAL_PR_HEAD (this evidence commit — its exact SHA is reported in the closing chat message and git-log.txt/git-status.txt rather than predicted in HANDOFF.md, avoiding the exact staleness this instruction was issued to fix). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PNHD
force-pushed
the
feature/lw-w5-product-ux
branch
from
August 12, 2026 06:53
8db0572 to
32b3280
Compare
PNHD
marked this pull request as ready for review
August 12, 2026 06:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final closeout — LW-W5-R1
W5 web UX completion is fully live-verified and ready to merge.
Key outcomes:
/previewdemoted to internal QA403 forbiddenwhile preserving isolationValidation:
5bfcf750ddb5fce3c4cdb804482299da154016cd32b328005baafd51cd113e8753e6c2239713224431571063852— SUCCESScharacter-chat: v8 ACTIVE,verify_jwt: true53c0b39a-c06f-444a-9ccd-25b819490975https://lorewish.pages.devKnown non-blocking follow-ups: motion polish and manual assistive-technology pass.
Independent review completed. W5_R1_PASS.