Apply the prototype new look across Let's Chat (phased) #539

Merged
vas2000-work merged 8 commits from feat/lc-ui-port into main 2026-07-11 06:32:12 +02:00
Owner

Applies the new look from the prototype (~/projects/lets-chat-prototype) across the app. Phased; this PR grows as regions land. Each phase is its own commit so it stays reviewable.

Phase 1 - global CSS foundation (this commit)

Accessibility + surface-separation fixes that the prototype's contrast-check.mjs (WCAG text contrast AND adjacent-surface separation, all 7 palettes x 4 modes) surfaced. The app's existing contrast-check.mjs (3 pairs x 28) still passes 84/84; these fixes cover the pairs it does not test.

  • --content-subtle darkened in the five light palettes below the 3.0 floor (cobalt, ink-ice, arctic, deep-sea, royal-navy).
  • blue-harbor light --accent-surface-content raised to blue-700 (reaction-pill / unread-banner text, was 4.24 vs AA 4.5).
  • arctic-light --border strengthened so cards separate from the near-white page.
  • --sidebar-elevated lightened in blue-harbor-dark / ink-ice / amethyst so the sidebar hover + active-count state is visible.
  • Rail-to-sidebar seam (#switcher border-right) so the rail reads as distinct chrome even when both are near-black (identical #000 in high-contrast, where the boundary vanished).
  • Global scrollbars reworked to thin, button-less, thumb-only, hover-reveal, theme-neutral.

Rationale and per-token detail are in the prototype's DEVIATIONS.md.

Remaining phases (landing on this branch)

  1. Chat-surface reconciliation (rail geometry, sidebar, header, timeline, composer, thread+details) to the prototype's spacing/tokens.
  2. Settings / Appearance (palette-swatch picker, mode control, UI scaling), room Info, Home.
  3. Extrapolate the interactive-component look to admin / auth / onboarding / modals.

#LC-569

Applies the new look from the prototype (`~/projects/lets-chat-prototype`) across the app. Phased; this PR grows as regions land. Each phase is its own commit so it stays reviewable. ## Phase 1 - global CSS foundation (this commit) Accessibility + surface-separation fixes that the prototype's `contrast-check.mjs` (WCAG text contrast AND adjacent-surface separation, all 7 palettes x 4 modes) surfaced. The app's existing `contrast-check.mjs` (3 pairs x 28) still passes 84/84; these fixes cover the pairs it does not test. - `--content-subtle` darkened in the five light palettes below the 3.0 floor (cobalt, ink-ice, arctic, deep-sea, royal-navy). - blue-harbor light `--accent-surface-content` raised to blue-700 (reaction-pill / unread-banner text, was 4.24 vs AA 4.5). - arctic-light `--border` strengthened so cards separate from the near-white page. - `--sidebar-elevated` lightened in blue-harbor-dark / ink-ice / amethyst so the sidebar hover + active-count state is visible. - Rail-to-sidebar seam (`#switcher` border-right) so the rail reads as distinct chrome even when both are near-black (identical `#000` in high-contrast, where the boundary vanished). - Global scrollbars reworked to thin, button-less, thumb-only, hover-reveal, theme-neutral. Rationale and per-token detail are in the prototype's `DEVIATIONS.md`. ## Remaining phases (landing on this branch) 2. Chat-surface reconciliation (rail geometry, sidebar, header, timeline, composer, thread+details) to the prototype's spacing/tokens. 3. Settings / Appearance (palette-swatch picker, mode control, UI scaling), room Info, Home. 4. Extrapolate the interactive-component look to admin / auth / onboarding / modals. #LC-569
style(theme): contrast + surface-separation fixes across all palettes
All checks were successful
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 7s
check-secrets / TruffleHog (pull_request) Successful in 7s
Check / clippy + fmt + tests (pull_request) Successful in 3m30s
07eeeb97b0
Applies the accessibility fixes surfaced by the prototype's contrast + surface-separation audit so all seven palettes in all four modes pass both WCAG text contrast and adjacent-surface separation, not just the three pairs the existing contrast-check covers.

Darkens tertiary text (--content-subtle) in the five light palettes that fell below the 3.0 large-text floor (cobalt, ink-ice, arctic, deep-sea, royal-navy); raises blue-harbor light --accent-surface-content to blue-700 so the reaction-pill and unread-banner text clears AA (was 4.24); strengthens arctic-light --border so cards separate from the near-white page; lightens --sidebar-elevated in blue-harbor-dark, ink-ice, and amethyst so the sidebar hover / active-count state is visible.

Adds a rail-to-sidebar seam (#switcher border-right) so the rail reads as distinct chrome from the sidebar even when both are near-black; in high-contrast modes they are identical #000 and the boundary otherwise vanishes.

Reworks the global scrollbars to thin, button-less, thumb-only, appearing on hover, in a neutral translucent grey that reads on every palette and mode.

#LC-569
#LC-553
feat(theme): server-persisted UI scaling (compact/default/large/xl)
All checks were successful
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / TruffleHog (push) Successful in 10s
check-secrets / TruffleHog (pull_request) Successful in 10s
check-secrets / Nosey parker (push) Successful in 11s
check-secrets / Kingfisher (pull_request) Successful in 12s
check-secrets / Kingfisher (push) Successful in 13s
Check / clippy + fmt + tests (pull_request) Successful in 3m16s
c8f075f95a
Adds a third appearance axis alongside mode and palette: `theme_scale`, matching the prototype's Scaling control. The whole app is rem-based (Tailwind utilities plus the .lc-* layer), so the scale is applied as a root font-size on html[data-scale], which scales type and spacing together rather than only text.

Plumbed exactly like theme_palette: migration 0038 adds the nullable `theme_scale` column (NULL = default), the column joins every users SELECT and the row projection, `set_user_theme_scale` persists it, `User::theme_scale_or_default()` validates it, the locale middleware syncs an `lc-scale` cookie from the saved pref so it follows the account across devices, and the base.html bootstrap stamps `data-scale` before paint so there is no flash. The appearance form submits it and a POST /settings/scale endpoint persists an instant pick, mirroring /settings/palette.

This is distinct from the existing device-local `lc-textsize` (LC-327), which only scales the message body (.lc-md); the two compose.

#LC-569
#LC-553
style(chat): reconcile the chat surface to the prototype
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / TruffleHog (pull_request) Successful in 6s
check-secrets / Kingfisher (push) Successful in 8s
check-secrets / Kingfisher (pull_request) Successful in 7s
Check / clippy + fmt + tests (pull_request) Successful in 3m38s
3211d38f5a
Brings the sidebar, timeline, reactions, and composer onto the prototype's exact geometry and tones so the running app matches the reference.

Sidebar: the active room row becomes a solid --accent fill with --accent-content text, replacing the raised-tint plus accent-text plus left-bar treatment (the comps and the prototype both show a solid fill). The unread pill is now a neutral count pill that inverts to accent-content on the active row so it stays legible on the fill; inside .lc-sidebar the surface tokens already resolve to the sidebar ramp, so the pill picks up the navy automatically.

Timeline: message rows become inset rounded cards (12px padding, 10px radius) with the 20px side gutter moved onto the scroll container; avatars go 24px to 36px with the content indent and the follow-up time gutter widened to match (48px and 60px); the author name goes to bold. The day divider drops its bordered uppercase pill for plain text between hairlines, and the unread separator switches from danger red to the accent "new messages" divider, matching the comps.

Reactions sit on the elevated surface at rest (sunken on hover) with the prototype's spacing. Composer toolbar buttons go 28px to 30px and the send button 36px to 34px. The message hover-action buttons take the prototype's 28x26.

#LC-569
#LC-553
test(dev): mirror the chat surface into the theme gallery
Some checks failed
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 7s
check-secrets / Kingfisher (pull_request) Successful in 12s
check-secrets / Nosey parker (push) Successful in 12s
check-secrets / Kingfisher (push) Successful in 13s
check-secrets / TruffleHog (push) Successful in 13s
Check / clippy + fmt + tests (pull_request) Has been cancelled
0374084708
The room view needs a real Bunyip login, so the mock-SSO dev overlay cannot reach it and the components reconciled to the prototype had no unauthenticated way to be checked. Adds them to /dev/theme-gallery, which already exists to render every shared component across all seven palettes and four modes: the sidebar active row and count pill, the day and unread dividers, a message row at the new avatar and indent geometry with reaction pills and the hover-action bar, and the composer toolbar and send button.

This makes the whole ported chat surface screenshot-verifiable across all 28 states without auth, and keeps it that way for future palette or component work.

#LC-569
style(room): give the room-info page the prototype hero
All checks were successful
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 18s
check-secrets / TruffleHog (pull_request) Successful in 20s
Check / clippy + fmt + tests (pull_request) Successful in 7m16s
3bc4a41729
Replaces the compact title bar with the prototype's hero: an accent-surface hash badge beside the room name and topic, with the back-to-chat action on the right. Keeps the existing About / Pinned / Files / Preferences tab shell, which carries more than the prototype's info panel does.

Also records the Phase 5 audit result: outside the chat surface the templates carry no hardcoded palette colors or raw hex (the only hex matches are emoji entities), so the corrected tokens from the first commit already propagate the new look to admin, auth, onboarding, and the modals with no further change needed.

#LC-569
#LC-553
Author
Owner

Port complete - all phases landed and verified

2. UI scaling (c8f075f9) - theme_scale (compact/default/large/xl) plumbed exactly like theme_palette: migration 0038, the column in every users SELECT + row projection, setter, validation, an lc-scale cookie synced by the locale middleware (follows the account across devices), a no-flash data-scale stamp in the base.html bootstrap, POST /settings/scale, and the appearance-form control. Applied as a root rem scale so type and spacing scale together. Distinct from the device-local lc-textsize, which only scales the message body.

3. Chat surface (3211d38f) - reconciled to the prototype. Active room row is now a solid --accent fill with --accent-content text (replacing the raised tint + accent text + left bar), with the unread pill inverting on it so it stays legible. Message rows become inset rounded cards (12px padding, 10px radius) with the 20px gutter moved onto the scroll container; avatars 24px to 36px with the content indent and follow-up gutter widened to match; bold author names. The day divider drops its bordered uppercase pill for plain text between hairlines, and the unread separator switches from danger red to the accent "new messages" divider. Reactions sit on the elevated surface at rest. Composer and hover-action button sizes take the prototype's values.

4. Room info hero (3bc4a417) - accent-surface hash badge, name, topic; keeps the existing tab shell.

5. Extrapolation - verified no-op. Outside the chat surface no template carries a hardcoded palette color or raw hex (the only hex matches are emoji entities), so the corrected tokens from phase 1 already carry the new look into admin, auth, onboarding, and the modals with no further change.

How it was verified

The room view needs a real Bunyip login, which the mock-SSO overlay cannot do, so 03740847 mirrors the reconciled chat components into /dev/theme-gallery (which exists precisely to render every shared component across all palettes x modes). Booted against the mock-SSO stack and screenshotted: the solid-accent active row with inverted count pill, the plain day divider, the 36px message row with reaction pills and hover bar, the accent "new messages" divider, and the composer all render correctly in all 28 states (light, dark, hc-light, hc-dark all checked). Migration 0038 applied cleanly on boot. cargo check and the i18n parity test pass; the existing contrast-check still reports 84/84.

Deliberately not ported

  • The prototype's Home dashboard (catch-up / mentions / threads / drafts cards) is a new feature, not a look change - the app's rail Home is the Home enclave. It deserves its own ticket rather than smuggling aggregate queries into a restyle.
  • The red mention badge was kept (the prototype used an accent one). Red is a meaningful escalation over a plain unread count; flagging it rather than silently downgrading it.
## Port complete - all phases landed and verified **2. UI scaling** (`c8f075f9`) - `theme_scale` (compact/default/large/xl) plumbed exactly like `theme_palette`: migration 0038, the column in every users SELECT + row projection, setter, validation, an `lc-scale` cookie synced by the locale middleware (follows the account across devices), a no-flash `data-scale` stamp in the base.html bootstrap, `POST /settings/scale`, and the appearance-form control. Applied as a root rem scale so type and spacing scale together. Distinct from the device-local `lc-textsize`, which only scales the message body. **3. Chat surface** (`3211d38f`) - reconciled to the prototype. Active room row is now a solid `--accent` fill with `--accent-content` text (replacing the raised tint + accent text + left bar), with the unread pill inverting on it so it stays legible. Message rows become inset rounded cards (12px padding, 10px radius) with the 20px gutter moved onto the scroll container; avatars 24px to 36px with the content indent and follow-up gutter widened to match; bold author names. The day divider drops its bordered uppercase pill for plain text between hairlines, and the unread separator switches from danger red to the accent "new messages" divider. Reactions sit on the elevated surface at rest. Composer and hover-action button sizes take the prototype's values. **4. Room info hero** (`3bc4a417`) - accent-surface hash badge, name, topic; keeps the existing tab shell. **5. Extrapolation - verified no-op.** Outside the chat surface no template carries a hardcoded palette color or raw hex (the only hex matches are emoji entities), so the corrected tokens from phase 1 already carry the new look into admin, auth, onboarding, and the modals with no further change. ## How it was verified The room view needs a real Bunyip login, which the mock-SSO overlay cannot do, so `03740847` mirrors the reconciled chat components into `/dev/theme-gallery` (which exists precisely to render every shared component across all palettes x modes). Booted against the mock-SSO stack and screenshotted: the solid-accent active row with inverted count pill, the plain day divider, the 36px message row with reaction pills and hover bar, the accent "new messages" divider, and the composer all render correctly in **all 28 states** (light, dark, hc-light, hc-dark all checked). Migration 0038 applied cleanly on boot. `cargo check` and the i18n parity test pass; the existing contrast-check still reports 84/84. ## Deliberately not ported - The prototype's **Home dashboard** (catch-up / mentions / threads / drafts cards) is a new *feature*, not a look change - the app's rail Home is the Home enclave. It deserves its own ticket rather than smuggling aggregate queries into a restyle. - The **red mention badge** was kept (the prototype used an accent one). Red is a meaningful escalation over a plain unread count; flagging it rather than silently downgrading it.
test(chat): regenerate message fixtures for the new row geometry
All checks were successful
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 9s
check-secrets / TruffleHog (pull_request) Successful in 10s
check-secrets / Kingfisher (pull_request) Successful in 14s
Check / clippy + fmt + tests (pull_request) Successful in 6m28s
0aec7dc4c0
The four LC-77 render fixtures assert the exact message markup, so reconciling the row to the prototype (36px avatar, 12px padding with a 10px radius, 48px content indent, bold author) diverged them. Regenerated with FIXTURE_WRITE=1; the diff is exactly that geometry change and nothing else.

Also forwards FIXTURE_WRITE through the dockerized ./dev/cargo wrapper. The wrapper only passed a fixed set of env vars, so the regeneration path the fixture test itself prints ("Run with FIXTURE_WRITE=1 to regenerate") could not actually be used through it. Extending the wrapper rather than reaching around it with a raw docker run.

#LC-569
Merge remote-tracking branch 'origin/main' into feat/lc-ui-port
Some checks failed
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Kingfisher (pull_request) Successful in 6s
check-secrets / Nosey parker (push) Successful in 7s
check-secrets / Kingfisher (push) Successful in 8s
Check / clippy + fmt + tests (pull_request) Has been cancelled
cee7ac0ccb
# Conflicts:
#	server/assets/main.css
#	server/templates/room/message.html
Merge branch 'main' into feat/lc-ui-port
All checks were successful
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / Kingfisher (push) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 10s
check-secrets / TruffleHog (pull_request) Successful in 10s
check-secrets / Kingfisher (pull_request) Successful in 10s
Check / clippy + fmt + tests (pull_request) Successful in 6m28s
Create release / Create release from merged PR (pull_request) Has been skipped
850a3f5d38
vas2000-work scheduled this pull request to auto merge when all checks succeed 2026-07-11 06:25:54 +02:00
Author
Owner

Merged main (LC-558 / LC-574 overlap) + fixed a broken gate

Rebased onto the newer main, which had landed overlapping mockup work: LC-558 (timeline polish) and LC-574 (composer one-row restructure). Both touch the same files as this branch.

Conflicts resolved in favour of main. Where this branch and the landed, reviewed work disagreed, I kept main's version rather than silently reverting an approved PR inside a merge. That leaves three deliberate divergences from the prototype, flagged for a call:

prototype / this branch kept (from main)
Author name weight font-bold (700) font-semibold (600) - LC-558 Option A
Day divider plain text between hairlines bordered uppercase pill, contrast-fixed - LC-558
Composer buttons 30px toolbar / 34px send 32px - part of LC-574's coherent one-row restructure

Worth noting on the day divider: LC-558's stated reason for keeping the pill was WCAG AA on the label. The plain-text form now clears AA anyway, because this branch's first commit darkened --content-subtle in the light palettes. So that constraint no longer forces the pill - it is purely a look call now.

Everything non-conflicting from this branch survived the merge and is verified present: 36px avatars + indent, solid-accent active row, neutral count pill, rail seam, accent unread divider, scrollbars, UI scaling, the token fixes.

A real gate that was failing

The full test suite had never been run on this branch - only cargo check and the i18n test. It was red: four golden-file fixtures in lc77_webhook_render_fixture.rs assert the rendered message markup, and the 24px-to-36px avatar change invalidated them. Regenerated (diff is exactly the intended geometry, nothing else).

Regenerating them required extending dev/cargo to forward FIXTURE_WRITE: the wrapper only passes a fixed allowlist of env vars, so nobody could regenerate these fixtures through it at all. One line, and the documented FIXTURE_WRITE=1 path in the test now actually works.

Gates now

  • Full suite: 179 test binaries ok, 0 failures
  • just check (fmt + clippy): clean
  • contrast-check.mjs: 84/84
  • Migration 0038 applies cleanly; chat surface verified live in all 28 palette x mode states via /dev/theme-gallery

Say the word on any of the three divergences and I will align them in a follow-up commit.

## Merged `main` (LC-558 / LC-574 overlap) + fixed a broken gate Rebased onto the newer `main`, which had landed overlapping mockup work: **LC-558** (timeline polish) and **LC-574** (composer one-row restructure). Both touch the same files as this branch. **Conflicts resolved in favour of `main`.** Where this branch and the landed, reviewed work disagreed, I kept `main`'s version rather than silently reverting an approved PR inside a merge. That leaves three deliberate divergences from the prototype, flagged for a call: | | prototype / this branch | kept (from `main`) | |---|---|---| | Author name weight | `font-bold` (700) | `font-semibold` (600) - LC-558 Option A | | Day divider | plain text between hairlines | bordered uppercase pill, contrast-fixed - LC-558 | | Composer buttons | 30px toolbar / 34px send | 32px - part of LC-574's coherent one-row restructure | Worth noting on the day divider: LC-558's stated reason for keeping the pill was WCAG AA on the label. The plain-text form now clears AA anyway, because this branch's first commit darkened `--content-subtle` in the light palettes. So that constraint no longer forces the pill - it is purely a look call now. Everything non-conflicting from this branch survived the merge and is verified present: 36px avatars + indent, solid-accent active row, neutral count pill, rail seam, accent unread divider, scrollbars, UI scaling, the token fixes. ## A real gate that was failing The full test suite had never been run on this branch - only `cargo check` and the i18n test. It was **red**: four golden-file fixtures in `lc77_webhook_render_fixture.rs` assert the rendered message markup, and the 24px-to-36px avatar change invalidated them. Regenerated (diff is exactly the intended geometry, nothing else). Regenerating them required extending `dev/cargo` to forward `FIXTURE_WRITE`: the wrapper only passes a fixed allowlist of env vars, so **nobody could regenerate these fixtures through it at all**. One line, and the documented `FIXTURE_WRITE=1` path in the test now actually works. ## Gates now - Full suite: **179 test binaries ok, 0 failures** - `just check` (fmt + clippy): clean - `contrast-check.mjs`: 84/84 - Migration 0038 applies cleanly; chat surface verified live in all 28 palette x mode states via `/dev/theme-gallery` Say the word on any of the three divergences and I will align them in a follow-up commit.
vas2000-work deleted branch feat/lc-ui-port 2026-07-11 06:32:12 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!539
No description provided.