@polycode-projects/the-mechanical-code-talker 4.0.0 → 4.0.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/README.md +176 -8
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +34 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/spider-fly-world.mjs +1 -1
- package/src/domain/sprite-templates.mjs +8 -6
- package/src/services/adventure-viz.mjs +130 -47
- package/src/services/adventure.mjs +440 -367
- package/src/services/chat-page-viz.mjs +37 -9
- package/src/services/chat.mjs +99 -18
- package/src/services/code-explorer-viz.mjs +52 -14
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +54 -26
- package/src/services/ledger-viz.mjs +105 -56
- package/src/services/memory-panel-viz.mjs +24 -0
- package/src/services/mud-viz.mjs +940 -21
- package/src/services/plan-viz.mjs +119 -61
- package/src/services/research-viz.mjs +151 -57
- package/src/services/spider-fly-turn.mjs +1 -1
- package/src/services/spider-fly-viz.mjs +67 -43
- package/src/services/sprite-catalog-viz.mjs +175 -51
- package/src/services/viz-theme.mjs +15 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +107 -107
- package/src/surfaces/web/mud-browser-entry.mjs +44 -2
|
@@ -26,7 +26,7 @@
|
|
|
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 { bandLabelFor, statsSummaryLine, clearSiteAssetCaches, fetchWithProgress, renderStatsPanelInto } from "./memory-panel-viz.mjs";
|
|
30
30
|
|
|
31
31
|
const DEFAULT_TITLE = "the-mechanical-code-talker — ingest";
|
|
32
32
|
|
|
@@ -69,7 +69,7 @@ export function loadProgressLine(parts) {
|
|
|
69
69
|
/** The self-contained ingest page. Pure — the same output for the same
|
|
70
70
|
* `title` every time; every piece of state (the session, each grounded fact)
|
|
71
71
|
* is computed live in the browser once the sibling ingest bundle loads. */
|
|
72
|
-
export function renderIngestHtml({ title = DEFAULT_TITLE } = {}) {
|
|
72
|
+
export function renderIngestHtml({ title = DEFAULT_TITLE, seedStamp = "" } = {}) {
|
|
73
73
|
return `<!doctype html>
|
|
74
74
|
<html lang="en">
|
|
75
75
|
<head>
|
|
@@ -83,8 +83,8 @@ export function renderIngestHtml({ title = DEFAULT_TITLE } = {}) {
|
|
|
83
83
|
The wink lemma/POS tier loads from ./vendor/wink.js — the site's own shared
|
|
84
84
|
first-party bundle (built by scripts/build-wink-vendor.mjs), one cached copy
|
|
85
85
|
for every page, no CDN. The ingest engine needs wink to split sentences and
|
|
86
|
-
to parse each teach frame
|
|
87
|
-
recognized"
|
|
86
|
+
to parse each teach frame. A failed load still answers "nothing
|
|
87
|
+
recognized" instead of erroring.
|
|
88
88
|
-->
|
|
89
89
|
<style>
|
|
90
90
|
${THEME_TOKENS_CSS}
|
|
@@ -103,6 +103,13 @@ ${THEME_TOKENS_CSS}
|
|
|
103
103
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .78rem; letter-spacing: .08em; color: var(--muted); }
|
|
104
104
|
.subtitle { font-size: .82rem; color: var(--muted); }
|
|
105
105
|
|
|
106
|
+
/* the live memory count, in the topbar rather than the status line: it is
|
|
107
|
+
the one number that says what this page's memory actually holds, and the
|
|
108
|
+
status line beside the buttons is where a wrong one goes unnoticed. */
|
|
109
|
+
.topbar-right { display: flex; align-items: center; gap: .7rem; }
|
|
110
|
+
.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; }
|
|
111
|
+
.fact-pill .fact-pill-value { font-size: .94rem; letter-spacing: 0; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
|
|
112
|
+
|
|
106
113
|
/* mode pills — Text | Document — the translate-tool idiom: two segments in
|
|
107
114
|
one rounded track, the active one filled. */
|
|
108
115
|
.pills { display: inline-flex; border: 1px solid var(--line); border-radius: 99px; overflow: hidden; font-family: ${MONO_STACK}; font-size: .72rem; }
|
|
@@ -140,7 +147,7 @@ ${THEME_TOKENS_CSS}
|
|
|
140
147
|
#facts .fact .pred { color: var(--corpus); white-space: nowrap; }
|
|
141
148
|
#facts .fact .obj { color: var(--ink); word-break: break-word; }
|
|
142
149
|
#facts .fact .prov { grid-column: 1 / -1; color: var(--muted); font-size: .66rem; }
|
|
143
|
-
#facts .empty { color: var(--muted);
|
|
150
|
+
#facts .empty { color: var(--muted); text-align: center; max-width: 24rem; line-height: 1.6; margin: 3rem auto 0; }
|
|
144
151
|
|
|
145
152
|
.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
153
|
.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 +160,7 @@ ${THEME_TOKENS_CSS}
|
|
|
153
160
|
the same class names and breakpoint chat-page-viz.mjs's own docked panel
|
|
154
161
|
uses, re-rendered after boot and after every ingest from
|
|
155
162
|
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; }
|
|
163
|
+
.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
164
|
.statsPanel h2 { font-size: .66rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); margin: 1.3rem 0 .5rem; }
|
|
158
165
|
.statsPanel h2:first-child { margin-top: 0; }
|
|
159
166
|
.statsPanel .band-row { display: flex; justify-content: space-between; gap: .6rem; margin: 0; padding: .12rem 0; }
|
|
@@ -161,9 +168,12 @@ ${THEME_TOKENS_CSS}
|
|
|
161
168
|
.statsPanel .taught-item { margin: 0 0 .7rem; }
|
|
162
169
|
.statsPanel .taught-tag { display: block; color: var(--muted); font-size: .66rem; margin-top: .15rem; word-break: break-word; }
|
|
163
170
|
.statsPanel .empty { color: var(--muted); margin: 0; }
|
|
164
|
-
|
|
171
|
+
/* The reset action anchors to the bottom of the rail via the auto margin —
|
|
172
|
+
on a tall viewport with little taught yet, that reads as a pinned
|
|
173
|
+
footer control instead of leaving a trailing gap under it. */
|
|
174
|
+
.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
175
|
.statsPanel .forget-btn:hover { color: var(--ink); }
|
|
166
|
-
.statsPanel .persist-note { color: var(--muted); font-size: .64rem; margin: .4rem 0 0; }
|
|
176
|
+
.statsPanel .persist-note { color: var(--muted); font-size: .64rem; margin: .4rem 0 0; flex: none; }
|
|
167
177
|
|
|
168
178
|
@media (max-width: 860px) {
|
|
169
179
|
.statsPanel { display: none; }
|
|
@@ -179,19 +189,25 @@ ${THEME_TOKENS_CSS}
|
|
|
179
189
|
<header class="topbar">
|
|
180
190
|
<div class="brand">
|
|
181
191
|
<span class="eyebrow">the-mechanical-code-talker</span>
|
|
182
|
-
<span class="subtitle">ingest — paste or drop text
|
|
192
|
+
<span class="subtitle">ingest — paste or drop text. It keeps the facts it can ground and skips the rest.</span>
|
|
183
193
|
</div>
|
|
184
|
-
<div class="
|
|
185
|
-
<
|
|
186
|
-
|
|
194
|
+
<div class="topbar-right">
|
|
195
|
+
<span class="fact-pill" id="factPill" aria-live="polite"
|
|
196
|
+
title="every fact this page's memory holds right now — the starter memory it booted with plus everything it has grounded since">
|
|
197
|
+
<span class="fact-pill-value" id="factPillValue">—</span> facts in memory
|
|
198
|
+
</span>
|
|
199
|
+
<div class="pills" role="group" aria-label="Input mode">
|
|
200
|
+
<button type="button" id="modeText" aria-pressed="true">Text</button>
|
|
201
|
+
<button type="button" id="modeDoc" aria-pressed="false">Document</button>
|
|
202
|
+
</div>
|
|
187
203
|
</div>
|
|
188
204
|
</header>
|
|
189
205
|
<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
|
|
206
|
+
<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
207
|
<input type="checkbox" id="seedToggle" checked>
|
|
192
208
|
seed with general knowledge
|
|
193
209
|
</label>
|
|
194
|
-
<label class="optionToggle" title="On a miss, also tries a copula or known relation verb
|
|
210
|
+
<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
211
|
<input type="checkbox" id="fuzzyToggle">
|
|
196
212
|
fuzzy tier (low-trust candidates)
|
|
197
213
|
</label>
|
|
@@ -234,6 +250,7 @@ ${THEME_TOKENS_CSS}
|
|
|
234
250
|
const loadProgressLine = ${loadProgressLine.toString()};
|
|
235
251
|
const bandLabelFor = ${bandLabelFor.toString()};
|
|
236
252
|
const statsSummaryLine = ${statsSummaryLine.toString()};
|
|
253
|
+
const clearSiteAssetCaches = ${clearSiteAssetCaches.toString()};
|
|
237
254
|
const fetchWithProgress = ${fetchWithProgress.toString()};
|
|
238
255
|
const renderStatsPanelInto = ${renderStatsPanelInto.toString()};
|
|
239
256
|
const el = (id) => document.getElementById(id);
|
|
@@ -256,6 +273,7 @@ ${THEME_TOKENS_CSS}
|
|
|
256
273
|
const seedToggleEl = el("seedToggle");
|
|
257
274
|
const fuzzyToggleEl = el("fuzzyToggle");
|
|
258
275
|
const statsPanelEl = el("statsPanel");
|
|
276
|
+
const factPillValueEl = el("factPillValue");
|
|
259
277
|
|
|
260
278
|
let session = null;
|
|
261
279
|
let grounded = 0; // facts on show in the right pane, from the CURRENT ingest only
|
|
@@ -371,9 +389,10 @@ ${THEME_TOKENS_CSS}
|
|
|
371
389
|
try { stats = await window.tmctIngest.memoryStats(session.memoryDir); }
|
|
372
390
|
catch { return; }
|
|
373
391
|
}
|
|
392
|
+
factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
|
|
374
393
|
renderStatsPanelInto(statsPanelEl, stats, {
|
|
375
394
|
bandLabel: bandLabelFor,
|
|
376
|
-
taughtHint: "nothing yet
|
|
395
|
+
taughtHint: "nothing yet. Ingest some text and its grounded facts land here, with their source.",
|
|
377
396
|
onForget: persist ? forgetEverything : null,
|
|
378
397
|
persistNote: "taught facts are kept best-effort on this device (IndexedDB), never sent anywhere.",
|
|
379
398
|
});
|
|
@@ -391,6 +410,12 @@ ${THEME_TOKENS_CSS}
|
|
|
391
410
|
try { localStorage.setItem(SEED_PREF_KEY, on ? "on" : "off"); } catch { /* private mode — this visit still works */ }
|
|
392
411
|
}
|
|
393
412
|
|
|
413
|
+
// The build's own content hash for the seed. It rides in the URL this page
|
|
414
|
+
// fetches the seed by, so the service worker's cache-first read can only
|
|
415
|
+
// ever return the copy this page asked for. Empty in a build with no seed.
|
|
416
|
+
const SEED_STAMP = ${JSON.stringify(seedStamp)};
|
|
417
|
+
const SEED_QUERY = SEED_STAMP ? "?b=" + SEED_STAMP : "";
|
|
418
|
+
|
|
394
419
|
let seedPayload = null;
|
|
395
420
|
let seedFacts = 0;
|
|
396
421
|
const progressParts = {};
|
|
@@ -406,7 +431,7 @@ ${THEME_TOKENS_CSS}
|
|
|
406
431
|
async function fetchSeedIfWanted() {
|
|
407
432
|
if (!seedToggleEl.checked) { seedPayload = null; seedFacts = 0; return; }
|
|
408
433
|
try {
|
|
409
|
-
const blob = await fetchWithProgress("./chat-seed.json", (loaded, total) => noteProgress("seed", loaded, total));
|
|
434
|
+
const blob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, (loaded, total) => noteProgress("seed", loaded, total));
|
|
410
435
|
seedPayload = JSON.parse(await blob.text());
|
|
411
436
|
seedFacts = (seedPayload.individuals || []).filter((i) => i.class === "Fact").length;
|
|
412
437
|
} catch (err) {
|
|
@@ -488,7 +513,7 @@ ${THEME_TOKENS_CSS}
|
|
|
488
513
|
clearFactsPane();
|
|
489
514
|
updateIngestEnabled();
|
|
490
515
|
const stats = await window.tmctIngest.memoryStats(session.memoryDir);
|
|
491
|
-
statusEl.textContent = "forgot everything taught on this device
|
|
516
|
+
statusEl.textContent = "forgot everything taught on this device. Back to the fresh seed (" + statsSummaryLine(stats, bandLabelFor) + ").";
|
|
492
517
|
await renderStatsPanel(stats);
|
|
493
518
|
}
|
|
494
519
|
|
|
@@ -506,7 +531,7 @@ ${THEME_TOKENS_CSS}
|
|
|
506
531
|
session = newSession();
|
|
507
532
|
clearFactsPane();
|
|
508
533
|
const stats = await window.tmctIngest.memoryStats(session.memoryDir);
|
|
509
|
-
statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + "
|
|
534
|
+
statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + ". Ready.";
|
|
510
535
|
await renderStatsPanel(stats);
|
|
511
536
|
updateIngestEnabled();
|
|
512
537
|
sourceEl.focus();
|
|
@@ -514,7 +539,7 @@ ${THEME_TOKENS_CSS}
|
|
|
514
539
|
|
|
515
540
|
async function boot() {
|
|
516
541
|
if (!window.tmctIngest) {
|
|
517
|
-
statusEl.textContent = "the ingest engine didn't load
|
|
542
|
+
statusEl.textContent = "the ingest engine didn't load. This page needs its build step (npm run demo:build)";
|
|
518
543
|
return;
|
|
519
544
|
}
|
|
520
545
|
seedToggleEl.checked = readSeedPref();
|
|
@@ -525,7 +550,7 @@ ${THEME_TOKENS_CSS}
|
|
|
525
550
|
]);
|
|
526
551
|
progressActive = false;
|
|
527
552
|
if (window.tmctIngest.openPersistedStore) {
|
|
528
|
-
persist = window.tmctIngest.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts });
|
|
553
|
+
persist = window.tmctIngest.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
|
|
529
554
|
}
|
|
530
555
|
const savedRecord = persist ? await persist.load() : null;
|
|
531
556
|
session = savedRecord && savedRecord.payload
|
|
@@ -536,9 +561,9 @@ ${THEME_TOKENS_CSS}
|
|
|
536
561
|
const stats = await window.tmctIngest.memoryStats(session.memoryDir);
|
|
537
562
|
const winkPart = winkStatus === "loaded"
|
|
538
563
|
? "wink-nlp: loaded"
|
|
539
|
-
: "wink-nlp unavailable
|
|
564
|
+
: "wink-nlp unavailable. The recognizer can't split sentences without it";
|
|
540
565
|
statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + " \\u00b7 " + winkPart
|
|
541
|
-
+ (savedRecord ? "
|
|
566
|
+
+ (savedRecord ? ". Restored from your last visit." : ". Paste or drop text, then ingest.");
|
|
542
567
|
await renderStatsPanel(stats);
|
|
543
568
|
sourceEl.focus();
|
|
544
569
|
}
|
|
@@ -562,7 +587,7 @@ ${THEME_TOKENS_CSS}
|
|
|
562
587
|
});
|
|
563
588
|
statusEl.textContent = summary.sentences + " sentence" + (summary.sentences === 1 ? "" : "s")
|
|
564
589
|
+ " read, " + summary.recognized + " grounded, " + summary.skipped + " skipped"
|
|
565
|
-
+ (summary.skipped ? " (not a recognized fact shape
|
|
590
|
+
+ (summary.skipped ? " (not a recognized fact shape, as expected)" : "");
|
|
566
591
|
if (!summary.recognized) {
|
|
567
592
|
const empty = factsEl.querySelector(".empty");
|
|
568
593
|
if (!empty) {
|
|
@@ -605,13 +630,16 @@ ${THEME_TOKENS_CSS}
|
|
|
605
630
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
606
631
|
});
|
|
607
632
|
|
|
608
|
-
// "reset to seed" is the full re-initialisation: drop
|
|
609
|
-
//
|
|
610
|
-
//
|
|
633
|
+
// "reset to seed" is the full re-initialisation: drop everything this device
|
|
634
|
+
// holds and reload, so boot re-seeds from the page's shipped seed as if on a
|
|
635
|
+
// first visit. Both stores go — what you taught lives in IndexedDB, and the
|
|
636
|
+
// seed asset itself lives in the service worker's Cache Storage, so clearing
|
|
637
|
+
// only the first would re-seed out of a cached copy of an older seed.
|
|
611
638
|
el("reinitStore").addEventListener("click", async () => {
|
|
612
639
|
clearTimeout(saveTimer);
|
|
613
640
|
saveTimer = null;
|
|
614
641
|
if (persist) await persist.clear();
|
|
642
|
+
await clearSiteAssetCaches();
|
|
615
643
|
window.location.reload();
|
|
616
644
|
});
|
|
617
645
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// doesn't do: it reads the checked-in chat-dock engine bundle.
|
|
16
16
|
|
|
17
17
|
import { loadMemory, readFactRows, findContradictions, normFactTerm } from "../adapters/memory/core.mjs";
|
|
18
|
-
import { THEME_TOKENS_CSS,
|
|
18
|
+
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml, embedJson } from "./viz-theme.mjs";
|
|
19
19
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
20
20
|
import { readFile } from "node:fs/promises";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
@@ -23,6 +23,34 @@ import { dirname, join } from "node:path";
|
|
|
23
23
|
|
|
24
24
|
const LEDGER_ROW_LIMIT_DEFAULT = 20000;
|
|
25
25
|
|
|
26
|
+
// The ledger reads as an observability dashboard (dense stat tiles, bar
|
|
27
|
+
// panels, a log-style fact stream) rather than an editorial page, so its
|
|
28
|
+
// chrome runs the system sans everywhere instead of THEME_TOKENS_CSS's
|
|
29
|
+
// serif body face; MONO_STACK still carries every metric, label, and log row.
|
|
30
|
+
const DASH_SANS_STACK = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;
|
|
31
|
+
|
|
32
|
+
// This page pins a dark, New-Relic-style dashboard register in BOTH site
|
|
33
|
+
// themes — a deliberate departure from THEME_TOKENS_CSS's light/dark switch,
|
|
34
|
+
// which every other generated page still follows. --taught/--corpus/--entail/
|
|
35
|
+
// --alert stay the shared semantic provenance tokens; here they're pinned to
|
|
36
|
+
// their dark-theme-friendly values (viz-theme.mjs's own dark token table) so
|
|
37
|
+
// the palette that ships in dark mode elsewhere is what this page always
|
|
38
|
+
// shows. viz-theme.mjs exports only the assembled THEME_TOKENS_CSS string,
|
|
39
|
+
// not its per-mode token table, so the dark set is restated here from that
|
|
40
|
+
// table rather than recomputed.
|
|
41
|
+
const DASH_DARK_CHROME_CSS = `
|
|
42
|
+
:root { color-scheme: dark; }
|
|
43
|
+
body {
|
|
44
|
+
--bg: #15181C; --ink: #E7E5DF; --muted: #9A9E95; --line: #2B3036; --card: #1C2126;
|
|
45
|
+
--taught: #5FBE8B; --corpus: #6C93BF; --entail: #D9A554; --alert: #D08070;
|
|
46
|
+
--taught-t1: rgba(95,190,139,.35); --taught-t2: rgba(95,190,139,.65); --taught-t3: rgba(95,190,139,1);
|
|
47
|
+
--corpus-t1: rgba(108,147,191,.35); --corpus-t2: rgba(108,147,191,.65); --corpus-t3: rgba(108,147,191,1);
|
|
48
|
+
--entail-t1: rgba(217,165,84,.35); --entail-t2: rgba(217,165,84,.65); --entail-t3: rgba(217,165,84,1);
|
|
49
|
+
--taught-soft: rgba(95,190,139,.14); --corpus-soft: rgba(108,147,191,.14);
|
|
50
|
+
--entail-soft: rgba(217,165,84,.16); --alert-soft: rgba(208,128,112,.16);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
26
54
|
/** Read the checked-in browser memory-ask-engine bundle
|
|
27
55
|
* (`src/surfaces/web/memory-ask-browser.bundle.js`) — the real memory-graph answer engine
|
|
28
56
|
* (chat.mjs's factAnswer/factReadBack) the chat dock runs on. Returns `""`,
|
|
@@ -407,7 +435,7 @@ function dashboardHtml(stats, { fresh = false } = {}) {
|
|
|
407
435
|
const total = s.totalFacts || 0;
|
|
408
436
|
const freshCls = fresh ? " fresh" : "";
|
|
409
437
|
if (!total) {
|
|
410
|
-
return `<section class="dash${freshCls}" id="dash" aria-label="Ledger metrics"><div class="tile"><span class="tile-label">facts.total</span><span class="tile-value">0</span><span class="tile-sub">nothing taught yet</span></div></section>`;
|
|
438
|
+
return `<section class="dash${freshCls}" id="dash" aria-label="Ledger metrics"><div class="kpirow"><div class="tile"><span class="tile-label">facts.total</span><span class="tile-value">0</span><span class="tile-sub">nothing taught yet</span></div></div></section>`;
|
|
411
439
|
}
|
|
412
440
|
const terms = s.totalTerms || 0;
|
|
413
441
|
const qualityCls = s.contradictionCount > 0 ? " tile-alert" : "";
|
|
@@ -415,33 +443,42 @@ function dashboardHtml(stats, { fresh = false } = {}) {
|
|
|
415
443
|
const predicatesHtml = s.predicates?.length
|
|
416
444
|
? microbarsHtml(s.predicates.map((p) => ({ label: p.phrase || p.predicate, count: p.count })))
|
|
417
445
|
: `<span class="tile-sub">none yet</span>`;
|
|
446
|
+
// Two explicit-column grids — a 4-up KPI row, then a 2-up row of bar-chart
|
|
447
|
+
// tiles — instead of one auto-fill grid: auto-fill's column count depends
|
|
448
|
+
// on viewport width, so a wide-tile mid-row wrap used to strand empty
|
|
449
|
+
// tracks on the right of both rows. Both stay `.tile` (the dashboard-strip
|
|
450
|
+
// e2e test counts six of them under `.dash`, whatever their sub-grid).
|
|
418
451
|
return `<section class="dash${freshCls}" id="dash" aria-label="Ledger metrics">
|
|
419
|
-
<div class="
|
|
420
|
-
<
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
<
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
<
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
<span class="tile-label">corpus.bundles</span>
|
|
440
|
-
<div class="bundlebars">${bundlesHtml}</div>
|
|
452
|
+
<div class="kpirow">
|
|
453
|
+
<div class="tile">
|
|
454
|
+
<span class="tile-label">facts.total</span>
|
|
455
|
+
<span class="tile-value">${total}</span>
|
|
456
|
+
<span class="tile-sub">${terms} term${terms === 1 ? "" : "s"} tracked</span>
|
|
457
|
+
</div>
|
|
458
|
+
<div class="tile tile-tier">
|
|
459
|
+
<span class="tile-label">facts.by-tier</span>
|
|
460
|
+
${tierTileHtml(s.byProv || {}, total)}
|
|
461
|
+
</div>
|
|
462
|
+
<div class="tile">
|
|
463
|
+
<span class="tile-label">graph.avg-degree</span>
|
|
464
|
+
<span class="tile-value">${(s.density || 0).toFixed(1)}</span>
|
|
465
|
+
<span class="tile-sub">facts per term</span>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="tile${qualityCls}">
|
|
468
|
+
<span class="tile-label">data.quality</span>
|
|
469
|
+
<span class="tile-value">${s.contradictionCount || 0}</span>
|
|
470
|
+
<span class="tile-sub">term${s.contradictionCount === 1 ? "" : "s"} with more than one answer</span>
|
|
471
|
+
</div>
|
|
441
472
|
</div>
|
|
442
|
-
<div class="
|
|
443
|
-
<
|
|
444
|
-
|
|
473
|
+
<div class="barpanels">
|
|
474
|
+
<div class="tile tile-bars">
|
|
475
|
+
<span class="tile-label">corpus.bundles</span>
|
|
476
|
+
<div class="bundlebars">${bundlesHtml}</div>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="tile tile-bars">
|
|
479
|
+
<span class="tile-label">predicate.top</span>
|
|
480
|
+
<div class="bundlebars">${predicatesHtml}</div>
|
|
481
|
+
</div>
|
|
445
482
|
</div>
|
|
446
483
|
</section>`;
|
|
447
484
|
}
|
|
@@ -534,7 +571,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
534
571
|
<button type="button" id="ingestToggle" class="dockbtn">ingest text…</button>
|
|
535
572
|
<button type="button" id="exportFacts" class="dockbtn">export facts</button>
|
|
536
573
|
</div>
|
|
537
|
-
<div class="researchrow" title="Fetches the topic from Simple English Wikipedia into this graph, then queues the topics its lead section links to
|
|
574
|
+
<div class="researchrow" title="Fetches the topic from Simple English Wikipedia into this graph, then queues the topics its lead section links to. Each queued topic runs as its own dock turn, paced politely. Asking is the network consent for these fetches.">
|
|
538
575
|
<label for="researchTopic" class="mono">research:</label>
|
|
539
576
|
<input id="researchTopic" type="text" autocomplete="off" spellcheck="false"
|
|
540
577
|
placeholder="a topic, e.g. owls" aria-label="Topic to research on Simple English Wikipedia">
|
|
@@ -544,7 +581,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
544
581
|
</div>
|
|
545
582
|
<div class="ingestpanel" id="ingestPanel" hidden>
|
|
546
583
|
<textarea id="ingestText" spellcheck="false" aria-label="Text to ingest into the graph"
|
|
547
|
-
placeholder="Paste text or drop a .txt/.md file. Each sentence it recognizes as a fact is added to the graph
|
|
584
|
+
placeholder="Paste text or drop a .txt/.md file. Each sentence it recognizes as a fact is added to the graph. The rest are skipped."></textarea>
|
|
548
585
|
<div class="ingestrow">
|
|
549
586
|
<button type="button" class="dockbtn" id="ingestBrowse">browse…</button>
|
|
550
587
|
<input type="file" id="ingestFile" accept=".txt,.md,text/plain,text/markdown" hidden>
|
|
@@ -562,7 +599,7 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
562
599
|
</form>
|
|
563
600
|
${ingestHtml}
|
|
564
601
|
</div>`
|
|
565
|
-
: `<div class="chat chat-off"><p class="chatnote">
|
|
602
|
+
: `<div class="chat chat-off"><p class="chatnote">Chat unavailable. Run <span class="mono">npm run build:ask-bundle</span> to enable the in-page ask engine.</p></div>`;
|
|
566
603
|
|
|
567
604
|
return `<!doctype html>
|
|
568
605
|
<html lang="en">
|
|
@@ -587,46 +624,52 @@ export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, wo
|
|
|
587
624
|
-->
|
|
588
625
|
<style>
|
|
589
626
|
${THEME_TOKENS_CSS}
|
|
627
|
+
${DASH_DARK_CHROME_CSS}
|
|
590
628
|
html { background: var(--bg); }
|
|
591
|
-
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${
|
|
629
|
+
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${DASH_SANS_STACK}; font-size: 15px; line-height: 1.5; }
|
|
592
630
|
.mono { font-family: ${MONO_STACK}; }
|
|
593
|
-
main { max-width:
|
|
631
|
+
main { max-width: 1200px; margin: 0 auto; padding: 1.4rem 1.2rem 3rem; }
|
|
594
632
|
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); display: flex; flex-wrap: wrap; gap: .4em 1.2em; margin-bottom: .9rem; }
|
|
595
633
|
button { font: inherit; color: inherit; background: none; border: none; padding: 0; cursor: pointer; }
|
|
596
|
-
button:focus-visible, input:focus-visible { outline: 2px solid var(--
|
|
597
|
-
.topbar { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem;
|
|
634
|
+
button:focus-visible, input:focus-visible { outline: 2px solid var(--corpus); outline-offset: 2px; border-radius: 4px; }
|
|
635
|
+
.topbar { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .5rem .7rem; margin-bottom: 1.1rem; }
|
|
598
636
|
.crumbs { display: flex; align-items: center; flex-wrap: wrap; gap: .35rem; font-size: .78rem; }
|
|
599
637
|
.crumbs .sep { color: var(--muted); }
|
|
600
|
-
.crumb { font-family: ${MONO_STACK}; font-size: .74rem; padding: .12rem .5rem; border: 1px solid var(--line); border-radius: 99px; background: var(--
|
|
638
|
+
.crumb { font-family: ${MONO_STACK}; font-size: .74rem; padding: .12rem .5rem; border: 1px solid var(--line); border-radius: 99px; background: var(--bg); }
|
|
601
639
|
.crumb[aria-current="true"] { background: var(--ink); color: var(--bg); border-color: var(--ink); }
|
|
602
640
|
.search { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
|
|
603
|
-
.search input { font-family: ${MONO_STACK}; font-size: .78rem; background: var(--
|
|
641
|
+
.search input { font-family: ${MONO_STACK}; font-size: .78rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: .3rem .6rem; width: 170px; }
|
|
604
642
|
.search .miss { font-size: .72rem; color: var(--alert); font-family: ${MONO_STACK}; }
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
.
|
|
643
|
+
/* Two explicit-column grids instead of one auto-fill grid: a 4-up KPI row,
|
|
644
|
+
then a 2-up row of bar-chart tiles, so both rows fill the container's
|
|
645
|
+
full width at any viewport rather than stranding a leftover track. */
|
|
646
|
+
.dash { display: flex; flex-direction: column; gap: .6rem; margin: 0 0 1.1rem; }
|
|
647
|
+
.kpirow { display: grid; grid-template-columns: repeat(4, 1fr); gap: .6rem; }
|
|
648
|
+
.barpanels { display: grid; grid-template-columns: repeat(2, 1fr); gap: .6rem; }
|
|
649
|
+
@media (max-width: 700px) { .kpirow { grid-template-columns: repeat(2, 1fr); } .barpanels { grid-template-columns: 1fr; } }
|
|
650
|
+
.tile { background: var(--card); border: 1px solid var(--line); border-left: 3px solid var(--line); border-radius: 6px; padding: .6rem .75rem .65rem; display: flex; flex-direction: column; gap: .18rem; min-width: 0; }
|
|
651
|
+
.tile-alert { border-left-color: var(--alert); }
|
|
609
652
|
.tile-label { font-family: ${MONO_STACK}; font-size: .62rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
|
|
610
|
-
.tile-value { font-family: ${MONO_STACK}; font-size: 1.
|
|
653
|
+
.tile-value { font-family: ${MONO_STACK}; font-size: 1.6rem; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.15; }
|
|
611
654
|
.tile-alert .tile-value { color: var(--alert); }
|
|
612
655
|
.tile-sub { font-family: ${MONO_STACK}; font-size: .68rem; color: var(--muted); }
|
|
613
|
-
.tierbar { display: flex; height: 8px; border-radius: 99px; overflow: hidden; background: var(--line); margin-top: .
|
|
656
|
+
.tierbar { display: flex; height: 8px; border-radius: 99px; overflow: hidden; background: var(--line); margin-top: .35rem; }
|
|
614
657
|
.tseg { height: 100%; }
|
|
615
658
|
.tseg.t-taught { background: var(--taught); } .tseg.t-corpus { background: var(--corpus); } .tseg.t-entail { background: var(--entail); }
|
|
616
659
|
.tierlegend { display: flex; flex-wrap: wrap; gap: .15rem .7rem; margin-top: .35rem; font-family: ${MONO_STACK}; font-size: .65rem; }
|
|
617
660
|
.tleg.t-taught { color: var(--taught); } .tleg.t-corpus { color: var(--corpus); } .tleg.t-entail { color: var(--entail); }
|
|
618
|
-
.bundlebars { display: flex; flex-direction: column; gap: .
|
|
619
|
-
.bbar { display: grid; grid-template-columns: minmax(0,1fr) 3.4rem 1.6rem; align-items: center; gap: .4rem; font-family: ${MONO_STACK}; font-size: .
|
|
661
|
+
.bundlebars { display: flex; flex-direction: column; gap: .26rem; margin-top: .4rem; }
|
|
662
|
+
.bbar { display: grid; grid-template-columns: minmax(0,1fr) 3.4rem 1.6rem; align-items: center; gap: .4rem; font-family: ${MONO_STACK}; font-size: .7rem; }
|
|
620
663
|
.bblabel { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
621
664
|
.bbtrack { background: var(--line); border-radius: 99px; height: 5px; overflow: hidden; }
|
|
622
|
-
.bbfill { display: block; height: 100%; background: var(--ink); opacity: .
|
|
665
|
+
.bbfill { display: block; height: 100%; background: var(--ink); opacity: .6; border-radius: 99px; }
|
|
623
666
|
.bbn { color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
|
|
624
667
|
.sparkwrap { margin-top: .5rem; }
|
|
625
668
|
.spark { display: block; width: 100%; height: 44px; }
|
|
626
669
|
.spark .fill { fill: var(--muted); opacity: .18; stroke: none; }
|
|
627
670
|
.spark .line { fill: none; stroke: var(--ink); stroke-width: 1.4; }
|
|
628
671
|
.spark .dot { fill: var(--ink); }
|
|
629
|
-
.app { display: grid; grid-template-columns: 190px minmax(0,1fr)
|
|
672
|
+
.app { display: grid; grid-template-columns: 190px minmax(0,1fr) 240px; gap: 1.1rem; grid-template-areas: "rail ledger aside"; align-items: start; }
|
|
630
673
|
.rail { grid-area: rail; } .ledger { grid-area: ledger; } .aside { grid-area: aside; }
|
|
631
674
|
@media (max-width: 880px) { .app { grid-template-columns: 1fr; grid-template-areas: "ledger" "aside" "rail"; } .search { margin-left: 0; } }
|
|
632
675
|
.rail h2, .aside h2 { font-family: ${MONO_STACK}; font-size: .66rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 400; margin: 1rem 0 .4rem; }
|
|
@@ -641,7 +684,7 @@ ${THEME_TOKENS_CSS}
|
|
|
641
684
|
.seg.on.neutral { background: var(--ink); color: var(--bg); } .seg.on.neutral .n { color: var(--bg); }
|
|
642
685
|
.seg.on.c-taught { background: var(--taught); } .seg.on.c-corpus { background: var(--corpus); } .seg.on.c-entail { background: var(--entail); }
|
|
643
686
|
.d-taught { background: var(--taught); } .d-corpus { background: var(--corpus); } .d-entail { background: var(--entail); }
|
|
644
|
-
.focuscard { background: var(--card); border: 1px solid var(--line); border-radius:
|
|
687
|
+
.focuscard { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: .85rem 1rem; margin-bottom: 1rem; }
|
|
645
688
|
.focuscard .term { font-size: 1.35rem; font-weight: 700; }
|
|
646
689
|
.focuscard .klass, .focuscard .stats { font-family: ${MONO_STACK}; font-size: .72rem; color: var(--muted); margin-top: .3rem; font-variant-numeric: tabular-nums; }
|
|
647
690
|
.focuscard .focusdigest { margin-top: .7rem; font-size: .92rem; line-height: 1.5; }
|
|
@@ -656,8 +699,13 @@ ${THEME_TOKENS_CSS}
|
|
|
656
699
|
.group { margin: 1.1rem 0; }
|
|
657
700
|
.group h3 { font-family: ${MONO_STACK}; font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 400; margin: 0 0 .35rem; display: flex; align-items: baseline; gap: .5rem; }
|
|
658
701
|
.group h3::after { content: ""; flex: 1; border-top: 1px solid var(--line); transform: translateY(-.2em); }
|
|
659
|
-
|
|
660
|
-
|
|
702
|
+
/* The fact stream reads as a dense query-result log: full-width rows, a
|
|
703
|
+
hairline between each, a provenance-colored left rail, no per-row card
|
|
704
|
+
chrome. */
|
|
705
|
+
.rows { display: flex; flex-direction: column; }
|
|
706
|
+
.row { background: transparent; border: none; border-left: 3px solid var(--line); border-bottom: 1px solid var(--line); border-radius: 0; padding: .42rem .7rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .5rem; font-family: ${MONO_STACK}; font-size: .82rem; }
|
|
707
|
+
.rows .row:last-child { border-bottom: none; }
|
|
708
|
+
.row:hover { background: var(--line); }
|
|
661
709
|
.row.p-taught.t3 { border-left-color: var(--taught-t3); } .row.p-taught.t2 { border-left-color: var(--taught-t2); } .row.p-taught.t1 { border-left-color: var(--taught-t1); }
|
|
662
710
|
.row.p-corpus.t3 { border-left-color: var(--corpus-t3); } .row.p-corpus.t2 { border-left-color: var(--corpus-t2); } .row.p-corpus.t1 { border-left-color: var(--corpus-t1); }
|
|
663
711
|
.row.p-entail.t3 { border-left-color: var(--entail-t3); } .row.p-entail.t2 { border-left-color: var(--entail-t2); } .row.p-entail.t1 { border-left-color: var(--entail-t1); }
|
|
@@ -668,18 +716,18 @@ ${THEME_TOKENS_CSS}
|
|
|
668
716
|
.prov.p-taught { color: var(--taught); background: var(--taught-soft); }
|
|
669
717
|
.prov.p-corpus { color: var(--corpus); background: var(--corpus-soft); }
|
|
670
718
|
.prov.p-entail { color: var(--entail); background: var(--entail-soft); }
|
|
671
|
-
.contra { border: 1px solid var(--alert); border-radius:
|
|
719
|
+
.contra { border: 1px solid var(--alert); border-radius: 6px; padding: .5rem; margin-top: .35rem; }
|
|
672
720
|
.contra .label { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--alert); margin: 0 0 .35rem .2rem; }
|
|
673
|
-
.empty { color: var(--muted); font-size: .
|
|
721
|
+
.empty { color: var(--muted); font-size: .88rem; border: 1px dashed var(--line); border-radius: 6px; padding: .8rem 1rem; }
|
|
674
722
|
.looks { display: flex; flex-direction: column; gap: .45rem; }
|
|
675
|
-
.look { display: block; text-align: left; background: var(--card); border: 1px solid var(--line); border-radius:
|
|
723
|
+
.look { display: block; text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .5rem .65rem; font-size: .8rem; width: 100%; }
|
|
676
724
|
.look:hover { border-color: var(--ink); }
|
|
677
725
|
.look .tag { display: block; font-family: ${MONO_STACK}; font-size: .62rem; letter-spacing: .06em; text-transform: uppercase; margin-bottom: .18rem; }
|
|
678
726
|
.look.k-taught .tag { color: var(--taught); } .look.k-alert .tag { color: var(--alert); } .look.k-hub .tag { color: var(--corpus); }
|
|
679
|
-
.mapwrap { background: var(--card); border: 1px solid var(--line); border-radius:
|
|
727
|
+
.mapwrap { background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .45rem; margin-top: .45rem; }
|
|
680
728
|
.mapwrap canvas { display: block; width: 100%; height: 160px; cursor: pointer; }
|
|
681
729
|
.mapnote { font-family: ${MONO_STACK}; font-size: .62rem; color: var(--muted); margin: .3rem .2rem 0; }
|
|
682
|
-
.chat { background: var(--card); border: 1px solid var(--line); border-radius:
|
|
730
|
+
.chat { background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .65rem .85rem; margin-bottom: 1rem; }
|
|
683
731
|
.chatlog { display: flex; flex-direction: column; gap: .45rem; max-height: 220px; overflow-y: auto; }
|
|
684
732
|
.chatlog:empty { display: none; }
|
|
685
733
|
.chatlog .u { font-family: ${MONO_STACK}; font-size: .76rem; color: var(--muted); }
|
|
@@ -712,6 +760,7 @@ ${THEME_TOKENS_CSS}
|
|
|
712
760
|
.ingeststatus { font-size: .68rem; color: var(--muted); }
|
|
713
761
|
@media (prefers-reduced-motion: no-preference) {
|
|
714
762
|
.seg, .chip, .look { transition: border-color .12s ease, background-color .12s ease; }
|
|
763
|
+
.row { transition: background-color .1s ease; }
|
|
715
764
|
/* A live teach re-render swaps the dash section wholesale (dashboardHtml
|
|
716
765
|
is called again in full — see the inline script below), so the "this
|
|
717
766
|
just changed" signal has to be an animation on the fresh markup itself,
|
|
@@ -934,7 +983,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
934
983
|
for (const [, grp] of groupsHere) {
|
|
935
984
|
if (grp.length < 2) continue;
|
|
936
985
|
grp.forEach((r) => bracketed.add(r.id));
|
|
937
|
-
inner += '<div class="contra"><p class="label">more than one answer on record
|
|
986
|
+
inner += '<div class="contra"><p class="label">more than one answer on record, shown, not merged</p><div class="rows">' + grp.map(rowHtml).join("") + "</div></div>";
|
|
938
987
|
}
|
|
939
988
|
html += '<div class="group"><h3>' + esc(FAM_LABEL[fam]) + '</h3><div class="rows">' + inner + "</div></div>";
|
|
940
989
|
}
|
|
@@ -952,7 +1001,7 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
952
1001
|
looks.push({ cls: "k-taught", tag: "newest teach", target: w.newestTaught.term, body: r ? esc(r.s + " " + r.phrase + " " + r.o) : esc(w.newestTaught.term) });
|
|
953
1002
|
}
|
|
954
1003
|
if (w.contradictions && w.contradictions.count) {
|
|
955
|
-
looks.push({ cls: "k-alert", tag: w.contradictions.count + " with more than one answer", target: w.contradictions.firstFocusTerm, body: '<span class="mono">' + esc(w.contradictions.firstFocusTerm) + "</span> has answers that differ
|
|
1004
|
+
looks.push({ cls: "k-alert", tag: w.contradictions.count + " with more than one answer", target: w.contradictions.firstFocusTerm, body: '<span class="mono">' + esc(w.contradictions.firstFocusTerm) + "</span> has answers that differ. Read both." });
|
|
956
1005
|
}
|
|
957
1006
|
if (w.biggestHub) {
|
|
958
1007
|
looks.push({ cls: "k-hub", tag: "biggest hub", target: w.biggestHub.term, body: '<span class="mono">' + esc(w.biggestHub.term) + "</span> touches " + w.biggestHub.degree + " facts" });
|
|
@@ -49,6 +49,30 @@ export function statsSummaryLine(stats, bandLabel) {
|
|
|
49
49
|
: stats.total + " starter facts loaded";
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Drop every asset the site's service worker has cached, and unregister the
|
|
53
|
+
* worker itself, so the next load reads the shipped files off the network.
|
|
54
|
+
* A page's own taught-facts store lives in IndexedDB, a different store
|
|
55
|
+
* entirely — clearing that alone would still re-seed from whatever copy of
|
|
56
|
+
* chat-seed.json the worker cached, which is the whole reason this exists.
|
|
57
|
+
* Best-effort throughout: a browser with no Cache Storage, no worker, or a
|
|
58
|
+
* storage call that throws just leaves the caller to reload as before.
|
|
59
|
+
* Pure of this module's scope, so it stays `.toString()`-splice safe. */
|
|
60
|
+
export async function clearSiteAssetCaches() {
|
|
61
|
+
try {
|
|
62
|
+
if (globalThis.caches) {
|
|
63
|
+
for (const key of await caches.keys()) {
|
|
64
|
+
if (key.startsWith("tmct-precache-")) await caches.delete(key);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch { /* the reload still re-reads whatever the worker will serve */ }
|
|
68
|
+
try {
|
|
69
|
+
if (navigator.serviceWorker && navigator.serviceWorker.getRegistrations) {
|
|
70
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
71
|
+
await Promise.all(registrations.map((registration) => registration.unregister()));
|
|
72
|
+
}
|
|
73
|
+
} catch { /* an un-unregisterable worker still has an empty cache now */ }
|
|
74
|
+
}
|
|
75
|
+
|
|
52
76
|
/** Fetch `url` reading the body as a stream, reporting (loadedBytes,
|
|
53
77
|
* totalBytes) after every chunk — total is 0 when the response carries no
|
|
54
78
|
* Content-Length. Resolves to a Blob of the whole body. Falls back to a
|