Skip to content

Commit da423ef

Browse files
alexkromanclaude
andauthored
Tint transcript turns in speaker colors for visual distinction (#241)
## Summary Redesigned the visual rendering of conversation transcripts so that entire speaker turns (both label and body text) are tinted in the speaker's color, rather than just the label prefix. This makes "you:" and "agent:" lines visually distinct at a glance, even on terminals with limited color support. ## Key Changes - **Theme colors**: Introduced a new `AGENT` color (`#14B8A6`, teal) to replace the previous secondary accent (Cobolt 300) for agent labels. The teal was chosen specifically to downgrade to ANSI cyan on 16-color terminals, staying clearly distinct from the brand purple used for "you" lines. Previously, both speakers used near-identical cobolt purples that collapsed to the same hue after downsampling. - **Rendering**: Modified `_labeled()` in `aai_cli/agent/render.py` to apply the style to the entire line (label + body) rather than just the label prefix. This ensures the whole turn reads as a cohesive colored block. - **Tests**: Added `test_human_whole_turn_is_tinted_in_speaker_color()` to verify that both the label and body text are wrapped in the speaker's color SGR codes, and that the two speakers render in contrasting hues. Added `test_you_and_agent_stay_distinct_after_downsampling()` to ensure the colors remain visually distinct even after downsampling to 16-color ANSI palettes. ## Implementation Details - The `AGENT` teal (`#14B8A6`) was chosen outside the cobolt purple family on purpose: it downsamples to ANSI cyan, which is clearly distinct from the purple/blue that "you" downsamples to, ensuring readability across all terminal capabilities. - The `aai.you` style retains the brand purple and remains reserved (never reused for diarized speakers). - Updated theme documentation to clarify that the entire transcript turn is now tinted, not just the prefix. https://claude.ai/code/session_01AFrwHX8EdECmkNsU8LqJNf Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7281bf5 commit da423ef

4 files changed

Lines changed: 60 additions & 8 deletions

File tree

aai_cli/agent/render.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@
99

1010

1111
def _labeled(label: str, body: str, *, style: str = "aai.label") -> Text:
12-
"""A line whose `label` prefix is accented in `style` and whose body is default."""
13-
return Text.assemble((label, style), body)
12+
"""A transcript line tinted entirely in `style` — both the `label` prefix and the body.
13+
14+
Coloring the whole turn (not just the prefix) is what makes a "you:" line and an
15+
"agent:" line read as different colors top to bottom; the two styles resolve to
16+
contrasting hues (see ``theme.aai.you`` / ``theme.aai.agent``).
17+
"""
18+
return Text(f"{label}{body}", style=style)
1419

1520

1621
class AgentRenderer(BaseRenderer):

aai_cli/ui/theme.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@
1717

1818
# Brand accent. Defined once so the whole CLI can be re-tinted here.
1919
BRAND = COBOLT_400
20-
# Secondary accent — the lighter Cobolt 300 used where a second hue is needed (agent
21-
# label, links), so it stays in the brand family yet reads distinct from BRAND.
20+
# Secondary accent — the lighter Cobolt 300 used where a second hue is needed (links),
21+
# so it stays in the brand family yet reads distinct from BRAND.
2222
ACCENT = COBOLT_300
2323

24+
# The agent's voice in a live conversation. A cool teal chosen *outside* the cobolt
25+
# purple family on purpose: "you" keeps the brand purple, and the two were previously
26+
# both purples (Cobolt 400 vs 300) that collapsed to the same hue once a terminal
27+
# downsampled truecolor to its 16-color palette — so "you:" and "agent:" lines looked
28+
# identically colored. Teal downsamples to ANSI cyan, staying clearly distinct from the
29+
# purple/blue "you" on any terminal.
30+
AGENT = "#14B8A6"
31+
2432
# Semantic error red, matching the design system's --color-error. Rich downsamples it
2533
# to plain red on terminals without truecolor.
2634
ERROR = "#F04438"
@@ -55,11 +63,13 @@
5563
"aai.brand": f"bold {BRAND}",
5664
"aai.heading": f"bold {BRAND}",
5765
"aai.label": BRAND,
58-
# Conversation labels: the human keeps the brand accent (reserved — never reused
59-
# for a diarized speaker, see SPEAKER_STYLES), the agent gets a distinct hue so
60-
# "you:" and "agent:" are easy to tell apart at a glance.
66+
# Conversation colors: a whole transcript turn is tinted in its speaker's hue
67+
# (not just the "you:"/"agent:" prefix), so the two read as different colors top
68+
# to bottom. The human keeps the brand purple (reserved — never reused for a
69+
# diarized speaker, see SPEAKER_STYLES); the agent gets a contrasting teal so the
70+
# two are unmistakable at a glance, even after downsampling (see AGENT).
6171
"aai.you": BRAND,
62-
"aai.agent": ACCENT,
72+
"aai.agent": AGENT,
6373
# Links/URLs in the lighter Cobolt secondary accent so a clickable target stands
6474
# out from prose without shouting (Vercel/Supabase use a cool accent for the same).
6575
"aai.url": ACCENT,

tests/test_agent_render.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,26 @@ def test_human_you_label_is_colored():
192192
assert "you: " in out
193193
assert "what is the time" in out
194194
assert "\x1b[" in out
195+
196+
197+
def _truecolor_sgr(style_name: str) -> str:
198+
"""The foreground SGR a truecolor console emits for a named theme style."""
199+
color = theme.make_console(color_system="truecolor").get_style(style_name).color
200+
assert color is not None
201+
t = color.get_truecolor()
202+
return f"\x1b[38;2;{t.red};{t.green};{t.blue}m"
203+
204+
205+
def test_human_whole_turn_is_tinted_in_speaker_color():
206+
# The whole line — label *and* body — is wrapped in the speaker's color, and the two
207+
# speakers render in different colors, so a glance separates "you" from "agent".
208+
r, buf = _human(color_system="truecolor")
209+
r.user_final("hello")
210+
r.agent_transcript("hi there", interrupted=False)
211+
r.close()
212+
out = buf.getvalue()
213+
you_sgr, agent_sgr = _truecolor_sgr("aai.you"), _truecolor_sgr("aai.agent")
214+
assert you_sgr != agent_sgr # contrasting hues, not two shades of the same color
215+
# The body text rides inside the colored span (it follows the SGR, before any reset).
216+
assert f"{you_sgr}you: hello" in out
217+
assert f"{agent_sgr}agent: hi there" in out

tests/test_theme.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ def test_you_color_reserved_outside_speaker_palette():
5555
assert you_color not in speaker_colors
5656

5757

58+
def test_you_and_agent_stay_distinct_after_downsampling():
59+
# "you" and "agent" used to be two near-identical cobolt purples that downsampled to
60+
# the *same* 16-color ANSI slot, so a transcript looked single-colored on a basic
61+
# terminal. Assert they're different hues and stay different once downgraded.
62+
from rich.color import ColorSystem
63+
64+
console = theme.make_console()
65+
you = console.get_style("aai.you").color
66+
agent = console.get_style("aai.agent").color
67+
assert you is not None and agent is not None
68+
assert you != agent
69+
assert you.downgrade(ColorSystem.STANDARD) != agent.downgrade(ColorSystem.STANDARD)
70+
71+
5872
def test_output_console_is_themed_and_error_is_styled(monkeypatch):
5973
from aai_cli.core.errors import CLIError
6074
from aai_cli.ui import output, theme

0 commit comments

Comments
 (0)