@webmcp-auto-ui/agent 2.5.26 → 2.5.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/autoui-server.ts +27 -0
  3. package/src/index.ts +20 -6
  4. package/src/loop.ts +4 -12
  5. package/src/notebook-widgets/compact.ts +312 -0
  6. package/src/notebook-widgets/document.ts +372 -0
  7. package/src/notebook-widgets/editorial.ts +348 -0
  8. package/src/notebook-widgets/recipes/compact.md +104 -0
  9. package/src/notebook-widgets/recipes/document.md +100 -0
  10. package/src/notebook-widgets/recipes/editorial.md +104 -0
  11. package/src/notebook-widgets/recipes/workspace.md +94 -0
  12. package/src/notebook-widgets/shared.ts +1064 -0
  13. package/src/notebook-widgets/workspace.ts +328 -0
  14. package/src/prompts/claude-prompt-builder.ts +81 -0
  15. package/src/prompts/gemma4-prompt-builder.ts +205 -0
  16. package/src/prompts/index.ts +55 -0
  17. package/src/prompts/mistral-prompt-builder.ts +90 -0
  18. package/src/prompts/qwen-prompt-builder.ts +90 -0
  19. package/src/prompts/tool-call-parsers.ts +322 -0
  20. package/src/prompts/tool-refs.ts +196 -0
  21. package/src/providers/factory.ts +20 -3
  22. package/src/providers/transformers-models.ts +143 -0
  23. package/src/providers/transformers-serialize.ts +81 -0
  24. package/src/providers/transformers.ts +329 -0
  25. package/src/providers/transformers.worker.ts +667 -0
  26. package/src/providers/wasm.ts +132 -332
  27. package/src/recipes/_generated.ts +242 -0
  28. package/src/recipes/hackathon-assemblee-nationale.md +111 -0
  29. package/src/recipes/notebook-playbook.md +129 -0
  30. package/src/tool-layers.ts +7 -403
  31. package/src/trace-observer.ts +669 -0
  32. package/src/types.ts +17 -7
  33. package/src/util/opfs-cache.ts +265 -0
  34. package/tests/gemma-prompt.test.ts +472 -0
  35. package/tests/loop.test.ts +3 -3
  36. package/tests/transformers-serialize.test.ts +103 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/agent",
3
- "version": "2.5.26",
3
+ "version": "2.5.27",
4
4
  "description": "LLM agent loop + remote/WASM/local providers + MCP wrapper",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -5,6 +5,22 @@
5
5
  import { createWebMcpServer, parseFrontmatter } from '@webmcp-auto-ui/core';
6
6
  import { RAW_RECIPES } from './recipes/_generated.js';
7
7
 
8
+ // Notebook widget recipes (vanilla renderers)
9
+ // @ts-ignore — Vite raw imports, not resolved by tsc
10
+ import compactRecipe from './notebook-widgets/recipes/compact.md?raw';
11
+ // @ts-ignore
12
+ import workspaceRecipe from './notebook-widgets/recipes/workspace.md?raw';
13
+ // @ts-ignore
14
+ import documentRecipe from './notebook-widgets/recipes/document.md?raw';
15
+ // @ts-ignore
16
+ import editorialRecipe from './notebook-widgets/recipes/editorial.md?raw';
17
+
18
+ // Notebook widget renderers (vanilla JS)
19
+ import { render as renderCompact } from './notebook-widgets/compact.js';
20
+ import { render as renderWorkspace } from './notebook-widgets/workspace.js';
21
+ import { render as renderDocument } from './notebook-widgets/document.js';
22
+ import { render as renderEditorial } from './notebook-widgets/editorial.js';
23
+
8
24
  // ---------------------------------------------------------------------------
9
25
  // Inline recipes (frontmatter + body)
10
26
  // ---------------------------------------------------------------------------
@@ -1008,6 +1024,17 @@ for (const recipe of RECIPES) {
1008
1024
  autoui.registerWidget(recipe, undefined);
1009
1025
  }
1010
1026
 
1027
+ // Notebook widgets — vanilla renderers (resolved via WidgetRenderer vanilla path)
1028
+ const NOTEBOOK_WIDGETS: Array<[string, (container: HTMLElement, data: any) => any]> = [
1029
+ [compactRecipe as string, renderCompact],
1030
+ [workspaceRecipe as string, renderWorkspace],
1031
+ [documentRecipe as string, renderDocument],
1032
+ [editorialRecipe as string, renderEditorial],
1033
+ ];
1034
+ for (const [recipe, renderer] of NOTEBOOK_WIDGETS) {
1035
+ autoui.registerWidget(recipe, renderer as any);
1036
+ }
1037
+
1011
1038
  // Register flow recipes (multi-step procedures) from the global recipe registry
1012
1039
  // that declare this server (autoui) in their frontmatter.
1013
1040
  for (const [key, rawMd] of Object.entries(RAW_RECIPES)) {
package/src/index.ts CHANGED
@@ -3,8 +3,18 @@
3
3
  // Providers
4
4
  export { RemoteLLMProvider } from './providers/remote.js';
5
5
  export type { RemoteLLMProviderOptions } from './providers/remote.js';
6
- export { WasmProvider, buildGemmaPrompt } from './providers/wasm.js';
7
- export type { WasmProviderOptions, WasmStatus, BuildGemmaPromptInput } from './providers/wasm.js';
6
+ export { WasmProvider } from './providers/wasm.js';
7
+ export type { WasmProviderOptions, WasmStatus } from './providers/wasm.js';
8
+ export { TransformersProvider } from './providers/transformers.js';
9
+ export type { TransformersProviderOptions, TransformersStatus } from './providers/transformers.js';
10
+ export { TRANSFORMERS_MODELS, getTransformersModel, listTransformersModels } from './providers/transformers-models.js';
11
+ export type { TransformersModelEntry, TransformersFamily, ToolCallFormat } from './providers/transformers-models.js';
12
+ export { parseToolCalls } from './prompts/tool-call-parsers.js';
13
+ export type { ParseResult } from './prompts/tool-call-parsers.js';
14
+ export { loadOrDownloadModel, clearModelCache } from './util/opfs-cache.js';
15
+ export type { ModelFileSpec, CacheProgress } from './util/opfs-cache.js';
16
+ export { buildGemmaPrompt } from './prompts/index.js';
17
+ export type { BuildGemmaPromptInput } from './prompts/index.js';
8
18
  export { LocalLLMProvider } from './providers/local.js';
9
19
  export type { LocalLLMProviderOptions, LocalBackend } from './providers/local.js';
10
20
  export { createProvider } from './providers/factory.js';
@@ -18,15 +28,16 @@ export type { GemmaProviderOptions, GemmaStatus } from './providers/gemma.js';
18
28
 
19
29
  // Agent loop
20
30
  export { runAgentLoop, toProviderTools, fromMcpTools, trimConversationHistory } from './loop.js';
21
- export { buildSystemPrompt } from './tool-layers.js';
31
+ export { buildSystemPrompt, buildSystemPromptWithAliases } from './prompts/index.js';
32
+ export type { SystemPromptResult } from './prompts/index.js';
22
33
  export type { AgentLoopOptions } from './loop.js';
23
34
 
24
35
  // autoui — built-in WebMCP server
25
36
  export { autoui, NATIVE_WIDGET_NAMES } from './autoui-server.js';
26
37
 
27
38
  // Tool layers
28
- export { buildToolsFromLayers, buildDiscoveryTools, buildDiscoveryToolsWithAliases, activateServerTools, resolveCanonicalTools, toolAliasMap, buildSystemPromptWithAliases, flattenPathMaps, buildDiscoveryCache } from './tool-layers.js';
29
- export type { ToolLayer, McpLayer, WebMcpLayer, SystemPromptResult, DiscoveryToolsResult, SchemaTransformOptions, BuildToolsResult, ProviderKind } from './tool-layers.js';
39
+ export { buildToolsFromLayers, buildDiscoveryTools, buildDiscoveryToolsWithAliases, activateServerTools, resolveCanonicalTools, toolAliasMap, flattenPathMaps, buildDiscoveryCache } from './tool-layers.js';
40
+ export type { ToolLayer, McpLayer, WebMcpLayer, DiscoveryToolsResult, SchemaTransformOptions, BuildToolsResult, ProviderKind } from './tool-layers.js';
30
41
 
31
42
  // Discovery cache
32
43
  export { DiscoveryCache, DISCOVERY_TOOL_NAMES } from './discovery-cache.js';
@@ -63,12 +74,15 @@ export type { RepairResult } from './auto-repair.js';
63
74
  // Pipeline trace
64
75
  export { PipelineTrace, type TraceEntry } from './pipeline-trace.js';
65
76
 
77
+ // Trace observer — live visual trace for runAgentLoop
78
+ export { createTraceObserver, type TraceObserver, type TraceObserverContext, type RoundTripDetail } from './trace-observer.js';
79
+
66
80
  // Nano-RAG — context compaction
67
81
  export { ContextRAG, type ContextRAGOptions } from './nano-rag/mod.js';
68
82
 
69
83
  // Types
70
84
  export type {
71
- RemoteModelId, WasmModelId, LLMId, ModelId,
85
+ RemoteModelId, WasmModelId, TransformersModelId, LLMId, ModelId,
72
86
  ChatMessage, ContentBlock, McpToolDef, ProviderTool,
73
87
  LLMProvider, LLMResponse, ToolCall, AgentMetrics, AgentResult, AgentCallbacks,
74
88
  Recipe, McpRecipe,
package/src/loop.ts CHANGED
@@ -9,7 +9,8 @@ import type {
9
9
  LLMProvider, ProviderTool, McpToolDef, AgentCallbacks,
10
10
  } from './types.js';
11
11
  import type { ToolLayer, SchemaTransformOptions } from './tool-layers.js';
12
- import { buildToolsFromLayers, buildSystemPromptWithAliases, buildDiscoveryToolsWithAliases, buildSystemPrompt, activateServerTools, toProviderTools, sanitizeServerName, flattenPathMaps } from './tool-layers.js';
12
+ import { buildToolsFromLayers, buildDiscoveryToolsWithAliases, activateServerTools, toProviderTools, sanitizeServerName, flattenPathMaps } from './tool-layers.js';
13
+ import { buildSystemPromptWithAliases, buildSystemPrompt } from './prompts/index.js';
13
14
  import type { DiscoveryCache } from './discovery-cache.js';
14
15
  import { unflattenParams, validateJsonSchema } from '@webmcp-auto-ui/core';
15
16
  import type { JsonSchema } from '@webmcp-auto-ui/core';
@@ -17,7 +18,7 @@ import { autoRepairParams } from './auto-repair.js';
17
18
  import { PipelineTrace } from './pipeline-trace.js';
18
19
 
19
20
  // Re-export buildSystemPrompt for backward compat
20
- export { buildSystemPrompt } from './tool-layers.js';
21
+ export { buildSystemPrompt } from './prompts/index.js';
21
22
 
22
23
  const MAX_RESULT_LEN = 10_000;
23
24
 
@@ -122,7 +123,7 @@ export interface AgentLoopOptions {
122
123
  }
123
124
 
124
125
  export async function runAgentLoop(
125
- userMessage: string,
126
+ userMessage: string | ContentBlock[],
126
127
  options: AgentLoopOptions
127
128
  ): Promise<AgentResult> {
128
129
  const {
@@ -209,7 +210,6 @@ export async function runAgentLoop(
209
210
  const allToolCalls: ToolCall[] = [];
210
211
  let lastText = '';
211
212
  let finishedNormally = false;
212
- let discoveryPhase = false;
213
213
  let iterationsWithoutRender = 0;
214
214
  let nudgedOnce = false;
215
215
  let hasRendered = false;
@@ -320,14 +320,6 @@ export async function runAgentLoop(
320
320
  const toolResults: ContentBlock[] = [];
321
321
  for (const block of toolBlocks) {
322
322
  const call: ToolCall = { id: block.id, name: block.name, args: block.input };
323
- const wasDiscovering = discoveryPhase;
324
- if (isDiscoveryTool(block.name)) {
325
- discoveryPhase = true;
326
- call.guided = false;
327
- } else {
328
- call.guided = wasDiscovering;
329
- discoveryPhase = false;
330
- }
331
323
  const t1 = performance.now();
332
324
 
333
325
  try {
@@ -0,0 +1,312 @@
1
+ // @ts-nocheck
2
+ // ---------------------------------------------------------------------------
3
+ // notebook-compact — reactive minimalist layout (marimo-like)
4
+ // Left gutter with type label + vertical line, named outputs, fresh/stale status.
5
+ // ---------------------------------------------------------------------------
6
+
7
+ import {
8
+ createState, injectStyles, mountRunControls, mountHistoryPanel,
9
+ setupDnD, deleteCellWithConfirm, restoreCellFromSnapshot, addCell,
10
+ logHistory, autosize, openShareModal, registerHistoryObserver,
11
+ buildServersButton,
12
+ type NotebookState, type NotebookCell,
13
+ } from './shared.js';
14
+
15
+ export async function render(container: HTMLElement, data: Record<string, unknown>): Promise<() => void> {
16
+ injectStyles();
17
+ injectLayoutStyles();
18
+
19
+ const state: NotebookState = createState({
20
+ id: data.id as string,
21
+ title: data.title as string,
22
+ mode: (data.mode as any) ?? 'edit',
23
+ cells: data.cells as any,
24
+ });
25
+
26
+ container.classList.add('nb-root');
27
+ container.classList.toggle('nb-view-mode', state.mode === 'view');
28
+
29
+ container.innerHTML = `
30
+ <div class="nbc-shell">
31
+ <div class="nbc-toolbar">
32
+ <div class="nbc-status">
33
+ <span class="nbc-status-dot"></span>
34
+ <span class="nbc-status-text">reactive · 0 cells</span>
35
+ </div>
36
+ <div class="nbc-actions">
37
+ <div class="nb-mode-switch">
38
+ <button class="nb-mode-edit nb-on">edit</button>
39
+ <button class="nb-mode-view">view</button>
40
+ </div>
41
+ <button class="nb-btn nb-add-cell" data-add="md">+ md</button>
42
+ <button class="nb-btn nb-add-cell" data-add="sql">+ sql</button>
43
+ <button class="nb-btn nb-add-cell" data-add="js">+ js</button>
44
+ <button class="nb-btn nbc-history-btn">⟲ history</button>
45
+ <span class="nbc-servers-slot"></span>
46
+ <button class="nb-btn nbc-share-btn">share</button>
47
+ </div>
48
+ </div>
49
+ <div class="nb-history-panel nbc-history-panel"></div>
50
+ <div class="nbc-cells"></div>
51
+ </div>`;
52
+
53
+ const shell = container.querySelector('.nbc-shell') as HTMLElement;
54
+ const cellsEl = shell.querySelector('.nbc-cells') as HTMLElement;
55
+ const historyPanel = shell.querySelector('.nbc-history-panel') as HTMLElement;
56
+
57
+ function renderCells() {
58
+ cellsEl.innerHTML = '';
59
+ state.cells.forEach((cell) => cellsEl.appendChild(renderCell(cell, state, rerender)));
60
+ updateStatus();
61
+ }
62
+
63
+ function updateStatus() {
64
+ const n = state.cells.length;
65
+ const stale = state.cells.filter((c) => c.status === 'stale').length;
66
+ (shell.querySelector('.nbc-status-text') as HTMLElement).textContent =
67
+ stale > 0 ? `reactive · ${n} cells · ${stale} stale` : `reactive · ${n} cells · synced`;
68
+ (shell.querySelector('.nbc-status-dot') as HTMLElement).classList.toggle('nbc-stale', stale > 0);
69
+ }
70
+
71
+ function rerender() {
72
+ mountHistoryPanel(historyPanel, state, (snap) => { restoreCellFromSnapshot(state, snap); rerender(); });
73
+ renderCells();
74
+ }
75
+
76
+ // Toolbar bindings
77
+ shell.querySelectorAll<HTMLElement>('[data-add]').forEach((btn) => {
78
+ btn.addEventListener('click', () => {
79
+ const type = btn.dataset.add as any;
80
+ addCell(state, type, { varname: type === 'sql' ? 'rows_' + (state.cells.length + 1) : undefined });
81
+ rerender();
82
+ });
83
+ });
84
+ (shell.querySelector('.nbc-history-btn') as HTMLElement).addEventListener('click', () => {
85
+ historyPanel.classList.toggle('nb-open');
86
+ });
87
+ (shell.querySelector('.nbc-share-btn') as HTMLElement).addEventListener('click', () => {
88
+ openShareModal(state, (fmt) => console.log('[notebook-compact] share as', fmt, state));
89
+ });
90
+ const editBtn = shell.querySelector('.nb-mode-edit') as HTMLElement;
91
+ const viewBtn = shell.querySelector('.nb-mode-view') as HTMLElement;
92
+ editBtn.addEventListener('click', () => {
93
+ state.mode = 'edit';
94
+ container.classList.remove('nb-view-mode');
95
+ editBtn.classList.add('nb-on'); viewBtn.classList.remove('nb-on');
96
+ });
97
+ viewBtn.addEventListener('click', () => {
98
+ state.mode = 'view';
99
+ container.classList.add('nb-view-mode');
100
+ viewBtn.classList.add('nb-on'); editBtn.classList.remove('nb-on');
101
+ });
102
+
103
+ buildServersButton(state, shell.querySelector('.nbc-servers-slot') as HTMLElement, data, rerender);
104
+
105
+ setupDnD(cellsEl, state, rerender);
106
+ const unsubHistory = registerHistoryObserver(() => mountHistoryPanel(historyPanel, state, (snap) => { restoreCellFromSnapshot(state, snap); rerender(); }));
107
+
108
+ rerender();
109
+
110
+ return () => { unsubHistory(); };
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Cell rendering (compact layout)
115
+ // ---------------------------------------------------------------------------
116
+
117
+ function renderCell(cell: NotebookCell, state: NotebookState, rerender: () => void): HTMLElement {
118
+ const wrap = document.createElement('div');
119
+ wrap.className = 'nb-cell-wrapper nbc-cell';
120
+ wrap.dataset.id = cell.id;
121
+
122
+ const row = document.createElement('div');
123
+ row.className = 'nbc-row';
124
+
125
+ const handle = document.createElement('span');
126
+ handle.className = 'nb-drag-handle';
127
+ handle.draggable = true;
128
+ handle.textContent = '⋮⋮';
129
+ row.appendChild(handle);
130
+
131
+ const gutter = document.createElement('div');
132
+ gutter.className = `nbc-gutter nbc-gutter-${cell.type}`;
133
+ gutter.innerHTML = `<span class="nbc-type-label">${cell.type}</span><span class="nbc-line"></span>`;
134
+ row.appendChild(gutter);
135
+
136
+ const body = document.createElement('div');
137
+ body.className = 'nbc-body';
138
+ body.style.minWidth = '0';
139
+
140
+ if (cell.type === 'md') {
141
+ const mdBody = document.createElement('div');
142
+ mdBody.className = 'nbc-md-body';
143
+ const ta = document.createElement('textarea');
144
+ ta.className = 'nb-md-edit';
145
+ ta.value = cell.content;
146
+ ta.rows = 2;
147
+ ta.placeholder = 'write markdown…';
148
+ ta.addEventListener('input', () => { cell.content = ta.value; autosize(ta); });
149
+ mdBody.appendChild(ta);
150
+ body.appendChild(mdBody);
151
+ setTimeout(() => autosize(ta), 0);
152
+
153
+ const del = document.createElement('button');
154
+ del.className = 'nb-icon-btn nb-danger nbc-md-del';
155
+ del.textContent = '✕';
156
+ del.title = 'delete cell';
157
+ del.addEventListener('click', () => deleteCellWithConfirm(state, cell, (c) => 'markdown cell', rerender));
158
+ wrap.appendChild(del);
159
+ } else {
160
+ const codeCell = document.createElement('div');
161
+ codeCell.className = 'nb-code-cell nbc-code-cell';
162
+
163
+ // Cell title row with run controls FIRST (left), then meta
164
+ const titleRow = document.createElement('div');
165
+ titleRow.className = 'nbc-title-row';
166
+ titleRow.innerHTML = `
167
+ <span class="nbc-run-controls"></span>
168
+ ${cell.varname ? `<span class="nbc-arrow-var">→ ${cell.varname}</span>` : ''}
169
+ <span class="nbc-meta-info">${cell.type === 'sql' ? '4 rows' : 'depends on rows'}</span>
170
+ <button class="nb-icon-btn nb-toggle-src">${cell.hideSource ? '▸ src' : '◂ src'}</button>
171
+ <button class="nb-icon-btn nb-toggle-res">${cell.hideResult ? '▸ res' : '◂ res'}</button>
172
+ <button class="nb-icon-btn nb-danger nbc-code-del">✕</button>
173
+ `;
174
+ codeCell.appendChild(titleRow);
175
+ mountRunControls(titleRow.querySelector('.nbc-run-controls') as HTMLElement, cell, wrap, rerender);
176
+
177
+ const codeBody = document.createElement('div');
178
+ codeBody.className = 'nbc-code-body' + (cell.hideSource ? ' nbc-hidden' : '');
179
+ const ta = document.createElement('textarea');
180
+ ta.className = 'nb-code-edit';
181
+ ta.value = cell.content;
182
+ ta.rows = 1;
183
+ ta.spellcheck = false;
184
+ ta.addEventListener('input', () => { cell.content = ta.value; autosize(ta); cell.status = 'stale'; });
185
+ codeBody.appendChild(ta);
186
+ codeCell.appendChild(codeBody);
187
+ setTimeout(() => autosize(ta), 0);
188
+
189
+ const result = document.createElement('div');
190
+ result.className = 'nbc-result-body' + (cell.hideResult ? ' nbc-hidden' : '');
191
+ if (cell.type === 'sql') {
192
+ result.innerHTML = `
193
+ <div class="nbc-result-row">
194
+ <span>row_1</span><span>42</span>
195
+ <span>row_2</span><span>17</span>
196
+ <span>row_3</span><span>8</span>
197
+ <span>row_4</span><span>3</span>
198
+ </div>`;
199
+ } else {
200
+ result.className = 'nbc-chart-result' + (cell.hideResult ? ' nbc-hidden' : '');
201
+ result.innerHTML = `
202
+ <div class="nbc-bar" style="height:100%"></div>
203
+ <div class="nbc-bar" style="height:68%"></div>
204
+ <div class="nbc-bar" style="height:52%"></div>
205
+ <div class="nbc-bar" style="height:22%"></div>`;
206
+ }
207
+ codeCell.appendChild(result);
208
+ body.appendChild(codeCell);
209
+
210
+ (titleRow.querySelector('.nb-toggle-src') as HTMLElement).addEventListener('click', () => { cell.hideSource = !cell.hideSource; rerender(); });
211
+ (titleRow.querySelector('.nb-toggle-res') as HTMLElement).addEventListener('click', () => { cell.hideResult = !cell.hideResult; rerender(); });
212
+ (titleRow.querySelector('.nbc-code-del') as HTMLElement).addEventListener('click', () =>
213
+ deleteCellWithConfirm(state, cell, (c) => `${c.type} cell${c.varname ? ' → ' + c.varname : ''}`, rerender)
214
+ );
215
+ }
216
+
217
+ row.appendChild(body);
218
+ wrap.appendChild(row);
219
+ return wrap;
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Layout-specific styles
224
+ // ---------------------------------------------------------------------------
225
+
226
+ function injectLayoutStyles(): void {
227
+ if (document.getElementById('nbc-styles')) return;
228
+ const style = document.createElement('style');
229
+ style.id = 'nbc-styles';
230
+ style.textContent = `
231
+ .nbc-shell {
232
+ background: var(--color-surface); border: 1px solid var(--color-border);
233
+ border-radius: 12px; padding: 18px;
234
+ }
235
+ .nbc-toolbar {
236
+ display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;
237
+ }
238
+ .nbc-status {
239
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
240
+ font-size: 11px; color: var(--color-text2);
241
+ display: inline-flex; align-items: center; gap: 8px;
242
+ }
243
+ .nbc-status-dot {
244
+ width: 6px; height: 6px; border-radius: 50%;
245
+ background: var(--color-teal);
246
+ }
247
+ .nbc-status-dot.nbc-stale { background: var(--color-amber); }
248
+ .nbc-actions { display: flex; gap: 6px; align-items: center; }
249
+ .nbc-history-panel { margin-bottom: 12px; }
250
+
251
+ .nbc-cell { margin-bottom: 14px; position: relative; }
252
+ .nbc-cell:last-child { margin-bottom: 0; }
253
+ .nbc-row {
254
+ display: grid; grid-template-columns: 20px 34px 1fr; gap: 6px;
255
+ }
256
+ .nbc-gutter {
257
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
258
+ }
259
+ .nbc-type-label {
260
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
261
+ font-size: 10px; letter-spacing: 0.12em; color: var(--color-text2);
262
+ }
263
+ .nbc-gutter-sql .nbc-type-label { color: var(--color-accent); }
264
+ .nbc-gutter-js .nbc-type-label { color: var(--color-teal); }
265
+ .nbc-line { width: 1px; flex: 1; background: var(--color-border); }
266
+ .nbc-md-body { padding: 4px 2px; border: 1px dashed transparent; border-radius: 4px; }
267
+ .nbc-md-body:focus-within { border-color: var(--color-border); background: var(--color-bg); }
268
+ .nbc-md-del {
269
+ position: absolute; top: 4px; right: 4px;
270
+ opacity: 0; transition: opacity 0.15s;
271
+ }
272
+ .nbc-cell:hover .nbc-md-del { opacity: 0.5; }
273
+ .nbc-md-del:hover { opacity: 1 !important; }
274
+
275
+ .nbc-code-cell {
276
+ background: var(--color-bg); border: 1px solid var(--color-border);
277
+ border-radius: 8px; overflow: hidden;
278
+ transition: border-color 0.15s;
279
+ }
280
+ .nbc-code-cell:focus-within { border-color: var(--color-border2); }
281
+ .nbc-title-row {
282
+ display: flex; align-items: center; gap: 8px;
283
+ padding: 8px 12px; border-bottom: 1px solid var(--color-border);
284
+ background: var(--color-surface2);
285
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
286
+ font-size: 10.5px; color: var(--color-text2);
287
+ }
288
+ .nbc-title-row .nbc-arrow-var { color: var(--color-accent); }
289
+ .nbc-title-row .nbc-meta-info { margin-right: auto; }
290
+ .nbc-code-body { padding: 10px 14px; }
291
+ .nbc-hidden { display: none !important; }
292
+ .nbc-result-body {
293
+ padding: 10px 14px;
294
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
295
+ font-size: 12px;
296
+ border-top: 1px solid var(--color-border);
297
+ }
298
+ .nbc-result-row {
299
+ display: grid; grid-template-columns: 1fr auto; gap: 3px 24px;
300
+ color: var(--color-text2);
301
+ }
302
+ .nbc-result-row span:nth-child(even) {
303
+ color: var(--color-text1); font-variant-numeric: tabular-nums;
304
+ }
305
+ .nbc-chart-result {
306
+ padding: 14px; display: flex; align-items: flex-end; gap: 6px;
307
+ height: 92px; border-top: 1px solid var(--color-border);
308
+ }
309
+ .nbc-bar { flex: 1; background: var(--color-accent); border-radius: 2px 2px 0 0; opacity: 0.55; }
310
+ `;
311
+ document.head.appendChild(style);
312
+ }