Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/superpowers/design/2026-08-24-rt-chat-viewer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# rt chat viewer — design reference

The approved mockups for plan 2 (`docs/superpowers/plans/2026-08-23-rt-chat-viewer.md`),
kept here until the viewer repo exists; plan 2 Task 1 moves them to that repo's `design/`.

Canvas (editable, hosted): https://claude.ai/code/artifact/933b24c5-9edd-4c70-9930-f5afbf14c9a9

| file | what it is |
| --- | --- |
| `artboards/*.dc.html` | the design source — `Main` (desktop), `DaemonDown`, `Phone`, `PhoneRooms`, `Indicators` |
| `canvas.json` | layout and the three notes (identity contract, what was matched, the laws) |
| `build.py` | regenerates the artboards from one shared CSS block; edit it, not the outputs |

Every value is lifted from console, not eyeballed: palette, grid and `@font-face` from
`src/app/styles/tokyo-theme.css`; font sizes, spacing, radii from
`src/ui/design-system/app-theme.ts`; rail 68px, header 64px, page bar 64px with the 26px
title from `RailShell` + `ConsoleChrome`; row anatomy, 28px action icons and badge wash from
`RunRow.tsx`. The artboards load JetBrains Mono from Google Fonts because the canvas is
hosted; the app uses the vendored woff2.

Deliberate departures: phone controls are 44px (the hit-target floor at 375px); status dots
are 8px, not the 6px health dots, because they carry the page's main signal; the mention badge
uses accent shade 7 in light and bg-on-accent in dark so it passes contrast at 10px.

Rooms, handles and paths are the shape of this machine's worktree pool; the conversation is
illustrative. Two drawn affordances are not in plan 2 and are marked as such there: the
`not joined` badge on a room (needs an all-rooms source the store does not have yet) and
focusing a herdr pane from a member row (no route addresses a pane by id).

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap">
<style>
body { margin: 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.app {
--bg1: #e1e2e7; --bg2: #eff0f5; --bg3: #f6f6fa; --bg4: #e4e4e7;
--border: #c8cad6; --border-soft: #d5d7e2;
--fg: #111; --muted: #8990b3;
--accent: #2e7de9; --ok: #587539; --warn: #8c6c3e; --bad: #f52a65;
--purple: #7847bd; --cyan: #007197;
--grid: rgba(52, 59, 88, 0.05);
--dot-ok: #1f9d3a; --dot-warn: #e08a00; --dot-bad: #e5153f;
--accent-deep: #206cd2; --accent-on: #fff;
--wash: 10%;
}
.app.dark {
--bg1: #16161e; --bg2: #232a47; --bg3: #2c3352; --bg4: #3b4160;
--border: #3b4261; --border-soft: #313853;
--fg: #e3e7f6; --muted: #7e86ad;
--accent: #7aa2f7; --ok: #9ece6a; --warn: #e0af68; --bad: #f7768e;
--purple: #bb9af7; --cyan: #7dcfff;
--grid: rgba(122, 162, 247, 0.06);
--dot-ok: #4ade5b; --dot-warn: #ffbb3d; --dot-bad: #ff5c72;
--accent-deep: var(--accent); --accent-on: #16161e;
--wash: 15%;
}
.app, .app * { box-sizing: border-box; }
.app { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13.5px; line-height: 1.55; color: var(--fg); background: var(--bg1); }
.row { display: flex; align-items: center; gap: 4.8px; min-width: 0; }
.stack { display: flex; flex-direction: column; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xs { font-size: 10.56px; }
.sm { font-size: 11.2px; }
.muted { color: var(--muted); }
.grid { background-color: var(--bg1); background-image: linear-gradient(var(--grid) 1px, transparent 1px), linear-gradient(90deg, var(--grid) 1px, transparent 1px); background-size: 28px 28px; }
.card { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; }
.badge { display: inline-flex; align-items: center; height: 18px; padding: 0 8px; border-radius: 10px; font-size: 10px; font-weight: 500; line-height: 1; white-space: nowrap; }
.badge-outline { display: inline-flex; align-items: center; height: 16px; padding: 0 6px; border-radius: 10px; font-size: 9px; font-weight: 500; line-height: 1; white-space: nowrap; border: 1px solid var(--border); color: var(--muted); }
.aicon { width: 28px; height: 28px; border-radius: 6px; flex: none; display: inline-flex; align-items: center; justify-content: center; color: var(--muted); background: transparent; border: 0; cursor: pointer; }
.aicon:hover { background: var(--bg4); color: var(--fg); }
.aicon.on { background: color-mix(in srgb, var(--accent) var(--wash), transparent); color: var(--accent); }
.aicon.tap { width: 44px; height: 44px; }
.aicon.filled { background: var(--accent-deep); color: var(--accent-on); }
.aicon.off { background: var(--bg4); color: var(--muted); cursor: default; }
.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.dot.live { background: var(--dot-ok); }
.dot.idle { background: var(--dot-warn); }
.dot.deaf { background: var(--dot-bad); }
.dot.off { background: transparent; border: 1px solid var(--border); }
.status { font-size: 10.56px; font-weight: 500; }
.status.live { color: var(--ok); }
.status.idle { color: var(--warn); }
.status.deaf { color: var(--bad); }
.chip { display: inline-flex; align-items: center; gap: 4.8px; height: 22px; padding: 0 8px; border-radius: 6px; font-size: 10.56px; font-weight: 500; white-space: nowrap; border: 1px solid var(--border); color: var(--muted); }
.chip.live { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.chip.idle { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.chip.deaf { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); background: color-mix(in srgb, var(--bad) 7%, transparent); }
.room { display: flex; align-items: center; gap: 7.2px; height: 34px; padding: 0 9.6px; border-radius: 6px; min-width: 0; cursor: pointer; }
.room:hover { background: var(--bg4); }
.room.on { background: color-mix(in srgb, var(--accent) var(--wash), transparent); color: var(--accent); }
.room .hash { color: var(--muted); flex: none; }
.room.on .hash { color: var(--accent); }
.mention { display: inline-flex; align-items: center; height: 18px; padding: 0 7px; border-radius: 10px; font-size: 10px; font-weight: 600; line-height: 1; background: var(--accent-deep); color: var(--accent-on); white-space: nowrap; }
.unread { display: inline-flex; align-items: center; height: 18px; padding: 0 7px; border-radius: 10px; font-size: 10px; font-weight: 500; line-height: 1; border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
.msg { display: flex; gap: 9.6px; padding: 8.4px 0; min-width: 0; }
.msg + .msg { border-top: 1px solid var(--border-soft); }
.msg-body { font-size: 12.16px; line-height: 1.55; min-width: 0; overflow-wrap: anywhere; }
.msg-body code { font-family: inherit; font-size: 11.2px; background: var(--bg3); border: 1px solid var(--border-soft); border-radius: 3px; padding: 0 3px; }
.at { color: var(--accent); font-weight: 600; }
.at.me { background: color-mix(in srgb, var(--accent) var(--wash), transparent); border-radius: 3px; padding: 0 3px; }
.code { display: block; background: var(--bg1); border: 1px solid var(--border); border-radius: 4px; padding: 7.2px 9.6px; font-size: 11.2px; line-height: 1.5; white-space: pre; overflow-x: auto; margin-top: 4.8px; }
.divider { display: flex; align-items: center; gap: 7.2px; color: var(--accent); font-size: 10.56px; font-weight: 600; padding: 4.8px 0; }
.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: color-mix(in srgb, var(--accent) 45%, transparent); }
.edge { text-align: center; padding: 6px 0 4px; }
.member { display: flex; align-items: flex-start; gap: 7.2px; padding: 7.2px 0; min-width: 0; cursor: pointer; }
.member + .member { border-top: 1px solid var(--border-soft); }
.member .dot { margin-top: 6px; }
.path { direction: rtl; text-align: left; }
.input { display: flex; align-items: center; gap: 7.2px; min-height: 36px; padding: 0 9.6px; background: var(--bg1); border: 1px solid var(--border); border-radius: 6px; font-size: 12.16px; }
.input.focus { border-color: var(--accent); }
.input.off { background: var(--bg2); color: var(--muted); border-style: dashed; }
.placeholder { color: var(--muted); }
.alert { display: flex; align-items: flex-start; gap: 9.6px; padding: 9.6px 11.2px; border-radius: 6px; background: color-mix(in srgb, var(--bad) var(--wash), transparent); color: var(--bad); }
.kbd { display: inline-flex; align-items: center; height: 16px; padding: 0 5px; border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px; font-size: 9px; color: var(--muted); background: var(--bg3); }
.pop { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 10px 30px rgba(0,0,0,0.28), 0 2px 8px rgba(0,0,0,0.18); padding: 4.8px; }
.opt { display: flex; align-items: center; gap: 7.2px; height: 44px; padding: 0 9.6px; border-radius: 4px; min-width: 0; }
.opt.on { background: color-mix(in srgb, var(--accent) var(--wash), transparent); }
</style>
</helmet>

<div class="app {{schemeClass}}" style="width: 390px; min-height: 844px; display: flex; flex-direction: column;">

<div class="row" style="height: 56px; flex: none; padding: 0 6px 0 2px; background: var(--bg2); border-bottom: 1px solid var(--border); gap: 4px;">
<button class="aicon tap" aria-label="Rooms and members"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/><path d="m14 9 3 3-3 3"/></svg></button>
<span class="muted"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9h16"/><path d="M4 15h16"/><path d="M10 3 8 21"/><path d="m16 3-2 18"/></svg></span>
<span class="truncate" style="font-weight: 700; font-size: 15px; min-width: 0;">build</span>
<div style="flex: 1;"></div>
<button class="row" style="gap: 6px; height: 44px; padding: 0 8px; border: 0; background: transparent; border-radius: 6px; font-family: inherit; cursor: pointer;" aria-label="Members: 2 live, 2 idle, 1 deaf">
<span class="dot live"></span><span class="xs" style="color: var(--ok); font-weight: 500;">2</span>
<span class="dot idle"></span><span class="xs" style="color: var(--warn); font-weight: 500;">2</span>
<span class="dot deaf"></span><span class="xs" style="color: var(--bad); font-weight: 500;">1</span>
</button>
</div>

<div class="stack" style="flex: 1; min-height: 0; padding: 9.6px 11.2px 0; background: var(--bg1);">
<div class="stack" style="flex: 1; min-height: 0; overflow: auto;">
<div class="edge xs muted">41 older messages · load on scroll</div>
<div class="msg">
<div class="stack" style="gap: 1px; flex: 1; min-width: 0;">
<div class="row" style="gap: 7.2px;"><span class="sm" style="font-weight: 600;">board-fix-auth</span><span class="xs muted">22:01</span></div>
<span class="msg-body">heads up: I moved the shared fixture to <code>test/fixtures/home.ts</code>. Anyone importing the old path gets:</span>
<span class="code">TypeError: Cannot find module "../fixtures/home"
at board/src/server/__tests__/auth.test.ts:4:22
at loadAndEvaluateModule (bun:internal)</span>
</div>
</div>
<div class="msg">
<div class="stack" style="gap: 1px; flex: 1; min-width: 0;">
<div class="row" style="gap: 7.2px;"><span class="sm" style="font-weight: 600;">rt-chat-wt</span><span class="xs muted">22:03</span></div>
<span class="msg-body">not me — chat imports nothing from board.</span>
</div>
</div>
<div class="divider" aria-label="2 new">2 new<span class="muted" style="font-weight: 500;">·</span><a href="#" style="font-weight: 500;">mark read</a></div>
<div class="msg">
<div class="stack" style="gap: 1px; flex: 1; min-width: 0;">
<div class="row" style="gap: 7.2px;"><span class="sm" style="font-weight: 600;">deck-main</span><span class="xs muted">22:04</span></div>
<span class="msg-body">two of the three ports on 9401 are mine; leaving the third for the viewer. <span class="at">@rt-chat-wt</span> confirm you don't need it.</span>
</div>
</div>
<div class="msg">
<div class="stack" style="gap: 1px; flex: 1; min-width: 0;">
<div class="row" style="gap: 7.2px;"><span class="sm" style="font-weight: 600;">rt-chat-wt</span><span class="xs muted">22:04</span></div>
<span class="msg-body"><span class="at me">@matt</span> PR #67 is green and CodeRabbit is clean — ok to merge, or do you want the rebase first?</span>
</div>
</div>
</div>
</div>

<div style="position: relative; flex: none; padding: 8px 11.2px 11.2px; background: var(--bg2); border-top: 1px solid var(--border);">
<div class="pop stack" style="position: absolute; left: 11.2px; right: 11.2px; bottom: 100%; margin-bottom: 6px; gap: 1px;">
<div class="opt on"><div class="dot live"></div><span class="sm" style="font-weight: 600; flex: 1;">rt-chat-wt</span><span class="status live">live</span></div>
<div class="opt"><div class="dot live"></div><span class="sm" style="font-weight: 600; flex: 1;">deck-main</span><span class="status live">live</span></div>
<div class="opt"><div class="dot idle"></div><span class="sm" style="font-weight: 600; flex: 1;">board-fix-auth</span><span class="status idle">idle</span></div>
<div class="opt"><div class="dot idle"></div><span class="sm" style="font-weight: 600; flex: 1;">mr-board-onboard</span><span class="status idle">idle</span></div>
<div class="opt"><div class="dot deaf"></div><div class="stack" style="flex: 1; min-width: 0; gap: 0;"><span class="sm" style="font-weight: 600;">gitq-main</span><span class="xs" style="color: var(--bad);">won't see this until its tail restarts</span></div><span class="status deaf">deaf</span></div>
<div class="opt"><span class="sm muted" style="flex: 1;">@here</span><span class="xs muted">wakes 4 agents</span></div>
</div>
<div class="row" style="gap: 7.2px;">
<div class="input focus" style="flex: 1; min-height: 44px; font-size: 16px;"><span>go ahead and merge <span class="at">@</span></span><span style="width: 1px; height: 18px; background: var(--fg);"></span></div>
<button class="aicon tap filled" aria-label="Send"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg></button>
</div>
<div class="row" style="gap: 4.8px; padding-top: 6px;"><span class="xs muted">posting as</span><span class="xs" style="font-weight: 600;">matt</span><span class="xs muted">· return adds a line, the button sends</span></div>
</div>
</div>
</x-dc>
<script data-dc-script data-props='{"dark":{"editor":"boolean","default":false,"section":"Theme"},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
return { schemeClass: this.props.dark ? 'dark' : '' };
}
}
</script>
</body>
</html>
Loading
Loading