@polycode-projects/the-mechanical-code-talker 4.0.0 → 4.1.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 (108) hide show
  1. package/README.md +178 -9
  2. package/corpus/reference/index.json.gz +0 -0
  3. package/corpus/reference/manifest.json +8 -8
  4. package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
  5. package/corpus/sprites/src/sprite-facts.jsonl +401 -0
  6. package/corpus/worlds/index.json.gz +0 -0
  7. package/corpus/worlds/manifest.json +49 -9
  8. package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
  9. package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
  10. package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
  11. package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
  12. package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
  13. package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
  14. package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
  15. package/corpus/worlds/src/mud-hollow.jsonl +82 -0
  16. package/corpus/worlds/src/mud-warren.jsonl +124 -0
  17. package/corpus/worlds/src/spider-fly.jsonl +1 -1
  18. package/data/sprites/animal-icon.toml +11 -5
  19. package/data/sprites/book-icon.toml +9 -5
  20. package/data/sprites/cabinet-icon.toml +10 -5
  21. package/data/sprites/cellar-icon.toml +16 -6
  22. package/data/sprites/container-icon.toml +7 -3
  23. package/data/sprites/desk-icon.toml +8 -5
  24. package/data/sprites/dog-icon.toml +8 -5
  25. package/data/sprites/dog-with-colour-icon.toml +13 -12
  26. package/data/sprites/drawing-room-icon.toml +14 -5
  27. package/data/sprites/egg-icon.toml +6 -3
  28. package/data/sprites/fly-icon.toml +10 -5
  29. package/data/sprites/furniture-icon.toml +5 -3
  30. package/data/sprites/garden-icon.toml +10 -3
  31. package/data/sprites/key-icon.toml +3 -1
  32. package/data/sprites/kitchen-icon.toml +15 -6
  33. package/data/sprites/lamp-icon.toml +9 -4
  34. package/data/sprites/letter-icon.toml +7 -4
  35. package/data/sprites/library-icon.toml +14 -3
  36. package/data/sprites/pan-icon.toml +7 -3
  37. package/data/sprites/person-icon.toml +6 -2
  38. package/data/sprites/poodle-icon.toml +1 -1
  39. package/data/sprites/portable-icon.toml +6 -4
  40. package/data/sprites/portrait-icon.toml +7 -4
  41. package/data/sprites/room-icon.toml +11 -2
  42. package/data/sprites/spider-icon.toml +9 -3
  43. package/data/sprites/study-icon.toml +10 -2
  44. package/package.json +5 -4
  45. package/src/adapters/memory/core.mjs +20 -0
  46. package/src/domain/ask-vocab.mjs +71 -0
  47. package/src/domain/ask.mjs +168 -0
  48. package/src/domain/game-config.mjs +11 -0
  49. package/src/domain/grammar/ace.mjs +40 -4
  50. package/src/domain/grammar/lexicon-core.json +2 -1
  51. package/src/domain/grammar/lexicon.mjs +18 -0
  52. package/src/domain/mud-facts.mjs +15 -0
  53. package/src/domain/reference-pack.mjs +31 -7
  54. package/src/domain/router/drive.mjs +35 -9
  55. package/src/domain/router/registry.mjs +24 -4
  56. package/src/domain/router/resolver.mjs +102 -40
  57. package/src/domain/scene-compose.mjs +117 -0
  58. package/src/domain/spider-fly-world.mjs +37 -1
  59. package/src/domain/sprite-facts.mjs +0 -0
  60. package/src/domain/sprite-request.mjs +156 -0
  61. package/src/domain/sprite-templates.mjs +169 -20
  62. package/src/services/adventure-editor.mjs +8 -14
  63. package/src/services/adventure-viz.mjs +209 -157
  64. package/src/services/adventure.mjs +526 -391
  65. package/src/services/chat-page-viz.mjs +69 -25
  66. package/src/services/chat.mjs +200 -51
  67. package/src/services/code-explorer-viz.mjs +102 -62
  68. package/src/services/extract-facts.mjs +4 -7
  69. package/src/services/ingest-viz.mjs +68 -82
  70. package/src/services/ledger-viz.mjs +136 -67
  71. package/src/services/memory-panel-viz.mjs +62 -0
  72. package/src/services/mud-editor.mjs +10 -15
  73. package/src/services/mud-turn.mjs +6 -6
  74. package/src/services/mud-viz.mjs +1016 -208
  75. package/src/services/p2p-room.mjs +90 -23
  76. package/src/services/plan-pddl.mjs +3 -1
  77. package/src/services/plan-viz.mjs +123 -64
  78. package/src/services/research-viz.mjs +160 -108
  79. package/src/services/spider-fly-turn.mjs +15 -23
  80. package/src/services/spider-fly-viz.mjs +146 -161
  81. package/src/services/spider-fly.mjs +69 -11
  82. package/src/services/sprite-catalog-viz.mjs +414 -240
  83. package/src/services/viz-boot.mjs +71 -0
  84. package/src/services/viz-room-graph.mjs +203 -0
  85. package/src/services/viz-theme.mjs +90 -1
  86. package/src/services/viz-ticker.mjs +22 -0
  87. package/src/surfaces/web/adventure-browser-entry.mjs +49 -33
  88. package/src/surfaces/web/chat-browser-entry.mjs +30 -105
  89. package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
  90. package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
  91. package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
  92. package/src/surfaces/web/memory-ask-browser.bundle.js +127 -124
  93. package/src/surfaces/web/memory-stats.mjs +11 -0
  94. package/src/surfaces/web/mud-browser-entry.mjs +71 -29
  95. package/src/surfaces/web/plan-browser-entry.mjs +22 -40
  96. package/src/surfaces/web/research-browser-entry.mjs +26 -41
  97. package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
  98. package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
  99. package/src/surfaces/web/turn-session.mjs +120 -0
  100. package/src/tools/definitions.mjs +30 -0
  101. package/src/tools/handlers/index.mjs +6 -3
  102. package/src/tools/handlers/kit.mjs +19 -2
  103. package/src/tools/handlers/tmct-ask.mjs +11 -6
  104. package/src/tools/handlers/tmct-ingest.mjs +5 -1
  105. package/src/tools/handlers/tmct-related.mjs +4 -4
  106. package/src/tools/handlers/tmct-sprite.mjs +147 -0
  107. package/src/tools/memory-fallthrough.mjs +9 -2
  108. package/src/tools/server.mjs +25 -1
@@ -26,50 +26,24 @@
26
26
  // input. scripts/build-demo-site.mjs calls it directly and writes the result
27
27
  // to public/ingest.html, after ingest-browser.bundle.js already exists.
28
28
  import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
29
- import { bandLabelFor, statsSummaryLine, fetchWithProgress, renderStatsPanelInto } from "./memory-panel-viz.mjs";
29
+ import {
30
+ bandLabelFor,
31
+ statsSummaryLine,
32
+ clearSiteAssetCaches,
33
+ fetchWithProgress,
34
+ renderStatsPanelInto,
35
+ loadProgressLine,
36
+ factTripleParts,
37
+ } from "./memory-panel-viz.mjs";
38
+ import { loadWinkVendor } from "./viz-boot.mjs";
39
+ import { cloneMemoryPayload } from "../adapters/memory/core.mjs";
30
40
 
31
41
  const DEFAULT_TITLE = "the-mechanical-code-talker — ingest";
32
42
 
33
- /** One grounded fact as a canonical triple line — subject, predicate, object,
34
- * each in its own cell so the pane can align them. Pure and
35
- * `.toString()`-splice safe (no outer refs): the inline script splices this
36
- * in and calls it per row. */
37
- export function factTripleParts(fact) {
38
- return {
39
- subject: String((fact && fact.subject) || ""),
40
- predicate: String((fact && fact.predicate) || ""),
41
- object: String((fact && fact.object) || ""),
42
- provenance: String((fact && fact.provenance) || ""),
43
- };
44
- }
45
-
46
- /** The boot statusline while the big assets stream in — the same aggregator
47
- * chat-page-viz.mjs's own loadProgressLine is (kept as this page's own copy
48
- * rather than a shared import — the two pages' boot lines diverge slightly
49
- * and neither is a collaborator the other calls). `parts` is an array of
50
- * { loaded, total } byte counts (total 0 when the response carried no
51
- * Content-Length); with no usable total the line shows loaded bytes alone
52
- * rather than inventing a denominator. Self-contained, `.toString()`-splice
53
- * safe. */
54
- export function loadProgressLine(parts) {
55
- const mb = (n) => (n / 1048576).toFixed(1);
56
- let loaded = 0;
57
- let total = 0;
58
- let totalKnown = true;
59
- for (const p of parts || []) {
60
- loaded += (p && p.loaded) || 0;
61
- if (p && p.total > 0) total += p.total;
62
- else totalKnown = false;
63
- }
64
- return totalKnown && total > 0
65
- ? "loading the engine… " + mb(loaded) + " MB / " + mb(total) + " MB"
66
- : "loading the engine… " + mb(loaded) + " MB";
67
- }
68
-
69
43
  /** The self-contained ingest page. Pure — the same output for the same
70
44
  * `title` every time; every piece of state (the session, each grounded fact)
71
45
  * is computed live in the browser once the sibling ingest bundle loads. */
72
- export function renderIngestHtml({ title = DEFAULT_TITLE } = {}) {
46
+ export function renderIngestHtml({ title = DEFAULT_TITLE, seedStamp = "" } = {}) {
73
47
  return `<!doctype html>
74
48
  <html lang="en">
75
49
  <head>
@@ -83,8 +57,8 @@ export function renderIngestHtml({ title = DEFAULT_TITLE } = {}) {
83
57
  The wink lemma/POS tier loads from ./vendor/wink.js — the site's own shared
84
58
  first-party bundle (built by scripts/build-wink-vendor.mjs), one cached copy
85
59
  for every page, no CDN. The ingest engine needs wink to split sentences and
86
- to parse each teach frame; a failed load degrades to the honest "nothing
87
- recognized" answer, never an error.
60
+ to parse each teach frame. A failed load still answers "nothing
61
+ recognized" instead of erroring.
88
62
  -->
89
63
  <style>
90
64
  ${THEME_TOKENS_CSS}
@@ -103,6 +77,13 @@ ${THEME_TOKENS_CSS}
103
77
  .eyebrow { font-family: ${MONO_STACK}; font-size: .78rem; letter-spacing: .08em; color: var(--muted); }
104
78
  .subtitle { font-size: .82rem; color: var(--muted); }
105
79
 
80
+ /* the live memory count, in the topbar rather than the status line: it is
81
+ the one number that says what this page's memory actually holds, and the
82
+ status line beside the buttons is where a wrong one goes unnoticed. */
83
+ .topbar-right { display: flex; align-items: center; gap: .7rem; }
84
+ .fact-pill { display: inline-flex; align-items: baseline; gap: .34rem; font-family: ${MONO_STACK}; font-size: .66rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--corpus-t1); border-radius: 99px; padding: .2rem .8rem; background: var(--corpus-soft); white-space: nowrap; }
85
+ .fact-pill .fact-pill-value { font-size: .94rem; letter-spacing: 0; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
86
+
106
87
  /* mode pills — Text | Document — the translate-tool idiom: two segments in
107
88
  one rounded track, the active one filled. */
108
89
  .pills { display: inline-flex; border: 1px solid var(--line); border-radius: 99px; overflow: hidden; font-family: ${MONO_STACK}; font-size: .72rem; }
@@ -140,7 +121,7 @@ ${THEME_TOKENS_CSS}
140
121
  #facts .fact .pred { color: var(--corpus); white-space: nowrap; }
141
122
  #facts .fact .obj { color: var(--ink); word-break: break-word; }
142
123
  #facts .fact .prov { grid-column: 1 / -1; color: var(--muted); font-size: .66rem; }
143
- #facts .empty { color: var(--muted); padding: .5rem 0; }
124
+ #facts .empty { color: var(--muted); text-align: center; max-width: 24rem; line-height: 1.6; margin: 3rem auto 0; }
144
125
 
145
126
  .actions { flex: 0 0 auto; display: flex; align-items: center; gap: .6rem; padding: .6rem 1.1rem; border-top: 1px solid var(--line); flex-wrap: wrap; }
146
127
  .actions .btn { font-family: ${MONO_STACK}; font-size: .74rem; color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: .35rem .9rem; background: var(--card); }
@@ -153,7 +134,7 @@ ${THEME_TOKENS_CSS}
153
134
  the same class names and breakpoint chat-page-viz.mjs's own docked panel
154
135
  uses, re-rendered after boot and after every ingest from
155
136
  window.tmctIngest's own memoryStats(). */
156
- .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; }
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; }
157
138
  .statsPanel h2 { font-size: .66rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); margin: 1.3rem 0 .5rem; }
158
139
  .statsPanel h2:first-child { margin-top: 0; }
159
140
  .statsPanel .band-row { display: flex; justify-content: space-between; gap: .6rem; margin: 0; padding: .12rem 0; }
@@ -161,9 +142,12 @@ ${THEME_TOKENS_CSS}
161
142
  .statsPanel .taught-item { margin: 0 0 .7rem; }
162
143
  .statsPanel .taught-tag { display: block; color: var(--muted); font-size: .66rem; margin-top: .15rem; word-break: break-word; }
163
144
  .statsPanel .empty { color: var(--muted); margin: 0; }
164
- .statsPanel .forget-btn { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); border: 1px solid var(--line); border-radius: 4px; padding: .18rem .55rem; margin-top: 1.1rem; background: var(--card); }
145
+ /* The reset action anchors to the bottom of the rail via the auto margin
146
+ on a tall viewport with little taught yet, that reads as a pinned
147
+ footer control instead of leaving a trailing gap under it. */
148
+ .statsPanel .forget-btn { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); border: 1px solid var(--line); border-radius: 4px; padding: .18rem .55rem; margin-top: auto; background: var(--card); flex: none; }
165
149
  .statsPanel .forget-btn:hover { color: var(--ink); }
166
- .statsPanel .persist-note { color: var(--muted); font-size: .64rem; margin: .4rem 0 0; }
150
+ .statsPanel .persist-note { color: var(--muted); font-size: .64rem; margin: .4rem 0 0; flex: none; }
167
151
 
168
152
  @media (max-width: 860px) {
169
153
  .statsPanel { display: none; }
@@ -179,19 +163,25 @@ ${THEME_TOKENS_CSS}
179
163
  <header class="topbar">
180
164
  <div class="brand">
181
165
  <span class="eyebrow">the-mechanical-code-talker</span>
182
- <span class="subtitle">ingest &mdash; paste or drop text; it keeps only the facts it can ground, and skips the rest honestly</span>
166
+ <span class="subtitle">ingest &mdash; paste or drop text. It keeps the facts it can ground and skips the rest.</span>
183
167
  </div>
184
- <div class="pills" role="group" aria-label="Input mode">
185
- <button type="button" id="modeText" aria-pressed="true">Text</button>
186
- <button type="button" id="modeDoc" aria-pressed="false">Document</button>
168
+ <div class="topbar-right">
169
+ <span class="fact-pill" id="factPill" aria-live="polite"
170
+ title="every fact this page's memory holds right now — the starter memory it booted with plus everything it has grounded since">
171
+ <span class="fact-pill-value" id="factPillValue">&mdash;</span> facts in memory
172
+ </span>
173
+ <div class="pills" role="group" aria-label="Input mode">
174
+ <button type="button" id="modeText" aria-pressed="true">Text</button>
175
+ <button type="button" id="modeDoc" aria-pressed="false">Document</button>
176
+ </div>
187
177
  </div>
188
178
  </header>
189
179
  <div class="optionsRow" id="optionsRow">
190
- <label class="optionToggle" title="Loads chat.html's own starter memory (persona, ConceptNet, WordNet and the rest) before ingesting, so a taught fact can link into what it already knows. Off keeps the previous empty-store fast path.">
180
+ <label class="optionToggle" title="Loads chat.html's own starter memory (persona, ConceptNet, WordNet and the rest) before ingesting, so a taught fact can link into what it already knows. Off starts from an empty store, which loads faster.">
191
181
  <input type="checkbox" id="seedToggle" checked>
192
182
  seed with general knowledge
193
183
  </label>
194
- <label class="optionToggle" title="On a miss, also tries a copula or known relation verb flanked by two resolvable entities as a low-trust candidate fact, tagged optimistic-extract below every curated source, and never able to corroborate one.">
184
+ <label class="optionToggle" title="On a miss, also tries a copula or a known relation verb between two resolvable entities, tagged optimistic-extract. It ranks below every curated source and can never corroborate one.">
195
185
  <input type="checkbox" id="fuzzyToggle">
196
186
  fuzzy tier (low-trust candidates)
197
187
  </label>
@@ -234,6 +224,7 @@ ${THEME_TOKENS_CSS}
234
224
  const loadProgressLine = ${loadProgressLine.toString()};
235
225
  const bandLabelFor = ${bandLabelFor.toString()};
236
226
  const statsSummaryLine = ${statsSummaryLine.toString()};
227
+ const clearSiteAssetCaches = ${clearSiteAssetCaches.toString()};
237
228
  const fetchWithProgress = ${fetchWithProgress.toString()};
238
229
  const renderStatsPanelInto = ${renderStatsPanelInto.toString()};
239
230
  const el = (id) => document.getElementById(id);
@@ -256,6 +247,7 @@ ${THEME_TOKENS_CSS}
256
247
  const seedToggleEl = el("seedToggle");
257
248
  const fuzzyToggleEl = el("fuzzyToggle");
258
249
  const statsPanelEl = el("statsPanel");
250
+ const factPillValueEl = el("factPillValue");
259
251
 
260
252
  let session = null;
261
253
  let grounded = 0; // facts on show in the right pane, from the CURRENT ingest only
@@ -371,9 +363,10 @@ ${THEME_TOKENS_CSS}
371
363
  try { stats = await window.tmctIngest.memoryStats(session.memoryDir); }
372
364
  catch { return; }
373
365
  }
366
+ factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
374
367
  renderStatsPanelInto(statsPanelEl, stats, {
375
368
  bandLabel: bandLabelFor,
376
- taughtHint: "nothing yet \\u2014 ingest some text and its grounded facts land here, with their source.",
369
+ taughtHint: "nothing yet. Ingest some text and its grounded facts land here, with their source.",
377
370
  onForget: persist ? forgetEverything : null,
378
371
  persistNote: "taught facts are kept best-effort on this device (IndexedDB), never sent anywhere.",
379
372
  });
@@ -391,6 +384,12 @@ ${THEME_TOKENS_CSS}
391
384
  try { localStorage.setItem(SEED_PREF_KEY, on ? "on" : "off"); } catch { /* private mode — this visit still works */ }
392
385
  }
393
386
 
387
+ // The build's own content hash for the seed. It rides in the URL this page
388
+ // fetches the seed by, so the service worker's cache-first read can only
389
+ // ever return the copy this page asked for. Empty in a build with no seed.
390
+ const SEED_STAMP = ${JSON.stringify(seedStamp)};
391
+ const SEED_QUERY = SEED_STAMP ? "?b=" + SEED_STAMP : "";
392
+
394
393
  let seedPayload = null;
395
394
  let seedFacts = 0;
396
395
  const progressParts = {};
@@ -406,7 +405,7 @@ ${THEME_TOKENS_CSS}
406
405
  async function fetchSeedIfWanted() {
407
406
  if (!seedToggleEl.checked) { seedPayload = null; seedFacts = 0; return; }
408
407
  try {
409
- const blob = await fetchWithProgress("./chat-seed.json", (loaded, total) => noteProgress("seed", loaded, total));
408
+ const blob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, (loaded, total) => noteProgress("seed", loaded, total));
410
409
  seedPayload = JSON.parse(await blob.text());
411
410
  seedFacts = (seedPayload.individuals || []).filter((i) => i.class === "Fact").length;
412
411
  } catch (err) {
@@ -415,30 +414,14 @@ ${THEME_TOKENS_CSS}
415
414
  console.warn("tmct ingest: chat-seed.json unavailable — starting unseeded", err);
416
415
  }
417
416
  }
418
- function cloneSeed() {
419
- if (!seedPayload) return null;
420
- try { return structuredClone(seedPayload); } catch { return JSON.parse(JSON.stringify(seedPayload)); }
421
- }
417
+ const cloneMemoryPayload = ${cloneMemoryPayload.toString()};
422
418
  function newSession() {
423
- return window.tmctIngest.createIngestSession({ seedPayload: cloneSeed(), vocabSeeded: Boolean(seedPayload) });
419
+ return window.tmctIngest.createIngestSession({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload) });
424
420
  }
425
421
 
426
422
  // ---- engine boot ---------------------------------------------------------
427
- const WINK_LOAD_TIMEOUT_MS = 8000;
428
- async function tryLoadWink() {
429
- let settled = false;
430
- const timeout = new Promise((_, reject) => setTimeout(() => { if (!settled) reject(new Error("wink load stalled")); }, WINK_LOAD_TIMEOUT_MS));
431
- try {
432
- const mod = await Promise.race([import("./vendor/wink.js"), timeout]);
433
- settled = true;
434
- window.tmctIngest.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
435
- return "loaded";
436
- } catch (err) {
437
- settled = true;
438
- console.warn("tmct ingest: the wink vendor asset failed to load; the recognizer needs it to split and parse sentences", err);
439
- return "unavailable";
440
- }
441
- }
423
+ const loadWinkVendor = ${loadWinkVendor.toString()};
424
+ const tryLoadWink = loadWinkVendor({ register: (factory) => window.tmctIngest.registerWinkModel(factory) });
442
425
 
443
426
  // The deploy's own version, read off the service worker file the build
444
427
  // already stamps — the only same-origin place the number exists at runtime
@@ -488,7 +471,7 @@ ${THEME_TOKENS_CSS}
488
471
  clearFactsPane();
489
472
  updateIngestEnabled();
490
473
  const stats = await window.tmctIngest.memoryStats(session.memoryDir);
491
- statusEl.textContent = "forgot everything taught on this device \\u2014 back to the fresh seed (" + statsSummaryLine(stats, bandLabelFor) + ").";
474
+ statusEl.textContent = "forgot everything taught on this device. Back to the fresh seed (" + statsSummaryLine(stats, bandLabelFor) + ").";
492
475
  await renderStatsPanel(stats);
493
476
  }
494
477
 
@@ -506,7 +489,7 @@ ${THEME_TOKENS_CSS}
506
489
  session = newSession();
507
490
  clearFactsPane();
508
491
  const stats = await window.tmctIngest.memoryStats(session.memoryDir);
509
- statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + " \\u2014 ready.";
492
+ statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + ". Ready.";
510
493
  await renderStatsPanel(stats);
511
494
  updateIngestEnabled();
512
495
  sourceEl.focus();
@@ -514,7 +497,7 @@ ${THEME_TOKENS_CSS}
514
497
 
515
498
  async function boot() {
516
499
  if (!window.tmctIngest) {
517
- statusEl.textContent = "the ingest engine didn't load \\u2014 this page needs its build step (npm run demo:build)";
500
+ statusEl.textContent = "the ingest engine didn't load. This page needs its build step (npm run demo:build)";
518
501
  return;
519
502
  }
520
503
  seedToggleEl.checked = readSeedPref();
@@ -525,7 +508,7 @@ ${THEME_TOKENS_CSS}
525
508
  ]);
526
509
  progressActive = false;
527
510
  if (window.tmctIngest.openPersistedStore) {
528
- persist = window.tmctIngest.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts });
511
+ persist = window.tmctIngest.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
529
512
  }
530
513
  const savedRecord = persist ? await persist.load() : null;
531
514
  session = savedRecord && savedRecord.payload
@@ -536,9 +519,9 @@ ${THEME_TOKENS_CSS}
536
519
  const stats = await window.tmctIngest.memoryStats(session.memoryDir);
537
520
  const winkPart = winkStatus === "loaded"
538
521
  ? "wink-nlp: loaded"
539
- : "wink-nlp unavailable \\u2014 the recognizer can't split sentences without it";
522
+ : "wink-nlp unavailable. The recognizer can't split sentences without it";
540
523
  statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + " \\u00b7 " + winkPart
541
- + (savedRecord ? " \\u2014 restored from your last visit." : " \\u2014 paste or drop text, then ingest.");
524
+ + (savedRecord ? ". Restored from your last visit." : ". Paste or drop text, then ingest.");
542
525
  await renderStatsPanel(stats);
543
526
  sourceEl.focus();
544
527
  }
@@ -562,7 +545,7 @@ ${THEME_TOKENS_CSS}
562
545
  });
563
546
  statusEl.textContent = summary.sentences + " sentence" + (summary.sentences === 1 ? "" : "s")
564
547
  + " read, " + summary.recognized + " grounded, " + summary.skipped + " skipped"
565
- + (summary.skipped ? " (not a recognized fact shape \\u2014 honest, expected)" : "");
548
+ + (summary.skipped ? " (not a recognized fact shape, as expected)" : "");
566
549
  if (!summary.recognized) {
567
550
  const empty = factsEl.querySelector(".empty");
568
551
  if (!empty) {
@@ -605,13 +588,16 @@ ${THEME_TOKENS_CSS}
605
588
  setTimeout(() => URL.revokeObjectURL(url), 1000);
606
589
  });
607
590
 
608
- // "reset to seed" is the full re-initialisation: drop the persisted payload
609
- // outright and reload, so boot re-seeds from the page's shipped seed as if
610
- // on a first visit.
591
+ // "reset to seed" is the full re-initialisation: drop everything this device
592
+ // holds and reload, so boot re-seeds from the page's shipped seed as if on a
593
+ // first visit. Both stores go — what you taught lives in IndexedDB, and the
594
+ // seed asset itself lives in the service worker's Cache Storage, so clearing
595
+ // only the first would re-seed out of a cached copy of an older seed.
611
596
  el("reinitStore").addEventListener("click", async () => {
612
597
  clearTimeout(saveTimer);
613
598
  saveTimer = null;
614
599
  if (persist) await persist.clear();
600
+ await clearSiteAssetCaches();
615
601
  window.location.reload();
616
602
  });
617
603