@lopecode/channel 0.1.3 → 0.1.5

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.
@@ -23,15 +23,23 @@ const _cc_messages = function _cc_messages(Inputs){return(
23
23
  )};
24
24
 
25
25
  const _cc_watches = function _cc_watches(Inputs){return(
26
- Inputs.input([])
26
+ Inputs.input([
27
+ { name: "hash", module: null },
28
+ { name: "currentModules", module: null }
29
+ ])
30
+ )};
31
+
32
+ const _cc_module = function _cc_module(thisModule){return(
33
+ thisModule()
27
34
  )};
28
35
 
29
- const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages, viewof_cc_watches, summarizeJS, observe, realize, runtime, invalidation){return(
36
+ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages, viewof_cc_watches, summarizeJS, observe, realize, compile, createModule, deleteModule, lookupVariable, exportToHTML, cc_module, runtime, invalidation){return(
30
37
  (function() {
31
38
  var port = cc_config.port, host = cc_config.host;
32
39
  var ws = null;
33
40
  var paired = false;
34
-
41
+ // Cache the observer factory (used by lopepage to render cells)
42
+ var ojs_observer = window.__ojs_observer || null;
35
43
 
36
44
  function serializeValue(value, maxLen) {
37
45
  maxLen = maxLen || 500;
@@ -79,22 +87,21 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
79
87
  var name = cmd.params.name;
80
88
  var moduleName = cmd.params.module;
81
89
  var targetModule = findModule(runtime, moduleName);
82
- for (var v of runtime._variables) {
83
- if (v._name === name && (!targetModule || v._module === targetModule)) {
84
- return {
85
- ok: true,
86
- result: {
87
- name: v._name,
88
- hasValue: v._value !== undefined,
89
- hasError: v._error !== undefined,
90
- value: serializeValue(v._value),
91
- error: v._error ? v._error.message : undefined,
92
- reachable: v._reachable
93
- }
94
- };
95
- }
96
- }
97
- return { ok: false, error: "Variable not found: " + name };
90
+ if (!targetModule) return { ok: false, error: "Module not found: " + (moduleName || "default") };
91
+ return lookupVariable(name, targetModule).then(function(v) {
92
+ if (!v) return { ok: false, error: "Variable not found: " + name };
93
+ return {
94
+ ok: true,
95
+ result: {
96
+ name: v._name,
97
+ hasValue: v._value !== undefined,
98
+ hasError: v._error !== undefined,
99
+ value: serializeValue(v._value),
100
+ error: v._error ? v._error.message : undefined,
101
+ reachable: v._reachable
102
+ }
103
+ };
104
+ });
98
105
  }
99
106
 
100
107
  case "define-variable": {
@@ -102,61 +109,95 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
102
109
  var definition = cmd.params.definition;
103
110
  var inputs = cmd.params.inputs || [];
104
111
  var moduleName = cmd.params.module;
105
- var mod = runtime.mains && runtime.mains.get(moduleName);
106
- if (!mod && runtime.mains) {
107
- for (var entry of runtime.mains) {
108
- if (!FRAMEWORK_MODULES.has(entry[0])) { mod = entry[1]; break; }
109
- }
110
- }
112
+ var mod = findModule(runtime, moduleName);
111
113
  if (!mod) return { ok: false, error: "Module not found: " + (moduleName || "default") };
112
114
 
113
115
  return realize([definition], runtime).then(function(results) {
114
116
  var fn = results[0];
115
117
  if (typeof fn !== "function") return { ok: false, error: "Definition must evaluate to a function" };
116
118
 
117
- var existingVar = null;
118
- for (var v of runtime._variables) {
119
- if (v._name === name && v._module === mod) { existingVar = v; break; }
120
- }
121
-
119
+ // Use module._scope to check for existing variable
120
+ var existingVar = mod._scope.get(name);
122
121
  if (existingVar) {
123
122
  existingVar.define(name, inputs, fn);
124
123
  } else {
125
- var obsFactory = null;
126
- for (var v of runtime._variables) {
127
- if (v._name === "__ojs_observer" && typeof v._value === "function") { obsFactory = v._value; break; }
128
- }
129
- mod.variable(obsFactory ? obsFactory(name) : {}).define(name, inputs, fn);
124
+ mod.variable(ojs_observer ? ojs_observer(name) : {}).define(name, inputs, fn);
130
125
  }
126
+ // Auto-watch the defined variable so the result arrives reactively
127
+ watchVariable(runtime, name, moduleName || null);
131
128
  return { ok: true, result: { success: true, name: name, module: moduleName || "default" } };
132
129
  }).catch(function(e) {
133
130
  return { ok: false, error: "define failed: " + e.message };
134
131
  });
135
132
  }
136
133
 
134
+ case "define-cell": {
135
+ var source = cmd.params.source;
136
+ var moduleName = cmd.params.module;
137
+ var mod = findModule(runtime, moduleName);
138
+ if (!mod) return { ok: false, error: "Module not found: " + (moduleName || "default") };
139
+
140
+ try {
141
+ var compiled = compile(source);
142
+ if (!compiled || compiled.length === 0) return { ok: false, error: "Compilation returned no definitions" };
143
+
144
+ var definitions = [];
145
+ for (var ci = 0; ci < compiled.length; ci++) {
146
+ definitions.push(compiled[ci]._definition);
147
+ }
148
+
149
+ return realize(definitions, runtime).then(function(fns) {
150
+ var defined = [];
151
+ for (var di = 0; di < compiled.length; di++) {
152
+ var cellDef = compiled[di];
153
+ var fn = fns[di];
154
+ var cellName = cellDef._name;
155
+ var cellInputs = cellDef._inputs || [];
156
+
157
+ var existingVar = mod._scope.get(cellName);
158
+ if (existingVar) {
159
+ existingVar.define(cellName, cellInputs, fn);
160
+ } else {
161
+ mod.variable(ojs_observer ? ojs_observer(cellName) : {}).define(cellName, cellInputs, fn);
162
+ }
163
+ defined.push(cellName);
164
+
165
+ // Auto-watch non-internal variables
166
+ if (cellName && !cellName.startsWith("module ")) {
167
+ watchVariable(runtime, cellName, moduleName || null);
168
+ }
169
+ }
170
+ return { ok: true, result: { success: true, defined: defined, module: moduleName || "default" } };
171
+ }).catch(function(e) {
172
+ return { ok: false, error: "define-cell realize failed: " + e.message };
173
+ });
174
+ } catch (e) {
175
+ return { ok: false, error: "define-cell compile failed: " + e.message };
176
+ }
177
+ }
178
+
137
179
  case "delete-variable": {
138
180
  var name = cmd.params.name;
139
181
  var moduleName = cmd.params.module;
140
182
  var targetModule = findModule(runtime, moduleName);
141
183
  if (!targetModule) return { ok: false, error: "Module not found: " + (moduleName || "main") };
142
- for (var v of runtime._variables) {
143
- if (v._name === name && v._module === targetModule) {
144
- v.delete();
145
- return { ok: true, result: { success: true, name: name, module: targetModule._name || "main" } };
146
- }
147
- }
148
- return { ok: false, error: "Variable not found: " + name + " in module " + (moduleName || "main") };
184
+ return lookupVariable(name, targetModule).then(function(v) {
185
+ if (!v) return { ok: false, error: "Variable not found: " + name + " in module " + (moduleName || "main") };
186
+ v.delete();
187
+ return { ok: true, result: { success: true, name: name, module: targetModule._name || "main" } };
188
+ });
149
189
  }
150
190
 
151
191
  case "list-variables": {
152
192
  var moduleName = cmd.params.module;
153
193
  var targetModule = findModule(runtime, moduleName);
194
+ if (!targetModule) return { ok: false, error: "Module not found: " + (moduleName || "default") };
195
+ // Use module._scope instead of scanning runtime._variables
154
196
  var variables = [];
155
- for (var v of runtime._variables) {
156
- if (!v._name) continue;
157
- if (targetModule && v._module !== targetModule) continue;
197
+ for (var entry of targetModule._scope) {
198
+ var v = entry[1];
158
199
  variables.push({
159
- name: v._name,
200
+ name: entry[0],
160
201
  module: (v._module && v._module._name) || "main",
161
202
  hasValue: v._value !== undefined,
162
203
  hasError: v._error !== undefined,
@@ -167,12 +208,56 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
167
208
  return { ok: true, result: variables };
168
209
  }
169
210
 
211
+ case "list-cells": {
212
+ var moduleName = cmd.params.module;
213
+ var targetModule = findModule(runtime, moduleName);
214
+ if (!targetModule) return { ok: false, error: "Module not found: " + (moduleName || "default") };
215
+ var cells = [];
216
+ for (var entry of targetModule._scope) {
217
+ var v = entry[1];
218
+ var defStr = "";
219
+ try { defStr = String(v._definition).slice(0, 300); } catch(e) {}
220
+ cells.push({
221
+ name: entry[0],
222
+ inputs: (v._inputs || []).map(function(inp) { return inp._name || "?"; }),
223
+ definition: defStr,
224
+ hasValue: v._value !== undefined,
225
+ hasError: v._error !== undefined,
226
+ error: v._error ? (v._error.message || String(v._error)) : undefined
227
+ });
228
+ }
229
+ cells.sort(function(a, b) { return a.name.localeCompare(b.name); });
230
+ return { ok: true, result: cells };
231
+ }
232
+
170
233
  case "run-tests": {
171
234
  var filter = cmd.params.filter;
172
235
  var timeout = cmd.params.timeout || 30000;
173
236
  return runTests(runtime, filter, timeout);
174
237
  }
175
238
 
239
+ case "create-module": {
240
+ var moduleName = cmd.params.name;
241
+ if (!moduleName) return { ok: false, error: "Module name is required" };
242
+ try {
243
+ createModule(moduleName, runtime);
244
+ return { ok: true, result: { success: true, name: moduleName } };
245
+ } catch (e) {
246
+ return { ok: false, error: e.message };
247
+ }
248
+ }
249
+
250
+ case "delete-module": {
251
+ var moduleName = cmd.params.name;
252
+ if (!moduleName) return { ok: false, error: "Module name is required" };
253
+ try {
254
+ deleteModule(moduleName, runtime);
255
+ return { ok: true, result: { success: true, name: moduleName } };
256
+ } catch (e) {
257
+ return { ok: false, error: e.message };
258
+ }
259
+ }
260
+
176
261
  case "eval": {
177
262
  var code = cmd.params.code;
178
263
  try {
@@ -184,7 +269,7 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
184
269
  }
185
270
 
186
271
  case "fork": {
187
- return handleFork(runtime);
272
+ return handleFork();
188
273
  }
189
274
 
190
275
  case "watch": {
@@ -302,17 +387,14 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
302
387
  var key = (moduleName || "main") + ":" + name;
303
388
  if (watchers.has(key)) return { ok: true, result: { already_watching: true, key: key } };
304
389
 
305
- var targetModule = findModule(runtime, moduleName);
306
- var targetVar = null;
307
- for (var v of runtime._variables) {
308
- if (v._name === name && (!targetModule || v._module === targetModule)) {
309
- targetVar = v;
310
- break;
311
- }
312
- }
390
+ // Use findModule for named modules, cc_module (thisModule) for unqualified lookups
391
+ var targetModule = moduleName ? findModule(runtime, moduleName) : cc_module;
392
+ if (!targetModule) return Promise.resolve({ ok: false, error: "Module not found: " + (moduleName || "default") });
393
+
394
+ return lookupVariable(name, targetModule).then(function(targetVar) {
313
395
  if (!targetVar) return { ok: false, error: "Variable not found: " + name };
314
396
 
315
- var moduleName = (targetVar._module && targetVar._module._name) || "main";
397
+ var resolvedModule = (targetVar._module && targetVar._module._name) || "main";
316
398
  var debounceTimer = null;
317
399
  var latestValue = undefined;
318
400
  var latestError = undefined;
@@ -328,19 +410,19 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
328
410
  var watches = viewof_cc_watches.value.slice();
329
411
  var found = false;
330
412
  for (var i = 0; i < watches.length; i++) {
331
- if (watches[i].name === name && watches[i].module === moduleName) {
332
- watches[i] = { name: name, module: moduleName, value: serialized.slice(0, 200), updated: now };
413
+ if (watches[i].name === name && (watches[i].module === resolvedModule || watches[i].module == null)) {
414
+ watches[i] = { name: name, module: resolvedModule, value: serialized.slice(0, 200), updated: now };
333
415
  found = true;
334
416
  break;
335
417
  }
336
418
  }
337
- if (!found) watches.push({ name: name, module: moduleName, value: serialized.slice(0, 200), updated: now });
419
+ if (!found) watches.push({ name: name, module: resolvedModule, value: serialized.slice(0, 200), updated: now });
338
420
  viewof_cc_watches.value = watches;
339
421
  viewof_cc_watches.dispatchEvent(new Event("input"));
340
422
 
341
423
  // Send over WebSocket if connected
342
424
  if (!paired || !ws) return;
343
- var msg = { type: "variable-update", name: name, module: moduleName };
425
+ var msg = { type: "variable-update", name: name, module: resolvedModule };
344
426
  if (latestError) { msg.error = latestError.message || String(latestError); }
345
427
  else { msg.value = serialized; }
346
428
  ws.send(JSON.stringify(msg));
@@ -370,7 +452,7 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
370
452
  cancel();
371
453
  // Remove from watches table
372
454
  var watches = viewof_cc_watches.value.filter(function(w) {
373
- return !(w.name === name && w.module === moduleName);
455
+ return !(w.name === name && w.module === resolvedModule);
374
456
  });
375
457
  viewof_cc_watches.value = watches;
376
458
  viewof_cc_watches.dispatchEvent(new Event("input"));
@@ -379,6 +461,7 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
379
461
  });
380
462
 
381
463
  return { ok: true, result: { watching: true, key: key } };
464
+ }); // close lookupVariable.then
382
465
  }
383
466
 
384
467
  function unwatchVariable(name, moduleName) {
@@ -397,30 +480,30 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
397
480
  return { ok: true, result: { unwatched_all: true, count: keys.length } };
398
481
  }
399
482
 
400
- function handleFork(runtime) {
401
- return new Promise(function(resolve) {
402
- for (var v of runtime._variables) {
403
- if ((v._name === "_exportToHTML" || v._name === "exportToHTML") && typeof v._value === "function") {
404
- try {
405
- Promise.resolve(v._value()).then(function(html) {
406
- resolve({ ok: true, result: { html: html } });
407
- }).catch(function(e) {
408
- resolve({ ok: false, error: "Export failed: " + e.message });
409
- });
410
- return;
411
- } catch (e) {
412
- resolve({ ok: false, error: "Export failed: " + e.message });
413
- return;
414
- }
415
- }
483
+ function handleFork() {
484
+ if (typeof exportToHTML !== "function") {
485
+ return { ok: false, error: "exportToHTML not available. Does this notebook include @tomlarkworthy/exporter-2?" };
486
+ }
487
+ return Promise.resolve(exportToHTML({ mains: runtime.mains })).then(function(result) {
488
+ // exportToHTML returns { source: string, report: object }
489
+ var html = typeof result === "string" ? result : result.source;
490
+ if (!html || typeof html !== "string") {
491
+ return { ok: false, error: "Export returned no HTML source" };
416
492
  }
417
- resolve({ ok: false, error: "_exportToHTML not found. Does this notebook include @tomlarkworthy/exporter-2?" });
493
+ return { ok: true, result: { html: html } };
494
+ }).catch(function(e) {
495
+ return { ok: false, error: "Export failed: " + e.message };
418
496
  });
419
497
  }
420
498
 
421
499
  function connect(token) {
422
500
  if (ws) { ws.close(); ws = null; }
423
501
 
502
+ // Persist token for reconnection across reloads
503
+ if (token) {
504
+ try { sessionStorage.setItem("lopecode_cc_token", token); } catch(e) {}
505
+ }
506
+
424
507
  // Parse port from token format LOPE-PORT-XXXX
425
508
  var connectPort = port;
426
509
  if (token) {
@@ -472,11 +555,13 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
472
555
  hash: location.hash
473
556
  }));
474
557
 
475
- // Auto-watch default variables
558
+ // Set up watches from cc_watches (initialized with defaults via dependency resolution)
476
559
  var runtime2 = window.__ojs_runtime;
477
560
  if (runtime2) {
478
- watchVariable(runtime2, "hash", null);
479
- watchVariable(runtime2, "currentModules", null);
561
+ var initialWatches = viewof_cc_watches.value || [];
562
+ for (var i = 0; i < initialWatches.length; i++) {
563
+ watchVariable(runtime2, initialWatches[i].name, initialWatches[i].module);
564
+ }
480
565
  }
481
566
  break;
482
567
 
@@ -496,6 +581,17 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
496
581
  cc_messages.dispatchEvent(new Event("input"));
497
582
  break;
498
583
 
584
+ case "tool-activity":
585
+ var msgs2 = cc_messages.value.concat([{
586
+ role: "tool",
587
+ tool_name: msg.tool_name,
588
+ content: msg.summary,
589
+ timestamp: msg.timestamp || Date.now()
590
+ }]);
591
+ cc_messages.value = msgs2;
592
+ cc_messages.dispatchEvent(new Event("input"));
593
+ break;
594
+
499
595
  case "command":
500
596
  Promise.resolve(handleCommand(msg)).then(function(result) {
501
597
  ws.send(JSON.stringify({
@@ -527,13 +623,23 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
527
623
  ws.onerror = function() {};
528
624
  }
529
625
 
530
- // Auto-connect if cc=TOKEN is in the hash fragment
531
- // Hash format: #view=R100(...)&cc=LOPE-PORT-XXXX
626
+ // Auto-connect: check hash param first, then sessionStorage fallback
532
627
  (function autoConnect() {
628
+ var token = null;
629
+
630
+ // 1. Check &cc=TOKEN in hash
533
631
  var hash = location.hash || "";
534
632
  var match = hash.match(/[&?]cc=(LOPE-[A-Z0-9-]+)/);
535
633
  if (match) {
536
- var token = match[1];
634
+ token = match[1];
635
+ }
636
+
637
+ // 2. Fallback to sessionStorage (survives reloads even if hash is mangled)
638
+ if (!token) {
639
+ try { token = sessionStorage.getItem("lopecode_cc_token"); } catch(e) {}
640
+ }
641
+
642
+ if (token) {
537
643
  // Small delay to let the WebSocket server be ready
538
644
  setTimeout(function() { connect(token); }, 500);
539
645
  }
@@ -555,38 +661,31 @@ const _cc_watch_table = function _cc_watch_table(cc_watches, Inputs){return(
555
661
  })
556
662
  )};
557
663
 
558
- const _cc_change_forwarder = function _cc_change_forwarder(cc_ws, invalidation){return(
664
+ const _cc_change_forwarder = function _cc_change_forwarder(cc_ws, history, invalidation){return(
559
665
  (function() {
560
666
  var highWaterMark = 0;
561
667
  var initializing = true;
562
668
 
563
669
  var interval = setInterval(function() {
564
670
  if (!cc_ws.paired || !cc_ws.ws) return;
565
- var runtime = window.__ojs_runtime;
566
- if (!runtime) return;
671
+ if (!history || !Array.isArray(history)) return;
567
672
 
568
- for (var v of runtime._variables) {
569
- if (v._name === "history" && v._value && Array.isArray(v._value)) {
570
- var history = v._value;
571
- var total = history.length;
673
+ var total = history.length;
572
674
 
573
- if (initializing) { highWaterMark = total; initializing = false; return; }
574
- if (total <= highWaterMark) return;
675
+ if (initializing) { highWaterMark = total; initializing = false; return; }
676
+ if (total <= highWaterMark) return;
575
677
 
576
- var newEntries = history.slice(highWaterMark).map(function(e) {
577
- return {
578
- t: e.t, op: e.op, module: e.module, _name: e._name,
579
- _inputs: e._inputs,
580
- _definition: typeof e._definition === "function"
581
- ? e._definition.toString().slice(0, 500)
582
- : String(e._definition || "").slice(0, 500)
583
- };
584
- });
585
- highWaterMark = total;
586
- cc_ws.ws.send(JSON.stringify({ type: "cell-change", changes: newEntries }));
587
- break;
588
- }
589
- }
678
+ var newEntries = history.slice(highWaterMark).map(function(e) {
679
+ return {
680
+ t: e.t, op: e.op, module: e.module, _name: e._name,
681
+ _inputs: e._inputs,
682
+ _definition: typeof e._definition === "function"
683
+ ? e._definition.toString().slice(0, 500)
684
+ : String(e._definition || "").slice(0, 500)
685
+ };
686
+ });
687
+ highWaterMark = total;
688
+ cc_ws.ws.send(JSON.stringify({ type: "cell-change", changes: newEntries }));
590
689
  }, 1000);
591
690
 
592
691
  invalidation.then(function() { clearInterval(interval); });
@@ -603,39 +702,41 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
603
702
  disconnected: "#ef4444"
604
703
  };
605
704
 
606
- function renderSetup() {
705
+ function renderConnect() {
706
+ var row = document.createElement("div");
707
+ row.style.cssText = "display:flex;gap:8px;justify-content:center;align-items:center;padding:16px;";
708
+
607
709
  var tokenInput = document.createElement("input");
608
710
  tokenInput.type = "text";
609
711
  tokenInput.placeholder = "LOPE-XXXX";
610
- tokenInput.style.cssText = "font-family:monospace;font-size:16px;padding:8px 12px;border:2px solid #d1d5db;border-radius:6px;width:140px;text-transform:uppercase;letter-spacing:2px;";
712
+ tokenInput.style.cssText = "font-family:var(--monospace, monospace);font-size:16px;padding:8px 12px;border:2px solid var(--theme-foreground-faint);border-radius:6px;width:140px;text-transform:uppercase;letter-spacing:2px;background:var(--theme-background-a);color:var(--theme-foreground);";
611
713
 
612
714
  var connectBtn = document.createElement("button");
613
715
  connectBtn.textContent = "Connect";
614
- connectBtn.style.cssText = "padding:8px 20px;background:#2563eb;color:white;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;";
716
+ connectBtn.style.cssText = "padding:8px 20px;background:var(--theme-foreground);color:var(--theme-background-a);border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;";
615
717
  connectBtn.onclick = function() {
616
718
  var token = tokenInput.value.trim();
617
719
  if (token) cc_ws.connect(token);
618
720
  };
619
-
620
721
  tokenInput.addEventListener("keydown", function(e) {
621
722
  if (e.key === "Enter") connectBtn.click();
622
723
  });
623
724
 
725
+ row.append(tokenInput, connectBtn);
726
+
727
+ var guide = document.createElement("details");
728
+ guide.style.cssText = "padding:0 16px 16px;font-size:13px;color:var(--theme-foreground);";
729
+ guide.innerHTML = '<summary style="cursor:pointer;font-weight:600;font-size:14px;">Setup guide</summary>' +
730
+ '<ol style="margin:8px 0 0;padding-left:20px;line-height:1.8;">' +
731
+ '<li>Install <a href="https://bun.sh" target="_blank">Bun</a> if needed, then:<br>' +
732
+ '<code style="background:var(--theme-background-alt);padding:2px 6px;border-radius:3px;font-size:12px;">bun install -g @lopecode/channel</code><br>' +
733
+ '<code style="background:var(--theme-background-alt);padding:2px 6px;border-radius:3px;font-size:12px;">claude mcp add lopecode bunx @lopecode/channel</code></li>' +
734
+ '<li>Start Claude Code:<br><code style="background:var(--theme-background-alt);padding:2px 6px;border-radius:3px;font-size:12px;">claude --dangerously-load-development-channels server:lopecode</code></li>' +
735
+ '<li>Ask Claude for a pairing token, then paste it above</li>' +
736
+ '</ol>';
737
+
624
738
  var container = document.createElement("div");
625
- container.className = "cc-setup";
626
- container.innerHTML = '<div style="padding:24px;max-width:400px;margin:0 auto;text-align:center;">' +
627
- '<div style="font-size:24px;margin-bottom:8px;">Claude Channel</div>' +
628
- '<p style="color:#6b7280;margin-bottom:20px;font-size:14px;">Connect to Claude Code to chat with Claude from this notebook.</p>' +
629
- '<div style="text-align:left;background:#f3f4f6;padding:16px;border-radius:8px;margin-bottom:20px;font-size:13px;">' +
630
- '<div style="font-weight:600;margin-bottom:8px;">Setup:</div>' +
631
- '<ol style="margin:0;padding-left:20px;line-height:1.8;">' +
632
- '<li>Start Claude with the channel flag:<br><code style="background:#e5e7eb;padding:2px 6px;border-radius:3px;font-size:12px;">claude --dangerously-load-development-channels server:lopecode</code></li>' +
633
- '<li>Copy the pairing token from the terminal</li>' +
634
- '<li>Paste it below and click Connect</li>' +
635
- '</ol></div>' +
636
- '<div style="display:flex;gap:8px;justify-content:center;align-items:center;" class="cc-token-row"></div>' +
637
- '</div>';
638
- container.querySelector(".cc-token-row").append(tokenInput, connectBtn);
739
+ container.append(row, guide);
639
740
  return container;
640
741
  }
641
742
 
@@ -648,11 +749,11 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
648
749
  textarea.className = "cc-input";
649
750
  textarea.placeholder = "Message Claude...";
650
751
  textarea.rows = 2;
651
- textarea.style.cssText = "width:100%;box-sizing:border-box;resize:none;border:1px solid #d1d5db;border-radius:8px;padding:10px 12px;font-family:inherit;font-size:14px;outline:none;";
752
+ textarea.style.cssText = "width:100%;box-sizing:border-box;resize:none;border:1px solid var(--theme-foreground-faint);border-radius:8px;padding:10px 12px;font-family:inherit;font-size:14px;outline:none;background:var(--theme-background-a);color:var(--theme-foreground);";
652
753
 
653
754
  var sendBtn = document.createElement("button");
654
755
  sendBtn.textContent = "Send";
655
- sendBtn.style.cssText = "padding:8px 16px;background:#2563eb;color:white;border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:500;margin-left:auto;";
756
+ sendBtn.style.cssText = "padding:8px 16px;background:var(--theme-foreground);color:var(--theme-background-a);border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:500;margin-left:auto;";
656
757
 
657
758
  function sendMessage() {
658
759
  var text = textarea.value.trim();
@@ -670,7 +771,7 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
670
771
  });
671
772
 
672
773
  var inputRow = document.createElement("div");
673
- inputRow.style.cssText = "display:flex;gap:8px;padding:12px;align-items:flex-end;border-top:1px solid #e5e7eb;";
774
+ inputRow.style.cssText = "display:flex;gap:8px;padding:12px;align-items:flex-end;border-top:1px solid var(--theme-foreground-faint);";
674
775
  inputRow.append(textarea, sendBtn);
675
776
 
676
777
  var container = document.createElement("div");
@@ -680,15 +781,47 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
680
781
 
681
782
  function updateMessages() {
682
783
  messagesDiv.innerHTML = "";
683
- for (var i = 0; i < cc_messages.value.length; i++) {
684
- var msg = cc_messages.value[i];
784
+ var msgs = cc_messages.value;
785
+ var i = 0;
786
+ while (i < msgs.length) {
787
+ var msg = msgs[i];
788
+
789
+ // Group consecutive tool messages into a collapsible block
790
+ if (msg.role === "tool") {
791
+ var toolGroup = [];
792
+ while (i < msgs.length && msgs[i].role === "tool") {
793
+ toolGroup.push(msgs[i]);
794
+ i++;
795
+ }
796
+ var details = document.createElement("details");
797
+ details.style.cssText = "max-width:90%;align-self:flex-start;font-size:12px;opacity:0.85;margin:2px 0;";
798
+ var summary = document.createElement("summary");
799
+ summary.style.cssText = "cursor:pointer;padding:4px 10px;border-radius:8px;" +
800
+ "background:var(--theme-background-alt, #f0f0f0);color:var(--theme-foreground, #333);" +
801
+ "font-family:var(--monospace, monospace);border-left:2px solid var(--theme-foreground-faint, #ccc);list-style:inside;font-size:12px;";
802
+ summary.textContent = toolGroup.length === 1
803
+ ? "\u{1F527} " + toolGroup[0].content
804
+ : "\u{1F527} " + toolGroup.length + " tool calls \u2014 " + toolGroup[toolGroup.length - 1].content;
805
+ details.appendChild(summary);
806
+ var list = document.createElement("div");
807
+ list.style.cssText = "padding:4px 10px 4px 20px;font-family:var(--monospace, monospace);color:var(--theme-foreground, #333);line-height:1.6;font-size:11px;";
808
+ for (var j = 0; j < toolGroup.length; j++) {
809
+ var line = document.createElement("div");
810
+ line.textContent = toolGroup[j].content;
811
+ list.appendChild(line);
812
+ }
813
+ details.appendChild(list);
814
+ messagesDiv.appendChild(details);
815
+ continue;
816
+ }
817
+
685
818
  var bubble = document.createElement("div");
686
819
  bubble.className = "cc-msg cc-msg-" + msg.role;
687
820
  var isUser = msg.role === "user";
688
821
  bubble.style.cssText = "max-width:80%;padding:10px 14px;border-radius:12px;font-size:14px;line-height:1.5;" +
689
822
  (isUser
690
- ? "align-self:flex-end;background:#2563eb;color:white;border-bottom-right-radius:4px;"
691
- : "align-self:flex-start;background:#f3f4f6;color:#1f2937;border-bottom-left-radius:4px;");
823
+ ? "align-self:flex-end;background:var(--theme-foreground);color:var(--theme-background-a);border-bottom-right-radius:4px;"
824
+ : "align-self:flex-start;background:var(--theme-background-b);color:var(--theme-foreground);border-bottom-left-radius:4px;");
692
825
 
693
826
  if (isUser) {
694
827
  bubble.textContent = msg.content;
@@ -700,6 +833,7 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
700
833
  } catch(e) { bubble.textContent = msg.content; }
701
834
  }
702
835
  messagesDiv.appendChild(bubble);
836
+ i++;
703
837
  }
704
838
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
705
839
  }
@@ -709,13 +843,16 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
709
843
  return container;
710
844
  }
711
845
 
846
+ var isConnected = (cc_status.value === "connected");
847
+
712
848
  var wrapper = document.createElement("div");
713
849
  wrapper.className = "cc-chat";
714
- wrapper.style.cssText = "border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;height:400px;display:flex;flex-direction:column;background:white;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;";
850
+ wrapper.style.cssText = "border:1px solid var(--theme-foreground-faint);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;background:var(--theme-background-a);font-family:inherit;" +
851
+ (isConnected ? "height:400px;" : "");
715
852
 
716
853
  var statusBar = document.createElement("div");
717
854
  statusBar.className = "cc-status-bar";
718
- statusBar.style.cssText = "display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;background:#fafafa;";
855
+ statusBar.style.cssText = "display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--theme-foreground-faint);font-size:12px;color:var(--theme-foreground-faint);background:var(--theme-background-b);";
719
856
 
720
857
  var statusDot = document.createElement("span");
721
858
  statusDot.style.cssText = "width:8px;height:8px;border-radius:50%;display:inline-block;";
@@ -735,18 +872,21 @@ const _cc_chat = function _cc_chat(cc_messages, cc_status, cc_ws, md, htl, Input
735
872
 
736
873
  function render() {
737
874
  var status = cc_status.value || "disconnected";
875
+ var connected = (status === "connected");
738
876
  statusDot.style.background = statusColors[status] || "#ef4444";
739
- statusText.textContent = status === "connected" ? "Connected to Claude Code"
877
+ statusText.textContent = connected ? "Connected to Claude Code"
740
878
  : (status === "connecting" || status === "pairing") ? "Connecting..."
741
879
  : "Not connected";
742
880
 
881
+ wrapper.style.height = connected ? "400px" : "";
882
+
743
883
  body.innerHTML = "";
744
- if (status === "connected") {
884
+ if (connected) {
745
885
  chatView = renderChat();
746
886
  body.appendChild(chatView);
747
887
  } else {
748
888
  chatView = null;
749
- body.appendChild(renderSetup());
889
+ body.appendChild(renderConnect());
750
890
  }
751
891
  }
752
892
 
@@ -788,18 +928,31 @@ export default function define(runtime, observer) {
788
928
  $def("_cc_messages", "cc_messages", ["Inputs"], _cc_messages);
789
929
  $def("_cc_watches", "viewof cc_watches", ["Inputs"], _cc_watches);
790
930
  main.variable().define("cc_watches", ["Generators", "viewof cc_watches"], (G, v) => G.input(v));
791
- $def("_cc_ws", "cc_ws", ["cc_config","cc_notebook_id","cc_status","cc_messages","viewof cc_watches","summarizeJS","observe","realize","runtime","invalidation"], _cc_ws);
792
- $def("_cc_change_forwarder", "cc_change_forwarder", ["cc_ws","invalidation"], _cc_change_forwarder);
931
+ $def("_cc_module", "viewof cc_module", ["thisModule"], _cc_module);
932
+ main.variable().define("cc_module", ["Generators", "viewof cc_module"], (G, v) => G.input(v));
933
+ $def("_cc_ws", "cc_ws", ["cc_config","cc_notebook_id","cc_status","cc_messages","viewof cc_watches","summarizeJS","observe","realize","compile","createModule","deleteModule","lookupVariable","exportToHTML","cc_module","runtime","invalidation"], _cc_ws);
934
+ $def("_cc_change_forwarder", "cc_change_forwarder", ["cc_ws","history","invalidation"], _cc_change_forwarder);
793
935
 
794
936
  // Imports
795
937
  main.define("module @tomlarkworthy/module-map", async () => runtime.module((await import("/@tomlarkworthy/module-map.js?v=4")).default));
796
938
  main.define("currentModules", ["module @tomlarkworthy/module-map", "@variable"], (_, v) => v.import("currentModules", _));
797
939
  main.define("moduleMap", ["module @tomlarkworthy/module-map", "@variable"], (_, v) => v.import("moduleMap", _));
940
+ main.define("module @tomlarkworthy/exporter-2", async () => runtime.module((await import("/@tomlarkworthy/exporter-2.js?v=4")).default));
941
+ main.define("exportToHTML", ["module @tomlarkworthy/exporter-2", "@variable"], (_, v) => v.import("exportToHTML", _));
942
+ main.define("module @tomlarkworthy/observablejs-toolchain", async () => runtime.module((await import("/@tomlarkworthy/observablejs-toolchain.js?v=4")).default));
943
+ main.define("compile", ["module @tomlarkworthy/observablejs-toolchain", "@variable"], (_, v) => v.import("compile", _));
944
+ main.define("module @tomlarkworthy/local-change-history", async () => runtime.module((await import("/@tomlarkworthy/local-change-history.js?v=4")).default));
945
+ main.define("viewof history", ["module @tomlarkworthy/local-change-history", "@variable"], (_, v) => v.import("viewof history", _));
946
+ main.define("history", ["Generators", "viewof history"], (G, v) => G.input(v));
798
947
  main.define("module @tomlarkworthy/runtime-sdk", async () => runtime.module((await import("/@tomlarkworthy/runtime-sdk.js?v=4")).default));
799
948
  main.define("viewof runtime_variables", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("viewof runtime_variables", _));
800
949
  main.define("runtime_variables", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("runtime_variables", _));
801
950
  main.define("observe", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("observe", _));
802
951
  main.define("realize", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("realize", _));
952
+ main.define("createModule", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("createModule", _));
953
+ main.define("deleteModule", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("deleteModule", _));
954
+ main.define("lookupVariable", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("lookupVariable", _));
955
+ main.define("thisModule", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("thisModule", _));
803
956
  main.define("runtime", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("runtime", _));
804
957
  main.define("module d/57d79353bac56631@44", async () => runtime.module((await import("/d/57d79353bac56631@44.js?v=4")).default));
805
958
  main.define("hash", ["module d/57d79353bac56631@44", "@variable"], (_, v) => v.import("hash", _));