Skip to content

Commit 7c2abd6

Browse files
alari76claude
andauthored
fix: implement UI style audit recommendations (prose, contrast, palette) (#508)
Implements steps 1-3 of the 2026-06-11 UI style audit: - Add full markdown hierarchy to .prose-themed/.prose-invert (heading scale, list markers, blockquotes, hr) — the prose classes were inert because @tailwindcss/typography is not installed - Darken light-mode terminal tokens (neutral-5/6, primary-5, accent-5/6, success-5) to meet WCAG AA on the cream background - Remove the light-mode font-weight:465 hack (no-op for static Lato) - Delete the unused info color scale (3 copies, ~40 tokens) - Disambiguate exit/restart banners from notifications (neutral text) - Warm the syntax highlighting palette in both themes (replaces cool VS Code blues; softens pure #0000ff/#a31515 in light mode) - Add faint warm tint to user bubbles in dark mode; brighter dark-mode links; stronger scroll-to-bottom button elevation - Introduce semantic alias tokens (page/surface/edge/ink) that resolve through the scale vars in every override scope - Lift dark-mode chrome to #11181a so the cool-chrome/warm-content split is actually visible Verified by rendering ChatView/InputBar fixtures in headless Chromium in both themes and re-measuring contrast ratios. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bff219c commit 7c2abd6

2 files changed

Lines changed: 134 additions & 79 deletions

File tree

src/components/ChatView.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,14 @@ function parseApiError(text: string): { prefix: string; errorType: string; messa
6262

6363
/** Renders system banners (init, error, exit, notification) with color-coded dot and optional API error formatting. */
6464
function SystemMessage({ msg }: { msg: ChatMessage & { type: 'system' } }) {
65+
// Notifications keep the gold (attention-seeking); exit/restart are
66+
// informational session-lifecycle events and use the neutral treatment so
67+
// they're visually distinct from notifications (gold-on-gold was ambiguous).
6568
const colorClass = msg.subtype === 'init'
6669
? 'text-success-5'
6770
: msg.subtype === 'notification'
6871
? 'text-primary-5'
69-
: msg.subtype === 'exit' || msg.subtype === 'restart'
70-
? 'text-warning-5'
71-
: msg.subtype === 'info'
72+
: msg.subtype === 'exit' || msg.subtype === 'restart' || msg.subtype === 'info'
7273
? 'text-neutral-5'
7374
: 'text-error-5'
7475

@@ -602,7 +603,7 @@ export function ChatView({ messages, fontSize, disabled, planningMode, activityL
602603
{showScrollButton && (
603604
<button
604605
onClick={scrollToBottom}
605-
className="absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full bg-neutral-8/90 p-2 text-neutral-3 shadow-lg transition hover:bg-neutral-7 hover:text-neutral-1"
606+
className="absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full bg-neutral-8 p-2 text-neutral-2 shadow-xl shadow-black/40 ring-1 ring-neutral-6/40 transition hover:bg-neutral-7 hover:text-neutral-1"
606607
title="Scroll to bottom"
607608
>
608609
<IconArrowDown size={16} stroke={2} />

src/index.css

Lines changed: 129 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -100,22 +100,22 @@
100100
--color-success-11: #0c2b1a;
101101
--color-success-12: #07180e;
102102

103-
/* Info — cyan-blue */
104-
--color-info-1: #eff8fb;
105-
--color-info-2: #d0edf5;
106-
--color-info-3: #a0d9ea;
107-
--color-info-4: #6dc2da;
108-
--color-info-5: #40a9c8;
109-
--color-info-6: #2d8fb0;
110-
--color-info-7: #247592;
111-
--color-info-8: #1d5e76;
112-
--color-info-9: #17495c;
113-
--color-info-10: #113644;
114-
--color-info-11: #0c242e;
115-
--color-info-12: #07151a;
116-
117103
/* Font family */
118104
--font-sans: 'Lato', sans-serif;
105+
106+
/* ── Semantic aliases ──────────────────────────────────────────────────
107+
Role-based tokens that resolve through the scale variables, so they
108+
automatically follow every override scope (.terminal-area, light mode,
109+
etc.). Prefer these in new/edited components over raw scale steps:
110+
bg-surface, border-edge, text-ink-muted, ... */
111+
--color-page: var(--color-neutral-12); /* app/page background */
112+
--color-surface: var(--color-neutral-11); /* cards, code blocks */
113+
--color-surface-raised: var(--color-neutral-10);/* hovered/raised surfaces */
114+
--color-edge: var(--color-neutral-9); /* default borders */
115+
--color-edge-strong: var(--color-neutral-8); /* emphasized borders */
116+
--color-ink: var(--color-neutral-2); /* primary text */
117+
--color-ink-muted: var(--color-neutral-5); /* secondary text */
118+
--color-ink-faint: var(--color-neutral-6); /* de-emphasized metadata */
119119
}
120120

121121
/* Terminal / chat area — warm gray palette.
@@ -219,20 +219,6 @@
219219
--color-success-10: #1d3f2a;
220220
--color-success-11: #142b1d;
221221
--color-success-12: #0b1811;
222-
223-
/* Info — warmer cyan, text lightened */
224-
--color-info-1: #f3f9f9;
225-
--color-info-2: #daecf0;
226-
--color-info-3: #b2d9e0;
227-
--color-info-4: #87c1cd;
228-
--color-info-5: #5ea8b8;
229-
--color-info-6: #478fa0;
230-
--color-info-7: #387486;
231-
--color-info-8: #2d5d6c;
232-
--color-info-9: #234854;
233-
--color-info-10: #1a353e;
234-
--color-info-11: #12242b;
235-
--color-info-12: #0a1519;
236222
}
237223

238224
/* ── Light mode overrides ────────────────────────────────────────────────── */
@@ -242,8 +228,6 @@
242228
readable accents on light backgrounds. */
243229

244230
[data-theme="light"] {
245-
font-weight: 465;
246-
247231
/* Neutral — inverted: dark text on light backgrounds */
248232
--color-neutral-1: #0f0e0d;
249233
--color-neutral-2: #1b1a18;
@@ -341,20 +325,14 @@
341325
--color-success-10: #d0f2e2;
342326
--color-success-11: #e5f8ef;
343327
--color-success-12: #effaf5;
328+
}
344329

345-
/* Info — inverted */
346-
--color-info-1: #07151a;
347-
--color-info-2: #113644;
348-
--color-info-3: #17495c;
349-
--color-info-4: #1d5e76;
350-
--color-info-5: #247592;
351-
--color-info-6: #2d8fb0;
352-
--color-info-7: #40a9c8;
353-
--color-info-8: #6dc2da;
354-
--color-info-9: #a0d9ea;
355-
--color-info-10: #d0edf5;
356-
--color-info-11: #e5f4fa;
357-
--color-info-12: #eff8fb;
330+
/* Dark mode — lift the chrome (sidebar/top bar) one step above the terminal
331+
so the cool-chrome/warm-content split actually reads; at the raw scale
332+
values both backgrounds rendered as indistinguishable near-black. */
333+
[data-theme="dark"] .app-left-sidebar,
334+
[data-theme="dark"] .app-right-sidebar {
335+
background-color: #11181a;
358336
}
359337

360338
/* Light mode — component-specific background overrides */
@@ -423,14 +401,23 @@
423401
background-color: color-mix(in srgb, var(--color-neutral-8) 80%, transparent);
424402
}
425403

426-
/* Terminal area overrides in light mode */
404+
/* User message bubble — faint warm tint in dark mode so the user's turns
405+
stand apart from assistant text without shouting */
406+
[data-theme="dark"] .terminal-area .user-bubble {
407+
background-color: color-mix(in srgb, var(--color-primary-11) 35%, var(--color-neutral-11));
408+
}
409+
410+
/* Terminal area overrides in light mode.
411+
Steps 5–6 are darkened relative to the plain inverted scale so muted
412+
text (tool summaries, timestamps, request IDs) meets ~4.5:1 on the
413+
cream background. */
427414
[data-theme="light"] .terminal-area {
428415
--color-neutral-1: #0f0e0d;
429416
--color-neutral-2: #1b1a18;
430417
--color-neutral-3: #333131;
431418
--color-neutral-4: #424040;
432-
--color-neutral-5: #777571;
433-
--color-neutral-6: #93918c;
419+
--color-neutral-5: #6b6964;
420+
--color-neutral-6: #7c7a75;
434421
--color-neutral-7: #e4e2de;
435422
--color-neutral-8: #eeebe6;
436423
--color-neutral-9: #f3f1ec;
@@ -442,7 +429,7 @@
442429
--color-primary-2: #584528;
443430
--color-primary-3: #775c35;
444431
--color-primary-4: #977443;
445-
--color-primary-5: #af8958;
432+
--color-primary-5: #8a6a3c; /* darkened for AA on cream (notification text) */
446433
--color-primary-6: #cba557;
447434
--color-primary-7: #be9d5a;
448435
--color-primary-8: #eccc8e;
@@ -455,8 +442,8 @@
455442
--color-accent-2: #1e3c39;
456443
--color-accent-3: #29524d;
457444
--color-accent-4: #356b65;
458-
--color-accent-5: #42847d;
459-
--color-accent-6: #559f97;
445+
--color-accent-5: #356f69; /* darkened for AA on cream (links) */
446+
--color-accent-6: #3d7a72; /* darkened for AA on cream (tool names) */
460447
--color-accent-7: #6cb7af;
461448
--color-accent-8: #93ccc6;
462449
--color-accent-9: #bae1dc;
@@ -494,7 +481,7 @@
494481
--color-success-2: #1d3f2a;
495482
--color-success-3: #275438;
496483
--color-success-4: #326c48;
497-
--color-success-5: #3f8559;
484+
--color-success-5: #327249; /* darkened for AA on cream (init banner) */
498485
--color-success-6: #53a578;
499486
--color-success-7: #66bb8e;
500487
--color-success-8: #8ccfab;
@@ -608,11 +595,45 @@ html, body, #root {
608595
background: var(--color-neutral-6);
609596
}
610597

611-
/* Prose overrides — shared structural rules */
598+
/* Prose overrides — shared structural rules.
599+
Note: @tailwindcss/typography is NOT installed; these rules provide the
600+
full markdown styling (heading scale, list markers, blockquotes) that the
601+
`prose` class would otherwise supply. */
612602
.prose.prose-themed code {
613603
font-family: 'Inconsolata', monospace;
614604
}
615605

606+
.prose.prose-themed h1 { font-size: 1.4em; font-weight: 700; }
607+
.prose.prose-themed h2 { font-size: 1.2em; font-weight: 600; }
608+
.prose.prose-themed h3 { font-size: 1.05em; font-weight: 600; }
609+
.prose.prose-themed h4 { font-size: 1em; font-weight: 600; }
610+
611+
.prose.prose-themed ul { list-style: disc; padding-left: 1.5em; }
612+
.prose.prose-themed ol { list-style: decimal; padding-left: 1.5em; }
613+
.prose.prose-themed li { margin: 0.2em 0; }
614+
.prose.prose-themed li > ul,
615+
.prose.prose-themed li > ol { margin: 0.1em 0; }
616+
617+
.prose.prose-themed blockquote {
618+
border-left: 3px solid var(--tw-prose-quote-borders);
619+
padding-left: 1em;
620+
margin: 0.5em 0;
621+
color: var(--tw-prose-quotes);
622+
}
623+
624+
.prose.prose-themed hr {
625+
border: none;
626+
border-top: 1px solid var(--tw-prose-hr);
627+
margin: 1em 0;
628+
}
629+
630+
.prose.prose-themed strong { color: var(--tw-prose-bold); font-weight: 700; }
631+
.prose.prose-themed h1, .prose.prose-themed h2,
632+
.prose.prose-themed h3, .prose.prose-themed h4 { color: var(--tw-prose-headings); }
633+
.prose.prose-themed a { color: var(--tw-prose-links); }
634+
.prose.prose-themed :not(pre) > code { color: var(--tw-prose-code); }
635+
.prose.prose-themed th { font-weight: 600; }
636+
616637
.prose.prose-themed pre {
617638
background: var(--color-neutral-11);
618639
border: 1px solid var(--color-neutral-9);
@@ -656,7 +677,7 @@ html, body, #root {
656677
[data-theme="dark"] .prose.prose-themed {
657678
--tw-prose-body: var(--color-neutral-2);
658679
--tw-prose-headings: var(--color-neutral-2);
659-
--tw-prose-links: var(--color-accent-5);
680+
--tw-prose-links: var(--color-accent-4); /* one step brighter for salience */
660681
--tw-prose-bold: var(--color-neutral-2);
661682
--tw-prose-code: var(--color-accent-4);
662683
--tw-prose-pre-bg: var(--color-neutral-11);
@@ -706,6 +727,37 @@ html, body, #root {
706727
font-family: 'Inconsolata', monospace;
707728
}
708729

730+
.prose.prose-invert h1 { font-size: 1.4em; font-weight: 700; }
731+
.prose.prose-invert h2 { font-size: 1.2em; font-weight: 600; }
732+
.prose.prose-invert h3 { font-size: 1.05em; font-weight: 600; }
733+
.prose.prose-invert h4 { font-size: 1em; font-weight: 600; }
734+
735+
.prose.prose-invert ul { list-style: disc; padding-left: 1.5em; }
736+
.prose.prose-invert ol { list-style: decimal; padding-left: 1.5em; }
737+
.prose.prose-invert li { margin: 0.2em 0; }
738+
.prose.prose-invert li > ul,
739+
.prose.prose-invert li > ol { margin: 0.1em 0; }
740+
741+
.prose.prose-invert blockquote {
742+
border-left: 3px solid var(--tw-prose-quote-borders);
743+
padding-left: 1em;
744+
margin: 0.5em 0;
745+
color: var(--tw-prose-quotes);
746+
}
747+
748+
.prose.prose-invert hr {
749+
border: none;
750+
border-top: 1px solid var(--tw-prose-hr);
751+
margin: 1em 0;
752+
}
753+
754+
.prose.prose-invert strong { color: var(--tw-prose-bold); font-weight: 700; }
755+
.prose.prose-invert h1, .prose.prose-invert h2,
756+
.prose.prose-invert h3, .prose.prose-invert h4 { color: var(--tw-prose-headings); }
757+
.prose.prose-invert a { color: var(--tw-prose-links); }
758+
.prose.prose-invert :not(pre) > code { color: var(--tw-prose-code); }
759+
.prose.prose-invert th { font-weight: 600; }
760+
709761
.prose.prose-invert pre {
710762
background: var(--color-neutral-11);
711763
border: 1px solid var(--color-neutral-9);
@@ -757,60 +809,62 @@ html, body, #root {
757809
font-family: 'Inconsolata', monospace;
758810
}
759811

760-
/* Dark theme tokens */
812+
/* Dark theme tokens — warm-leaning palette to match the terminal area
813+
(cool VS Code blues replaced with gold/teal/sage; warm tokens kept). */
761814
[data-theme="dark"] .hljs-keyword,
762-
[data-theme="dark"] .hljs-tag { color: #569cd6; }
763-
[data-theme="dark"] .hljs-built_in { color: #4ec9b0; }
815+
[data-theme="dark"] .hljs-tag { color: #d8a657; }
816+
[data-theme="dark"] .hljs-built_in { color: #7daea3; }
764817
[data-theme="dark"] .hljs-type,
765-
[data-theme="dark"] .hljs-title.class_ { color: #4ec9b0; }
818+
[data-theme="dark"] .hljs-title.class_ { color: #7daea3; }
766819
[data-theme="dark"] .hljs-title.function_ { color: #dcdcaa; }
767820
[data-theme="dark"] .hljs-string,
768821
[data-theme="dark"] .hljs-addition { color: #ce9178; }
769822
[data-theme="dark"] .hljs-number { color: #b5cea8; }
770823
[data-theme="dark"] .hljs-comment,
771-
[data-theme="dark"] .hljs-quote { color: #6a9955; }
824+
[data-theme="dark"] .hljs-quote { color: #8f8a80; }
772825
[data-theme="dark"] .hljs-variable,
773-
[data-theme="dark"] .hljs-template-variable { color: #9cdcfe; }
826+
[data-theme="dark"] .hljs-template-variable { color: #83a598; }
774827
[data-theme="dark"] .hljs-attr,
775-
[data-theme="dark"] .hljs-attribute { color: #9cdcfe; }
776-
[data-theme="dark"] .hljs-literal { color: #569cd6; }
828+
[data-theme="dark"] .hljs-attribute { color: #83a598; }
829+
[data-theme="dark"] .hljs-literal { color: #d8a657; }
777830
[data-theme="dark"] .hljs-regexp { color: #d16969; }
778831
[data-theme="dark"] .hljs-symbol { color: #b5cea8; }
779-
[data-theme="dark"] .hljs-meta { color: #c586c0; }
832+
[data-theme="dark"] .hljs-meta { color: #d3869b; }
780833
[data-theme="dark"] .hljs-selector-tag,
781834
[data-theme="dark"] .hljs-selector-class,
782835
[data-theme="dark"] .hljs-selector-id { color: #d7ba7d; }
783836
[data-theme="dark"] .hljs-deletion { color: #ce9178; background: rgba(206,145,120,0.1); }
784-
[data-theme="dark"] .hljs-name { color: #569cd6; }
785-
[data-theme="dark"] .hljs-params { color: #9cdcfe; }
837+
[data-theme="dark"] .hljs-name { color: #d8a657; }
838+
[data-theme="dark"] .hljs-params { color: #83a598; }
786839

787-
/* Light theme tokens */
840+
/* Light theme tokens — softened, warm-leaning palette (pure #0000ff/#a31515
841+
were harsh against the cream background). */
788842
[data-theme="light"] .hljs { background: var(--color-neutral-10); }
789843
[data-theme="light"] .hljs-keyword,
790-
[data-theme="light"] .hljs-tag { color: #0000ff; }
844+
[data-theme="light"] .hljs-tag { color: #2d5e8f; }
791845
[data-theme="light"] .hljs-built_in { color: #267f99; }
792846
[data-theme="light"] .hljs-type,
793847
[data-theme="light"] .hljs-title.class_ { color: #267f99; }
794848
[data-theme="light"] .hljs-title.function_ { color: #795e26; }
795849
[data-theme="light"] .hljs-string,
796-
[data-theme="light"] .hljs-addition { color: #a31515; }
850+
[data-theme="light"] .hljs-addition { color: #9a4a32; }
797851
[data-theme="light"] .hljs-number { color: #098658; }
798852
[data-theme="light"] .hljs-comment,
799-
[data-theme="light"] .hljs-quote { color: #008000; }
853+
[data-theme="light"] .hljs-quote { color: #5a7a4a; }
800854
[data-theme="light"] .hljs-variable,
801-
[data-theme="light"] .hljs-template-variable { color: #001080; }
855+
[data-theme="light"] .hljs-template-variable { color: #38556e; }
802856
[data-theme="light"] .hljs-attr,
803-
[data-theme="light"] .hljs-attribute { color: #001080; }
804-
[data-theme="light"] .hljs-literal { color: #0000ff; }
857+
[data-theme="light"] .hljs-attribute { color: #38556e; }
858+
[data-theme="light"] .hljs-literal { color: #2d5e8f; }
805859
[data-theme="light"] .hljs-regexp { color: #811f3f; }
806860
[data-theme="light"] .hljs-symbol { color: #098658; }
807-
[data-theme="light"] .hljs-meta { color: #af00db; }
861+
[data-theme="light"] .hljs-meta { color: #8a4a8f; }
808862
[data-theme="light"] .hljs-selector-tag,
809863
[data-theme="light"] .hljs-selector-class,
810-
[data-theme="light"] .hljs-selector-id { color: #800000; }
811-
[data-theme="light"] .hljs-deletion { color: #a31515; background: rgba(163,21,21,0.1); }
812-
[data-theme="light"] .hljs-name { color: #0000ff; }
813-
[data-theme="light"] .hljs-params { color: #001080; }
864+
[data-theme="light"] .hljs-selector-id { color: #7a4a3a; }
865+
[data-theme="light"] .hljs-deletion { color: #9a4a32; background: rgba(154,74,50,0.1); }
866+
[data-theme="light"] .hljs-name { color: #2d5e8f; }
867+
[data-theme="light"] .hljs-params { color: #38556e; }
814868

815869
/* ── Docs browser header (inverted) ────────────────────────────────────── */
816870
/* Inverted neutral colors so the header clearly signals "not terminal". */

0 commit comments

Comments
 (0)