@polycode-projects/the-mechanical-code-talker 4.1.0 → 4.1.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; indexes a repo on request (tmct index) or reads any producer's graph.",
@@ -10,7 +10,7 @@
10
10
  // spider-fly-viz.mjs's threadCellsForSpiderPlan takes its grid geometry as an
11
11
  // argument: a function with no module-scope references survives `.toString()`
12
12
  // splicing into a page script, and the browser already holds its own copies of
13
- // those primitives on window.tmctSpiderFly.
13
+ // those primitives on window.tmct.
14
14
  //
15
15
  // The resolution CHAIN is derived by OBSERVATION, never by re-implementing
16
16
  // sprite-templates.mjs's specificity order. At each term of the ancestor chain
@@ -42,17 +42,17 @@
42
42
  //
43
43
  // Four further pure helpers are exported for testing but NOT spliced,
44
44
  // because each calls another module's export — the in-page script instead
45
- // reaches through the browser bundle's own `tmctAdventure` global (mirroring
46
- // how the inline script calls `tmctSpiderFly.*` rather than re-importing
45
+ // reaches through the browser bundle's own `tmct` surface (mirroring
46
+ // how the spider-fly page's inline script calls it rather than re-importing
47
47
  // spider-fly-world.mjs): `roomCaptionText` (calls `worldDigestRows`; the
48
- // in-page `captionFor` mirrors it against `tmctAdventure.worldDigestRows`),
48
+ // in-page `captionFor` mirrors it against `tmct.page.worldDigestRows`),
49
49
  // `pillsForRoom` (a thin wrapper over adventure.mjs's own exported
50
50
  // `roomAffordances`, whose header explains why its list can never promise an
51
51
  // action one of take/open/talk/examine would then refuse; the in-page
52
- // `pillsFor` mirrors it against `tmctAdventure.roomAffordances`),
52
+ // `pillsFor` mirrors it against `tmct.page.roomAffordances`),
53
53
  // `goalStatusLines` (calls `foldWorldState` and adventure-autoplay.mjs's own
54
54
  // `exposedFacts`; the in-page `goalStatusLinesFor` mirrors both against the
55
- // `tmctAdventure` global too), and `visitedRoomGraph` (a directions-only
55
+ // `tmct` surface too), and `visitedRoomGraph` (a directions-only
56
56
  // layout of the rooms a session has actually visited, now a thin wrapper over
57
57
  // viz-room-graph.mjs's shared `directedGridLayout` — mud-viz.mjs's own
58
58
  // burrowGraph wrote the same BFS-grid layout a second time under its own
@@ -60,7 +60,7 @@
60
60
  // header for the algorithm and what `hints`/disconnected components mean).
61
61
  // The in-page script never splices `directedGridLayout`/`roomGraphSvg`
62
62
  // either, for the same not-self-contained reason: it calls
63
- // `tmctAdventure.directedGridLayout`/`tmctAdventure.roomGraphSvg` straight
63
+ // `tmct.page.directedGridLayout`/`tmct.page.roomGraphSvg` straight
64
64
  // through the bundle instead of re-implementing the room map a second time.
65
65
  //
66
66
  // The chat dock (chatlog/chatform/chatq/pills, below) mirrors
@@ -479,8 +479,8 @@ export function groundedPlaceholder(actions, fallback) {
479
479
  * row. `[]` on an honest miss (neither lookup returns anything) — never a
480
480
  * fabricated suggestion. NOT .toString()-splice-safe (it calls two other
481
481
  * modules' exports) — the in-page script mirrors this same combination
482
- * against the browser bundle's own `tmctAdventure.relatedForTerm`/
483
- * `tmctAdventure.classAncestorChain`, the same reach-through-the-global
482
+ * against the browser bundle's own `tmct.page.relatedForTerm`/
483
+ * `tmct.page.classAncestorChain`, the same reach-through-the-global
484
484
  * pattern `pillsFor`/`captionFor` already use for their own adventure.mjs
485
485
  * calls (see this module's header). */
486
486
  export function suggestionsForTerm(rows, term) {
@@ -1199,7 +1199,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1199
1199
  let allStoreRows = [];
1200
1200
 
1201
1201
  // ---- persistence — the manor remembers your visit, on this device -------
1202
- // Best-effort IndexedDB (tmctAdventure.openPersistedStore): after any
1202
+ // Best-effort IndexedDB (tmct.page.openPersistedStore): after any
1203
1203
  // state-changing redraw, the whole Backend-B payload plus the visited-room
1204
1204
  // exposure set snapshots under the "adventure" key, debounced. Restored on
1205
1205
  // boot; reset clears it and starts the world over. The home page's preview
@@ -1297,22 +1297,22 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1297
1297
 
1298
1298
  function captionFor(rows, state, here) {
1299
1299
  const hereCased = here.charAt(0).toUpperCase() + here.slice(1);
1300
- const lines = tmctAdventure.worldDigestRows(rows, state)
1300
+ const lines = tmct.page.worldDigestRows(rows, state)
1301
1301
  .filter((row) => row.subject !== "Player" && (row.object === here || row.subject === hereCased))
1302
1302
  .map((row) => row.subject + " " + row.predicate + " " + row.object + ".");
1303
1303
  return lines.length ? lines.join(" ") : "Nothing more about the " + here + " is written down yet.";
1304
1304
  }
1305
1305
 
1306
1306
  // ---- goal status — mirrors adventure-viz.mjs's own goalStatusLines
1307
- // against tmctAdventure.foldWorldState/tmctAdventure.exposedFacts (the
1307
+ // against tmct.page.foldWorldState/tmct.page.exposedFacts (the
1308
1308
  // same mirroring captionFor/pillsFor already do against their own
1309
1309
  // adventure.mjs exports), so a location is never claimed before the room
1310
1310
  // holding it has actually been visited this session.
1311
1311
  function goalStatusLinesFor(rows, state, visitedRoomIds) {
1312
1312
  const ids = Array.from(new Set(rows.filter((r) => r.predicate === "mgx:is-objective" && r.object === "true").map((r) => r.subject)));
1313
1313
  const carriedIds = new Set(carriedItems(rows, state, ACTING_SUBJECT).map((o) => o.subject));
1314
- const exposedRows = tmctAdventure.exposedFacts(rows, visitedRoomIds);
1315
- const exposedState = tmctAdventure.foldWorldState(exposedRows);
1314
+ const exposedRows = tmct.page.exposedFacts(rows, visitedRoomIds);
1315
+ const exposedState = tmct.page.foldWorldState(exposedRows);
1316
1316
  return ids.map((id) => {
1317
1317
  if (carriedIds.has(id)) return { subject: id, status: "carried", text: "carrying the " + id + " \\u2014 the adventure is won." };
1318
1318
  const room = visibleRoomOf(exposedRows, exposedState, id, ACTING_SUBJECT);
@@ -1340,21 +1340,21 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1340
1340
  // cursor per node; play mode's own map stays purely informational.
1341
1341
  // roomGraphSvgFor mirrors the OLD inline roomMapSvg's own board-game sizing
1342
1342
  // (a 64px square cell, 56x26px room footprints) through the shared
1343
- // viz-room-graph.mjs renderer, reached via the tmctAdventure global the
1343
+ // viz-room-graph.mjs renderer, reached via the tmct surface the
1344
1344
  // same way captionFor/pillsFor already reach their own adventure.mjs
1345
1345
  // calls: roomGraphSvg needs escapeHtml and a module-level exit-delta table
1346
1346
  // this splice-safe script carries neither of, so it runs through the
1347
1347
  // bundle rather than being spliced as text (see this page's own module
1348
1348
  // header). visitedRoomGraphFor is the same posture for the layout half.
1349
1349
  function roomGraphSvgFor(graph, clickable) {
1350
- return tmctAdventure.roomGraphSvg(graph, {
1350
+ return tmct.page.roomGraphSvg(graph, {
1351
1351
  cellX: 64, cellY: 64, roomW: 56, roomH: 26,
1352
1352
  clickable, selectedRoomId,
1353
1353
  label: clickable ? "the whole manor \\u2014 click a room to inspect it" : "the rooms visited so far",
1354
1354
  });
1355
1355
  }
1356
1356
  function visitedRoomGraphFor(state, visitedIds) {
1357
- return tmctAdventure.directedGridLayout(state, visitedIds, { actingSubject: ACTING_SUBJECT });
1357
+ return tmct.page.directedGridLayout(state, visitedIds, { actingSubject: ACTING_SUBJECT });
1358
1358
  }
1359
1359
  function renderRoomMap(rows, state, visitedRoomIds) {
1360
1360
  mapWrapEl.innerHTML = roomGraphSvgFor(visitedRoomGraphFor(state, visitedRoomIds), false) || '<span class="empty-note">nowhere yet</span>';
@@ -1417,7 +1417,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1417
1417
  addChatLine("u", esc(line));
1418
1418
  addObjDockLine("u", esc(line));
1419
1419
  return withLock(async () => {
1420
- const result = await session.turn(line);
1420
+ const result = await tmct.turn(line);
1421
1421
  addChatLine("a", esc(result.answer).replace(/\\n/g, "<br>"));
1422
1422
  addObjDockLine("a", esc(result.answer).replace(/\\n/g, "<br>"));
1423
1423
  const snap = await session.snapshot();
@@ -1442,7 +1442,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1442
1442
  // nothing, so it needs no main-transcript echo — it IS the board's own
1443
1443
  // content); pill/typed turns below do echo, since they can change state.
1444
1444
  await withLock(async () => {
1445
- const result = await session.turn("look " + subject);
1445
+ const result = await tmct.turn("look " + subject);
1446
1446
  objLookEl.textContent = result.answer;
1447
1447
  });
1448
1448
  }
@@ -1505,8 +1505,8 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1505
1505
 
1506
1506
  // ---- contextual pills — refreshed every redraw from the CURRENT room's
1507
1507
  // own roomAffordances-derived list (mirroring pillsForRoom against
1508
- // tmctAdventure.roomAffordances, the same way captionFor above mirrors
1509
- // roomCaptionText against tmctAdventure.worldDigestRows), so a pill for a
1508
+ // tmct.page.roomAffordances, the same way captionFor above mirrors
1509
+ // roomCaptionText against tmct.page.worldDigestRows), so a pill for a
1510
1510
  // room the player has left, or an object already taken, never lingers.
1511
1511
  // Clicking one inserts its exact command text into the input and focuses
1512
1512
  // it; it never auto-submits, so free typing still works and a clicked
@@ -1517,7 +1517,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1517
1517
  // ever wants to edit before running, and drawing it in both places would
1518
1518
  // make one control a suggestion and its twin an action.
1519
1519
  function pillsFor(rows, state, here) {
1520
- return tmctAdventure.roomAffordances(rows, state, here);
1520
+ return tmct.page.roomAffordances(rows, state, here);
1521
1521
  }
1522
1522
  function renderPills(rows, state, here) {
1523
1523
  const actions = pillsFor(rows, state, here).filter((a) => a.indexOf("go ") !== 0);
@@ -1560,10 +1560,10 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1560
1560
  const ROOT_FALLBACK_BY_CLASS = { adventurer: "person", person: "person", room: "room" };
1561
1561
  const rootFallbackFor = (cls) => ROOT_FALLBACK_BY_CLASS[cls] || "portable";
1562
1562
  function resolveObjectSprite(rows, s) {
1563
- if (s.subject === "you") return tmctAdventure.resolveSpriteAsset("adventurer", [], [], activeSpriteTemplates, tmctAdventure.SPRITE_REGISTRY, { rootFallback: "person", instanceKey: "you" });
1564
- return tmctAdventure.resolveSpriteAsset(
1563
+ if (s.subject === "you") return tmct.page.resolveSpriteAsset("adventurer", [], [], activeSpriteTemplates, tmct.page.SPRITE_REGISTRY, { rootFallback: "person", instanceKey: "you" });
1564
+ return tmct.page.resolveSpriteAsset(
1565
1565
  s.subject, spriteAncestryRows(rows, s.subject), factsForSubject(rows, s.subject),
1566
- activeSpriteTemplates, tmctAdventure.SPRITE_REGISTRY,
1566
+ activeSpriteTemplates, tmct.page.SPRITE_REGISTRY,
1567
1567
  { rootFallback: rootFallbackFor(s.spriteClass), instanceKey: s.subject },
1568
1568
  );
1569
1569
  }
@@ -1574,9 +1574,9 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1574
1574
  // everything else falls back through room's own ancestor chain to the
1575
1575
  // generic room icon.
1576
1576
  function roomKindIconSvg(rows, here) {
1577
- return tmctAdventure.resolveSpriteAsset(
1577
+ return tmct.page.resolveSpriteAsset(
1578
1578
  here, spriteAncestryRows(rows, here), factsForSubject(rows, here),
1579
- activeSpriteTemplates, tmctAdventure.SPRITE_REGISTRY,
1579
+ activeSpriteTemplates, tmct.page.SPRITE_REGISTRY,
1580
1580
  { rootFallback: "room", instanceKey: "roomkind-" + here },
1581
1581
  );
1582
1582
  }
@@ -1641,7 +1641,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1641
1641
  ticker.pause();
1642
1642
  addChatLine("u", esc(line));
1643
1643
  return withLock(async () => {
1644
- const result = await session.turn(line);
1644
+ const result = await tmct.turn(line);
1645
1645
  addChatLine("a", esc(result.answer).replace(/\\n/g, "<br>"));
1646
1646
  const snap = await session.snapshot();
1647
1647
  redraw(snap);
@@ -1703,9 +1703,9 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1703
1703
  const classes = new Set(["adventurer"]);
1704
1704
  subjects.forEach((s) => { if (s !== ACTING_SUBJECT) classes.add(spriteClassForObject(rows, s)); });
1705
1705
  legendListEl.innerHTML = Array.from(classes).sort().map((cls) => {
1706
- const svg = tmctAdventure.resolveSpriteAsset(
1706
+ const svg = tmct.page.resolveSpriteAsset(
1707
1707
  cls, spriteAncestryRows(rows, cls), factsForSubject(rows, cls),
1708
- activeSpriteTemplates, tmctAdventure.SPRITE_REGISTRY,
1708
+ activeSpriteTemplates, tmct.page.SPRITE_REGISTRY,
1709
1709
  { rootFallback: rootFallbackFor(cls), instanceKey: "legend-" + cls },
1710
1710
  );
1711
1711
  return spriteCardHtml(cls, cls, svg);
@@ -1724,8 +1724,8 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1724
1724
  }
1725
1725
 
1726
1726
  // ---- cursor-driven suggestion pills — the lateral SKOS neighbourhood
1727
- // (tmctAdventure.relatedForTerm) plus the vertical is-a ancestor chain
1728
- // (tmctAdventure.classAncestorChain), mirroring adventure-viz.mjs's own
1727
+ // (tmct.page.relatedForTerm) plus the vertical is-a ancestor chain
1728
+ // (tmct.page.classAncestorChain), mirroring adventure-viz.mjs's own
1729
1729
  // suggestionsForTerm against the browser bundle's global (the same
1730
1730
  // reach-through-the-global pattern captionFor/pillsFor already use).
1731
1731
  // Reads the FULL store (allStoreRows), not worldOnlyRows — a term's
@@ -1734,8 +1734,8 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1734
1734
  function renderSuggestionPills() {
1735
1735
  const term = wordBeforeCursor(editorTextEl.value, editorTextEl.selectionStart);
1736
1736
  if (!term) { editorPillsEl.innerHTML = ""; return; }
1737
- const related = tmctAdventure.relatedForTerm(allStoreRows, term);
1738
- const chain = tmctAdventure.classAncestorChain(term, allStoreRows);
1737
+ const related = tmct.page.relatedForTerm(allStoreRows, term);
1738
+ const chain = tmct.page.classAncestorChain(term, allStoreRows);
1739
1739
  const seen = new Set([term]);
1740
1740
  const out = [];
1741
1741
  const push = (label) => { if (label && !seen.has(label)) { seen.add(label); out.push(label); } };
@@ -1766,7 +1766,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1766
1766
  const snap = await session.snapshot();
1767
1767
  allStoreRows = snap.rows;
1768
1768
  editRows = worldOnlyRows(snap.rows);
1769
- editState = tmctAdventure.foldWorldState(editRows);
1769
+ editState = tmct.page.foldWorldState(editRows);
1770
1770
  if (result.unrecognized.length) {
1771
1771
  const lines = result.unrecognized.map((u) => u.line).join(", ");
1772
1772
  editorStatusEl.className = "edit-status pending";
@@ -1797,7 +1797,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1797
1797
  const snap = await session.snapshot();
1798
1798
  allStoreRows = snap.rows;
1799
1799
  editRows = worldOnlyRows(snap.rows);
1800
- editState = tmctAdventure.foldWorldState(editRows);
1800
+ editState = tmct.page.foldWorldState(editRows);
1801
1801
  editorTextEl.value = renderWorldEditorText(editRows, editState);
1802
1802
  editorStatusEl.className = "edit-status";
1803
1803
  editorStatusEl.textContent = "";
@@ -1818,7 +1818,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1818
1818
 
1819
1819
  async function boot({ fresh = false } = {}) {
1820
1820
  const saved = !fresh && persist ? await persist.load() : null;
1821
- session = await tmctAdventure.createAdventureSession(
1821
+ session = await tmct.open(
1822
1822
  world(),
1823
1823
  saved && saved.payload && saved.payload.memoryPayload
1824
1824
  ? { restoredPayload: saved.payload.memoryPayload, restoredVisitedRoomIds: saved.payload.visitedRoomIds }
@@ -1877,9 +1877,9 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1877
1877
  // to. Without that, one world's snapshot would restore into another's graph.
1878
1878
  let siteVersion = "";
1879
1879
  async function openPersistFor(worldName) {
1880
- if (preview || !tmctAdventure.openPersistedStore) return;
1880
+ if (preview || !tmct.page.openPersistedStore) return;
1881
1881
  if (!siteVersion) siteVersion = await fetchSiteVersion();
1882
- persist = tmctAdventure.openPersistedStore({ storeKey: "adventure", stamp: siteVersion + ":" + worldName });
1882
+ persist = tmct.page.openPersistedStore({ storeKey: "adventure", stamp: siteVersion + ":" + worldName });
1883
1883
  }
1884
1884
 
1885
1885
  if (scenarioSelectEl) {
@@ -2,7 +2,7 @@
2
2
  // document shaped exactly like spider-fly-viz.mjs/adventure-viz.mjs's own
3
3
  // page-builders — one inlined <style> importing viz-theme.mjs's shared
4
4
  // tokens, behaviour as an inlined IIFE — running the full chat engine
5
- // (chat-browser.bundle.js's globalThis.tmctChat, chat-seed.json,
5
+ // (chat-browser.bundle.js's globalThis.tmct, chat-seed.json,
6
6
  // public/reference-pack/) by same-origin relative paths. The same
7
7
  // relationship spider-fly-viz.mjs's own inlined chat dock has with
8
8
  // createSpiderFlySession: both call the shared session.turn(line), neither
@@ -539,7 +539,7 @@ ${THEME_TOKENS_CSS}
539
539
 
540
540
  /* the provenance stats panel: what this session's memory holds, docked to
541
541
  the right of the chat column (a real layout column, not an overlay) —
542
- re-rendered after boot and after every turn from window.tmctChat's own
542
+ re-rendered after boot and after every turn from window.tmct's own
543
543
  memoryStats(), never a second provenance computation. */
544
544
  .statsPanel { flex: 0 0 300px; max-width: 300px; overflow-y: auto; border-left: 1px solid var(--line); padding: 1.1rem 1.2rem 1.6rem; font-family: ${MONO_STACK}; font-size: .74rem; line-height: 1.55; }
545
545
  .statsPanel h2 { font-size: .66rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); margin: 1.3rem 0 .5rem; }
@@ -554,7 +554,7 @@ ${THEME_TOKENS_CSS}
554
554
  .statsPanel .persist-note { color: var(--muted); font-size: .64rem; margin: .4rem 0 0; }
555
555
 
556
556
  /* the "researched this session" panel: its own section under the memory
557
- stats, filled from window.tmctChat.researchedFactRows() plus each
557
+ stats, filled from window.tmct.page.researchedFactRows() plus each
558
558
  settled research turn's own answer text — the passage tmct actually
559
559
  read, the article it read it from, and the facts that passage grounded.
560
560
  A sibling section, not folded into #statsPanelStats — that div's own
@@ -1124,7 +1124,7 @@ ${THEME_TOKENS_CSS}
1124
1124
  })(),
1125
1125
  stallGuard(),
1126
1126
  ]);
1127
- window.tmctChat.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
1127
+ window.tmct.page.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
1128
1128
  winkStatus = "loaded";
1129
1129
  } catch (err) {
1130
1130
  winkStatus = "unavailable";
@@ -1170,8 +1170,8 @@ ${THEME_TOKENS_CSS}
1170
1170
  if (!seedPayload) return null;
1171
1171
  try { return structuredClone(seedPayload); } catch { return JSON.parse(JSON.stringify(seedPayload)); }
1172
1172
  };
1173
- function newSession() {
1174
- return window.tmctChat.createChatSession({
1173
+ async function newSession() {
1174
+ return window.tmct.open({
1175
1175
  seedPayload: cloneSeed(),
1176
1176
  vocabSeeded: Boolean(seedPayload),
1177
1177
  liveReference: liveReferenceForMode(checkedWikiMode()),
@@ -1205,7 +1205,7 @@ ${THEME_TOKENS_CSS}
1205
1205
  };
1206
1206
 
1207
1207
  // ---- persistence: what you taught it survives a reload, on this device -
1208
- // Best-effort IndexedDB (window.tmctChat.openPersistedStore): the whole
1208
+ // Best-effort IndexedDB (window.tmct.page.openPersistedStore): the whole
1209
1209
  // Backend-B payload snapshots after each teach turn, debounced so a burst
1210
1210
  // of teaching costs one multi-MB write, not one per fact. The stamp ties a
1211
1211
  // snapshot to this deploy (site version) AND this seed (its fact count and
@@ -1246,14 +1246,14 @@ ${THEME_TOKENS_CSS}
1246
1246
  // it would keep merging peers' facts into a store nothing reads any more.
1247
1247
  // Rejoining is a fresh invite, which is what a dropped node needs anyway.
1248
1248
  dropRoom();
1249
- window.tmctChatSession = newSession();
1250
- const stats = await window.tmctChat.memoryStats(window.tmctChatSession.memoryDir);
1249
+ window.tmctChatSession = await newSession();
1250
+ const stats = await window.tmct.page.memoryStats(window.tmctChatSession.memoryDir);
1251
1251
  addSystemLine("forgot everything taught on this device \\u2014 back to the fresh seed (" + statsSummaryLine(stats, bandLabelFor) + ").");
1252
1252
  await renderStatsPanel(stats);
1253
1253
  }
1254
1254
 
1255
1255
  // ---- memory stats: the boot message's own numbers, and the docked panel -
1256
- // Both read window.tmctChat.memoryStats(memoryDir) (chat-browser-entry.mjs)
1256
+ // Both read window.tmct.page.memoryStats(memoryDir) (chat-browser-entry.mjs)
1257
1257
  // — one computation, reused, so the boot line and the panel can never
1258
1258
  // disagree with each other about what this session's memory holds.
1259
1259
  // bandLabelFor/statsSummaryLine/renderStatsPanelInto are the shared
@@ -1266,8 +1266,8 @@ ${THEME_TOKENS_CSS}
1266
1266
  * rather than blanking it. */
1267
1267
  async function renderStatsPanel(stats) {
1268
1268
  if (!stats) {
1269
- if (!window.tmctChatSession || !window.tmctChat.memoryStats) return;
1270
- try { stats = await window.tmctChat.memoryStats(window.tmctChatSession.memoryDir); }
1269
+ if (!window.tmctChatSession || !window.tmct.page.memoryStats) return;
1270
+ try { stats = await window.tmct.page.memoryStats(window.tmctChatSession.memoryDir); }
1271
1271
  catch { return; }
1272
1272
  }
1273
1273
  factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
@@ -1282,7 +1282,7 @@ ${THEME_TOKENS_CSS}
1282
1282
  // and grounded so far — each entry pairs the passage a settled research
1283
1283
  // turn's own answer cites (parseResearchAnswer, off the SAME "(source:
1284
1284
  // research article ...)" text the chat bubble already shows) with the real
1285
- // facts that turn stored, read back through window.tmctChat.
1285
+ // facts that turn stored, read back through window.tmct.
1286
1286
  // researchedFactRows(memoryDir) rather than re-deriving them from the
1287
1287
  // answer text — the citation names WHERE tmct read, the fact rows name
1288
1288
  // WHAT it kept, and this panel never invents either from the other.
@@ -1348,9 +1348,9 @@ ${THEME_TOKENS_CSS}
1348
1348
  * own entry — the passage was still read, even where nothing new stuck. */
1349
1349
  async function noteResearchLearned(result) {
1350
1350
  if (result.research === undefined || !result.record || result.record.miss) return;
1351
- if (!window.tmctChat.researchedFactRows || !window.tmctChatSession) return;
1351
+ if (!window.tmct.page.researchedFactRows || !window.tmctChatSession) return;
1352
1352
  let rows;
1353
- try { rows = await window.tmctChat.researchedFactRows(window.tmctChatSession.memoryDir); }
1353
+ try { rows = await window.tmct.page.researchedFactRows(window.tmctChatSession.memoryDir); }
1354
1354
  catch { return; }
1355
1355
  const newFacts = [];
1356
1356
  for (const row of rows) {
@@ -1446,7 +1446,7 @@ ${THEME_TOKENS_CSS}
1446
1446
  setBusy(true);
1447
1447
  let result = null;
1448
1448
  try {
1449
- result = await window.tmctChatSession.turn(q);
1449
+ result = await window.tmct.turn(q);
1450
1450
  settleAssistantBubble(pendingRow, result.answer, result.record);
1451
1451
  // Persist on ANY store write, not just a teach turn: a learn-on-miss
1452
1452
  // load (a child pack, a reference or live-Wikipedia article, a
@@ -1587,10 +1587,10 @@ ${THEME_TOKENS_CSS}
1587
1587
  // CLI paths emit), so what you taught leaves in the standard shape.
1588
1588
  el("exportFacts").addEventListener("click", async () => {
1589
1589
  const session = window.tmctChatSession;
1590
- if (!session || !window.tmctChat.exportFactsJsonl) return;
1590
+ if (!session || !window.tmct.page.exportFactsJsonl) return;
1591
1591
  let jsonl;
1592
1592
  try {
1593
- jsonl = await window.tmctChat.exportFactsJsonl(session.memoryDir);
1593
+ jsonl = await window.tmct.page.exportFactsJsonl(session.memoryDir);
1594
1594
  } catch (err) {
1595
1595
  statusEl.textContent = "couldn't export the facts (" + (err && err.message ? err.message : err) + ")";
1596
1596
  return;
@@ -1607,7 +1607,7 @@ ${THEME_TOKENS_CSS}
1607
1607
  });
1608
1608
 
1609
1609
  // "ingest file" feeds a whole .txt/.md through the SAME session, one
1610
- // sentence at a time (window.tmctChat.splitSentences, then session.turn),
1610
+ // sentence at a time (window.tmct.page.splitSentences, then session.turn),
1611
1611
  // teaching every sentence the recognizer grounds and skipping the rest
1612
1612
  // honestly — the same pipeline the ingest page runs, reaching the chat's own
1613
1613
  // memory so the taught facts answer questions straight away.
@@ -1616,7 +1616,7 @@ ${THEME_TOKENS_CSS}
1616
1616
  const file = e.target.files && e.target.files[0];
1617
1617
  e.target.value = "";
1618
1618
  const session = window.tmctChatSession;
1619
- if (!file || busy || !session || !window.tmctChat.splitSentences) return;
1619
+ if (!file || busy || !session || !window.tmct.page.splitSentences) return;
1620
1620
  let text;
1621
1621
  try {
1622
1622
  text = await file.text();
@@ -1624,14 +1624,14 @@ ${THEME_TOKENS_CSS}
1624
1624
  addSystemLine("couldn't read that file (" + (err && err.message ? err.message : err) + ").");
1625
1625
  return;
1626
1626
  }
1627
- const sentences = window.tmctChat.splitSentences(text);
1627
+ const sentences = window.tmct.page.splitSentences(text);
1628
1628
  if (!sentences.length) { addSystemLine("nothing to ingest in " + file.name + "."); return; }
1629
1629
  setBusy(true);
1630
1630
  statusEl.textContent = "ingesting " + file.name + "\\u2026";
1631
1631
  let grounded = 0;
1632
1632
  try {
1633
1633
  for (const sentence of sentences) {
1634
- const result = await session.turn(sentence);
1634
+ const result = await tmct.turn(sentence);
1635
1635
  if (result.record && result.record.via === "assert" && !result.record.miss) grounded += 1;
1636
1636
  }
1637
1637
  } catch (err) {
@@ -2221,16 +2221,16 @@ ${THEME_TOKENS_CSS}
2221
2221
  }
2222
2222
 
2223
2223
  async function boot() {
2224
- if (!window.tmctChat) {
2224
+ if (!window.tmct) {
2225
2225
  statusEl.textContent = "the chat engine didn't load \\u2014 this page needs its build step (npm run demo:build)";
2226
2226
  inputEl.placeholder = "chat engine unavailable";
2227
2227
  return;
2228
2228
  }
2229
2229
  await Promise.all([fetchSeed(), tryLoadWink(), fetchSiteVersion().then((v) => { siteVersion = v; })]);
2230
2230
  progressActive = false;
2231
- window.tmctChat.registerReferencePackProvider(fetchPackProvider);
2232
- if (window.tmctChat.openPersistedStore) {
2233
- persist = window.tmctChat.openPersistedStore({ storeKey: "chat", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
2231
+ window.tmct.page.registerReferencePackProvider(fetchPackProvider);
2232
+ if (window.tmct.page.openPersistedStore) {
2233
+ persist = window.tmct.page.openPersistedStore({ storeKey: "chat", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
2234
2234
  }
2235
2235
  const savedRecord = persist ? await persist.load() : null;
2236
2236
  const initialMode = readWikiMode();
@@ -2239,7 +2239,7 @@ ${THEME_TOKENS_CSS}
2239
2239
  synthSliderEl.value = String(readSynthBudget());
2240
2240
  synthValueEl.textContent = synthSliderEl.value;
2241
2241
  if (savedRecord && savedRecord.payload) {
2242
- window.tmctChatSession = window.tmctChat.createChatSession({
2242
+ window.tmctChatSession = await window.tmct.open({
2243
2243
  seedPayload: savedRecord.payload,
2244
2244
  vocabSeeded: true,
2245
2245
  liveReference: liveReferenceForMode(initialMode),
@@ -2248,9 +2248,9 @@ ${THEME_TOKENS_CSS}
2248
2248
  digestStructures: DIGEST_STRUCTURES,
2249
2249
  });
2250
2250
  } else {
2251
- window.tmctChatSession = newSession();
2251
+ window.tmctChatSession = await newSession();
2252
2252
  }
2253
- const stats = await window.tmctChat.memoryStats(window.tmctChatSession.memoryDir);
2253
+ const stats = await window.tmct.page.memoryStats(window.tmctChatSession.memoryDir);
2254
2254
  if (savedRecord) restoredCount = stats.taught.length;
2255
2255
  const restoredNote = savedRecord
2256
2256
  ? " Restored " + restoredCount + " taught fact" + (restoredCount === 1 ? "" : "s")
@@ -2264,14 +2264,14 @@ ${THEME_TOKENS_CSS}
2264
2264
  // seen-set from them so a later research turn only reports what's
2265
2265
  // actually new, without fabricating passages for a visit this page
2266
2266
  // was never open to read.
2267
- if (window.tmctChat.researchedFactRows) {
2267
+ if (window.tmct.page.researchedFactRows) {
2268
2268
  try {
2269
- const existingResearch = await window.tmctChat.researchedFactRows(window.tmctChatSession.memoryDir);
2269
+ const existingResearch = await window.tmct.page.researchedFactRows(window.tmctChatSession.memoryDir);
2270
2270
  for (const row of existingResearch) researchedFactKeysSeen.add(row.subject + "|" + row.predicate + "|" + row.object);
2271
2271
  } catch { /* best-effort seeding only — a fresh session has none to seed */ }
2272
2272
  }
2273
2273
  renderResearchedPanel();
2274
- inputEl.placeholder = seedPayload ? 'try "what is a dog" or "list facts"' : window.tmctChat.vocabExampleHint(false);
2274
+ inputEl.placeholder = seedPayload ? 'try "what is a dog" or "list facts"' : window.tmct.page.vocabExampleHint(false);
2275
2275
  renderStatus();
2276
2276
  setBusy(false);
2277
2277
  inputEl.focus();
@@ -2127,7 +2127,7 @@ export { moduleCountOf };
2127
2127
  /** A KNOWN-empty code graph: a loaded graph object with 0 modules. A null graph
2128
2128
  * (a bare runTurn that wasn't handed one) is "unknown", NOT empty — the empty
2129
2129
  * orientation/greeting only fires when we actually hold an empty graph. */
2130
- const noCodeGraph = (graph) => !!graph && moduleCountOf(graph) === 0;
2130
+ export const noCodeGraph = (graph) => !!graph && moduleCountOf(graph) === 0;
2131
2131
 
2132
2132
  /** LIVE orientation examples: the example queries on the orientation card name
2133
2133
  * entities from the LOADED graph — the sorted-first Module label and the
@@ -110,7 +110,7 @@ export function computeCodeExplorerData(payload, opts = {}) {
110
110
  const CLIENT_JS = String.raw`
111
111
  (function () {
112
112
  var DATA = window.__CODE_EXPLORER__;
113
- var api = window.tmctCodeExplorer || null;
113
+ var api = window.tmct ? window.tmct.page : null;
114
114
  var els = {
115
115
  focus: document.getElementById("focus-name"),
116
116
  ledger: document.getElementById("ledger"),
@@ -2,7 +2,7 @@
2
2
  // self-contained document shaped exactly like chat-page-viz.mjs's own
3
3
  // page-builder — one inlined <style> importing viz-theme.mjs's shared tokens,
4
4
  // behaviour as an inlined IIFE — running the ingest engine
5
- // (ingest-browser.bundle.js's globalThis.tmctIngest) by same-origin relative
5
+ // (ingest-browser.bundle.js's globalThis.tmct) by same-origin relative
6
6
  // paths.
7
7
  //
8
8
  // The page's own chrome is a two-pane translate-tool layout: mode pills
@@ -133,7 +133,7 @@ ${THEME_TOKENS_CSS}
133
133
  the right of the ingest column (a real layout column, not an overlay) —
134
134
  the same class names and breakpoint chat-page-viz.mjs's own docked panel
135
135
  uses, re-rendered after boot and after every ingest from
136
- window.tmctIngest's own memoryStats(). */
136
+ window.tmct's own memoryStats(). */
137
137
  .statsPanel { flex: 0 0 300px; max-width: 300px; overflow-y: auto; border-left: 1px solid var(--line); padding: 1.1rem 1.2rem 1.6rem; font-family: ${MONO_STACK}; font-size: .74rem; line-height: 1.55; display: flex; flex-direction: column; }
138
138
  .statsPanel h2 { font-size: .66rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); margin: 1.3rem 0 .5rem; }
139
139
  .statsPanel h2:first-child { margin-top: 0; }
@@ -359,8 +359,8 @@ ${THEME_TOKENS_CSS}
359
359
  // ---- memory stats: the docked panel, same convention as chat.html --------
360
360
  async function renderStatsPanel(stats) {
361
361
  if (!stats) {
362
- if (!session || !window.tmctIngest.memoryStats) return;
363
- try { stats = await window.tmctIngest.memoryStats(session.memoryDir); }
362
+ if (!session || !window.tmct.page.memoryStats) return;
363
+ try { stats = await window.tmct.page.memoryStats(session.memoryDir); }
364
364
  catch { return; }
365
365
  }
366
366
  factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
@@ -415,13 +415,13 @@ ${THEME_TOKENS_CSS}
415
415
  }
416
416
  }
417
417
  const cloneMemoryPayload = ${cloneMemoryPayload.toString()};
418
- function newSession() {
419
- return window.tmctIngest.createIngestSession({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload) });
418
+ async function newSession() {
419
+ return window.tmct.open({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload) });
420
420
  }
421
421
 
422
422
  // ---- engine boot ---------------------------------------------------------
423
423
  const loadWinkVendor = ${loadWinkVendor.toString()};
424
- const tryLoadWink = loadWinkVendor({ register: (factory) => window.tmctIngest.registerWinkModel(factory) });
424
+ const tryLoadWink = loadWinkVendor({ register: (factory) => window.tmct.page.registerWinkModel(factory) });
425
425
 
426
426
  // The deploy's own version, read off the service worker file the build
427
427
  // already stamps — the only same-origin place the number exists at runtime
@@ -467,10 +467,10 @@ ${THEME_TOKENS_CSS}
467
467
  clearTimeout(saveTimer);
468
468
  saveTimer = null;
469
469
  if (persist) await persist.clear();
470
- session = newSession();
470
+ session = await newSession();
471
471
  clearFactsPane();
472
472
  updateIngestEnabled();
473
- const stats = await window.tmctIngest.memoryStats(session.memoryDir);
473
+ const stats = await window.tmct.page.memoryStats(session.memoryDir);
474
474
  statusEl.textContent = "forgot everything taught on this device. Back to the fresh seed (" + statsSummaryLine(stats, bandLabelFor) + ").";
475
475
  await renderStatsPanel(stats);
476
476
  }
@@ -486,9 +486,9 @@ ${THEME_TOKENS_CSS}
486
486
  await fetchSeedIfWanted();
487
487
  clearTimeout(saveTimer);
488
488
  saveTimer = null;
489
- session = newSession();
489
+ session = await newSession();
490
490
  clearFactsPane();
491
- const stats = await window.tmctIngest.memoryStats(session.memoryDir);
491
+ const stats = await window.tmct.page.memoryStats(session.memoryDir);
492
492
  statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + ". Ready.";
493
493
  await renderStatsPanel(stats);
494
494
  updateIngestEnabled();
@@ -496,7 +496,7 @@ ${THEME_TOKENS_CSS}
496
496
  });
497
497
 
498
498
  async function boot() {
499
- if (!window.tmctIngest) {
499
+ if (!window.tmct) {
500
500
  statusEl.textContent = "the ingest engine didn't load. This page needs its build step (npm run demo:build)";
501
501
  return;
502
502
  }
@@ -507,16 +507,16 @@ ${THEME_TOKENS_CSS}
507
507
  fetchSiteVersion().then((v) => { siteVersion = v; }),
508
508
  ]);
509
509
  progressActive = false;
510
- if (window.tmctIngest.openPersistedStore) {
511
- persist = window.tmctIngest.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
510
+ if (window.tmct.page.openPersistedStore) {
511
+ persist = window.tmct.page.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
512
512
  }
513
513
  const savedRecord = persist ? await persist.load() : null;
514
514
  session = savedRecord && savedRecord.payload
515
- ? window.tmctIngest.createIngestSession({ seedPayload: savedRecord.payload, vocabSeeded: true })
516
- : newSession();
515
+ ? await window.tmct.open({ seedPayload: savedRecord.payload, vocabSeeded: true })
516
+ : await newSession();
517
517
  setMode(false);
518
518
  updateIngestEnabled();
519
- const stats = await window.tmctIngest.memoryStats(session.memoryDir);
519
+ const stats = await window.tmct.page.memoryStats(session.memoryDir);
520
520
  const winkPart = winkStatus === "loaded"
521
521
  ? "wink-nlp: loaded"
522
522
  : "wink-nlp unavailable. The recognizer can't split sentences without it";
@@ -569,10 +569,10 @@ ${THEME_TOKENS_CSS}
569
569
 
570
570
  // ---- download the canonical facts as JSONL -------------------------------
571
571
  downloadBtn.addEventListener("click", async () => {
572
- if (!session || !window.tmctIngest.exportFactsJsonl) return;
572
+ if (!session || !window.tmct.page.exportFactsJsonl) return;
573
573
  let jsonl;
574
574
  try {
575
- jsonl = await window.tmctIngest.exportFactsJsonl(session.memoryDir);
575
+ jsonl = await window.tmct.page.exportFactsJsonl(session.memoryDir);
576
576
  } catch (err) {
577
577
  statusEl.textContent = "couldn't build the download (" + (err && err.message ? err.message : err) + ")";
578
578
  return;