返回 CodeWhale
tideline-redesign.html
根目录 / docs / design / tideline-redesign.html
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="dark light">
7 <title>Codewhale — terminal workbench & Ratatui wiring</title>
8 <style>
9 /*
10 CODEWHALE TIDELINE / WORKBENCH
11 This is a self-contained visual + interaction contract, not a marketing
12 page. The terminal frame simulates a host terminal; production Ratatui
13 renders Color::Reset for Match Terminal, Dark, and Light backgrounds.
14
15 The two token blocks below are canonical. Theme implementations map
16 semantic roles to them; widgets do not create their own status colors.
17 */
18 :root {
19 --t-bg: #16181d; /* host terminal bg (dark) */
20 --t-fg: #d6d9e0; /* host terminal fg */
21 --t-dim: #8a8f9c; /* dim metadata */
22 --t-faint: #5c6270; /* hints, borders */
23 --accent: #6aaef2; /* interaction blue (ANSI blue when truecolor absent) */
24 --accent2: #48d7ff; /* cyan — bounded accents only */
25 --gold: #f6c453; /* signal gold — human input */
26 --ok: #9bd66f; /* working green */
27 --warn: #ff7a59; /* coral warning */
28 --err: #ff86b2; /* rose danger */
29 --reason: #e09948; /* reasoning tint */
30 --term-shadow: 0 18px 50px rgba(0,0,0,.45);
31 --term-frame: #23262d;
32 --page-bg: #0b0c0f;
33 --page-fg: #c9cdd6;
34 --page-dim: #7d8290;
35 --card: #121418;
36 --card-edge: #242832;
37 }
38 body.light {
39 --t-bg: #fafaf7;
40 --t-fg: #2c2f36;
41 --t-dim: #6b707c;
42 --t-faint: #9aa0ac;
43 --accent: #2f5dd8; /* cobalt — light-mode action */
44 --accent2: #0f7f9f;
45 --gold: #a96a08;
46 --ok: #4d8a1f;
47 --warn: #c2542f;
48 --err: #c2335f;
49 --reason: #9a6a1d;
50 --term-shadow: 0 14px 40px rgba(30,32,40,.18);
51 --term-frame: #e4e3dd;
52 --page-bg: #f2f1ec;
53 --page-fg: #33363e;
54 --page-dim: #767b87;
55 --card: #ffffff;
56 --card-edge: #e2e1da;
57 }
58 body.ansi {
59 --accent: #86a8ff;
60 --accent2: #86d7ff;
61 --gold: #e8d57b;
62 --ok: #b1df85;
63 --warn: #e6a172;
64 --err: #e79ab6;
65 --reason: #d4b36b;
66 }
67 body.ansi .tone-color { color: var(--t-fg) !important; }
68 body.ansi .status::before { color: var(--t-fg) !important; }
69 body.ansi .progress > i { background: var(--t-fg); }
70 body.ansi .deepsea-note { display: none; }
71
72 * { box-sizing: border-box; }
73 html { scroll-behavior: smooth; }
74 body {
75 margin: 0;
76 background: var(--page-bg);
77 color: var(--page-fg);
78 font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
79 transition: background .18s ease, color .18s ease;
80 }
81 button, input { font: inherit; }
82 .mono, code, kbd, .terminal { font-family: "SFMono-Regular", "Cascadia Code", "Berkeley Mono", Menlo, Consolas, monospace; }
83 .page { max-width: 1560px; margin: 0 auto; padding: 34px 28px 96px; }
84 .eyebrow { color: var(--accent2); font: 600 12px/1.2 var(--mono); letter-spacing: .12em; text-transform: uppercase; }
85 .hero { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 24px; align-items: end; margin-bottom: 24px; }
86 h1 { margin: 6px 0 7px; font-size: clamp(27px, 4vw, 46px); line-height: 1.06; letter-spacing: -.045em; }
87 .intro { max-width: 760px; margin: 0; color: var(--page-dim); font-size: 16px; }
88 .brief-state { max-width: 310px; color: var(--page-dim); font-size: 12px; text-align: right; }
89 .brief-state strong { color: var(--page-fg); font-weight: 650; }
90
91 .control-bar, .section-nav { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
92 .control-bar { margin: 16px 0 18px; }
93 .control-group { display: inline-flex; border: 1px solid var(--card-edge); border-radius: 8px; overflow: hidden; background: var(--card); }
94 .control-group button, .section-nav a {
95 border: 0; border-right: 1px solid var(--card-edge); background: transparent; color: var(--page-dim);
96 padding: 7px 10px; font-size: 12px; cursor: pointer; text-decoration: none; white-space: nowrap;
97 }
98 .control-group button:last-child { border-right: 0; }
99 .control-group button:hover, .control-group button[aria-pressed="true"], .section-nav a:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); }
100 .control-label { color: var(--page-dim); font-size: 12px; margin-right: -2px; }
101 .doc-note { margin-left: auto; color: var(--page-dim); font-size: 12px; }
102
103 .stage-grid { display: grid; grid-template-columns: minmax(0, 1fr) 310px; gap: 16px; align-items: start; }
104 .host-card { min-width: 0; border: 1px solid var(--card-edge); border-radius: 13px; background: var(--card); padding: 13px; box-shadow: var(--term-shadow); }
105 .host-caption { display: flex; justify-content: space-between; gap: 16px; padding: 0 2px 10px; color: var(--page-dim); font-size: 12px; }
106 .host-caption b { color: var(--page-fg); font-weight: 650; }
107 .host-caption .inherit { color: var(--accent); }
108
109 /* The browser needs a surface to simulate the terminal. This is not a
110 production background instruction: see data-background="reset" below. */
111 .terminal {
112 position: relative; min-height: 734px; overflow: hidden;
113 color: var(--t-fg); background: var(--t-bg); border: 1px solid var(--term-frame); border-radius: 7px;
114 font-size: 13px; line-height: 1.42;
115 }
116 .terminal.deepsea {
117 --sea-hi: rgba(33, 149, 214, .16);
118 --sea-low: #020a14;
119 background:
120 radial-gradient(1100px 440px at 68% 32%, var(--sea-hi), transparent 67%),
121 linear-gradient(174deg, #071526 0%, #03101d 54%, var(--sea-low) 100%);
122 }
123 .terminal.deepsea::before, .terminal.deepsea::after {
124 content: ""; pointer-events: none; position: absolute; inset: 0; opacity: .43;
125 background-image: radial-gradient(circle, rgba(72,215,255,.85) 0 1px, transparent 1.8px);
126 background-size: 168px 128px; background-position: 23px 20px;
127 mask-image: linear-gradient(to bottom, transparent, black 27%, transparent 88%);
128 }
129 .terminal.deepsea::after { background-size: 300px 190px; background-position: 140px 85px; opacity: .18; }
130 body.motion-reduced .terminal.deepsea::before, body.motion-reduced .terminal.deepsea::after { opacity: .16; }
131 .terminal > * { position: relative; z-index: 1; }
132
133 .topbar { min-height: 45px; display: flex; align-items: center; gap: 0; padding: 0 10px; border-bottom: 1px solid color-mix(in srgb, var(--t-faint) 60%, transparent); white-space: nowrap; overflow: hidden; }
134 .topbar button { border: 0; background: transparent; color: inherit; padding: 8px 11px; cursor: pointer; font: inherit; line-height: 1.15; }
135 .topbar button:hover, .topbar button:focus-visible { background: color-mix(in srgb, var(--accent) 12%, transparent); outline: 1px solid var(--accent); outline-offset: -1px; }
136 .topbar .divider { height: 19px; width: 1px; flex: 0 0 auto; background: var(--t-faint); opacity: .72; }
137 .brand-lockup { color: var(--gold) !important; font-weight: 760 !important; letter-spacing: .035em; }
138 .brand-cell { display: inline-block; width: 20px; margin-right: 7px; text-align: center; color: var(--gold); }
139 .topbar .label, .footer .label { color: var(--t-dim); }
140 .topbar .value { color: var(--accent2); }
141 .topbar .path { color: var(--accent); }
142 .topbar .context { margin-left: auto; }
143 .context-meter { width: 86px; height: 6px; display: inline-block; margin-left: 7px; overflow: hidden; border-radius: 2px; background: color-mix(in srgb, var(--t-faint) 62%, transparent); vertical-align: 1px; }
144 .context-meter i { display: block; width: 61%; height: 100%; background: var(--accent2); }
145 .attention-dot { color: var(--gold); margin-left: 4px; }
146
147 .work-layout { display: grid; grid-template-columns: 218px minmax(0, 1fr); min-height: 615px; }
148 .side-rail { padding: 13px 10px; border-right: 1px solid color-mix(in srgb, var(--t-faint) 54%, transparent); color: var(--t-dim); }
149 .side-rail h3 { margin: 13px 8px 5px; color: var(--t-dim); font: 700 11px/1.3 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
150 .side-rail h3:first-child { margin-top: 3px; }
151 .rail-row { display: flex; justify-content: space-between; align-items: center; width: 100%; min-height: 27px; padding: 4px 8px; color: inherit; border: 0; background: transparent; text-align: left; cursor: pointer; font: inherit; }
152 .rail-row:hover, .rail-row.selected { color: var(--t-fg); background: color-mix(in srgb, var(--accent) 13%, transparent); outline: 1px solid color-mix(in srgb, var(--accent) 50%, transparent); outline-offset: -1px; }
153 .rail-row .rail-count { color: var(--accent2); }
154 .rail-row .state-live { color: var(--ok); } .rail-row .state-wait { color: var(--warn); }
155 .workspace { min-width: 0; padding: 18px 21px 12px; }
156 .conversation { max-width: 920px; }
157 .turn { position: relative; padding: 0 0 14px 28px; }
158 .turn::before { content: ""; position: absolute; left: 7px; top: 19px; bottom: -4px; width: 1px; background: color-mix(in srgb, var(--t-faint) 65%, transparent); }
159 .turn:last-child::before { display: none; }
160 .turn-marker { position: absolute; left: 0; top: 2px; width: 16px; color: var(--accent2); font-weight: 800; }
161 .turn.assistant .turn-marker { color: var(--gold); }
162 .turn-head { display: flex; gap: 10px; align-items: baseline; margin-bottom: 4px; }
163 .turn-head strong { color: var(--accent2); } .turn.assistant .turn-head strong { color: var(--gold); }
164 .turn-head time { color: var(--t-dim); }
165 .turn-copy { max-width: 770px; color: var(--t-fg); font-size: 14px; }
166 .turn-copy .dim { color: var(--t-dim); }
167 .receipts { margin-top: 11px; padding-left: 1px; border-left: 2px solid var(--accent2); }
168 .receipt { display: grid; width: 100%; grid-template-columns: 24px 100px minmax(0,1fr) auto; gap: 8px; min-height: 26px; align-items: baseline; padding: 2px 8px; color: var(--t-dim); border: 0; background: transparent; text-align: left; cursor: pointer; font: inherit; }
169 .receipt:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
170 .receipt .glyph { color: var(--accent2); text-align: center; }
171 .receipt .kind { color: var(--accent2); font-weight: 650; }
172 .receipt .time { color: var(--t-faint); }
173 .receipt.ok .glyph, .receipt.ok .kind { color: var(--ok); }
174 .receipt.warn .glyph, .receipt.warn .kind { color: var(--warn); }
175 .pod-block { display: block; width: 100%; margin: 11px 0 15px; padding: 10px 12px; color: inherit; border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent); border-left: 2px solid var(--accent2); background: transparent; text-align: left; cursor: pointer; font: inherit; }
176 .pod-block:hover, .pod-block.selected { background: color-mix(in srgb, var(--accent) 9%, transparent); outline: 1px solid var(--accent2); outline-offset: -1px; }
177 .pod-title { color: var(--accent2); font-weight: 700; }
178 .pod-tree { margin: 7px 0 0 21px; color: var(--t-dim); line-height: 1.7; }
179 .pod-tree b { color: var(--t-fg); font-weight: 650; }
180 .pod-tree .live { color: var(--ok); } .pod-tree .idle { color: var(--t-dim); }
181 .evidence-line { margin: 9px 0 0; color: var(--t-dim); }
182 .evidence-line b { color: var(--warn); font-weight: 650; }
183
184 .ledger { margin: 15px 0 14px; border-top: 1px solid var(--t-faint); border-bottom: 1px solid var(--t-faint); }
185 .ledger-head { display: flex; justify-content: space-between; padding: 8px 0; color: var(--accent2); }
186 .ledger table { width: 100%; border-collapse: collapse; color: var(--t-dim); }
187 .ledger th { padding: 5px 6px; color: var(--t-faint); font-size: 10px; font-weight: 700; text-align: left; letter-spacing: .07em; text-transform: uppercase; }
188 .ledger td { padding: 6px; border-top: 1px solid color-mix(in srgb, var(--t-faint) 53%, transparent); }
189 .ledger tr[data-target] { cursor: pointer; }
190 .ledger tr[data-target]:hover, .ledger tr[data-target].selected { background: color-mix(in srgb, var(--accent) 12%, transparent); outline: 1px solid var(--accent2); outline-offset: -1px; }
191 .state-dot { color: var(--ok); } .state-done { color: var(--ok); } .state-waiting { color: var(--t-dim); }
192
193 .composer { display: grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: stretch; gap: 10px; min-height: 78px; padding: 11px 14px; border: 1px solid var(--accent); border-radius: 5px; }
194 .composer .prompt { color: var(--gold); padding-top: 2px; }
195 .composer button { border: 0; background: transparent; color: var(--t-dim); text-align: left; padding: 0; cursor: pointer; }
196 .composer button:hover { color: var(--t-fg); }
197 .composer .placeholder { color: var(--t-dim); }
198 .composer .send { align-self: end; color: var(--accent2); font-size: 22px; line-height: 1; }
199 .composer-hints { grid-column: 1 / -1; color: var(--t-faint); font-size: 11px; }
200 .composer-hints b { color: var(--t-dim); font-weight: 650; }
201
202 .footer { display: flex; gap: 13px; align-items: center; min-height: 35px; padding: 0 11px; border-top: 1px solid color-mix(in srgb, var(--t-faint) 60%, transparent); color: var(--t-dim); white-space: nowrap; overflow: hidden; }
203 .footer .sep { color: var(--t-faint); } .footer .route { color: var(--accent); }
204 .footer .cost { color: var(--ok); } .footer .keys { margin-left: auto; }
205
206 .screen { display: none; } .screen.active { display: block; }
207
208 /* Startup: a quiet first choice, not a blank terminal. */
209 .startup { min-height: 650px; display: grid; grid-template-rows: 1fr auto; padding: 0 10%; }
210 .startup-center { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 72px 0 38px; }
211 .startup h2 { margin: 20px 0 7px; font-size: 23px; letter-spacing: -.025em; font-weight: 650; }
212 .startup p { margin: 0; color: var(--t-dim); }
213 .quick-actions { border-top: 1px solid var(--t-faint); padding: 13px 0 0; }
214 .quick-head { color: var(--t-dim); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
215 .quick-grid { display: grid; gap: 0; margin: 10px 0 13px; }
216 .quick-row { display: grid; grid-template-columns: 42px minmax(160px, .8fr) minmax(0, 1.7fr) auto; align-items: center; gap: 10px; width: 100%; min-height: 47px; padding: 7px 10px; color: var(--t-fg); border: 0; border-top: 1px solid color-mix(in srgb, var(--t-faint) 48%, transparent); background: transparent; text-align: left; cursor: pointer; }
217 .quick-row:hover, .quick-row.selected { background: color-mix(in srgb, var(--accent) 12%, transparent); outline: 1px solid color-mix(in srgb, var(--accent2) 76%, transparent); outline-offset: -1px; }
218 .quick-row .quick-glyph { color: var(--gold); font-size: 18px; text-align: center; }
219 .quick-row .quick-title { font-weight: 650; } .quick-row .quick-note { color: var(--t-dim); }
220 .quick-row .quick-command { color: var(--accent2); } .quick-row .quick-arrow { color: var(--t-faint); }
221 .startup-options { display: grid; grid-template-columns: repeat(4,1fr); border-top: 1px solid var(--t-faint); }
222 .startup-options button { min-height: 67px; border: 0; border-right: 1px solid var(--t-faint); background: transparent; color: var(--t-fg); text-align: left; padding: 8px 10px; cursor: pointer; }
223 .startup-options button:last-child { border-right: 0; }
224 .startup-options button:hover { color: var(--accent2); background: color-mix(in srgb, var(--accent) 8%, transparent); }
225 .startup-options span { display: block; color: var(--t-dim); font-size: 11px; margin-top: 2px; }
226
227 /* Settings: typed facts and a live projection, not an ambiguous table. */
228 .settings-layout { display: grid; grid-template-columns: 192px minmax(0,1fr) minmax(300px,.9fr); min-height: 650px; }
229 .settings-nav { padding: 15px 10px; border-right: 1px solid color-mix(in srgb, var(--t-faint) 60%, transparent); }
230 .settings-nav button { display: flex; align-items: center; justify-content: space-between; width: 100%; min-height: 34px; border: 0; background: transparent; color: var(--t-dim); padding: 7px 10px; text-align: left; cursor: pointer; font: inherit; }
231 .settings-nav button:hover, .settings-nav button.selected { color: var(--accent2); background: color-mix(in srgb, var(--accent) 12%, transparent); }
232 .settings-main { padding: 18px; min-width: 0; }
233 .settings-title { color: var(--accent2); font-weight: 700; font-size: 15px; }
234 .settings-sub { margin: 5px 0 16px; color: var(--t-dim); }
235 .theme-list { border: 1px solid var(--t-faint); }
236 .theme-row { display: grid; grid-template-columns: 18px minmax(0,1fr) auto; align-items: center; gap: 9px; width: 100%; min-height: 39px; padding: 8px 10px; border: 0; border-bottom: 1px solid color-mix(in srgb, var(--t-faint) 62%, transparent); background: transparent; color: var(--t-fg); text-align: left; cursor: pointer; font: inherit; }
237 .theme-row:last-child { border-bottom: 0; }
238 .theme-row:hover, .theme-row.selected { background: color-mix(in srgb, var(--accent) 12%, transparent); outline: 1px solid var(--accent2); outline-offset: -1px; }
239 .theme-row .check { color: var(--accent2); } .theme-row .theme-note { color: var(--t-dim); font-size: 11px; }
240 .typed-facts { margin-top: 19px; border-top: 1px solid var(--t-faint); }
241 .fact { border-bottom: 1px solid color-mix(in srgb, var(--t-faint) 47%, transparent); }
242 .fact button { display: grid; grid-template-columns: minmax(120px,.95fr) minmax(0,1.2fr); width: 100%; gap: 8px 13px; padding: 10px 0; color: inherit; border: 0; background: transparent; text-align: left; cursor: pointer; font: inherit; }
243 .fact button:hover, .fact button.selected { background: color-mix(in srgb, var(--accent) 8%, transparent); outline: 1px solid color-mix(in srgb, var(--accent2) 62%, transparent); outline-offset: -1px; }
244 .fact .fact-name { color: var(--t-fg); font-weight: 650; }.fact .fact-value { color: var(--accent2); }
245 .fact .fact-meta { grid-column: 2; color: var(--t-dim); font-size: 11px; }.fact .fact-meta b { color: var(--t-fg); font-weight: 600; }
246 .settings-preview { padding: 18px; border-left: 1px solid color-mix(in srgb, var(--t-faint) 60%, transparent); }
247 .preview-label { color: var(--accent2); font-size: 11px; letter-spacing: .07em; text-transform: uppercase; }
248 .preview-terminal { position: relative; min-height: 438px; margin-top: 10px; padding: 12px; overflow: hidden; border: 1px solid var(--t-faint); background: var(--t-bg); }
249 .preview-terminal.deepsea { background: linear-gradient(168deg,#071526,#03101d 66%,#020a14); }
250 .preview-terminal.deepsea::before { content: ""; pointer-events: none; position: absolute; inset: 0; opacity: .45; background-image: radial-gradient(circle, rgba(72,215,255,.9) 0 1px, transparent 1.8px); background-size: 128px 102px; }
251 .preview-terminal > * { position: relative; }
252 .preview-message { padding: 7px 0 12px; border-bottom: 1px solid var(--t-faint); }
253 .preview-message strong { color: var(--gold); } .preview-message .name { color: var(--accent2); }
254 .preview-mini-pod { margin-top: 10px; padding-left: 9px; border-left: 2px solid var(--accent2); line-height: 1.7; }
255 .preview-mini-pod .pass { color: var(--ok); } .preview-mini-pod .fail { color: var(--err); } .preview-mini-pod .think { color: var(--reason); }
256 .preview-context { position: absolute; right: 11px; bottom: 9px; color: var(--t-dim); }
257 .motion-lines { margin: 16px 0 0; border: 1px solid var(--t-faint); }
258 .motion-line { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; min-height: 40px; padding: 8px 10px; border-bottom: 1px solid color-mix(in srgb, var(--t-faint) 58%, transparent); }
259 .motion-line:last-child { border-bottom: 0; }.motion-line .small { color: var(--t-dim); font-size: 11px; }.motion-line .on { color: var(--ok); }.motion-line .off { color: var(--t-dim); }
260
261 /* Notification center keeps evidence in one calm place. */
262 .notice-layout { display: grid; grid-template-columns: minmax(0,1fr) 235px; min-height: 650px; }
263 .notice-main { padding: 18px 21px; min-width: 0; }.notice-side { padding: 18px 14px; border-left: 1px solid color-mix(in srgb, var(--t-faint) 60%, transparent); }
264 .notice-header { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; border-bottom: 1px solid var(--t-faint); padding-bottom: 10px; }.notice-header h2 { margin: 0; font-size: 16px; color: var(--accent2); }.notice-header p { margin: 0; color: var(--t-dim); }
265 .notice-row { display: grid; grid-template-columns: auto minmax(0,1fr) auto; gap: 12px; align-items: start; padding: 12px 3px; border-bottom: 1px solid color-mix(in srgb, var(--t-faint) 50%, transparent); cursor: pointer; }
266 .notice-row:hover, .notice-row.selected { background: color-mix(in srgb, var(--accent) 8%, transparent); outline: 1px solid color-mix(in srgb, var(--accent2) 62%, transparent); outline-offset: -1px; }.notice-icon { color: var(--accent2); }.notice-icon.attention { color: var(--gold); }.notice-icon.complete { color: var(--ok); }.notice-title { color: var(--t-fg); font-weight: 650; }.notice-detail, .notice-time { color: var(--t-dim); font-size: 12px; }.notice-time { white-space: nowrap; }
267 .notice-side h3 { margin: 0 0 9px; color: var(--t-dim); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }.delivery-row { display: flex; justify-content: space-between; gap: 8px; min-height: 33px; padding: 6px 0; border-bottom: 1px solid color-mix(in srgb, var(--t-faint) 45%, transparent); }.delivery-row span:last-child { color: var(--ok); }.delivery-row.off span:last-child { color: var(--t-dim); }.notice-side p { color: var(--t-dim); font-size: 11px; }.notice-side button { width: 100%; margin-top: 9px; padding: 8px; border: 1px solid var(--t-faint); background: transparent; color: var(--accent2); cursor: pointer; text-align: left; }.notice-side button:hover { border-color: var(--accent2); }
268
269 /* Inspector gives every clickable target an explicit owner/action. */
270 .inspector { position: sticky; top: 16px; border: 1px solid var(--card-edge); border-radius: 11px; overflow: hidden; background: var(--card); }
271 .inspector-head { display: flex; justify-content: space-between; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--card-edge); color: var(--page-dim); font-size: 11px; letter-spacing: .07em; text-transform: uppercase; }.inspect-live { color: var(--accent2); }
272 .inspect-body { padding: 13px; }.inspect-id { color: var(--accent); font: 700 12px/1.35 var(--mono); word-break: break-word; }.inspect-title { margin: 7px 0 5px; color: var(--page-fg); font-weight: 700; }.inspect-copy { margin: 0; color: var(--page-dim); font-size: 12px; }.inspect-list { display: grid; gap: 8px; margin: 14px 0 0; }.inspect-item { padding-top: 8px; border-top: 1px solid var(--card-edge); }.inspect-k { display: block; color: var(--page-dim); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }.inspect-v { display: block; margin-top: 2px; color: var(--page-fg); font: 11px/1.45 var(--mono); word-break: break-word; }.inspector-hints { padding: 10px 12px; border-top: 1px solid var(--card-edge); color: var(--page-dim); font-size: 11px; }.inspector-hints b { color: var(--page-fg); }
273
274 /* Documentation after the working screens keeps the plan implementable. */
275 .doc-section { margin-top: 70px; }.doc-section h2 { margin: 0 0 7px; font-size: 24px; letter-spacing: -.025em; }.doc-section > p { max-width: 870px; margin: 0; color: var(--page-dim); }.rule-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; margin-top: 17px; }.rule { padding: 14px; border: 1px solid var(--card-edge); border-radius: 10px; background: var(--card); }.rule b { color: var(--accent); }.rule p { margin: 6px 0 0; color: var(--page-dim); font-size: 13px; }
276 .spec-table { width: 100%; margin-top: 17px; border-collapse: collapse; font-size: 12px; }.spec-table th, .spec-table td { padding: 9px 10px; border-bottom: 1px solid var(--card-edge); text-align: left; vertical-align: top; }.spec-table th { color: var(--page-dim); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; }.spec-table code { color: var(--accent); font-size: 11px; }.status-existing { color: var(--ok); }.status-proposed { color: var(--gold); }.status-guard { color: var(--warn); }
277 .manifest-note { margin-top: 13px; padding: 12px 14px; border-left: 2px solid var(--accent2); background: color-mix(in srgb, var(--accent) 5%, var(--card)); color: var(--page-dim); font-size: 13px; }.manifest-note b { color: var(--page-fg); }
278 .glyph-grid { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; margin-top: 17px; }.glyph-card { padding: 12px; border: 1px solid var(--card-edge); background: var(--card); }.glyph-sample { color: var(--accent2); font: 700 20px/1 var(--mono); }.glyph-card:nth-child(2) .glyph-sample { color: var(--gold); }.glyph-card:nth-child(3) .glyph-sample { color: var(--accent); }.glyph-card:nth-child(4) .glyph-sample { color: var(--reason); }.glyph-card b { display: block; margin-top: 8px; }.glyph-card p { margin: 3px 0 0; color: var(--page-dim); font-size: 12px; }
279 .acceptance { margin-top: 15px; columns: 2; column-gap: 32px; }.acceptance label { display: block; break-inside: avoid; margin: 0 0 9px; color: var(--page-dim); font-size: 13px; }.acceptance input { margin-right: 8px; accent-color: var(--accent); }
280
281 .hidden { display: none !important; }
282 .narrow-preview .terminal { max-width: 732px; min-height: 580px; }.narrow-preview .work-layout { grid-template-columns: 1fr; }.narrow-preview .side-rail { display: none; }.narrow-preview .topbar .collapse-on-narrow, .narrow-preview .footer .collapse-on-narrow { display: none; }.narrow-preview .ledger td:nth-child(4), .narrow-preview .ledger th:nth-child(4), .narrow-preview .ledger td:nth-child(5), .narrow-preview .ledger th:nth-child(5) { display: none; }.narrow-preview .settings-layout { grid-template-columns: 1fr; }.narrow-preview .settings-preview { display: none; }.narrow-preview .settings-nav { display: flex; gap: 5px; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--t-faint); padding: 8px; }.narrow-preview .settings-nav button { width: auto; white-space: nowrap; }.narrow-preview .notice-layout { grid-template-columns: 1fr; }.narrow-preview .notice-side { display: none; }.narrow-preview .startup { padding: 0 3%; }.narrow-preview .startup-options { grid-template-columns: repeat(2,1fr); }.narrow-preview .quick-row { grid-template-columns: 30px minmax(0,1fr) auto; }.narrow-preview .quick-note { display: none; }
283 .rail-collapsed .side-rail { display: none; }.rail-collapsed .work-layout { grid-template-columns: 1fr; }
284 @media (max-width: 1180px) { .stage-grid { grid-template-columns: 1fr; }.inspector { position: relative; top: 0; }.rule-grid { grid-template-columns: repeat(2,minmax(0,1fr)); } }
285 @media (max-width: 820px) { .page { padding: 22px 14px 70px; }.hero { grid-template-columns: 1fr; }.brief-state { text-align: left; }.doc-note { width: 100%; margin-left: 0; }.topbar { font-size: 11px; }.topbar .collapse-on-small { display: none; }.work-layout { grid-template-columns: 1fr; }.side-rail { display: none; }.settings-layout, .notice-layout { grid-template-columns: 1fr; }.settings-preview, .notice-side { display: none; }.rule-grid { grid-template-columns: 1fr; }.glyph-grid { grid-template-columns: repeat(2,minmax(0,1fr)); }.acceptance { columns: 1; }.startup { padding: 0 3%; }.startup-options { grid-template-columns: repeat(2,1fr); }.quick-row { grid-template-columns: 30px minmax(0,1fr) auto; }.quick-row .quick-note { display: none; }.terminal { min-height: 590px; }.receipt { grid-template-columns: 20px 82px minmax(0,1fr); }.receipt .time { display: none; } }
286 @media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; } }
287 </style>
288 </head>
289 <body class="dark" data-skin="match-terminal">
290 <main class="page" id="top">
291 <header class="hero">
292 <div>
293 <div class="eyebrow">Codewhale visual + interaction contract</div>
294 <h1>A normal coding workbench.<br>One unmistakable whale.</h1>
295 <p class="intro">Conversation stays primary. A pod, receipts, context, and settings are there when the work needs them. The surface inherits the terminal that brought it; Deepsea is an explicit atmosphere, not a default wallpaper.</p>
296 </div>
297 <p class="brief-state"><strong>Build target</strong><br>Ratatui shell + CWC parity<br><span class="deepsea-note">Deepsea is opt-in.</span></p>
298 </header>
299
300 <div class="control-bar" aria-label="Design controls">
301 <span class="control-label">screen</span>
302 <div class="control-group" role="group" aria-label="Screen">
303 <button type="button" data-screen="work" aria-pressed="true">workbench</button>
304 <button type="button" data-screen="startup" aria-pressed="false">startup</button>
305 <button type="button" data-screen="settings" aria-pressed="false">settings</button>
306 <button type="button" data-screen="notices" aria-pressed="false">notifications</button>
307 </div>
308 <span class="control-label">skin</span>
309 <div class="control-group" role="group" aria-label="Terminal skin">
310 <button type="button" data-skin="match-terminal" aria-pressed="true">match terminal</button>
311 <button type="button" data-skin="dark" aria-pressed="false">dark</button>
312 <button type="button" data-skin="light" aria-pressed="false">light</button>
313 <button type="button" data-skin="deepsea" aria-pressed="false">deepsea</button>
314 <button type="button" data-skin="ansi" aria-pressed="false">ANSI</button>
315 </div>
316 <span class="control-label">view</span>
317 <div class="control-group" role="group" aria-label="View controls">
318 <button type="button" id="toggle-narrow" aria-pressed="false">80 × 24</button>
319 <button type="button" id="toggle-rail" aria-pressed="false">fold rail</button>
320 <button type="button" id="toggle-motion" aria-pressed="false">low motion</button>
321 </div>
322 <span class="doc-note">Click any bordered surface to inspect its owner, typed state, action, and hitbox contract.</span>
323 </div>
324
325 <section class="stage-grid" aria-label="Interactive terminal specification">
326 <div class="host-card" id="host-card">
327 <div class="host-caption">
328 <span><b>host terminal simulation</b> <span class="inherit">· background is reset in Ratatui</span></span>
329 <span class="mono">wide 140 × 40</span>
330 </div>
331
332 <div class="terminal" id="terminal" data-background="Color::Reset">
333 <div class="topbar">
334 <button class="brand-lockup" type="button" data-target="brand.primary" title="Codewhale identity">CODEWHALE</button>
335 <span class="divider"></span>
336 <button type="button" data-target="header.workspace"><span class="label">folder</span> <span class="path">~/codewhale</span></button>
337 <span class="divider collapse-on-small"></span>
338 <button type="button" data-target="header.route"><span class="label">route</span> <span class="value">main</span></button>
339 <span class="divider collapse-on-narrow"></span>
340 <button type="button" class="collapse-on-small" data-target="header.model"><span class="label">model</span> <span class="value">Z.ai / GLM-5.3</span></button>
341 <span class="divider collapse-on-narrow"></span>
342 <button type="button" class="collapse-on-small" data-target="header.pod"><span class="label">pod</span> <span class="value">3 / 4 whales</span></button>
343 <button type="button" class="context" data-target="header.context"><span class="label">context</span> <span class="value">61%</span><span class="context-meter" aria-label="61 percent context"><i></i></span></button>
344 <button type="button" data-target="header.notifications" title="Notifications"><span class="attention-dot">●</span></button>
345 </div>
346
347 <div class="screen active" id="screen-work">
348 <div class="work-layout">
349 <aside class="side-rail" aria-label="Work navigation">
350 <h3>Runs</h3>
351 <button type="button" class="rail-row selected" data-target="sidebar.run.current"><span>▸ release 0.9.12</span><span class="rail-count">●</span></button>
352 <button type="button" class="rail-row" data-target="sidebar.run.previous"><span>&nbsp;&nbsp;release 0.9.11</span></button>
353 <button type="button" class="rail-row" data-target="sidebar.new-run"><span>+ New run</span></button>
354 <h3>Whales</h3>
355 <button type="button" class="rail-row" data-target="pod.scout"><span><i class="state-live">●</i> Scout</span><span class="state-live">working</span></button>
356 <button type="button" class="rail-row" data-target="pod.builder"><span>○ Builder</span><span>waiting</span></button>
357 <button type="button" class="rail-row" data-target="pod.verifier"><span><i class="state-live">✓</i> Verifier</span><span class="state-live">done</span></button>
358 <button type="button" class="rail-row" data-target="pod.navigator"><span><i class="state-wait">!</i> Navigator</span><span class="state-wait">idle</span></button>
359 <h3>Pod</h3>
360 <button type="button" class="rail-row" data-target="pod.launch"><span>▸ launch pod</span><span class="rail-count">⌁</span></button>
361 <button type="button" class="rail-row" data-target="pod.config"><span>&nbsp;&nbsp;pod config</span></button>
362 <button type="button" class="rail-row" data-target="pod.ledger"><span>&nbsp;&nbsp;pod ledger</span></button>
363 <h3>Context</h3>
364 <button type="button" class="rail-row" data-target="context.files"><span>files</span><span>61%</span></button>
365 <button type="button" class="rail-row" data-target="context.notes"><span>notes</span><span>9</span></button>
366 <button type="button" class="rail-row" data-target="settings.open"><span>⚙ settings</span></button>
367 </aside>
368
369 <section class="workspace">
370 <div class="conversation">
371 <article class="turn user">
372 <span class="turn-marker" aria-hidden="true">○</span>
373 <div class="turn-head"><strong>USER</strong><time>14:41:02</time></div>
374 <div class="turn-copy">Make release 0.9.12 ready.</div>
375 </article>
376 <article class="turn assistant">
377 <span class="turn-marker" aria-hidden="true">◌</span>
378 <div class="turn-head"><strong>CODEWHALE</strong><time>14:41:02</time></div>
379 <div class="turn-copy">I’ll prepare release 0.9.12. Spawning a pod to run checks in parallel.<br><span class="dim">Plan: fix CI, audit contributor ancestry, draft release notes.</span></div>
380
381 <button type="button" class="pod-block" data-target="pod.formation">
382 <span class="pod-title">⌁ pod formation</span> <span class="dim">launched launch pod with 3 whales</span>
383 <span class="pod-tree">├─ <span class="live">●</span> <b>Scout</b> &nbsp;&nbsp; audit contributor PRs<br>├─ ○ <b>Builder</b> &nbsp;&nbsp; repair CI<br>└─ <span class="live">✓</span> <b>Verifier</b> &nbsp;&nbsp; exact-head checks</span>
384 </button>
385
386 <div class="receipts" aria-label="Live receipts">
387 <button type="button" class="receipt" data-target="receipt.scout-commit"><span class="glyph">◌</span><span class="kind">Scout</span><span>2 commits pushed. CI: fix lint config</span><span class="time">2 receipts</span></button>
388 <button type="button" class="receipt" data-target="receipt.builder-ci"><span class="glyph">◌</span><span class="kind">Builder</span><span>ci-repair in progress</span><span class="time">1 receipt</span></button>
389 <button type="button" class="receipt warn" data-target="receipt.verifier-caution"><span class="glyph">!</span><span class="kind">Verifier</span><span>PR #1427 found; marking caution</span><span class="time">1 receipt</span></button>
390 <button type="button" class="receipt" data-target="receipt.verifier-notes"><span class="glyph">◌</span><span class="kind">Verifier</span><span>notes.md updated (412 additions)</span><span class="time">1 receipt</span></button>
391 <button type="button" class="receipt ok" data-target="receipt.verifier-pass"><span class="glyph">✓</span><span class="kind">Verifier</span><span>exact-head checks passed; pod ready</span><span class="time">1 receipt</span></button>
392 </div>
393 <p class="evidence-line"><b>Caution:</b> PR #1427 is marked do-not-merge and is in the integration ancestry.</p>
394 </article>
395 </div>
396
397 <section class="ledger" aria-label="Pod ledger">
398 <div class="ledger-head"><span>POD LEDGER <span class="label">launch pod</span></span><span>3/3 active</span></div>
399 <table>
400 <thead><tr><th>whale</th><th>assignment</th><th>state</th><th>elapsed</th><th>receipts</th><th>last update</th></tr></thead>
401 <tbody>
402 <tr data-target="pod.scout"><td>▸ Scout</td><td>audit contributor PRs</td><td class="state-dot">● working</td><td>3m 02s</td><td>2</td><td>14:44:05</td></tr>
403 <tr data-target="pod.builder"><td>&nbsp;&nbsp;Builder</td><td>repair CI</td><td class="state-waiting">○ waiting</td><td>2m 51s</td><td>1</td><td>14:43:54</td></tr>
404 <tr data-target="pod.verifier"><td>&nbsp;&nbsp;Verifier</td><td>exact-head checks</td><td class="state-done">✓ done</td><td>3m 18s</td><td>3</td><td>14:44:05</td></tr>
405 </tbody>
406 </table>
407 </section>
408 <form class="composer" data-target="composer.input" onsubmit="return false">
409 <span class="prompt">›</span>
410 <button type="button" data-target="composer.input"><span class="placeholder">Ask Codewhale to build, fix, verify…</span></button>
411 <button type="button" class="send" data-target="composer.send" title="Send">↑</button>
412 <div class="composer-hints"><b>↵</b> send &nbsp; <b>⇧↵</b> new line &nbsp; <b>esc</b> interrupt</div>
413 </form>
414 </section>
415 </div>
416 <div class="footer"><span><span class="label">route</span> <span class="route">main</span></span><span class="sep">•</span><span class="collapse-on-narrow"><span class="label">model</span> GLM-5.3</span><span class="sep collapse-on-narrow">•</span><span><span class="label">cost</span> <span class="cost">$0.87</span> <span>(1.2K in / 2.1K out)</span></span><span class="keys"><span class="label">tab</span> next &nbsp; <span class="label">↑/↓</span> move &nbsp; <span class="label">enter</span> inspect &nbsp; <span class="label">q</span> quit</span></div>
417 </div>
418
419 <div class="screen" id="screen-startup">
420 <div class="startup">
421 <div class="startup-center">
422 <h2>What are we working on?</h2>
423 <p>Folder and tool access are asked when needed.</p>
424 </div>
425 <section class="quick-actions">
426 <div class="quick-head">Quick actions <span class="label">· tab actions · enter choose · esc back</span></div>
427 <div class="quick-grid">
428 <button type="button" class="quick-row selected" data-target="startup.connect"><span class="quick-glyph">⌁</span><span class="quick-title">Connect a model</span><span class="quick-note">Required before the first model turn.</span><span class="quick-command">/connect</span><span class="quick-arrow">›</span></button>
429 <button type="button" class="quick-row" data-target="startup.resume"><span class="quick-glyph">↶</span><span class="quick-title">Resume recent work</span><span class="quick-note"><span class="tone-color" style="color:var(--ok)">2 sessions</span> · Pick up where you left off.</span><span class="quick-command">/resume</span><span class="quick-arrow">›</span></button>
430 <button type="button" class="quick-row" data-target="startup.work"><span class="quick-glyph">□</span><span class="quick-title">Start work</span><span class="quick-note">Plan and execute in this folder.</span><span class="quick-command">/work</span><span class="quick-arrow">›</span></button>
431 </div>
432 <div class="startup-options">
433 <button type="button" data-target="startup.worktree"><span class="tone-color" style="color:var(--gold)">⌘</span> New worktree<span>Create an isolated worktree.</span></button>
434 <button type="button" data-target="startup.chat"><span class="tone-color" style="color:var(--gold)">◌</span> Chat only<span>Conversation without changes.</span></button>
435 <button type="button" data-target="settings.theme"><span class="tone-color" style="color:var(--gold)">◒</span> Theme<span>Match terminal.</span></button>
436 <button type="button" data-target="startup.help"><span class="tone-color" style="color:var(--gold)">?</span> Help<span>Shortcuts & docs.</span></button>
437 </div>
438 <form class="composer" data-target="composer.input" onsubmit="return false" style="margin-top:14px"><span class="prompt">›</span><button type="button" data-target="composer.input"><span class="placeholder">Ask Codewhale anything…</span></button><button type="button" class="send" data-target="composer.send">↑</button><div class="composer-hints"><b>↵</b> send &nbsp; <b>⇧↵</b> new line</div></form>
439 </section>
440 </div>
441 <div class="footer"><span><span class="label">route</span> <span class="route">main</span></span><span class="sep">•</span><span><span class="label">model</span> <span style="color:var(--gold)">not connected</span></span><span class="keys"><span class="label">tab</span> next &nbsp; <span class="label">enter</span> choose &nbsp; <span class="label">esc</span> back</span></div>
442 </div>
443
444 <div class="screen" id="screen-settings">
445 <div class="settings-layout">
446 <nav class="settings-nav" aria-label="Settings categories">
447 <button type="button" class="selected" data-target="settings.appearance">Appearance <span>›</span></button>
448 <button type="button" data-target="settings.providers">Models & providers</button>
449 <button type="button" data-target="settings.pod">Pod</button>
450 <button type="button" data-target="settings.work">Work</button>
451 <button type="button" data-target="settings.tools">Tools & MCP</button>
452 <button type="button" data-target="settings.trust">Trust</button>
453 <button type="button" data-target="settings.motion">Motion</button>
454 <button type="button" data-target="settings.advanced">Advanced</button>
455 </nav>
456 <section class="settings-main">
457 <div class="settings-title">Appearance</div>
458 <p class="settings-sub">Match Terminal is the default. Dark and Light keep a reset background; Deepsea is the only theme that paints water.</p>
459 <div class="theme-list" role="listbox" aria-label="Theme selection">
460 <button type="button" class="theme-row selected" data-target="settings.theme.match"><span class="check">✓</span><span>Match Terminal <span class="theme-note">— recommended</span></span><span class="theme-note">background inherited</span></button>
461 <button type="button" class="theme-row" data-target="settings.theme.dark"><span class="check"></span><span>Dark</span><span class="theme-note">background reset</span></button>
462 <button type="button" class="theme-row" data-target="settings.theme.light"><span class="check"></span><span>Light</span><span class="theme-note">background reset</span></button>
463 <button type="button" class="theme-row" data-target="settings.theme.deepsea"><span class="check"></span><span>Deepsea</span><span class="theme-note">ocean + life, opt-in</span></button>
464 <button type="button" class="theme-row" data-target="settings.theme.other"><span class="check"></span><span>Grayscale · Catppuccin Mocha · Tokyo Night · Dracula · Gruvbox Dark · Claude · Matrix · Solarized Light · Uwu</span><span class="theme-note">selectable presets</span></button>
465 </div>
466
467 <div class="typed-facts" aria-label="Settings truth facts">
468 <div class="fact"><button type="button" data-target="settings.provider"><span class="fact-name">Current provider</span><span class="fact-value">Z.ai</span><span class="fact-meta"><b>effective now</b> · session authority · follows active route</span></button></div>
469 <div class="fact"><button type="button" data-target="settings.model"><span class="fact-name">Current model</span><span class="fact-value">GLM-5.3</span><span class="fact-meta"><b>effective now</b> · session authority · no stale provider label</span></button></div>
470 <div class="fact"><button type="button" data-target="settings.startup-provider"><span class="fact-name">Startup provider</span><span class="fact-value">OpenRouter</span><span class="fact-meta"><b>saved default</b> · persists for a new session · not asserted as current</span></button></div>
471 <div class="fact"><button type="button" data-target="settings.notification"><span class="fact-name">Completion sound</span><span class="fact-value">Off</span><span class="fact-meta"><b>saved default</b> · audio is independent from banner and in-app history</span></button></div>
472 </div>
473 </section>
474 <aside class="settings-preview">
475 <div class="preview-label">Live preview — match terminal</div>
476 <div class="preview-terminal" id="settings-preview-terminal">
477 <div class="preview-message"><strong>USER</strong> <span class="label">14:41:02</span><br>Make release 0.9.12 ready.</div>
478 <div class="preview-message"><span class="name">CODEWHALE</span> <span class="label">14:41:02</span><br>I’ll prepare release 0.9.12. Spawning a pod to run checks in parallel.</div>
479 <div class="preview-mini-pod"><span class="name">⌁ pod formation</span> launched launch pod with 3 whales<br>◌ Scout &nbsp; audit contributor PRs &nbsp; <span class="pass">(2 receipts)</span><br>◌ Builder &nbsp; ci-repair in progress<br><span class="pass">✓ Verifier</span> &nbsp; exact-head checks passed<br><span class="fail">! Builder</span> &nbsp; tests failed in ci-repair job<br><span class="think">Reasoning:</span> root cause appears to be flaky network mock.</div>
480 <div class="preview-context">context <span class="value">61%</span> ▰▰▰▱▱</div>
481 </div>
482 <div class="motion-lines">
483 <div class="motion-line"><span>Fancy animations <span class="small">subtle one-shot phase transitions</span></span><span class="on">on</span></div>
484 <div class="motion-line"><span>Low motion <span class="small">still equivalent, no ambient movement</span></span><span class="off">off</span></div>
485 </div>
486 </aside>
487 </div>
488 <div class="footer"><span><span class="label">arrows</span> move</span><span class="sep">|</span><span><span class="label">enter</span> preview / apply</span><span class="sep">|</span><span><span class="label">esc</span> back</span><span class="keys"><span class="label">tab</span> next section &nbsp; <span class="label">click</span> select</span></div>
489 </div>
490
491 <div class="screen" id="screen-notices">
492 <div class="notice-layout">
493 <section class="notice-main">
494 <header class="notice-header"><div><h2>Notifications</h2><p>History is inspectable. Banner, sound, and in-app delivery are separate choices.</p></div><button type="button" data-target="notifications.mark-read" style="border:0;background:transparent;color:var(--accent2);cursor:pointer">mark all read</button></header>
495 <button type="button" class="notice-row selected" data-target="notification.approval"><span class="notice-icon attention">◆</span><span><span class="notice-title">Approval needed</span><br><span class="notice-detail">bash · needs your answer before work continues</span></span><span class="notice-time">now</span></button>
496 <button type="button" class="notice-row" data-target="notification.pod-complete"><span class="notice-icon complete">✓</span><span><span class="notice-title">Pod complete</span><br><span class="notice-detail">3 of 3 whales settled · 6 receipts attached</span></span><span class="notice-time">4m ago</span></button>
497 <button type="button" class="notice-row" data-target="notification.model"><span class="notice-icon">◌</span><span><span class="notice-title">Model update</span><br><span class="notice-detail">A model-authored note is classified as informational; it cannot self-escalate.</span></span><span class="notice-time">13m ago</span></button>
498 </section>
499 <aside class="notice-side">
500 <h3>Delivery</h3>
501 <div class="delivery-row"><span>In app</span><span>on</span></div>
502 <div class="delivery-row"><span>Banner</span><span>unfocused only</span></div>
503 <div class="delivery-row off"><span>Completion sound</span><span>off</span></div>
504 <div class="delivery-row"><span>Subagent completion</span><span>final only</span></div>
505 <h3 style="margin-top:18px">Sound</h3>
506 <p>Resurface is a future optional sound: a short pressure-release, soft exhale, and tonal bloom. No stock splash, no cartoon whale.</p>
507 <button type="button" data-target="settings.notification">Open notification settings ›</button>
508 </aside>
509 </div>
510 <div class="footer"><span><span class="label">enter</span> inspect</span><span class="sep">|</span><span><span class="label">r</span> mark read</span><span class="keys"><span class="label">esc</span> back</span></div>
511 </div>
512 </div>
513 </div>
514
515 <aside class="inspector" aria-live="polite" aria-label="Implementation inspector">
516 <div class="inspector-head"><span>Implementation inspector</span><span class="inspect-live">live</span></div>
517 <div class="inspect-body" id="inspect-body">
518 <div class="inspect-id">header.context</div>
519 <div class="inspect-title">Context budget</div>
520 <p class="inspect-copy">Identity-blue progress; it owns the current context percentage and opens the context inspector.</p>
521 <div class="inspect-list">
522 <div class="inspect-item"><span class="inspect-k">owner</span><span class="inspect-v">underwater.rs + context inspector</span></div>
523 <div class="inspect-item"><span class="inspect-k">state</span><span class="inspect-v">ContextBudgetSnapshot</span></div>
524 <div class="inspect-item"><span class="inspect-k">input</span><span class="inspect-v">Enter/click → open context</span></div>
525 <div class="inspect-item"><span class="inspect-k">hitbox</span><span class="inspect-v">recorded Rect, keyboard focus parity</span></div>
526 </div>
527 </div>
528 <div class="inspector-hints"><b>Click</b> an element · <b>Tab</b> moves between targets · <b>Enter</b> inspects · <b>Esc</b> restores workbench</div>
529 </aside>
530 </section>
531
532 <nav class="section-nav" aria-label="Design document sections" style="margin-top:24px">
533 <a href="#principles">principles</a><a href="#glyphs">glyph grammar</a><a href="#wiring">wiring manifest</a><a href="#acceptance">acceptance</a>
534 </nav>
535
536 <section class="doc-section" id="principles">
537 <h2>What makes it Codewhale</h2>
538 <p>It should feel as legible as Codex, Claude Code, or a sober native workbench before it feels branded. The terminal uses timing, language, a small current of cyan, and a restrained wordmark; surfaces that can render the canonical raster use that exact asset.</p>
539 <div class="rule-grid">
540 <article class="rule"><b>Host first</b><p>Match Terminal, Dark, and Light use <code>Color::Reset</code> for the background. Their foreground palette adapts; the user’s terminal remains the surface.</p></article>
541 <article class="rule"><b>Conversation first</b><p>The transcript is the default lens. The rail and ledger can fold; receipts are quiet until a person asks for evidence.</p></article>
542 <article class="rule"><b>One fact, one owner</b><p>Context lives in the header. Work lives in the top/rail. Receipts remain at their event. Route, cost, and phase live in the footer.</p></article>
543 <article class="rule"><b>Deepsea is consent</b><p>Only Deepsea paints a deep blue field and underwater points. It stays still when low motion is selected, and it never becomes default wallpaper.</p></article>
544 <article class="rule"><b>Glow means live</b><p>Cyan/gold bloom is a bounded working or completion transition. Settled output is still; color never invents a state without typed evidence.</p></article>
545 <article class="rule"><b>Everything navigates</b><p>Any selectable row has visible focus, a recorded hitbox, keyboard and mouse parity, and an inspect action. No decorative glyph asks to be clicked.</p></article>
546 </div>
547 </section>
548
549 <section class="doc-section" id="glyphs">
550 <h2>Small whale grammar</h2>
551 <p>The canonical raster mark has no approved terminal-glyph substitute. These glyphs are working notation rather than brand assets, and all retain an ASCII fallback.</p>
552 <div class="glyph-grid">
553 <article class="glyph-card"><div class="glyph-sample">⌁</div><b>Pod formation</b><p>Parallel work beginning. Never a generic button icon.</p></article>
554 <article class="glyph-card"><div class="glyph-sample">◌</div><b>Wake / live work</b><p>Active receipt or a running worker. Static in low-motion and ANSI.</p></article>
555 <article class="glyph-card"><div class="glyph-sample">◆</div><b>Spout / attention</b><p>A real user decision is needed. Gold and words; not an error.</p></article>
556 <article class="glyph-card"><div class="glyph-sample">✓ ! ×</div><b>Outcome</b><p>Passed, caution, failure. Failure red stays reserved for actual failure.</p></article>
557 </div>
558 <div class="manifest-note"><b>Brand asset rule:</b> the graphic app consumes approved source assets through the brand manifest. Ratatui consumes a tested cell projection from the same manifest. This HTML intentionally does not invent an SVG substitute.</div>
559 </section>
560
561 <section class="doc-section" id="wiring">
562 <h2>Ratatui wiring manifest</h2>
563 <p>This maps the visual system to the current implementation owners and marks the necessary new projection types. It is deliberately a concrete migration path, not a second runtime or a new turn loop.</p>
564 <table class="spec-table">
565 <thead><tr><th>surface / fact</th><th>owner</th><th>typed state / contract</th><th>status</th></tr></thead>
566 <tbody>
567 <tr><td>Header, context %, phase, route/cost footer</td><td><code>crates/tui/src/tui/underwater.rs</code></td><td><code>ShellPhase</code>, <code>ContextBudgetSnapshot</code>, <code>OceanTreatment</code>; use <code>palette::grammar::ChromeInk</code></td><td class="status-existing">existing owner; compose new layout</td></tr>
568 <tr><td>Foldable work rail, runs, workers</td><td><code>tui/sidebar.rs</code> + <code>tui/work_surface/</code></td><td><code>SidebarAgentRow</code>, <code>SidebarSubagentSummary</code>, <code>WorkSurfaceState</code></td><td class="status-existing">existing owner; promote pod hierarchy</td></tr>
569 <tr><td>Transcript, receipts, composer</td><td><code>tui/ui.rs</code> + <code>tui/app/composer.rs</code></td><td><code>UiSnapshot</code> (proposed projection), existing transcript records and composer state</td><td class="status-proposed">add projection only; preserve engine</td></tr>
570 <tr><td>Pod command + legacy fleet migration</td><td><code>tui/views/fleet_setup.rs</code> + protocol/subagent owners</td><td>external <code>/pod</code>; <code>/fleet</code> alias; internal <code>FleetRole</code> migrates behind compatibility boundary</td><td class="status-proposed">one-way product migration</td></tr>
571 <tr><td>Settings truth and live preview</td><td><code>tui/views/mod.rs::ConfigView</code> + <code>tui/theme_picker.rs</code></td><td><code>SettingFact</code> proposed: current, effective, startup, saved, authority, apply semantics, editor, availability</td><td class="status-proposed">replace ambiguous Saved/Session claims</td></tr>
572 <tr><td>Notifications + settings</td><td><code>tui/notifications.rs</code>, <code>notification_payload.rs</code>, <code>sound_policy.rs</code></td><td><code>NotificationRecord</code> + <code>DeliveryPlan</code> proposed; existing <code>NotificationPayload</code>/<code>NotificationKind</code> retained</td><td class="status-proposed">in-app, banner, sound independent</td></tr>
573 <tr><td>Click / keyboard / inspect</td><td><code>tui/mouse_ui.rs</code> + each view</td><td><code>InteractionTarget</code> proposed registry; every selectable target records <code>Rect</code>, focus, action, inspect detail</td><td class="status-proposed">no visual-only controls</td></tr>
574 <tr><td>Theme + motion</td><td><code>tui/theme_picker.rs</code>, <code>tui/ocean.rs</code>, <code>tui/motion/</code></td><td><code>ThemeId</code>, <code>OceanTreatment</code>, <code>MotionPolicy::from_settings(low_motion, fancy_animations,…)</code></td><td class="status-existing">add Deepsea treatment; retain presets</td></tr>
575 <tr><td>Brand mark</td><td><code>tui/glyphs.rs</code> + <code>tui/underwater.rs</code></td><td><code>BrandMark</code>/<code>CellProjection</code> proposed; approval-gated source asset manifest; ASCII projection</td><td class="status-guard">asset approval before full fan-out</td></tr>
576 <tr><td>Turn lifecycle</td><td><code>core/engine/turn_loop.rs</code></td><td><code>Engine::run_turn</code> remains the sole turn loop; no new loop</td><td class="status-guard">guarded; do not change</td></tr>
577 <tr><td>Prompt</td><td><code>crates/tui/src/prompts/text.rs</code></td><td><code>BASE_PROMPT</code> untouched</td><td class="status-guard">out of scope</td></tr>
578 </tbody>
579 </table>
580 <div class="manifest-note"><b>CWC parity:</b> the web app shares the information architecture, terms, state grammar, typed notification facts, provider truth, and current-mark asset manifest. It does not imitate a terminal. Web controls render as normal app controls; the TUI renders the same contracts as cells.</div>
581 </section>
582
583 <section class="doc-section" id="acceptance">
584 <h2>Visual acceptance before “matches”</h2>
585 <p>Use this as the release checklist. Passing a unit test alone is not visual evidence.</p>
586 <div class="acceptance">
587 <label><input type="checkbox"> Dark terminal: host background is inherited; no painted body/surface leaks.</label>
588 <label><input type="checkbox"> Light terminal: hierarchy, focus, and semantic color remain legible.</label>
589 <label><input type="checkbox"> Deepsea: ocean only after explicit selection; life is subordinate to text.</label>
590 <label><input type="checkbox"> ANSI / NO_COLOR: words and shapes retain state meaning without truecolor.</label>
591 <label><input type="checkbox"> 80 × 24: rail and optional columns shed before transcript or composer.</label>
592 <label><input type="checkbox"> Keyboard and mouse reach every target, with visible focus and inspect data.</label>
593 <label><input type="checkbox"> Settings show current Z.ai / GLM-5.3 separately from saved startup defaults.</label>
594 <label><input type="checkbox"> Banner, in-app history, and audio settings do not masquerade as one switch.</label>
595 <label><input type="checkbox"> low_motion and fancy_animations both settle immediately and preserve the final state.</label>
596 <label><input type="checkbox"> No ASCII, block-glyph, or hand-drawn substitute is presented as the canonical raster mark.</label>
597 </div>
598 </section>
599 </main>
600
601 <script id="ratatui-wiring-manifest" type="application/json">
602 {
603 "schema": "codewhale.tideline.wiring.v1",
604 "backgroundPolicy": {
605 "match-terminal": "Color::Reset",
606 "dark": "Color::Reset",
607 "light": "Color::Reset",
608 "deepsea": "explicit OceanTreatment::Deepsea surface"
609 },
610 "targets": {
611 "brand.primary": {"title":"Primary Codewhale wordmark","owner":"glyphs.rs + underwater.rs","state":"BrandMark::Primary","action":"open product menu","hitbox":"header brand Rect","notes":"One-row terminal wordmark; it is not a substitute for the canonical raster mark."},
612 "brand.startup": {"title":"Startup prompt","owner":"underwater.rs","state":"OnboardingState","action":"none","hitbox":"none","notes":"Direct heading and subtitle; no approximate brand asset."},
613 "header.workspace": {"title":"Workspace identity","owner":"underwater.rs","state":"GitStatusSnapshot","action":"open workspace details","hitbox":"header workspace Rect","notes":"Metadata family; truncate before wrapping."},
614 "header.route": {"title":"Route","owner":"underwater.rs","state":"route identity","action":"open provider/route inspector","hitbox":"header route Rect","notes":"Identity-blue, not a status light."},
615 "header.model": {"title":"Effective model","owner":"underwater.rs + App","state":"effective provider/model route","action":"open provider inspector","hitbox":"header model Rect","notes":"Must come from current App route, not persisted config."},
616 "header.pod": {"title":"Pod capacity","owner":"sidebar.rs + work_surface","state":"SubAgentManager snapshot","action":"open pod ledger","hitbox":"header pod Rect","notes":"External product term is pod."},
617 "header.context": {"title":"Context budget","owner":"underwater.rs + context inspector","state":"ContextBudgetSnapshot","action":"open context inspector","hitbox":"header context Rect","notes":"Identity-blue percentage; state words remain present."},
618 "header.notifications": {"title":"Attention inbox","owner":"notifications.rs","state":"NotificationRecord[]","action":"open notification center","hitbox":"header notification Rect","notes":"Gold only for genuine attention."},
619 "sidebar.run.current": {"title":"Current run","owner":"sidebar.rs","state":"RunSummary","action":"select run","hitbox":"sidebar row Rect","notes":"Mouse/keyboard selection parity."},
620 "sidebar.run.previous": {"title":"Previous run","owner":"sidebar.rs","state":"RunSummary","action":"select run","hitbox":"sidebar row Rect","notes":"No separate hidden interaction."},
621 "sidebar.new-run": {"title":"New run","owner":"sidebar.rs + app","state":"RunLaunchIntent","action":"open run picker","hitbox":"sidebar row Rect","notes":"Does not start work until confirmed."},
622 "context.files": {"title":"Context files","owner":"context inspector","state":"ContextBudgetSnapshot","action":"open file contribution details","hitbox":"sidebar row Rect","notes":"Counts and percentage come from the current context snapshot."},
623 "context.notes": {"title":"Context notes","owner":"context inspector","state":"ContextBudgetSnapshot","action":"open note contribution details","hitbox":"sidebar row Rect","notes":"Passive metadata; it never claims active work."},
624 "pod.launch": {"title":"Launch pod","owner":"views/fleet_setup.rs + engine","state":"PodLaunchDraft","action":"open pod setup","hitbox":"sidebar row Rect","notes":"/pod is canonical; /fleet is an alias during migration."},
625 "pod.config": {"title":"Pod configuration","owner":"views/fleet_setup.rs","state":"PodConfiguration","action":"open pod config","hitbox":"sidebar row Rect","notes":"Profile, roles, limits, authority."},
626 "pod.ledger": {"title":"Pod ledger","owner":"sidebar.rs + work_surface","state":"SubAgentResult[]","action":"focus ledger","hitbox":"sidebar row Rect","notes":"Receipts stay inspectable beside the worker."},
627 "pod.formation": {"title":"Pod formation receipt","owner":"transcript renderer + subagent runtime","state":"PodFormationReceipt","action":"inspect launch tree","hitbox":"receipt block Rect","notes":"Pod glyph communicates parallel formation only."},
628 "pod.scout": {"title":"Scout","owner":"sidebar.rs + agent_details.rs","state":"SubAgentStatus","action":"open Scout detail","hitbox":"worker row Rect","notes":"State derives from runtime evidence."},
629 "pod.builder": {"title":"Builder","owner":"sidebar.rs + agent_details.rs","state":"SubAgentStatus","action":"open Builder detail","hitbox":"worker row Rect","notes":"Waiting is cognition/gold when a user action is needed, otherwise metadata."},
630 "pod.verifier": {"title":"Verifier","owner":"sidebar.rs + agent_details.rs","state":"SubAgentStatus","action":"open Verifier detail","hitbox":"worker row Rect","notes":"Passed is outcome green + a word."},
631 "pod.navigator": {"title":"Navigator","owner":"sidebar.rs + agent_details.rs","state":"SubAgentStatus","action":"open Navigator detail","hitbox":"worker row Rect","notes":"Idle is metadata, not failure."},
632 "receipt.scout-commit": {"title":"Commit receipt","owner":"transcript receipt renderer","state":"Receipt<CommitEvidence>","action":"inspect evidence","hitbox":"receipt row Rect","notes":"Activity cyan; source text stays selectable."},
633 "receipt.builder-ci": {"title":"CI receipt","owner":"transcript receipt renderer","state":"Receipt<CiState>","action":"inspect evidence","hitbox":"receipt row Rect","notes":"Working is typed state, not inferred from text."},
634 "receipt.verifier-caution": {"title":"Caution receipt","owner":"transcript receipt renderer","state":"Receipt<Caution>","action":"inspect provenance","hitbox":"receipt row Rect","notes":"Caution uses warning/attention, not failure red."},
635 "receipt.verifier-notes": {"title":"Artifact receipt","owner":"transcript receipt renderer","state":"Receipt<Artifact>","action":"inspect artifact","hitbox":"receipt row Rect","notes":"Receipts do not detach from the event."},
636 "receipt.verifier-pass": {"title":"Verification receipt","owner":"transcript receipt renderer","state":"Receipt<Verification>","action":"inspect test proof","hitbox":"receipt row Rect","notes":"Green means settled success only."},
637 "composer.input": {"title":"Composer","owner":"app/composer.rs","state":"ComposerState","action":"focus/edit","hitbox":"composer Rect","notes":"One normal input; no decorative tail control."},
638 "composer.send": {"title":"Send","owner":"app/composer.rs + Engine::run_turn","state":"SendIntent","action":"submit input","hitbox":"send Rect","notes":"One turn loop only."},
639 "startup.connect": {"title":"Connect a model","owner":"onboarding/mod.rs + provider picker","state":"ProviderSetupIntent","action":"open provider setup","hitbox":"quick action Rect","notes":"Only required setup gets priority."},
640 "startup.resume": {"title":"Resume recent work","owner":"onboarding/mod.rs","state":"RecentSessions","action":"open resumable sessions","hitbox":"quick action Rect","notes":"Exact count from storage."},
641 "startup.work": {"title":"Start work","owner":"onboarding/mod.rs + app","state":"WorkStartIntent","action":"open work mode","hitbox":"quick action Rect","notes":"Folder/tool access requested when needed."},
642 "startup.worktree": {"title":"New worktree","owner":"onboarding/mod.rs + git owner","state":"WorktreeIntent","action":"open worktree picker","hitbox":"quick action Rect","notes":"Requires explicit destination choice."},
643 "startup.chat": {"title":"Chat only","owner":"onboarding/mod.rs","state":"ChatModeIntent","action":"open chat mode","hitbox":"quick action Rect","notes":"No mutation authority implied."},
644 "startup.help": {"title":"Help","owner":"onboarding/mod.rs","state":"HelpIndex","action":"open help","hitbox":"quick action Rect","notes":"Keyboard discoverability."},
645 "settings.open": {"title":"Settings","owner":"views/mod.rs::ConfigView","state":"ConfigView","action":"open settings","hitbox":"sidebar row Rect","notes":"Categories replace an opaque configuration table."},
646 "settings.appearance": {"title":"Appearance","owner":"ConfigView + theme_picker.rs","state":"AppearanceSettings","action":"select category","hitbox":"settings category Rect","notes":"Preview is a projection, not a second state store."},
647 "settings.providers": {"title":"Models & providers","owner":"ConfigView + App","state":"SettingFact","action":"select category","hitbox":"settings category Rect","notes":"Current/effective/startup/saved are distinct facts."},
648 "settings.pod": {"title":"Pod settings","owner":"fleet_setup.rs migration","state":"PodConfiguration","action":"select category","hitbox":"settings category Rect","notes":"Uses new product term."},
649 "settings.work": {"title":"Work settings","owner":"ConfigView","state":"SettingFact","action":"select category","hitbox":"settings category Rect","notes":"Authority and apply semantics are explicit."},
650 "settings.tools": {"title":"Tools & MCP","owner":"ConfigView","state":"SettingFact","action":"select category","hitbox":"settings category Rect","notes":"Availability and permission are separate."},
651 "settings.trust": {"title":"Trust settings","owner":"ConfigView","state":"SettingFact","action":"select category","hitbox":"settings category Rect","notes":"Managed values are read-only with source named."},
652 "settings.motion": {"title":"Motion settings","owner":"motion/ + Settings","state":"MotionPolicy","action":"select category","hitbox":"settings category Rect","notes":"low_motion always wins over fancy animations."},
653 "settings.advanced": {"title":"Advanced settings","owner":"ConfigView","state":"SettingFact","action":"select category","hitbox":"settings category Rect","notes":"Raw settings remain available but not first."},
654 "settings.theme": {"title":"Theme","owner":"theme_picker.rs","state":"ThemeId + OceanTreatment","action":"open theme picker","hitbox":"settings entry Rect","notes":"Match Terminal default; Deepsea opt-in."},
655 "settings.theme.match": {"title":"Match Terminal","owner":"theme_picker.rs","state":"ThemeId::Terminal","action":"preview/apply","hitbox":"theme row Rect","notes":"Color::Reset background."},
656 "settings.theme.dark": {"title":"Dark","owner":"theme_picker.rs","state":"ThemeId::Whale","action":"preview/apply","hitbox":"theme row Rect","notes":"Foreground semantic palette; Color::Reset background."},
657 "settings.theme.light": {"title":"Light","owner":"theme_picker.rs","state":"ThemeId::WhaleLight","action":"preview/apply","hitbox":"theme row Rect","notes":"Foreground semantic palette; Color::Reset background."},
658 "settings.theme.deepsea": {"title":"Deepsea","owner":"theme_picker.rs + ocean.rs","state":"ThemeId + OceanTreatment::Deepsea","action":"preview/apply","hitbox":"theme row Rect","notes":"Explicit surface treatment; low motion settles still."},
659 "settings.theme.other": {"title":"Existing themes","owner":"theme_picker.rs","state":"SELECTABLE_THEMES","action":"open preset list","hitbox":"theme row Rect","notes":"No theme is silently removed."},
660 "settings.provider": {"title":"Current provider","owner":"ConfigView projection + App","state":"SettingFact { effective, authority }","action":"open provider details","hitbox":"setting fact Rect","notes":"Renders actual active session provider."},
661 "settings.model": {"title":"Current model","owner":"ConfigView projection + App","state":"SettingFact { effective, authority }","action":"open model details","hitbox":"setting fact Rect","notes":"GLM-5.3 is current in this illustrated state."},
662 "settings.startup-provider": {"title":"Startup provider","owner":"ConfigView projection + saved settings","state":"SettingFact { startup, saved }","action":"open startup route","hitbox":"setting fact Rect","notes":"Never labelled current when a session override exists."},
663 "settings.notification": {"title":"Notification settings","owner":"notifications.rs + sound_policy.rs","state":"DeliveryPlan","action":"open notification settings","hitbox":"setting fact Rect","notes":"Banner, history, and sound are independently controlled."},
664 "notifications.mark-read": {"title":"Mark notifications read","owner":"NotificationCenter","state":"NotificationRecord","action":"mark visible records read","hitbox":"notification toolbar Rect","notes":"A reversible state change."},
665 "notification.approval": {"title":"Approval needed","owner":"notifications.rs","state":"NotificationKind::ApprovalNeeded","action":"inspect and answer","hitbox":"notification row Rect","notes":"Gold + word means action is needed."},
666 "notification.pod-complete": {"title":"Pod complete","owner":"notifications.rs","state":"NotificationKind::SubagentTerminal","action":"inspect pod receipts","hitbox":"notification row Rect","notes":"Final-only suppresses mid-pod noise."},
667 "notification.model": {"title":"Model notification","owner":"notification_payload.rs","state":"NotificationKind::ModelNotify","action":"inspect classified note","hitbox":"notification row Rect","notes":"Model cannot choose critical semantics."}
668 }
669 }
670 </script>
671 <script>
672 (() => {
673 const body = document.body;
674 const terminal = document.getElementById('terminal');
675 const hostCard = document.getElementById('host-card');
676 const preview = document.getElementById('settings-preview-terminal');
677 const manifest = JSON.parse(document.getElementById('ratatui-wiring-manifest').textContent);
678 const inspect = document.getElementById('inspect-body');
679 const screenButtons = [...document.querySelectorAll('[data-screen]')];
680 const skinButtons = [...document.querySelectorAll('[data-skin]')];
681 const targets = [...document.querySelectorAll('[data-target]')];
682 let currentScreen = 'work';
683 let focusIndex = 0;
684
685 function setPressed(buttons, selected, attribute) {
686 buttons.forEach(button => button.setAttribute('aria-pressed', String(button.dataset[attribute] === selected)));
687 }
688 function showScreen(name) {
689 currentScreen = name;
690 document.querySelectorAll('.screen').forEach(screen => screen.classList.toggle('active', screen.id === `screen-${name}`));
691 setPressed(screenButtons, name, 'screen');
692 if (name === 'settings') renderPreview();
693 }
694 function setSkin(skin) {
695 body.dataset.skin = skin;
696 body.classList.toggle('light', skin === 'light');
697 body.classList.toggle('ansi', skin === 'ansi');
698 terminal.classList.toggle('deepsea', skin === 'deepsea');
699 preview.classList.toggle('deepsea', skin === 'deepsea');
700 setPressed(skinButtons, skin, 'skin');
701 renderPreview();
702 }
703 function renderPreview() {
704 const skin = body.dataset.skin;
705 const label = document.querySelector('.preview-label');
706 if (label) label.textContent = `Live preview — ${skin === 'deepsea' ? 'Deepsea' : skin === 'ansi' ? 'ANSI / no color' : skin.replace('-', ' ')}`;
707 }
708 function inspectTarget(id) {
709 const data = manifest.targets[id];
710 if (!data) return;
711 document.querySelectorAll('[data-target].selected').forEach(el => el.classList.remove('selected'));
712 document.querySelectorAll(`[data-target="${CSS.escape(id)}"]`).forEach(el => el.classList.add('selected'));
713 inspect.innerHTML = `
714 <div class="inspect-id"></div>
715 <div class="inspect-title"></div>
716 <p class="inspect-copy"></p>
717 <div class="inspect-list">
718 <div class="inspect-item"><span class="inspect-k">owner</span><span class="inspect-v"></span></div>
719 <div class="inspect-item"><span class="inspect-k">typed state</span><span class="inspect-v"></span></div>
720 <div class="inspect-item"><span class="inspect-k">interaction</span><span class="inspect-v"></span></div>
721 <div class="inspect-item"><span class="inspect-k">hitbox / focus</span><span class="inspect-v"></span></div>
722 </div>`;
723 const values = [id, data.title, data.notes, data.owner, data.state, data.action, data.hitbox];
724 const fields = inspect.querySelectorAll('.inspect-id, .inspect-title, .inspect-copy, .inspect-v');
725 fields.forEach((field, index) => field.textContent = values[index]);
726 }
727 function activateTarget(node) {
728 const id = node.dataset.target;
729 if (!id) return;
730 const screens = {
731 'settings.open': 'settings', 'settings.appearance': 'settings', 'settings.providers': 'settings', 'settings.pod': 'settings', 'settings.work': 'settings', 'settings.tools': 'settings', 'settings.trust': 'settings', 'settings.motion': 'settings', 'settings.advanced': 'settings', 'settings.theme': 'settings', 'settings.theme.match': 'settings', 'settings.theme.dark': 'settings', 'settings.theme.light': 'settings', 'settings.theme.deepsea': 'settings', 'settings.theme.other': 'settings', 'settings.provider': 'settings', 'settings.model': 'settings', 'settings.startup-provider': 'settings', 'settings.notification': 'settings', 'header.notifications': 'notices', 'notification.approval': 'notices', 'notification.pod-complete': 'notices', 'notification.model': 'notices'
732 };
733 if (screens[id]) showScreen(screens[id]);
734 if (id === 'settings.theme.match') setSkin('match-terminal');
735 if (id === 'settings.theme.dark') setSkin('dark');
736 if (id === 'settings.theme.light') setSkin('light');
737 if (id === 'settings.theme.deepsea') setSkin('deepsea');
738 inspectTarget(id);
739 }
740 screenButtons.forEach(button => button.addEventListener('click', () => showScreen(button.dataset.screen)));
741 skinButtons.forEach(button => button.addEventListener('click', () => setSkin(button.dataset.skin)));
742 targets.forEach(target => target.addEventListener('click', () => activateTarget(target)));
743 document.getElementById('toggle-narrow').addEventListener('click', event => {
744 const on = hostCard.classList.toggle('narrow-preview');
745 event.currentTarget.setAttribute('aria-pressed', String(on));
746 document.querySelector('.host-caption .mono').textContent = on ? 'narrow 80 × 24' : 'wide 140 × 40';
747 });
748 document.getElementById('toggle-rail').addEventListener('click', event => {
749 const on = terminal.classList.toggle('rail-collapsed');
750 event.currentTarget.setAttribute('aria-pressed', String(on));
751 });
752 document.getElementById('toggle-motion').addEventListener('click', event => {
753 const on = body.classList.toggle('motion-reduced');
754 event.currentTarget.setAttribute('aria-pressed', String(on));
755 });
756 document.addEventListener('keydown', event => {
757 if (event.key === 'Escape') { showScreen('work'); inspectTarget('header.context'); return; }
758 if (event.key === 'Tab') {
759 event.preventDefault();
760 const visible = targets.filter(node => node.getClientRects().length > 0 && node.matches('button, a, input, [tabindex]'));
761 if (!visible.length) return;
762 focusIndex = (focusIndex + (event.shiftKey ? -1 : 1) + visible.length) % visible.length;
763 visible[focusIndex].focus();
764 }
765 if (event.key === 'Enter' && document.activeElement?.dataset?.target) activateTarget(document.activeElement);
766 });
767 inspectTarget('header.context');
768 renderPreview();
769 })();
770 </script>
771 </body>
772 </html>
773
773 lines HTML