| 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> 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> pod config</span></button> |
| 362 | <button type="button" class="rail-row" data-target="pod.ledger"><span> 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> audit contributor PRs<br>├─ ○ <b>Builder</b> repair CI<br>└─ <span class="live">✓</span> <b>Verifier</b> 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> 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> 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 <b>⇧↵</b> new line <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 <span class="label">↑/↓</span> move <span class="label">enter</span> inspect <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 <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 <span class="label">enter</span> choose <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 audit contributor PRs <span class="pass">(2 receipts)</span><br>◌ Builder ci-repair in progress<br><span class="pass">✓ Verifier</span> exact-head checks passed<br><span class="fail">! Builder</span> 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 <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 |