feat(ui): tokenize soft-danger callouts for dark themes (LC-215) #257

Merged
nrupard merged 3 commits from feat/lc-215-danger-surface-tokens into main 2026-05-29 17:15:00 +02:00
Owner

Summary

The "Delete account" panel and 19 other soft-danger callouts (admin error banners, enclave/home welcome banners, the bridge status chip, the DND chip, the blocked-list error) stayed bright red-on-pale-red in dark / hc-dark themes because they hardcoded Tailwind's bg-red-50 / border-red-300 / text-red-800 classes. Reported by user on staging: "the Delete account section looks very ugly in dark mode. It stays bright, even when the rest of the page is dark."

Add two new palette tokens (--danger-surface, --danger-border) per theme in server/assets/main.css, expose them via server/tailwind.config.js as bg-danger-surface / border-danger-border, and sweep the 20 sites onto the tokens.

Per-theme values:

Token Light Dark hc-light hc-dark
--danger-surface #fef2f2 (red-50) #450a0a (red-950) #fee2e2 (red-100) #0a0000
--danger-border #fca5a5 (red-300) #7f1d1d (red-900) #991b1b (red-800) #fecaca (red-200)

Scope carve-out

layout.html:101 call-stop button (bg-white text-red-700 hover:bg-red-50) sits inside the parent bg-red-600 active-control banner at layout.html:99, which is in the deferred call-UI palette cluster (LC-189 wrap-up). Tokenizing the child button while the parent stays hardcoded creates a slate-on-bright-red contrast inversion in dark mode. The pair will be swept together when the call-UI palette ticket lands.

Similarly out of scope: voice/page.html:34, layout.html:39,92 solid-fill call buttons (bg-red-600) — call-UI palette.

Test plan

  • cargo check -p lets-chat-server clean.
  • just build-css emits .bg-danger-surface, .border-danger-border, .hover\:bg-danger-surface utility rules.
  • grep --recursive --extended-regexp 'bg-red-(50|100|200|300)|border-red-(100|200|300|400)|text-red-(700|800|900)' server/templates/ returns only the layout.html:101 carve-out hit.
  • Visual smoke on staging post-merge under all four themes (light, dark, hc-light, hc-dark):
    • /settings Delete account panel reads as danger zone, not as bright glow blob in dark / hc-dark.
    • /admin/settings with a forced error: banner themes correctly.
    • /admin/bridges with a stopped bridge: status chip themes.
    • /home welcome flash error themes.
    • DND active chip + blocked-list error themes.
## Summary The "Delete account" panel and 19 other soft-danger callouts (admin error banners, enclave/home welcome banners, the bridge status chip, the DND chip, the blocked-list error) stayed bright red-on-pale-red in dark / hc-dark themes because they hardcoded Tailwind's `bg-red-50` / `border-red-300` / `text-red-800` classes. Reported by user on staging: "the Delete account section looks very ugly in dark mode. It stays bright, even when the rest of the page is dark." Add two new palette tokens (`--danger-surface`, `--danger-border`) per theme in `server/assets/main.css`, expose them via `server/tailwind.config.js` as `bg-danger-surface` / `border-danger-border`, and sweep the 20 sites onto the tokens. Per-theme values: | Token | Light | Dark | hc-light | hc-dark | |---|---|---|---|---| | `--danger-surface` | `#fef2f2` (red-50) | `#450a0a` (red-950) | `#fee2e2` (red-100) | `#0a0000` | | `--danger-border` | `#fca5a5` (red-300) | `#7f1d1d` (red-900) | `#991b1b` (red-800) | `#fecaca` (red-200) | ## Scope carve-out `layout.html:101` call-stop button (`bg-white text-red-700 hover:bg-red-50`) sits inside the parent `bg-red-600` active-control banner at `layout.html:99`, which is in the deferred call-UI palette cluster (LC-189 wrap-up). Tokenizing the child button while the parent stays hardcoded creates a slate-on-bright-red contrast inversion in dark mode. The pair will be swept together when the call-UI palette ticket lands. Similarly out of scope: `voice/page.html:34`, `layout.html:39,92` solid-fill call buttons (`bg-red-600`) — call-UI palette. ## Test plan - [x] `cargo check -p lets-chat-server` clean. - [x] `just build-css` emits `.bg-danger-surface`, `.border-danger-border`, `.hover\:bg-danger-surface` utility rules. - [x] `grep --recursive --extended-regexp 'bg-red-(50|100|200|300)|border-red-(100|200|300|400)|text-red-(700|800|900)' server/templates/` returns only the `layout.html:101` carve-out hit. - [ ] Visual smoke on staging post-merge under all four themes (light, dark, hc-light, hc-dark): - [ ] `/settings` Delete account panel reads as danger zone, not as bright glow blob in dark / hc-dark. - [ ] `/admin/settings` with a forced error: banner themes correctly. - [ ] `/admin/bridges` with a stopped bridge: status chip themes. - [ ] `/home` welcome flash error themes. - [ ] DND active chip + blocked-list error themes.
feat(ui): tokenize soft-danger callouts for dark themes (LC-215)
All checks were successful
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 6s
Check / clippy + fmt + tests (pull_request) Successful in 2m7s
bd262a4c33
LC-190 introduced the semantic color palette (`--surface`, `--content`, `--accent`, `--danger`, etc.) and LC-193 swept most templates onto it, but the soft-danger callout pattern (red-50 / red-100 surface + red-200 / red-300 border + red-700 / red-800 heading and copy) survived in 20 templates. Tailwind's hardcoded `red-*` shades are constant across themes, so on `[data-theme="dark"]` and `[data-theme="hc-dark"]` these panels stay bright red-on-pale-red while the rest of the page is slate-on-near-black. The most visible offender is the "Delete account" panel at the bottom of `/settings`; the same pattern repeats across 12 admin/room/settings error banners, 3 enclave/home welcome banners, the settings DND chip + sessions-revoke button + password-form error, the blocked-users list error, and the bridge status chip.

Two new tokens (`--danger-surface`, `--danger-border`) added per theme in `server/assets/main.css`:
- light: `#fef2f2` (red-50) / `#fca5a5` (red-300) — preserves the existing visual.
- dark: `#450a0a` (red-950) / `#7f1d1d` (red-900) — dim red-tinted panel sits visibly against `--surface: #0f172a` slate-900.
- hc-light: `#fee2e2` (red-100) / `#991b1b` (red-800) — bright surface, hard outline; matches hc posture.
- hc-dark: `#0a0000` / `#fecaca` (red-200) — near-black surface, maximal-contrast outline; border carries the visual weight on pure-black ground.

`server/tailwind.config.js` exposes `bg-danger-surface` + `border-danger-border` Tailwind utilities mapped to the two new vars; the existing `text-danger` already pulls `--danger`. Built CSS verified (the three new utility rules emit correctly under bun + tailwind).

Sites swept:
- 12 admin/room error banners (`admin/{bots, bridges, backup_restore, branding, slash_commands, outgoing_webhooks, link_filter, settings}.html`, `room/{webhooks, email_inboxes, feeds}.html`, `settings/api_tokens.html`): `bg-red-50 text-danger text-sm` -> `bg-danger-surface border border-danger-border text-danger text-sm`.
- 4 enclave/home welcome banners (`enclave/{discover, settings, page, branding}.html`, `home/welcome.html`): `border border-red-200 bg-red-50 text-red-800` -> `border border-danger-border bg-danger-surface text-danger`.
- `settings/blocked.html:35` and `settings/page.html:266`: `bg-red-100 text-red-800` filled error -> `bg-danger-surface border border-danger-border text-danger`.
- `settings/page.html:200` DND chip: `bg-red-100 text-danger` -> `bg-danger-surface border border-danger-border text-danger`.
- `settings/page.html:309` sessions-revoke button: `border-red-300 hover:bg-red-50` -> `border-danger-border hover:bg-danger-surface`.
- `settings/page.html:353-367` Delete account panel: all 7 `bg-red-50`/`border-red-300`/`text-red-800` hits -> tokens. Button at L369 unchanged (already uses `bg-danger text-danger-content`).
- `admin/bridges.html:84` errored-bridge status chip: `bg-red-100 text-danger` -> `bg-danger-surface border border-danger-border text-danger`.

Scope carve-out (NOT swept here): `layout.html:101` call-stop button (`bg-white text-red-700 hover:bg-red-50`) sits inside the parent `bg-red-600` active-control banner at `layout.html:99`. The parent banner is in the deferred call-UI palette cluster (LC-189 wrap-up). Tokenizing the child button while the parent stays hardcoded creates a slate-on-bright-red contrast inversion in dark mode. The pair will be swept together when the call-UI palette ticket lands. Similarly, `voice/page.html:34`, `layout.html:39,92` solid-fill call buttons (`bg-red-600 hover:bg-red-700`) are call-UI palette and out of scope.

Verified: `cargo check -p lets-chat-server` clean; `just build-css` produces the three new utility classes; grep confirms no `bg-red-(50|100|200|300)` / `border-red-(100|200|300|400)` / `text-red-(700|800|900)` remains outside the layout.html:101 carve-out.

#LC-215
fix(ui): code-review fixes for LC-215 danger-surface sweep
All checks were successful
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / TruffleHog (push) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 7s
Check / clippy + fmt + tests (pull_request) Successful in 5m12s
6d2acfe579
Six findings from PR #257 self-review, addressed in one follow-up commit so the PR ships clean.

1. `server/assets/main.css` dark theme `--danger-surface: #450a0a` was identical to `--danger-content: #450a0a` (red-950 hex). The two never compose in any current site, but `bg-danger-surface text-danger-content` would silently render invisible text. Nudge the surface to `#3a0808` so the latent footgun cannot bite a future caller.

2. `server/assets/main.css` hc-dark theme `--danger-surface: #0a0000` was indistinguishable from `--surface: #000000` on most displays (only 3.9% R channel). The border (`#fecaca` red-200) was carrying all the visual; the surface contributed nothing. Switch to `#0a0a0a` (the same value `--surface-elevated` already uses in hc-dark) so the panel reads as a slightly-elevated surface, matching the rest of the hc-dark palette's tonal convention.

3. `server/templates/admin/bridges.html:84` errored-bridge status chip had `border border-danger-border` added in the original sweep. Sibling chips (healthy `bg-green-100`, stale `bg-amber-100`) have no border, so the danger chip stood out disproportionately heavier than its peers on a 10px-tall element. Drop the border; keep `bg-danger-surface text-danger`.

4. `server/templates/settings/page.html:200` DND-active chip had the same border-on-chip issue at `px-1.5 py-0.5`. Same fix: drop the border, keep `bg-danger-surface text-danger`.

5. `server/templates/partials/sidebar.html:52` Admin/Moderate sidebar link used `text-red-600` (hardcoded blue-600 sibling shade for "danger surface"). Escaped the original sweep because the AC regex matched only `text-red-(700|800|900)`, but it is the same drift class - it doesn't theme. Swap to `text-danger`.

6. `server/templates/status/picker.html:42` JS `c.classList.toggle('text-red-600', n >= 45)` on the custom-status char counter (turns the counter red when the user is approaching the 50-char cap). Same drift class as the sidebar link, same fix: `text-danger`. Tailwind's content glob includes `*.html` so the literal class string in the inline `oninput` attribute is JIT-picked up.

Verified: `cargo check -p lets-chat-server` clean, `just build-css` rebuilds the utility CSS, `grep --recursive --extended-regexp 'bg-red-(50|100|200|300)|border-red-(100|200|300|400)|text-red-(600|700|800|900)|hover:bg-red-50' server/templates/` returns only the documented `layout.html:101` call-UI carve-out.

#LC-215
fix(ui): tokenize outbox.js failed-send label (LC-215)
All checks were successful
check-secrets / TruffleHog (pull_request) Successful in 3s
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Nosey parker (pull_request) Successful in 3s
check-secrets / Kingfisher (pull_request) Successful in 6s
Check / clippy + fmt + tests (pull_request) Successful in 1m58s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 7s
Create release / Create release from merged PR (pull_request) Has been skipped
6de86d7e36
Second-pass review of PR #257 caught one more `text-red-600` site that escaped the original LC-215 sweep: `server/assets/outbox.js:125` builds an HTML span as `'<span class="text-red-600">Failed (...)'` for each failed-send entry in the offline outbox status panel. Same drift class as the sidebar + status-picker fixes; missed by the original sweep grep because that grep only covered `server/templates/`.

Tailwind's content glob includes `./assets/**/*.js` so the literal class string in the JS is JIT-picked up; both `text-red-600` and `text-danger` round-trip through the build.

#LC-215

The reviewer also flagged `admin/bridges.html:82` healthy chip (`bg-green-100 text-emerald-700`) and `admin/bridges.html:86` stale chip (`bg-amber-100 text-warning`) as scope-adjacent: in dark mode the now-fixed errored chip goes correctly dim while the success/warning siblings stay bright hardcoded surfaces. Out of LC-215 scope; would need parallel `--success-surface` / `--warning-surface` tokens to tokenize the surface, which is a larger sweep across ~20 admin templates that use `bg-green-50` / `bg-amber-50` callouts.

Single-line text-only swap of `text-emerald-700` -> `text-success` on the healthy chip is rejected: `text-success` light (`#16a34a`) on `bg-green-100` (`#dcfce7`) is ~3.4:1 contrast, which fails WCAG AA (4.5:1) for the bold 10px chip text; the current `text-emerald-700` is ~6.7:1. The right fix is the parallel surface sweep, not a one-line text-color swap that degrades accessibility.
nrupard deleted branch feat/lc-215-danger-surface-tokens 2026-05-29 17:15:01 +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!257
No description provided.