@webmcp-auto-ui/agent 2.5.25 → 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 (73) hide show
  1. package/package.json +1 -1
  2. package/src/autoui-server.ts +44 -0
  3. package/src/diagnostics.ts +6 -6
  4. package/src/discovery-cache.ts +17 -3
  5. package/src/index.ts +18 -4
  6. package/src/loop.ts +31 -34
  7. package/src/notebook-widgets/compact.ts +312 -0
  8. package/src/notebook-widgets/document.ts +372 -0
  9. package/src/notebook-widgets/editorial.ts +348 -0
  10. package/src/notebook-widgets/recipes/compact.md +104 -0
  11. package/src/notebook-widgets/recipes/document.md +100 -0
  12. package/src/notebook-widgets/recipes/editorial.md +104 -0
  13. package/src/notebook-widgets/recipes/workspace.md +94 -0
  14. package/src/notebook-widgets/shared.ts +1064 -0
  15. package/src/notebook-widgets/workspace.ts +328 -0
  16. package/src/prompts/claude-prompt-builder.ts +81 -0
  17. package/src/prompts/gemma4-prompt-builder.ts +205 -0
  18. package/src/prompts/index.ts +55 -0
  19. package/src/prompts/mistral-prompt-builder.ts +90 -0
  20. package/src/prompts/qwen-prompt-builder.ts +90 -0
  21. package/src/prompts/tool-call-parsers.ts +322 -0
  22. package/src/prompts/tool-refs.ts +196 -0
  23. package/src/providers/factory.ts +20 -3
  24. package/src/providers/transformers-models.ts +143 -0
  25. package/src/providers/transformers-serialize.ts +81 -0
  26. package/src/providers/transformers.ts +329 -0
  27. package/src/providers/transformers.worker.ts +667 -0
  28. package/src/providers/wasm.ts +150 -510
  29. package/src/recipes/_generated.ts +515 -0
  30. package/src/recipes/canary-data.md +50 -0
  31. package/src/recipes/canary-display.md +99 -0
  32. package/src/recipes/canary-middle.md +32 -0
  33. package/src/recipes/hackathon-assemblee-nationale.md +111 -0
  34. package/src/recipes/hummingbird-data.md +32 -0
  35. package/src/recipes/hummingbird-display.md +36 -0
  36. package/src/recipes/hummingbird-middle.md +18 -0
  37. package/src/recipes/notebook-playbook.md +129 -0
  38. package/src/tool-layers.ts +33 -157
  39. package/src/trace-observer.ts +669 -0
  40. package/src/types.ts +20 -5
  41. package/src/util/opfs-cache.ts +265 -0
  42. package/tests/gemma-prompt.test.ts +472 -0
  43. package/tests/loop.test.ts +5 -5
  44. package/tests/transformers-serialize.test.ts +103 -0
  45. package/src/providers/gemma.worker.legacy.ts +0 -123
  46. package/src/providers/litert.worker.ts +0 -294
  47. package/src/recipes/widgets/actions.md +0 -28
  48. package/src/recipes/widgets/alert.md +0 -27
  49. package/src/recipes/widgets/cards.md +0 -41
  50. package/src/recipes/widgets/carousel.md +0 -39
  51. package/src/recipes/widgets/chart-rich.md +0 -51
  52. package/src/recipes/widgets/chart.md +0 -32
  53. package/src/recipes/widgets/code.md +0 -21
  54. package/src/recipes/widgets/d3.md +0 -36
  55. package/src/recipes/widgets/data-table.md +0 -46
  56. package/src/recipes/widgets/gallery.md +0 -39
  57. package/src/recipes/widgets/grid-data.md +0 -57
  58. package/src/recipes/widgets/hemicycle.md +0 -43
  59. package/src/recipes/widgets/js-sandbox.md +0 -32
  60. package/src/recipes/widgets/json-viewer.md +0 -27
  61. package/src/recipes/widgets/kv.md +0 -31
  62. package/src/recipes/widgets/list.md +0 -24
  63. package/src/recipes/widgets/log.md +0 -39
  64. package/src/recipes/widgets/map.md +0 -49
  65. package/src/recipes/widgets/profile.md +0 -49
  66. package/src/recipes/widgets/recipe-browser.md +0 -102
  67. package/src/recipes/widgets/sankey.md +0 -54
  68. package/src/recipes/widgets/stat-card.md +0 -43
  69. package/src/recipes/widgets/stat.md +0 -35
  70. package/src/recipes/widgets/tags.md +0 -30
  71. package/src/recipes/widgets/text.md +0 -19
  72. package/src/recipes/widgets/timeline.md +0 -38
  73. package/src/recipes/widgets/trombinoscope.md +0 -39
@@ -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
+ }
@@ -0,0 +1,372 @@
1
+ // @ts-nocheck
2
+ // ---------------------------------------------------------------------------
3
+ // notebook-document — collaborative doc layout (deepnote-like)
4
+ // Title + avatars, inline highlights, margin comments, minimal cell chrome.
5
+ // ---------------------------------------------------------------------------
6
+
7
+ import {
8
+ createState, injectStyles, mountRunControls, mountHistoryPanel,
9
+ setupDnD, deleteCellWithConfirm, restoreCellFromSnapshot, addCell,
10
+ 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 ?? 'Untitled notebook',
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="nbd-shell">
31
+ <div class="nbd-presence">
32
+ <div class="nbd-avatars">
33
+ <div class="nbd-av nbd-av1">A</div>
34
+ <div class="nbd-av nbd-av2">B</div>
35
+ <div class="nbd-av nbd-av3">+1</div>
36
+ </div>
37
+ <span class="nbd-label">3 editors online</span>
38
+ <div class="nb-mode-switch" style="margin-left:auto;">
39
+ <button class="nb-mode-edit nb-on">edit</button>
40
+ <button class="nb-mode-view">view</button>
41
+ </div>
42
+ <button class="nb-btn nbd-history-btn">⟲ history</button>
43
+ <span class="nbd-servers-slot"></span>
44
+ </div>
45
+ <input class="nbd-title nb-doc-title" value="${escapeAttr(state.title)}">
46
+ <div class="nbd-meta">edited just now · saved ✓</div>
47
+ <div class="nb-history-panel nbd-history-panel"></div>
48
+ <div class="nbd-cells"></div>
49
+ <div class="nbd-footer">
50
+ <button class="nb-btn nb-add-cell" data-add="md">+ text</button>
51
+ <button class="nb-btn nb-add-cell" data-add="sql">+ sql</button>
52
+ <button class="nb-btn nb-add-cell" data-add="js">+ code</button>
53
+ <div class="nbd-spacer">
54
+ <span class="nbd-share-link nbd-share-btn">invite · share</span>
55
+ </div>
56
+ </div>
57
+ </div>`;
58
+
59
+ const shell = container.querySelector('.nbd-shell') as HTMLElement;
60
+ const cellsEl = shell.querySelector('.nbd-cells') as HTMLElement;
61
+ const historyPanel = shell.querySelector('.nbd-history-panel') as HTMLElement;
62
+
63
+ function renderCells() {
64
+ cellsEl.innerHTML = '';
65
+ state.cells.forEach((cell) => cellsEl.appendChild(renderCell(cell, state, rerender)));
66
+ }
67
+
68
+ function rerender() {
69
+ mountHistoryPanel(historyPanel, state, (snap) => { restoreCellFromSnapshot(state, snap); rerender(); });
70
+ renderCells();
71
+ }
72
+
73
+ shell.querySelectorAll<HTMLElement>('[data-add]').forEach((btn) => {
74
+ btn.addEventListener('click', () => {
75
+ const type = btn.dataset.add as any;
76
+ addCell(state, type);
77
+ rerender();
78
+ });
79
+ });
80
+ (shell.querySelector('.nbd-history-btn') as HTMLElement).addEventListener('click', () => {
81
+ historyPanel.classList.toggle('nb-open');
82
+ });
83
+ (shell.querySelector('.nbd-share-btn') as HTMLElement).addEventListener('click', () => {
84
+ openShareModal(state, (fmt) => console.log('[notebook-document] share as', fmt, state));
85
+ });
86
+ (shell.querySelector('.nbd-title') as HTMLInputElement).addEventListener('input', (e) => {
87
+ state.title = (e.target as HTMLInputElement).value;
88
+ });
89
+ const editBtn = shell.querySelector('.nb-mode-edit') as HTMLElement;
90
+ const viewBtn = shell.querySelector('.nb-mode-view') as HTMLElement;
91
+ editBtn.addEventListener('click', () => {
92
+ state.mode = 'edit';
93
+ container.classList.remove('nb-view-mode');
94
+ editBtn.classList.add('nb-on'); viewBtn.classList.remove('nb-on');
95
+ });
96
+ viewBtn.addEventListener('click', () => {
97
+ state.mode = 'view';
98
+ container.classList.add('nb-view-mode');
99
+ viewBtn.classList.add('nb-on'); editBtn.classList.remove('nb-on');
100
+ });
101
+
102
+ buildServersButton(state, shell.querySelector('.nbd-servers-slot') as HTMLElement, data, rerender);
103
+
104
+ setupDnD(cellsEl, state, rerender);
105
+ const unsubHistory = registerHistoryObserver(() => mountHistoryPanel(historyPanel, state, (snap) => { restoreCellFromSnapshot(state, snap); rerender(); }));
106
+
107
+ rerender();
108
+ return () => { unsubHistory(); };
109
+ }
110
+
111
+ function renderCell(cell: NotebookCell, state: NotebookState, rerender: () => void): HTMLElement {
112
+ const wrap = document.createElement('div');
113
+ wrap.className = 'nb-cell-wrapper nbd-cell';
114
+ wrap.dataset.id = cell.id;
115
+
116
+ if (cell.type === 'md') {
117
+ const handle = document.createElement('span');
118
+ handle.className = 'nb-drag-handle nbd-md-handle';
119
+ handle.draggable = true;
120
+ handle.textContent = '⋮⋮';
121
+ wrap.appendChild(handle);
122
+
123
+ const p = document.createElement('div');
124
+ p.className = 'nbd-prose';
125
+ p.contentEditable = 'true';
126
+ p.innerHTML = cell.content;
127
+ p.addEventListener('input', () => { cell.content = p.innerHTML; });
128
+ wrap.appendChild(p);
129
+
130
+ const del = document.createElement('button');
131
+ del.className = 'nb-icon-btn nb-danger nbd-del-abs';
132
+ del.textContent = '✕';
133
+ del.addEventListener('click', () =>
134
+ deleteCellWithConfirm(state, cell, () => 'markdown block', rerender)
135
+ );
136
+ wrap.appendChild(del);
137
+ return wrap;
138
+ }
139
+
140
+ const row = document.createElement('div');
141
+ row.className = 'nbd-row' + (cell.comment ? '' : ' nbd-no-comment');
142
+
143
+ const codeCell = document.createElement('div');
144
+ codeCell.className = 'nb-code-cell nbd-code-cell';
145
+
146
+ const head = document.createElement('div');
147
+ head.className = 'nbd-cell-head';
148
+ head.innerHTML = `
149
+ <span class="nb-drag-handle" draggable="true" title="drag">⋮⋮</span>
150
+ <span class="nbd-run-controls"></span>
151
+ <span class="${cell.type === 'sql' ? 'nbd-type-sql' : 'nbd-type-js'}">${cell.type}</span>
152
+ <span class="nbd-meta-info">${cell.lastMs != null ? cell.lastMs + 'ms' : ''}</span>
153
+ <div class="nbd-actions">
154
+ <button class="nb-icon-btn nb-toggle-src">${cell.hideSource ? '▸ src' : '◂ src'}</button>
155
+ <button class="nb-icon-btn nb-toggle-res">${cell.hideResult ? '▸ res' : '◂ res'}</button>
156
+ <button class="nb-icon-btn nb-danger nbd-del">✕</button>
157
+ </div>`;
158
+ codeCell.appendChild(head);
159
+ mountRunControls(head.querySelector('.nbd-run-controls') as HTMLElement, cell, wrap, rerender);
160
+
161
+ const body = document.createElement('div');
162
+ body.className = 'nbd-code-body' + (cell.hideSource ? ' nbd-hidden' : '');
163
+ const ta = document.createElement('textarea');
164
+ ta.className = 'nb-code-edit';
165
+ ta.value = cell.content;
166
+ ta.rows = 1;
167
+ ta.spellcheck = false;
168
+ ta.addEventListener('input', () => { cell.content = ta.value; autosize(ta); cell.status = 'stale'; });
169
+ body.appendChild(ta);
170
+ codeCell.appendChild(body);
171
+ setTimeout(() => autosize(ta), 0);
172
+
173
+ if (cell.type === 'sql' && !cell.hideResult) {
174
+ const res = document.createElement('div');
175
+ res.className = 'nbd-result-inline';
176
+ res.innerHTML = `
177
+ <table>
178
+ <tr><td>row_1</td><td>42</td></tr>
179
+ <tr><td>row_2</td><td>29</td></tr>
180
+ <tr><td>row_3</td><td>22</td></tr>
181
+ <tr><td>row_4</td><td>9</td></tr>
182
+ </table>`;
183
+ codeCell.appendChild(res);
184
+ } else if (cell.type === 'js' && !cell.hideResult) {
185
+ const chart = document.createElement('div');
186
+ chart.className = 'nbd-chart';
187
+ chart.innerHTML = `
188
+ <div class="nbd-bar" style="height:100%"></div>
189
+ <div class="nbd-bar" style="height:68%"></div>
190
+ <div class="nbd-bar" style="height:52%"></div>
191
+ <div class="nbd-bar" style="height:22%"></div>`;
192
+ codeCell.appendChild(chart);
193
+ }
194
+
195
+ row.appendChild(codeCell);
196
+
197
+ if (cell.comment) {
198
+ const c = document.createElement('div');
199
+ c.className = 'nbd-comment';
200
+ c.innerHTML = `
201
+ <div class="nbd-comment-who">
202
+ <div class="nbd-av-small">${escapeHtml(cell.comment.who.slice(0, 2).toUpperCase())}</div>
203
+ <span class="nbd-who-name">${escapeHtml(cell.comment.who)}</span>
204
+ <span class="nbd-when">${escapeHtml(cell.comment.when)}</span>
205
+ </div>
206
+ <div class="nbd-comment-body">${escapeHtml(cell.comment.body)}</div>`;
207
+ row.appendChild(c);
208
+ }
209
+
210
+ (head.querySelector('.nb-toggle-src') as HTMLElement).addEventListener('click', () => { cell.hideSource = !cell.hideSource; rerender(); });
211
+ (head.querySelector('.nb-toggle-res') as HTMLElement).addEventListener('click', () => { cell.hideResult = !cell.hideResult; rerender(); });
212
+ (head.querySelector('.nbd-del') as HTMLElement).addEventListener('click', () =>
213
+ deleteCellWithConfirm(state, cell, (c) => `${c.type} cell`, rerender)
214
+ );
215
+
216
+ wrap.appendChild(row);
217
+ return wrap;
218
+ }
219
+
220
+ function escapeHtml(s: string): string {
221
+ return (s ?? '').replace(/[&<>"']/g, (c) => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c]!));
222
+ }
223
+ function escapeAttr(s: string): string {
224
+ return (s ?? '').replace(/"/g, '&quot;');
225
+ }
226
+
227
+ function injectLayoutStyles(): void {
228
+ if (document.getElementById('nbd-styles')) return;
229
+ const style = document.createElement('style');
230
+ style.id = 'nbd-styles';
231
+ style.textContent = `
232
+ .nbd-shell {
233
+ background: var(--color-surface);
234
+ border: 1px solid var(--color-border);
235
+ border-radius: 12px;
236
+ padding: 28px 32px;
237
+ }
238
+ .nbd-presence { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
239
+ .nbd-avatars { display: flex; }
240
+ .nbd-av {
241
+ width: 22px; height: 22px; border-radius: 50%;
242
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
243
+ font-size: 9px; font-weight: 600;
244
+ display: flex; align-items: center; justify-content: center;
245
+ border: 2px solid var(--color-surface);
246
+ }
247
+ .nbd-av + .nbd-av { margin-left: -6px; }
248
+ .nbd-av1 { background: rgba(124,109,250,0.25); color: var(--color-accent); }
249
+ .nbd-av2 { background: rgba(62,207,178,0.22); color: var(--color-teal); }
250
+ .nbd-av3 { background: rgba(240,160,80,0.22); color: var(--color-amber); }
251
+ .nbd-label {
252
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
253
+ font-size: 11px; color: var(--color-text2);
254
+ }
255
+ .nbd-title {
256
+ font-family: var(--font-sans, 'Syne', sans-serif);
257
+ font-size: 24px; font-weight: 600;
258
+ letter-spacing: -0.02em; margin: 6px 0 2px;
259
+ background: transparent; border: none; outline: none;
260
+ color: var(--color-text1);
261
+ width: 100%; padding: 2px 4px; border-radius: 3px;
262
+ }
263
+ .nbd-title:focus { background: var(--color-bg); }
264
+ .nbd-meta {
265
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
266
+ font-size: 11px; color: var(--color-text2);
267
+ margin-bottom: 20px;
268
+ }
269
+ .nbd-history-panel { margin-bottom: 12px; }
270
+
271
+ .nbd-cell { position: relative; margin-bottom: 18px; }
272
+ .nbd-prose {
273
+ font-size: 15px; line-height: 1.7;
274
+ color: var(--color-text1);
275
+ outline: none;
276
+ padding: 4px 6px;
277
+ border-radius: 3px;
278
+ border: 1px dashed transparent;
279
+ }
280
+ .nbd-prose:focus { border-color: var(--color-border); background: var(--color-bg); }
281
+ .nbd-prose mark {
282
+ background: rgba(240,160,80,0.18);
283
+ color: var(--color-amber);
284
+ padding: 0 4px; border-radius: 2px;
285
+ }
286
+ .nbd-md-handle { position: absolute; left: -20px; top: 6px; }
287
+ .nbd-del-abs {
288
+ position: absolute; top: 4px; right: 4px;
289
+ opacity: 0; transition: opacity 0.15s;
290
+ }
291
+ .nbd-cell:hover .nbd-del-abs { opacity: 0.5; }
292
+ .nbd-del-abs:hover { opacity: 1 !important; }
293
+
294
+ .nbd-row {
295
+ display: grid;
296
+ grid-template-columns: 1fr 150px;
297
+ gap: 16px; align-items: start;
298
+ }
299
+ .nbd-row.nbd-no-comment { grid-template-columns: 1fr; }
300
+
301
+ .nbd-code-cell {
302
+ border: 1px solid var(--color-border);
303
+ border-radius: 8px; overflow: hidden;
304
+ background: var(--color-bg);
305
+ }
306
+ .nbd-cell-head {
307
+ padding: 7px 12px;
308
+ display: flex; align-items: center; gap: 8px;
309
+ border-bottom: 1px solid var(--color-border);
310
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
311
+ font-size: 10.5px; color: var(--color-text2);
312
+ }
313
+ .nbd-type-sql { color: var(--color-accent); text-transform: uppercase; letter-spacing: 0.08em; font-size: 9.5px; }
314
+ .nbd-type-js { color: var(--color-teal); text-transform: uppercase; letter-spacing: 0.08em; font-size: 9.5px; }
315
+ .nbd-actions { margin-left: auto; display: flex; gap: 6px; }
316
+ .nbd-code-body { padding: 11px 12px; }
317
+ .nbd-hidden { display: none !important; }
318
+ .nbd-result-inline {
319
+ background: var(--color-surface2);
320
+ padding: 10px 12px;
321
+ border-top: 1px solid var(--color-border);
322
+ }
323
+ .nbd-result-inline table { width: 100%; border-collapse: collapse; font-size: 11.5px; font-variant-numeric: tabular-nums; }
324
+ .nbd-result-inline table td { padding: 3px 0; color: var(--color-text1); }
325
+ .nbd-result-inline table td:first-child { color: var(--color-text2); font-variant-numeric: normal; }
326
+ .nbd-result-inline table td:last-child { text-align: right; }
327
+
328
+ .nbd-comment {
329
+ background: rgba(240,160,80,0.08);
330
+ border-left: 2px solid var(--color-amber);
331
+ border-radius: 0 6px 6px 0;
332
+ padding: 10px 12px;
333
+ font-size: 11.5px;
334
+ }
335
+ .nbd-comment-who { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
336
+ .nbd-av-small {
337
+ width: 15px; height: 15px; border-radius: 50%;
338
+ background: rgba(240,160,80,0.25); color: var(--color-amber);
339
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
340
+ font-size: 8px; font-weight: 600;
341
+ display: flex; align-items: center; justify-content: center;
342
+ }
343
+ .nbd-who-name {
344
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
345
+ font-size: 10.5px; color: var(--color-amber); font-weight: 500;
346
+ }
347
+ .nbd-when {
348
+ margin-left: auto;
349
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
350
+ font-size: 10px; color: var(--color-text2);
351
+ }
352
+ .nbd-comment-body { color: var(--color-text1); line-height: 1.5; }
353
+
354
+ .nbd-chart { padding: 16px; display: flex; align-items: flex-end; gap: 10px; height: 95px; }
355
+ .nbd-bar { flex: 1; background: var(--color-accent); border-radius: 2px 2px 0 0; }
356
+
357
+ .nbd-footer {
358
+ display: flex; gap: 8px;
359
+ padding-top: 14px; margin-top: 20px;
360
+ border-top: 1px solid var(--color-border);
361
+ align-items: center;
362
+ }
363
+ .nbd-spacer { margin-left: auto; }
364
+ .nbd-share-link {
365
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
366
+ font-size: 11px; color: var(--color-text2);
367
+ cursor: pointer;
368
+ }
369
+ .nbd-share-link:hover { color: var(--color-text1); }
370
+ `;
371
+ document.head.appendChild(style);
372
+ }