History

Rework research page around a tabbed context-window viewer

Rhys Sullivan committed 2 months ago

Commit cd450ba1 parent d0646921 file · +267 −321Browse files

Unified · Split

apps/marketing/src/pages/research/mcp-tool-visibility.astro+267−321
@@ -8,7 +8,7 @@ import opencodeLogo from "../../assets/logos/opencode.svg?url"; · 7 unmodified lines
8const GH = "https://github.com/UsefulSoftwareCo/executor";8const GH = "https://github.com/UsefulSoftwareCo/executor";
9const title = "How agents actually see your MCP tools";9const title = "How agents actually see your MCP tools";
10const description =10const description =
11 "The same MCP server was connected to five coding agents and the model-bound traffic was measured. Each harness shows the model something different: full definitions, bare names, or nothing at all.";11 "The same MCP server, connected to five coding agents, with the model-bound traffic measured. Each harness shows the model something different: full definitions, bare names, or nothing at all.";
12 12
13// Δ prompt tokens for the 31 per-integration search tools, provider-reported.13// Δ prompt tokens for the 31 per-integration search tools, provider-reported.
14const costs = [14const costs = [
@@ -63,229 +63,141 @@ const max = Math.max(...costs.map((c) => c.delta)); · 48 unmodified lines
63 {title}63 {title}
64 </h1>64 </h1>
65 <p class="mt-5 max-w-[62ch] text-[17px] leading-[1.6] text-ink-2 text-pretty">65 <p class="mt-5 max-w-[62ch] text-[17px] leading-[1.6] text-ink-2 text-pretty">
66 {description} We connected one Executor workspace — 31 integrations — to Claude Code,66 One Executor workspace — 31 integrations — connected to each harness twice: plain, and
67 Cursor's CLI, Codex, and two versions of opencode, twice each: once plain, once with67 with <code class="mono-inline">?search_tools=true</code>, which adds one minimal
68 Executor's <code class="mono-inline">?search_tools=true</code> option, which adds one68 <code class="mono-inline">search_&lt;integration&gt;</code> tool per integration. The
69 minimal <code class="mono-inline">search_&lt;integration&gt;</code> tool per connected69 difference between the two captured sessions is what 31 tool definitions cost, and
70 integration. The difference between those two sessions is exactly what 31 extra tool70 where they end up.
71 definitions cost, and where they end up.
72 </p>71 </p>
73 </div>72 </div>
74 </section>73 </section>
75 74
76 {/* ─── THE THREE REALITIES ─── */}75 {/* ─── CONTEXT VIEWER ─── */}
77 <section class="border-t border-rule px-6 py-14 sm:px-10 sm:py-20">76 <section class="border-t border-rule px-6 py-14 sm:px-10 sm:py-20">
78 <div class="mx-auto max-w-[1100px]">77 <div class="mx-auto max-w-[1100px]">
79 <span class="eyebrow">What each harness shows the model</span>78 <span class="eyebrow">The model's context window, per harness</span>
80 <h2 class="section-title mt-4 max-w-[24ch]">Five harnesses, three different realities</h2>79 <h2 class="section-title mt-4 max-w-[26ch]">Same server, four context windows</h2>
81 <p class="section-sub mt-4 max-w-[62ch]">
82 "The model sees your tools" hides three different mechanisms. Every card below is one
83 harness's view of the same server, drawn from the captured request that actually went
84 to the model. Solid blocks are in the context window on every turn; dashed blocks are
85 fetched on demand; crossed-out blocks never reach the model at all.
86 </p>
87 80
88 <div class="mt-10 grid gap-6">81 <div class="viewer surface-card mt-10">
89 {/* Inliners */}82 <div class="viewer-tabs" role="tablist" aria-label="Harness">
90 <div class="persp surface-card">83 <button class="vtab is-active" data-tab="claude" role="tab" aria-selected="true">
91 <div class="persp-bar">84 <img src={claudeLogo} alt="" width="16" height="16" /> Claude Code
92 <span class="persp-dot"></span><span class="persp-dot"></span><span class="persp-dot"></span>85 </button>
93 <span class="persp-icons">86 <button class="vtab" data-tab="opencode" role="tab" aria-selected="false">
94 <img src={claudeLogo} alt="Claude" width="18" height="18" />87 <img src={opencodeLogo} alt="" width="16" height="16" /> opencode
95 <img src={opencodeLogo} alt="opencode" width="18" height="18" />88 </button>
96 </span>89 <button class="vtab" data-tab="cursor" role="tab" aria-selected="false">
97 <span class="persp-name">Claude Code · opencode</span>90 <img src={cursorLogo} alt="" width="16" height="16" /> Cursor CLI
98 <span class="persp-ver">2.1.247 · 1.18.15</span>91 </button>
99 <span class="persp-cost">+3,399–3,464 tokens</span>92 <button class="vtab" data-tab="codex" role="tab" aria-selected="false">
93 <img src={codexLogo} alt="" width="16" height="16" /> Codex CLI
94 </button>
95 </div>
96
97 <div class="viewer-sub">
98 <span class="viewer-meta" data-meta="claude">v2.1.247</span>
99 <span class="viewer-meta" data-meta="opencode" hidden>v1.18.15 — beta behaves identically</span>
100 <span class="viewer-meta" data-meta="cursor" hidden>v2026.07.23</span>
101 <span class="viewer-meta" data-meta="codex" hidden>v0.147.0 — with or without the defer flag</span>
102 <div class="seg" data-seg="opencode" hidden>
103 <span class="seg-label">code mode</span>
104 <button class="seg-btn is-active" data-cm="off">Off</button>
105 <button class="seg-btn" data-cm="on">On</button>
100 </div>106 </div>
101 <p class="persp-lede">107 <span class="viewer-cost" data-cost>+3,464 tokens for the 31 tools</span>
102 Every definition is pasted into the prompt, every turn — name, description, and108 </div>
103 schema for all 38 tools.109
104 </p>110 {/* Claude Code */}
105 <div class="persp-body">111 <div class="pane" data-pane="claude">
106 <div class="ctx">112 <div class="blk blk-quiet"><span class="blk-tag">system prompt</span></div>
107 <div class="ctx-label">the model's context window</div>113 <div class="blk blk-quiet"><span class="blk-tag">built-in tools</span> 12 — Bash, Read, Edit, …</div>
108 <div class="ctx-block ctx-quiet">system prompt</div>114 <div class="blk blk-loud">
109 <div class="ctx-block ctx-loud">115 <span class="blk-tag">your MCP tools · 38 of 38 in context</span>
110 <b>38 tool definitions — full JSON schema each</b>116 <pre>{`{
111 <span>name + description + parameters for every tool, inlined as API function tools</span>
112 </div>
113 <div class="ctx-block ctx-quiet">conversation</div>
114 </div>
115 <div class="persp-detail">
116 <div class="code-window">
117 <div class="code-titlebar">
118 <span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span>
119 <span class="code-filename">tools[7] of 38 — as sent to the model</span>
120 </div>
121 <pre
122 class="code-body">{`{
123 "name": "mcp__executor__search_mcp_linear_app",117 "name": "mcp__executor__search_mcp_linear_app",
124 "description": "Search this integration's tools;118 "description": "Search this integration's tools; empty query lists all.
125 empty query lists all. Run results with execute.",119 Run results with execute.",
126 "input_schema": {120 "input_schema": { "type": "object",
127 "type": "object",121 "properties": { "query": { "type": "string" } } }
128 "properties": { "query": { "type": "string" } }
129 }
130}`}</pre>122}`}</pre>
131 </div>123 <span class="blk-note">full name + description + schema, × 38, resent every turn</span>
132 <p class="persp-path">
133 <span class="persp-path-label">To search Linear’s tools</span>
134 one call — <code class="mono-inline">search_mcp_linear_app({"{ query }"})</code>,
135 the definition is already in context.
136 </p>
137 </div>
138 </div>124 </div>
125 <div class="blk blk-quiet"><span class="blk-tag">conversation</span></div>
126 <div class="pane-measure">prompt 36,013 → 39,477 tokens · Δ +3,464</div>
139 </div>127 </div>
140 128
141 {/* opencode code mode */}129 {/* opencode, code mode off */}
142 <div class="persp surface-card">130 <div class="pane" data-pane="opencode-off" hidden>
143 <div class="persp-bar">131 <div class="blk blk-quiet"><span class="blk-tag">system prompt</span></div>
144 <span class="persp-dot"></span><span class="persp-dot"></span><span class="persp-dot"></span>132 <div class="blk blk-quiet"><span class="blk-tag">built-in tools</span> 13</div>
145 <span class="persp-icons">133 <div class="blk blk-loud">
146 <img src={opencodeLogo} alt="opencode" width="18" height="18" />134 <span class="blk-tag">your MCP tools · 38 of 38 in context</span>
147 </span>135 <pre>{`{
148 <span class="persp-name">opencode · code mode</span>136 "name": "executor_search_mcp_linear_app",
149 <span class="persp-ver">OPENCODE_EXPERIMENTAL_CODE_MODE</span>137 "description": "Search this integration's tools; empty query lists all.
150 <span class="persp-cost">+1,280 tokens</span>138 Run results with execute.",
139 "inputSchema": { "type": "object",
140 "properties": { "query": { "type": "string" } } }
141}`}</pre>
142 <span class="blk-note">full name + description + schema, × 38, resent every turn</span>
151 </div>143 </div>
152 <p class="persp-lede">144 <div class="blk blk-quiet"><span class="blk-tag">conversation</span></div>
153 Your tools collapse into one <code class="mono-inline">execute</code> tool and145 <div class="pane-measure">prompt 17,458 → 20,857 tokens · Δ +3,399</div>
154 reappear inside its description as typed signatures — still readable, 2.7× denser.146 </div>
155 </p>147
156 <div class="persp-body">148 {/* opencode, code mode on */}
157 <div class="ctx">149 <div class="pane" data-pane="opencode-on" hidden>
158 <div class="ctx-label">the model's context window</div>150 <div class="blk blk-quiet"><span class="blk-tag">system prompt</span></div>
159 <div class="ctx-block ctx-quiet">system prompt</div>151 <div class="blk blk-quiet"><span class="blk-tag">built-in tools</span> 13</div>
160 <div class="ctx-block ctx-quiet">13 built-in tools</div>152 <div class="blk blk-loud">
161 <div class="ctx-block ctx-loud">153 <span class="blk-tag">one execute tool — your 38 MCP tools inside its description</span>
162 <b>one <code class="mono-inline">execute</code> tool</b>154 <pre>{`- tools.executor.search_mcp_linear_app(input: {
163 <span>its description lists every MCP tool as a TypeScript signature — 2.7× denser than JSON definitions</span>
164 </div>
165 <div class="ctx-block ctx-quiet">conversation</div>
166 </div>
167 <div class="persp-detail">
168 <div class="code-window">
169 <div class="code-titlebar">
170 <span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span>
171 <span class="code-filename">inside execute's description</span>
172 </div>
173 <pre
174 class="code-body">{`- tools.executor.search_google_gmail(input: {
175 query?: string,155 query?: string,
176 }): Promise<unknown> // Search this integration's156 }): Promise<unknown> // Search this integration's tools;
177 tools; empty query lists all. Run results157 empty query lists all. Run results with execute.`}</pre>
178 with execute.`}</pre>158 <span class="blk-note">TypeScript signatures — 2.7× denser than JSON definitions</span>
179 </div>
180 <p class="persp-path">
181 <span class="persp-path-label">To search Linear’s tools</span>
182 one call through code —
183 <code class="mono-inline">execute("await tools.executor.search_mcp_linear_app(…)")</code>.
184 </p>
185 </div>
186 </div>159 </div>
160 <div class="blk blk-quiet"><span class="blk-tag">conversation</span></div>
161 <div class="pane-measure">prompt 16,126 → 17,406 tokens · Δ +1,280</div>
187 </div>162 </div>
188 163
189 {/* Cursor */}164 {/* Cursor */}
190 <div class="persp surface-card">165 <div class="pane" data-pane="cursor" hidden>
191 <div class="persp-bar">166 <div class="blk blk-quiet"><span class="blk-tag">system prompt</span></div>
192 <span class="persp-dot"></span><span class="persp-dot"></span><span class="persp-dot"></span>167 <div class="blk blk-quiet">
193 <span class="persp-icons">168 <span class="blk-tag">built-in tools</span> 19 — incl. <code class="mono-inline">GetDynamicTools</code> / <code class="mono-inline">CallDynamicTool</code>
194 <img src={cursorLogo} alt="Cursor" width="18" height="18" />
195 </span>
196 <span class="persp-name">Cursor CLI</span>
197 <span class="persp-ver">2026.07.23</span>
198 <span class="persp-cost">+170 tokens</span>
199 </div>169 </div>
200 <p class="persp-lede">170 <div class="blk blk-loud">
201 The model gets a names-only catalog. Descriptions and schemas exist on Cursor's171 <span class="blk-tag">your MCP tools · names only</span>
202 side, but the model has to ask for them.172 <pre>{`executor: execute · skills · resume ·
203 </p>173 search_axiom_mcp · search_github_rest · search_google_gmail ·
204 <div class="persp-body">174 search_mcp_linear_app · …`}</pre>
205 <div class="ctx">175 <span class="blk-note">≈5.5 tokens per tool</span>
206 <div class="ctx-label">the model's context window</div>176 </div>
207 <div class="ctx-block ctx-quiet">system prompt</div>177 <div class="blk blk-dashed">
208 <div class="ctx-block ctx-quiet">19 first-party tools, incl. <code class="mono-inline">GetDynamicTools</code> / <code class="mono-inline">CallDynamicTool</code></div>178 descriptions &amp; schemas — sent only when the model calls
209 <div class="ctx-block ctx-loud">179 <code class="mono-inline">GetDynamicTools</code>
210 <b>catalog: bare tool names, grouped by server</b>
211 <span>≈5.5 tokens per tool — the name is all the model gets</span>
212 </div>
213 <div class="ctx-block ctx-dashed">
214 descriptions &amp; schemas — fetched on demand via <code class="mono-inline">GetDynamicTools</code>
215 </div>
216 <div class="ctx-block ctx-quiet">conversation</div>
217 </div>
218 <div class="persp-detail">
219 <div class="code-window">
220 <div class="code-titlebar">
221 <span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span>
222 <span class="code-filename">the model's catalog view</span>
223 </div>
224 <pre
225 class="code-body">{`executor: execute · skills · resume ·
226 search_axiom_mcp · search_github_rest ·
227 search_google_gmail · search_mcp_linear_app · …
228
229(full definition available via GetDynamicTools —
230 not fetched unless the model asks)`}</pre>
231 </div>
232 <p class="persp-path">
233 <span class="persp-path-label">To search Linear’s tools</span>
234 the name is visible; call it through
235 <code class="mono-inline">CallDynamicTool("search_mcp_linear_app", …)</code>,
236 optionally fetching the schema first.
237 </p>
238 </div>
239 </div>180 </div>
181 <div class="blk blk-quiet"><span class="blk-tag">conversation</span></div>
182 <div class="pane-measure">prompt 16,140 → 16,310 tokens · Δ +170 (Cursor's usage meter)</div>
240 </div>183 </div>
241 184
242 {/* Codex */}185 {/* Codex */}
243 <div class="persp surface-card">186 <div class="pane" data-pane="codex" hidden>
244 <div class="persp-bar">187 <div class="blk blk-quiet"><span class="blk-tag">system prompt</span></div>
245 <span class="persp-dot"></span><span class="persp-dot"></span><span class="persp-dot"></span>188 <div class="blk blk-quiet">
246 <span class="persp-icons">189 <span class="blk-tag">native tools</span> 9 — <code class="mono-inline">exec</code>, collaboration, …
247 <img src={codexLogo} alt="Codex" width="18" height="18" />
248 </span>
249 <span class="persp-name">Codex CLI</span>
250 <span class="persp-ver">0.147.0</span>
251 <span class="persp-cost">+0 tokens</span>
252 </div>190 </div>
253 <p class="persp-lede">191 <div class="blk blk-never">
254 Nothing about your tools reaches the prompt. They exist only as callables inside192 <span class="blk-tag"><s>your MCP tools</s></span>
255 Codex's code sandbox — the model discovers them by writing code.193 <pre>{`"code_mode_tool_names": {
256 </p>194 "search_mcp_linear_app": { "namespace": "mcp__executor" },
257 <div class="persp-body">
258 <div class="ctx">
259 <div class="ctx-label">the model's context window</div>
260 <div class="ctx-block ctx-quiet">system prompt</div>
261 <div class="ctx-block ctx-quiet">9 native tools — <code class="mono-inline">exec</code>, collaboration, …</div>
262 <div class="ctx-block ctx-never">
263 <b>your MCP tools</b>
264 <span>carried only in request metadata the model never reads; prompts were byte-identical with 7 vs 38 tools</span>
265 </div>
266 <div class="ctx-block ctx-quiet">conversation</div>
267 </div>
268 <div class="persp-detail">
269 <div class="code-window">
270 <div class="code-titlebar">
271 <span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span>
272 <span class="code-filename">request metadata — not model-visible</span>
273 </div>
274 <pre
275 class="code-body">{`"code_mode_tool_names": {
276 "search_google_gmail":
277 { "namespace": "mcp__executor" },
278 … 37 more195 … 37 more
279}`}</pre>196}`}</pre>
280 </div>197 <span class="blk-note">request metadata — never rendered into the prompt; callable only from code inside <code class="mono-inline">exec</code></span>
281 <p class="persp-path">
282 <span class="persp-path-label">To search Linear’s tools</span>
283 the model must call <code class="mono-inline">exec</code> and write code —
284 <code class="mono-inline">await tools.mcp__executor__search_mcp_linear_app(…)</code> —
285 discovering what exists only from inside the sandbox.
286 </p>
287 </div>
288 </div>198 </div>
199 <div class="blk blk-quiet"><span class="blk-tag">conversation</span></div>
200 <div class="pane-measure">prompt 15,776 → 15,776 tokens · Δ 0, byte-identical</div>
289 </div>201 </div>
290 </div>202 </div>
291 </div>203 </div>
@@ -410,6 +322,57 @@ const max = Math.max(...costs.map((c) => c.delta)); · 118 unmodified lines
410 </main>322 </main>
411</Layout>323</Layout>
412 324
325<script>
326 const viewer = document.querySelector(".viewer");
327 if (viewer) {
328 const tabs = [...viewer.querySelectorAll(".vtab")];
329 const panes = [...viewer.querySelectorAll(".pane")];
330 const metas = [...viewer.querySelectorAll(".viewer-meta")];
331 const seg = viewer.querySelector("[data-seg='opencode']");
332 const segBtns = [...viewer.querySelectorAll(".seg-btn")];
333 const cost = viewer.querySelector("[data-cost]");
334
335 const COSTS = {
336 claude: "+3,464 tokens for the 31 tools",
337 "opencode-off": "+3,399 tokens for the 31 tools",
338 "opencode-on": "+1,280 tokens for the 31 tools",
339 cursor: "+170 tokens for the 31 tools",
340 codex: "0 tokens — not in the prompt",
341 };
342
343 let tab = "claude";
344 let codemode = false;
345
346 const paneKey = () => (tab === "opencode" ? (codemode ? "opencode-on" : "opencode-off") : tab);
347
348 const update = () => {
349 const key = paneKey();
350 for (const t of tabs) {
351 const active = t.dataset.tab === tab;
352 t.classList.toggle("is-active", active);
353 t.setAttribute("aria-selected", String(active));
354 }
355 for (const p of panes) p.hidden = p.dataset.pane !== key;
356 for (const m of metas) m.hidden = m.dataset.meta !== tab;
357 if (seg) seg.hidden = tab !== "opencode";
358 for (const b of segBtns) b.classList.toggle("is-active", (b.dataset.cm === "on") === codemode);
359 if (cost) cost.textContent = COSTS[key];
360 };
361
362 for (const t of tabs)
363 t.addEventListener("click", () => {
364 tab = t.dataset.tab ?? "claude";
365 update();
366 });
367 for (const b of segBtns)
368 b.addEventListener("click", () => {
369 codemode = b.dataset.cm === "on";
370 update();
371 });
372 update();
373 }
374</script>
375
413<style>376<style>
414 .mono-inline {377 .mono-inline {
415 font-family: var(--font-mono);378 font-family: var(--font-mono);
@@ -421,192 +384,175 @@ const max = Math.max(...costs.map((c) => c.delta)); · 5 unmodified lines
421 white-space: nowrap;384 white-space: nowrap;
422 }385 }
423 386
424 /* ── perspective cards ── */387 /* ── viewer ── */
425 .persp {388 .viewer {
426 padding: 0;389 padding: 0;
427 overflow: hidden;390 overflow: hidden;
428 }391 }
429 /* window titlebar — the card IS the harness's window */392 .viewer-tabs {
430 .persp-bar {
431 display: flex;393 display: flex;
432 align-items: center;394 align-items: stretch;
433 gap: 10px;
434 padding: 12px 18px;
435 border-bottom: 1px solid var(--color-rule);395 border-bottom: 1px solid var(--color-rule);
436 background: var(--color-surface-2);396 overflow-x: auto;
437 }
438 .persp-dot {
439 width: 9px;
440 height: 9px;
441 border-radius: 9999px;
442 background: var(--color-rule-strong);
443 flex-shrink: 0;
444 }
445 .persp-dot + .persp-dot {
446 margin-left: -3px;
447 }397 }
448 .persp-icons {398 .vtab {
449 display: flex;399 display: flex;
450 align-items: center;400 align-items: center;
451 gap: 7px;401 gap: 8px;
452 flex-shrink: 0;402 padding: 13px 18px;
453 margin-left: 8px;403 font-size: 14px;
404 font-weight: 500;
405 color: var(--color-ink-2);
406 border: 0;
407 border-bottom: 2px solid transparent;
408 background: none;
409 cursor: pointer;
410 white-space: nowrap;
411 transition: color 0.15s;
454 }412 }
455 .persp-icons img {413 .vtab img {
456 display: block;414 display: block;
457 height: 18px;415 height: 16px;
458 width: 18px;416 width: 16px;
417 opacity: 0.75;
459 }418 }
460 .persp-name {419 .vtab:hover {
461 font-weight: 600;
462 font-size: 15px;
463 letter-spacing: -0.01em;
464 color: var(--color-ink);420 color: var(--color-ink);
465 white-space: nowrap;
466 }421 }
467 .persp-ver {422 .vtab.is-active {
423 color: var(--color-ink);
424 border-bottom-color: var(--color-ink);
425 }
426 .vtab.is-active img {
427 opacity: 1;
428 }
429
430 .viewer-sub {
431 display: flex;
432 align-items: center;
433 gap: 14px;
434 padding: 10px 18px;
435 border-bottom: 1px solid var(--color-rule);
436 background: var(--color-surface-2);
437 }
438 .viewer-meta {
468 font-family: var(--font-mono);439 font-family: var(--font-mono);
469 font-size: 11px;440 font-size: 11.5px;
470 color: var(--color-ink-3);441 color: var(--color-ink-3);
471 overflow: hidden;442 overflow: hidden;
472 text-overflow: ellipsis;443 text-overflow: ellipsis;
473 white-space: nowrap;444 white-space: nowrap;
474 min-width: 0;
475 }445 }
476 .persp-cost {446 .viewer-cost {
447 margin-left: auto;
477 font-family: var(--font-mono);448 font-family: var(--font-mono);
478 font-size: 12.5px;449 font-size: 12px;
479 color: var(--color-ink);450 color: var(--color-ink);
480 white-space: nowrap;451 white-space: nowrap;
481 margin-left: auto;
482 }452 }
483 @media (max-width: 640px) {453 .seg {
484 .persp-ver {454 display: flex;
485 display: none;455 align-items: center;
486 }456 gap: 4px;
487 }457 }
488 .persp-lede {458 .seg-label {
489 padding: 16px 24px 0;459 font-family: var(--font-mono);
490 font-size: 14.5px;460 font-size: 11px;
491 line-height: 1.6;461 letter-spacing: 0.08em;
462 text-transform: uppercase;
463 color: var(--color-ink-3);
464 margin-right: 4px;
465 }
466 .seg-btn {
467 font-family: var(--font-mono);
468 font-size: 11.5px;
469 padding: 3px 10px;
470 border: 1px solid var(--color-rule-strong);
471 background: var(--color-surface);
492 color: var(--color-ink-2);472 color: var(--color-ink-2);
493 max-width: 72ch;473 cursor: pointer;
494 }474 }
495 /* one window chrome per card: inner excerpts keep a filename bar, no dots */475 .seg-btn:first-of-type {
496 .persp-detail .code-dot {476 border-radius: 5px 0 0 5px;
497 display: none;
498 }477 }
499 .persp-body {478 .seg-btn:last-of-type {
500 display: grid;479 border-radius: 0 5px 5px 0;
501 grid-template-columns: 340px 1fr;480 margin-left: -1px;
502 gap: 0;
503 }481 }
504 @media (max-width: 820px) {482 .seg-btn.is-active {
505 .persp-body {483 background: var(--color-accent);
506 grid-template-columns: 1fr;484 border-color: var(--color-accent);
507 }485 color: var(--color-surface);
508 }486 }
509 487
510 /* context-window schematic */488 .pane {
511 .ctx {
512 padding: 20px 24px;
513 border-right: 1px solid var(--color-rule);
514 display: flex;489 display: flex;
515 flex-direction: column;490 flex-direction: column;
516 gap: 6px;491 gap: 8px;
492 padding: 20px;
517 }493 }
518 @media (max-width: 820px) {494 .blk {
519 .ctx {495 border-radius: 6px;
520 border-right: 0;496 padding: 10px 14px;
521 border-bottom: 1px solid var(--color-rule);497 font-size: 13px;
522 }498 line-height: 1.5;
499 color: var(--color-ink-2);
523 }500 }
524 .ctx-label {501 .blk-tag {
525 font-family: var(--font-mono);502 font-family: var(--font-mono);
526 font-size: 10.5px;503 font-size: 11px;
527 letter-spacing: 0.08em;504 letter-spacing: 0.08em;
528 text-transform: uppercase;505 text-transform: uppercase;
529 color: var(--color-ink-3);506 color: var(--color-ink-3);
530 margin-bottom: 4px;507 margin-right: 8px;
531 }
532 .ctx-block {
533 border-radius: 6px;
534 padding: 9px 12px;
535 font-size: 12.5px;
536 line-height: 1.45;
537 }508 }
538 .ctx-quiet {509 .blk-quiet {
539 border: 1px solid var(--color-rule);510 border: 1px solid var(--color-rule);
540 background: var(--color-surface-2);511 background: var(--color-surface-2);
541 color: var(--color-ink-2);
542 }512 }
543 .ctx-loud {513 .blk-loud {
544 border: 1px solid var(--color-rule-strong);514 border: 1px solid var(--color-rule-strong);
545 background: var(--color-surface);515 background: var(--color-surface);
546 color: var(--color-ink);516 color: var(--color-ink);
547 }517 }
548 .ctx-loud b {518 .blk-loud .blk-tag {
549 display: block;519 color: var(--color-ink);
550 font-weight: 600;
551 font-size: 13px;
552 }
553 .ctx-loud span {
554 display: block;
555 margin-top: 2px;
556 color: var(--color-ink-2);
557 font-size: 12px;
558 }520 }
559 .ctx-dashed {521 .blk-dashed {
560 border: 1px dashed var(--color-rule-strong);522 border: 1px dashed var(--color-rule-strong);
561 color: var(--color-ink-3);523 color: var(--color-ink-3);
562 background: transparent;
563 }524 }
564 .ctx-never {525 .blk-never {
565 border: 1px dashed var(--color-rule);526 border: 1px dashed var(--color-rule);
566 background: transparent;
567 color: var(--color-ink-3);527 color: var(--color-ink-3);
568 }528 }
569 .ctx-never b {529 .blk pre {
570 display: block;530 margin: 10px 0 6px;
571 font-weight: 500;531 font-family: var(--font-mono);
572 font-size: 13px;
573 text-decoration: line-through;
574 color: var(--color-ink-3);
575 }
576 .ctx-never span {
577 display: block;
578 margin-top: 2px;
579 font-size: 12px;
580 }
581
582 .persp-detail {
583 padding: 20px 24px;
584 display: flex;
585 flex-direction: column;
586 gap: 14px;
587 min-width: 0;
588 }
589 .persp-detail .code-window {
590 margin: 0;
591 }
592 .persp-detail .code-body {
593 font-size: 12px;532 font-size: 12px;
594 line-height: 1.6;533 line-height: 1.6;
534 color: var(--color-ink-2);
535 background: var(--color-surface-2);
536 border: 1px solid var(--color-rule);
537 border-radius: 6px;
538 padding: 12px 14px;
595 overflow-x: auto;539 overflow-x: auto;
540 white-space: pre;
596 }541 }
597 .persp-path {542 .blk-never pre {
598 font-size: 13.5px;543 background: transparent;
599 line-height: 1.6;
600 color: var(--color-ink-2);
601 }544 }
602 .persp-path-label {545 .blk-note {
603 display: block;546 display: block;
547 font-size: 12px;
548 color: var(--color-ink-3);
549 }
550 .pane-measure {
551 margin-top: 8px;
604 font-family: var(--font-mono);552 font-family: var(--font-mono);
605 font-size: 10.5px;553 font-size: 12px;
606 letter-spacing: 0.08em;
607 text-transform: uppercase;
608 color: var(--color-ink-3);554 color: var(--color-ink-3);
609 margin-bottom: 3px;555 text-align: right;
610 }556 }
611 557
612 /* ── cost chart ── */558 /* ── cost chart ── */