@polycode-projects/the-mechanical-code-talker 2.10.3 → 2.11.0

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 (52) hide show
  1. package/README.md +68 -12
  2. package/bin/tmct.mjs +5 -2
  3. package/corpus/sprites/src/sprite-facts.jsonl +18 -0
  4. package/corpus/worlds/manifest.json +5 -5
  5. package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
  6. package/corpus/worlds/src/ashcombe-hall.jsonl +27 -0
  7. package/data/sprites/book-icon.toml +12 -0
  8. package/data/sprites/cellar-icon.toml +12 -0
  9. package/data/sprites/drawing-room-icon.toml +13 -0
  10. package/data/sprites/garden-icon.toml +12 -0
  11. package/data/sprites/kitchen-icon.toml +13 -0
  12. package/data/sprites/library-icon.toml +12 -0
  13. package/data/sprites/pan-icon.toml +11 -0
  14. package/data/sprites/study-icon.toml +12 -0
  15. package/package.json +7 -2
  16. package/src/adapters/corpus/wikipedia-live.mjs +182 -26
  17. package/src/adapters/corpus/worlds-pack.mjs +8 -2
  18. package/src/adapters/memory/core.mjs +8 -1
  19. package/src/adapters/toml-config.mjs +6 -0
  20. package/src/domain/cli-verbs.mjs +2 -0
  21. package/src/domain/memory/trust.mjs +32 -2
  22. package/src/domain/sense-split.mjs +203 -0
  23. package/src/domain/worlds-pack.mjs +50 -0
  24. package/src/services/adventure-autoplay.mjs +5 -2
  25. package/src/services/adventure-viz.mjs +301 -33
  26. package/src/services/adventure.mjs +162 -14
  27. package/src/services/chat-page-viz.mjs +341 -197
  28. package/src/services/chat-session.mjs +24 -9
  29. package/src/services/chat.mjs +580 -47
  30. package/src/services/code-explorer-viz.mjs +198 -76
  31. package/src/services/extract-facts.mjs +384 -82
  32. package/src/services/fold.mjs +1 -1
  33. package/src/services/ingest-viz.mjs +637 -0
  34. package/src/services/ledger-viz.mjs +209 -0
  35. package/src/services/memory-panel-viz.mjs +159 -0
  36. package/src/services/research.mjs +266 -0
  37. package/src/services/sentences.mjs +19 -0
  38. package/src/services/session-log-format.mjs +64 -0
  39. package/src/services/sessions.mjs +56 -22
  40. package/src/services/spider-fly-turn.mjs +54 -1
  41. package/src/services/spider-fly-viz.mjs +41 -23
  42. package/src/surfaces/web/adventure-browser-entry.mjs +9 -5
  43. package/src/surfaces/web/chat-browser-entry.mjs +32 -11
  44. package/src/surfaces/web/code-explorer-browser-entry.mjs +27 -11
  45. package/src/surfaces/web/ingest-browser-entry.mjs +208 -0
  46. package/src/surfaces/web/ledger-browser-entry.mjs +24 -5
  47. package/src/surfaces/web/memory-ask-browser.bundle.js +134 -125
  48. package/src/surfaces/web/memory-stats.mjs +53 -0
  49. package/src/tools/definitions.mjs +14 -0
  50. package/src/tools/handlers/index.mjs +2 -0
  51. package/src/tools/handlers/tmct-ingest.mjs +43 -0
  52. package/src/tools/server.mjs +5 -2
@@ -1,13 +1,20 @@
1
- // code-explorer-viz.mjs — the code-graph "ledger" the desktop shell renders:
2
- // each import/call/contains edge read back as a plain sentence, a hint rail of
3
- // suggested next queries, and a live chat dock over the same graph. The two
4
- // derivations are pure so the shell, the packaging script, and the unit tests
5
- // all share one code path; renderCodeExplorerHtml builds one self-contained
6
- // document with no external requests.
1
+ // code-explorer-viz.mjs — the code explorer as a full-viewport IDE shell:
2
+ // a title bar (graph source, Open graph…/Open repo…), an explorer sidebar
3
+ // reading each import/call/contains edge back as a plain sentence, a chat
4
+ // centre over the same graph with a rail of suggested questions, and a status
5
+ // bar carrying the graph's own counts. The chat session seeds BOTH the loaded
6
+ // code graph and chat.html's general-knowledge bands (./chat-seed.json,
7
+ // fetched lazily at runtime), so one conversation answers "what is a queue"
8
+ // and "what imports src/core/model.mjs" alike — and degrades to graph-only
9
+ // when the seed asset is unavailable.
7
10
  //
8
- // The channel is deliberately thin: this is the SAME ledger-pattern UI the
9
- // browser ledger page uses, refocused on a code graph, and it stays servable
10
- // as a plain page only the Electron shell around it (electron/) is desktop.
11
+ // The derivations are pure so the shell, the packaging scripts, and the unit
12
+ // tests all share one code path; renderCodeExplorerHtml builds one
13
+ // self-contained document that both packagers ship unchanged the site build
14
+ // (scripts/build-demo-site.mjs) and the Electron desktop shell
15
+ // (scripts/build-electron-app.mjs). Panels are plain sections inside the
16
+ // sidebar/centre grid, so a later panel is one more <section class="panel">,
17
+ // not a re-architecture.
11
18
 
12
19
  import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText } from "./viz-theme.mjs";
13
20
  import { generateCodeHints } from "../domain/code-explorer-hints.mjs";
@@ -31,6 +38,17 @@ export function edgePhrase(kind) {
31
38
  return EDGE_PHRASE.get(String(kind || "")) || String(kind || "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
32
39
  }
33
40
 
41
+ // Both packagers (build-electron-app.mjs, build-demo-site.mjs) place
42
+ // vendor/wink.js one level below the page they render, so the same loader
43
+ // script inlines into renderCodeExplorerHtml's `winkLoaderInline` slot for
44
+ // either channel.
45
+ export const VENDOR_WINK_LOADER_JS = `window.__WINK_LOADER__ = async function () {
46
+ var m = await import("./vendor/wink.js");
47
+ return { winkNLP: m.winkNLP, model: m.model };
48
+ };`;
49
+
50
+ export const DESKTOP_APP_URL = "https://gitlab.com/polycode-projects/the-mechanical-code-talker#the-code-explorer-desktop";
51
+
34
52
  const LEDGER_ROW_LIMIT_DEFAULT = 4000;
35
53
 
36
54
  /** Pure derivation over an entities payload (individuals + objectProperties).
@@ -110,6 +128,7 @@ const CLIENT_JS = String.raw`
110
128
  input: document.getElementById("chat-input"),
111
129
  dockNote: document.getElementById("dock-note"),
112
130
  source: document.getElementById("source-name"),
131
+ seedStatus: document.getElementById("seed-status"),
113
132
  };
114
133
  var session = null;
115
134
 
@@ -163,7 +182,7 @@ const CLIENT_JS = String.raw`
163
182
  renderHints(data);
164
183
  }
165
184
 
166
- function appendLog(role, text) {
185
+ function appendTurn(role, text) {
167
186
  var div = document.createElement("div");
168
187
  div.className = "turn turn-" + role;
169
188
  div.innerHTML = '<span class="who">' + (role === "you" ? "you" : "tmct") + '</span><span class="said">' + esc(text) + '</span>';
@@ -171,23 +190,93 @@ const CLIENT_JS = String.raw`
171
190
  els.log.scrollTop = els.log.scrollHeight;
172
191
  }
173
192
 
193
+ function appendNote(text) {
194
+ var div = document.createElement("div");
195
+ div.className = "turn turn-note";
196
+ div.textContent = text;
197
+ els.log.appendChild(div);
198
+ }
199
+
200
+ // ---- the general-knowledge seed --------------------------------------
201
+ // The same chat-seed.json chat.html boots from, loaded lazily so the shell
202
+ // paints instantly: same-origin fetch on the site, the preload bridge's
203
+ // readSeed under the desktop shell (a file:// page cannot fetch). The
204
+ // status bar narrates the load; a missing or failing seed leaves the chat
205
+ // graph-only and says so, never broken.
206
+ var seedState = { status: api ? "loading" : "absent", payload: null, facts: 0 };
207
+ function seedNote(text) { if (els.seedStatus) els.seedStatus.textContent = text; }
208
+ function mbText(n) { return (n / 1048576).toFixed(1); }
209
+
210
+ async function fetchTextWithProgress(url, onProgress) {
211
+ var res = await fetch(url);
212
+ if (!res.ok) throw new Error("HTTP " + res.status);
213
+ var total = Number(res.headers.get("content-length")) || 0;
214
+ if (!res.body || !res.body.getReader) return res.text();
215
+ var reader = res.body.getReader();
216
+ var chunks = [];
217
+ var loaded = 0;
218
+ for (;;) {
219
+ var step = await reader.read();
220
+ if (step.done) break;
221
+ chunks.push(step.value);
222
+ loaded += step.value.byteLength;
223
+ onProgress(loaded, total);
224
+ }
225
+ return new Blob(chunks).text();
226
+ }
227
+
228
+ async function loadSeed() {
229
+ try {
230
+ var text = null;
231
+ if (window.tmctDesktop && typeof window.tmctDesktop.readSeed === "function") {
232
+ seedNote("loading general knowledge…");
233
+ text = await window.tmctDesktop.readSeed();
234
+ } else {
235
+ text = await fetchTextWithProgress("./chat-seed.json", function (loaded, total) {
236
+ seedNote("loading general knowledge… " + mbText(loaded) + (total ? " of " + mbText(total) : "") + " MB");
237
+ });
238
+ }
239
+ if (text) {
240
+ seedState.payload = JSON.parse(text);
241
+ seedState.facts = (seedState.payload.individuals || []).filter(function (i) { return i.class === "Fact"; }).length;
242
+ seedState.status = "ready";
243
+ seedNote("general knowledge: " + seedState.facts + " facts");
244
+ return;
245
+ }
246
+ } catch (e) {
247
+ console.warn("tmct code explorer: chat-seed unavailable, continuing graph-only", e);
248
+ }
249
+ seedState.status = "absent";
250
+ seedNote("graph-only — general knowledge unavailable");
251
+ }
252
+ var seedPromise = api ? loadSeed() : Promise.resolve();
253
+
254
+ function cloneSeed() {
255
+ if (!seedState.payload) return null;
256
+ try { return structuredClone(seedState.payload); } catch (e) { return JSON.parse(JSON.stringify(seedState.payload)); }
257
+ }
258
+
174
259
  async function ensureSession() {
175
260
  if (session || !api || !api.createCodeExplorerSession) return session;
176
- var winkLoaded = true;
177
261
  if (api.registerWinkModel && window.__WINK_LOADER__) {
178
262
  try { var mod = await window.__WINK_LOADER__(); api.registerWinkModel(function () { return mod; }); }
179
- catch (e) { winkLoaded = false; }
263
+ catch (e) { /* the lemma/POS tier is optional */ }
180
264
  }
181
- session = api.createCodeExplorerSession({ graphPayload: DATA.payload });
265
+ await seedPromise;
266
+ session = api.createCodeExplorerSession({
267
+ graphPayload: DATA.payload,
268
+ seedPayload: cloneSeed(),
269
+ vocabSeeded: seedState.status === "ready",
270
+ });
182
271
  return session;
183
272
  }
184
273
 
185
274
  async function ask(q) {
186
- appendLog("you", q);
275
+ appendTurn("you", q);
187
276
  var s = await ensureSession();
188
- if (!s) { appendLog("tmct", "the live dock is not loaded on this page."); return; }
277
+ if (!s) { appendTurn("tmct", "the live chat is not loaded on this page."); return; }
189
278
  var res = await s.turn(q);
190
- appendLog("tmct", res.answer);
279
+ appendTurn("tmct", res.answer);
191
280
  }
192
281
 
193
282
  // Delegate term + hint clicks.
@@ -208,7 +297,8 @@ const CLIENT_JS = String.raw`
208
297
  });
209
298
  }
210
299
 
211
- // Desktop pickers, present only under the Electron shell.
300
+ // Desktop pickers, present only under the Electron shell. A swapped graph
301
+ // starts a fresh session; the general-knowledge seed carries over.
212
302
  function wirePicker(id, method, updateSource) {
213
303
  var btn = document.getElementById(id);
214
304
  if (!btn) return;
@@ -234,8 +324,11 @@ const CLIENT_JS = String.raw`
234
324
  wirePicker("open-repo", "openRepo", true);
235
325
 
236
326
  if (!api) {
237
- if (els.dockNote) els.dockNote.textContent = "static view — the live chat dock is unavailable on this page.";
327
+ if (els.dockNote) els.dockNote.textContent = "static view — the live chat is unavailable on this page.";
238
328
  if (els.input) els.input.disabled = true;
329
+ seedNote("static view");
330
+ } else {
331
+ appendNote("Ask about this code graph — or anything its general knowledge covers, like “what is a queue”.");
239
332
  }
240
333
 
241
334
  mountView(DATA);
@@ -243,16 +336,17 @@ const CLIENT_JS = String.raw`
243
336
  `;
244
337
 
245
338
  /**
246
- * One self-contained HTML document for the code explorer. `data` is
247
- * computeCodeExplorerData's output. `bundleInline` inlines the dock engine
339
+ * One self-contained HTML document: the full-viewport IDE shell over
340
+ * computeCodeExplorerData's output. `bundleInline` inlines the chat engine
248
341
  * (for a single-file page / a data: URL); otherwise `bundleAvailable` links
249
342
  * `./code-explorer.bundle.js`. `winkLoaderInline` optionally inlines a wink
250
- * model loader as `window.__WINK_LOADER__`.
343
+ * model loader as `window.__WINK_LOADER__`. `showDesktopLink` adds a line
344
+ * pointing at the desktop app's README section — the desktop shell itself
345
+ * renders this page too and passes `false`, since it has nothing to point at.
251
346
  */
252
- export function renderCodeExplorerHtml(data, { bundleInline = "", bundleAvailable = false, winkLoaderInline = "", sourceName = "demo code graph" } = {}) {
347
+ export function renderCodeExplorerHtml(data, { bundleInline = "", bundleAvailable = false, winkLoaderInline = "", sourceName = "demo code graph", showDesktopLink = false } = {}) {
253
348
  const payloadJson = embedJson(data.payload);
254
349
  const dataJson = embedJson({ ledger: data.ledger, hints: data.hints, focus: data.focus, meta: data.meta });
255
- const title = escapeHtml(data.meta?.title || "code explorer");
256
350
 
257
351
  return `<!doctype html>
258
352
  <html lang="en">
@@ -263,74 +357,102 @@ export function renderCodeExplorerHtml(data, { bundleInline = "", bundleAvailabl
263
357
  <style>
264
358
  ${THEME_TOKENS_CSS}
265
359
  * { box-sizing: border-box; }
266
- body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${SERIF_STACK}; }
267
- header { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; padding: 0.8rem 1.1rem; border-bottom: 1px solid var(--line); }
268
- header h1 { font-size: 1.05rem; margin: 0; font-weight: 600; }
269
- header .sub { color: var(--muted); font-size: 0.85rem; }
270
- header .pickers { margin-left: auto; display: flex; gap: 0.5rem; }
360
+ html, body { height: 100%; }
361
+ body { margin: 0; overflow: hidden; background: var(--bg); color: var(--ink); font-family: ${SERIF_STACK}; }
362
+ .shell { height: 100%; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
363
+
364
+ .titlebar { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; padding: 0.5rem 0.9rem; background: var(--card); border-bottom: 1px solid var(--line); box-shadow: inset 0 -2px 0 var(--entail-soft); }
365
+ .titlebar .mark { width: 15px; height: 15px; color: var(--entail); flex: none; }
366
+ .titlebar h1 { font-size: 0.95rem; margin: 0; font-weight: 600; letter-spacing: 0.02em; }
367
+ .titlebar .sub { color: var(--muted); font-size: 0.76rem; font-family: ${MONO_STACK}; }
368
+ .titlebar .sub a { color: var(--corpus); }
369
+ .titlebar .pickers { margin-left: auto; display: flex; gap: 0.45rem; }
271
370
  button { font: inherit; cursor: pointer; }
272
371
  button:disabled { cursor: default; opacity: 0.5; }
273
- .pickers button { background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: 0.35rem 0.7rem; font-size: 0.85rem; }
274
- #stats { padding: 0.4rem 1.1rem; color: var(--muted); font-size: 0.8rem; font-family: ${MONO_STACK}; border-bottom: 1px solid var(--line); }
275
- main { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(260px, 1fr); gap: 0; align-items: stretch; }
276
- @media (max-width: 720px) { main { grid-template-columns: 1fr; } }
277
- .ledger-pane { padding: 0.6rem 1.1rem 2rem; min-height: 60vh; }
278
- .rail { border-left: 1px solid var(--line); padding: 0.6rem 1rem 2rem; display: flex; flex-direction: column; gap: 1rem; }
279
- h2 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 0.4rem; }
372
+ button:focus-visible, input:focus-visible { outline: 2px solid var(--entail); outline-offset: 1px; }
373
+ .pickers button { background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 4px; padding: 0.3rem 0.65rem; font-size: 0.8rem; }
374
+ .pickers button:hover { border-color: var(--entail); }
375
+
376
+ .workbench { display: grid; grid-template-columns: minmax(230px, 320px) minmax(0, 1fr); min-height: 0; }
377
+ .sidebar { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--line); }
378
+ .panel { display: flex; flex-direction: column; min-height: 0; flex: 1; }
379
+ .panel-head { margin: 0; padding: 0.45rem 0.9rem; flex: none; display: flex; align-items: baseline; gap: 0.45rem; font-family: ${MONO_STACK}; font-size: 0.66rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted); border-bottom: 1px solid var(--line); }
380
+ .panel-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0.5rem 0.9rem 1rem; }
381
+ .focus-line { font-family: ${MONO_STACK}; font-size: 0.72rem; color: var(--corpus); text-transform: none; letter-spacing: 0; overflow-wrap: anywhere; }
280
382
  ul.rows { list-style: none; margin: 0; padding: 0; }
281
- .row { padding: 0.28rem 0.4rem; border-radius: 5px; font-size: 0.95rem; line-height: 1.5; }
383
+ .row { padding: 0.26rem 0.35rem; border-radius: 4px; font-size: 0.88rem; line-height: 1.45; }
282
384
  .row-focus { background: var(--corpus-soft); }
283
385
  .row.muted, .muted { color: var(--muted); }
284
- .term { background: none; border: none; padding: 0; color: var(--corpus); font-family: ${MONO_STACK}; font-size: 0.85rem; text-decoration: underline; text-decoration-color: var(--line); }
386
+ .term { background: none; border: none; padding: 0; color: var(--corpus); font-family: ${MONO_STACK}; font-size: 0.8rem; text-align: left; text-decoration: underline; text-decoration-color: var(--line); overflow-wrap: anywhere; }
285
387
  .term:hover { text-decoration-color: var(--corpus); }
286
388
  .verb { color: var(--muted); }
287
- .hints { display: flex; flex-direction: column; gap: 0.35rem; }
288
- .hint { text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: 0.35rem 0.55rem; font-size: 0.85rem; color: var(--ink); }
289
- .hint:hover { border-color: var(--corpus); }
290
- .dock { display: flex; flex-direction: column; gap: 0.4rem; }
291
- #chat-log { display: flex; flex-direction: column; gap: 0.4rem; max-height: 40vh; overflow-y: auto; }
292
- .turn { font-size: 0.9rem; line-height: 1.45; }
293
- .turn .who { display: block; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
294
- .turn-you .said { color: var(--ink); }
295
- .turn-tmct .said { color: var(--taught); white-space: pre-wrap; }
296
- #chat-form { display: flex; gap: 0.4rem; }
297
- #chat-input { flex: 1; font: inherit; padding: 0.4rem 0.5rem; border: 1px solid var(--line); border-radius: 6px; background: var(--card); color: var(--ink); }
298
- #chat-form button { background: var(--corpus); color: #fff; border: none; border-radius: 6px; padding: 0.4rem 0.8rem; }
299
- #dock-note { color: var(--muted); font-size: 0.78rem; }
300
- .focus-line { font-family: ${MONO_STACK}; font-size: 0.85rem; }
389
+
390
+ .editor { display: flex; flex-direction: column; min-height: 0; background: var(--card); }
391
+ #chat-log { flex: 1; min-height: 0; overflow-y: auto; padding: 0.9rem 1.1rem; display: flex; flex-direction: column; gap: 0.7rem; }
392
+ .turn { max-width: 46rem; font-size: 0.95rem; line-height: 1.5; }
393
+ .turn .who { display: block; font-family: ${MONO_STACK}; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted); margin-bottom: 0.15rem; }
394
+ .turn-you .said { font-family: ${MONO_STACK}; font-size: 0.84rem; }
395
+ .turn-tmct { border-left: 2px solid var(--entail); padding-left: 0.7rem; }
396
+ .turn-tmct .said { white-space: pre-wrap; }
397
+ .turn-note { color: var(--muted); font-style: italic; font-size: 0.85rem; }
398
+ .suggest { flex: none; border-top: 1px solid var(--line); padding: 0.45rem 1.1rem 0.1rem; }
399
+ .suggest-label { font-family: ${MONO_STACK}; font-size: 0.66rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted); }
400
+ .hints { display: flex; flex-wrap: wrap; gap: 0.35rem; max-height: 5.6rem; overflow-y: auto; padding: 0.35rem 0 0.45rem; }
401
+ .hint { background: var(--bg); border: 1px solid var(--line); border-radius: 999px; padding: 0.24rem 0.7rem; font-size: 0.8rem; color: var(--ink); text-align: left; }
402
+ .hint:hover { border-color: var(--entail); }
403
+ #chat-form { flex: none; display: flex; gap: 0.5rem; padding: 0.55rem 1.1rem 0.8rem; border-top: 1px solid var(--line); }
404
+ #chat-input { flex: 1; min-width: 0; font: inherit; font-size: 0.92rem; padding: 0.5rem 0.65rem; border: 1px solid var(--line); border-radius: 4px; background: var(--bg); color: var(--ink); }
405
+ #chat-form button { background: var(--entail); color: var(--card); font-weight: 600; border: none; border-radius: 4px; padding: 0.5rem 1rem; font-size: 0.9rem; }
406
+
407
+ .statusbar { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; padding: 0.32rem 0.9rem; font-family: ${MONO_STACK}; font-size: 0.7rem; color: var(--muted); background: var(--entail-soft); border-top: 1px solid var(--entail); }
408
+ #seed-status { margin-left: auto; text-align: right; }
409
+
410
+ @media (max-width: 760px) {
411
+ .workbench { grid-template-columns: 1fr; grid-template-rows: minmax(0, 34%) minmax(0, 1fr); }
412
+ .sidebar { border-right: none; border-bottom: 1px solid var(--line); }
413
+ }
301
414
  </style>
302
415
  </head>
303
416
  <body>
304
- <header>
305
- <h1>tmct code explorer</h1>
306
- <span class="sub">source: <span id="source-name">${escapeHtml(sourceName)}</span></span>
307
- <div class="pickers">
308
- <button id="open-graph">Open graph…</button>
309
- <button id="open-repo">Open repo…</button>
310
- </div>
311
- </header>
312
- <div id="stats"></div>
313
- <main>
314
- <section class="ledger-pane">
315
- <h2>Facts around <span class="focus-line" id="focus-name">—</span></h2>
316
- <ul class="rows" id="ledger"></ul>
317
- </section>
318
- <aside class="rail">
319
- <div>
320
- <h2>Try asking</h2>
321
- <div class="hints" id="hints"></div>
417
+ <div class="shell">
418
+ <header class="titlebar">
419
+ <svg class="mark" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 0l1 2.3a5.8 5.8 0 0 1 1.9.8L13.3 2l.7.7-1.1 2.4c.4.6.6 1.2.8 1.9L16 8l-2.3 1a5.8 5.8 0 0 1-.8 1.9l1.1 2.4-.7.7-2.4-1.1a5.8 5.8 0 0 1-1.9.8L8 16l-1-2.3a5.8 5.8 0 0 1-1.9-.8L2.7 14l-.7-.7 1.1-2.4a5.8 5.8 0 0 1-.8-1.9L0 8l2.3-1c.2-.7.4-1.3.8-1.9L2 2.7l.7-.7 2.4 1.1A5.8 5.8 0 0 1 7 2.3L8 0zm0 5.2A2.8 2.8 0 1 0 8 10.8 2.8 2.8 0 0 0 8 5.2z"/></svg>
420
+ <h1>tmct code explorer</h1>
421
+ <span class="sub">source: <span id="source-name">${escapeHtml(sourceName)}</span></span>
422
+ ${showDesktopLink ? `<span class="sub">Also available as a <a href="${DESKTOP_APP_URL}">desktop app</a>.</span>` : ""}
423
+ <div class="pickers">
424
+ <button id="open-graph">Open graph…</button>
425
+ <button id="open-repo">Open repo…</button>
322
426
  </div>
323
- <div class="dock">
324
- <h2>Chat</h2>
427
+ </header>
428
+ <div class="workbench">
429
+ <aside class="sidebar">
430
+ <section class="panel" data-panel="explorer">
431
+ <h2 class="panel-head">Facts around <span class="focus-line" id="focus-name">—</span></h2>
432
+ <div class="panel-body">
433
+ <ul class="rows" id="ledger"></ul>
434
+ </div>
435
+ </section>
436
+ </aside>
437
+ <section class="editor" data-panel="conversation">
438
+ <h2 class="panel-head">Conversation</h2>
325
439
  <div id="chat-log"></div>
440
+ <div class="suggest">
441
+ <span class="suggest-label">Try asking</span>
442
+ <div class="hints" id="hints"></div>
443
+ </div>
326
444
  <form id="chat-form">
327
- <input id="chat-input" type="text" autocomplete="off" placeholder="ask about this graph…">
445
+ <input id="chat-input" type="text" autocomplete="off" placeholder="ask about this graph, or anything it knows…">
328
446
  <button type="submit">Ask</button>
329
447
  </form>
330
- <div id="dock-note"></div>
331
- </div>
332
- </aside>
333
- </main>
448
+ </section>
449
+ </div>
450
+ <footer class="statusbar">
451
+ <span id="stats"></span>
452
+ <span id="dock-note"></span>
453
+ <span id="seed-status"></span>
454
+ </footer>
455
+ </div>
334
456
  <script>window.__CODE_EXPLORER__ = Object.assign({ payload: ${payloadJson} }, ${dataJson});</script>
335
457
  ${winkLoaderInline ? `<script>\n${embedScriptText(winkLoaderInline)}\n</script>` : ""}
336
458
  ${bundleInline ? `<script>\n${embedScriptText(bundleInline)}\n</script>` : ""}