SLOPSHOPPER

rainbow-replies

Colors each 🟢🟡🔴🔵🟣 line of Claude's replies, and the bullets under it, in the ultracode palette

newrows
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · rainbow-replies
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ▎⟨Claude Code's own drawing⟩ ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Tool row
▎⟨Claude Code's own drawing⟩
Source 1 files
hooks/register.tsx 103 lines
1import type { Register } from 'claude-code'
2
3// Ultracode slider look: lavender family, red only when something broke.
4const COLOR: Record<string, string> = {
5  '🟢': '#A99BF5',
6  '🟡': '#D6CFFF',
7  '🔴': '#EB5F57',
8  '🔵': '#A99BF5',
9  '🟣': '#D6CFFF',
10}
11const MARKER = /^\s*(🟢|🟡|🔴|🔵|🟣)/u
12// [label](url) | bare url | `code` | **bold**
13const INLINE = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|(https?:\/\/[^\s)]+)|`([^`]+)`|\*\*([^*]+)\*\*/g
14
15type Row = { kind: 'color'; text: string; color: string } | { kind: 'md'; text: string }
16
17// Only the lead phrase gets color: marker through the first '.', ':' or '?' that ends a clause.
18export function split(line: string): [string, string] {
19  const m = /^(.*?[.:?])(\s|$)/u.exec(line)
20  return m ? [m[1], line.slice(m[1].length)] : [line, '']
21}
22
23export function rows(text: string): Row[] {
24  const out: Row[] = []
25  let fenced = false
26  for (const line of text.split('\n')) {
27    if (line.trimStart().startsWith('```')) fenced = !fenced
28    const m = fenced ? null : MARKER.exec(line)
29    if (m) {
30      out.push({ kind: 'color', text: line.trim(), color: COLOR[m[1]] })
31    } else {
32      const last = out[out.length - 1]
33      if (last?.kind === 'md') last.text += '\n' + line
34      else out.push({ kind: 'md', text: line })
35    }
36  }
37  return out.filter(r => r.kind === 'color' || r.text.trim())
38}
39
40// Thin bar beside each work step: one muted color per kind of step.
41export function stepColor(tool: string, failed: boolean): string {
42  if (failed) return '#EB5F57'
43  if (tool === 'Bash') return '#A99BF5'
44  if (/^(Edit|Write|NotebookEdit)$/.test(tool)) return '#D6CFFF'
45  if (/^(Read|Grep|Glob|ToolSearch|WebSearch|WebFetch)$/.test(tool)) return '#6E66A0'
46  if (/^(Agent|Skill|Workflow|SendMessage)$/.test(tool)) return '#FFFFFF'
47  return '#8A84A8'
48}
49
50export const register: Register = on => {
51  on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
52    const drawn = await next(e)
53    if (!drawn) return drawn
54    const { Box, Text } = $.ui.resolve(e)
55    return (
56      <Box flexDirection="row">
57        <Text color={stepColor(e.props.tool, e.props.isErrored)}>▎</Text>
58        <Box flexDirection="column" flexGrow={1}>
59          {drawn}
60        </Box>
61      </Box>
62    )
63  })
64
65  on('ui.render', { component: 'AssistantMessage' }, ($, e, next) => {
66    if (!MARKER.test(e.props.text) && !/\n\s*(🟢|🟡|🔴|🔵|🟣)/u.test(e.props.text)) return next(e)
67    const { Box, Text, Link, Markdown } = $.ui.resolve(e)
68
69    const inline = (s: string, key: string) => {
70      const parts: unknown[] = []
71      let last = 0
72      let i = 0
73      for (const m of s.matchAll(INLINE)) {
74        if (m.index! > last) parts.push(s.slice(last, m.index))
75        const k = `${key}-${i++}`
76        if (m[2]) parts.push(<Link key={k} href={m[2]} label={m[1]} />)
77        else if (m[3]) parts.push(<Link key={k} href={m[3]} />)
78        else parts.push(<Text key={k} bold>{m[4] ?? m[5]}</Text>)
79        last = m.index! + m[0].length
80      }
81      if (last < s.length) parts.push(s.slice(last))
82      return parts
83    }
84
85    return (
86      <Box flexDirection="column">
87        {rows(e.props.text).map((r, n) =>
88          r.kind === 'md' ? (
89            <Markdown key={`md-${n}`} text={r.text} />
90          ) : (
91            <Text key={`line-${n}`}>
92              <Text color={r.color} bold>
93                {inline(split(r.text)[0], `head-${n}`)}
94              </Text>
95              {inline(split(r.text)[1], `tail-${n}`)}
96            </Text>
97          ),
98        )}
99      </Box>
100    )
101  })
102}
103