@polycode-projects/the-mechanical-code-talker 3.3.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 +52 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +53 -13
- 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-garden.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-garden.jsonl +34 -1
- 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/adapters/p2p/webrtc-transport.mjs +146 -0
- package/src/domain/game-config.mjs +26 -5
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +31 -0
- package/src/domain/memory/trust.mjs +6 -4
- package/src/domain/p2p/facts.mjs +81 -0
- package/src/domain/p2p/peer-id.mjs +32 -0
- package/src/domain/p2p/provenance-relabel.mjs +26 -0
- package/src/domain/p2p/sync-filter.mjs +31 -0
- package/src/domain/p2p/wire.mjs +123 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/spider-fly-world.mjs +1 -1
- package/src/domain/sprite-map.mjs +10 -2
- package/src/domain/sprite-templates.mjs +8 -6
- package/src/services/adventure-editor.mjs +10 -2
- package/src/services/adventure-viz.mjs +309 -73
- package/src/services/adventure.mjs +1034 -330
- package/src/services/chat-page-viz.mjs +1097 -16
- 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-editor.mjs +313 -0
- package/src/services/mud-turn.mjs +264 -87
- package/src/services/mud-viz.mjs +2606 -384
- package/src/services/p2p-room.mjs +559 -0
- 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 +109 -109
- package/src/surfaces/web/mud-browser-entry.mjs +226 -18
- package/src/surfaces/web/p2p-browser-entry.mjs +39 -0
|
@@ -19,12 +19,32 @@
|
|
|
19
19
|
// renderResearchHtml() is pure: no I/O, deterministic output for identical
|
|
20
20
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result to
|
|
21
21
|
// public/research.html, after research-browser.bundle.js already exists.
|
|
22
|
-
import { THEME_TOKENS_CSS,
|
|
22
|
+
import { THEME_TOKENS_CSS, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
23
23
|
import { fetchWithProgress } from "./memory-panel-viz.mjs";
|
|
24
24
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
25
25
|
|
|
26
26
|
const DEFAULT_TITLE = "the-mechanical-code-talker — research";
|
|
27
27
|
|
|
28
|
+
// research.html reads as ledger.html's dashboard sibling (dense stat/control
|
|
29
|
+
// panels, monospace metrics), so it runs the same system sans + forced dark
|
|
30
|
+
// chrome — see ledger-viz.mjs's own DASH_SANS_STACK/DASH_DARK_CHROME_CSS for
|
|
31
|
+
// the fuller rationale. Kept as a separate copy rather than a shared import:
|
|
32
|
+
// each generated-page builder is a standalone, self-contained module (no
|
|
33
|
+
// cross-page runtime dependency), matching every sibling page builder here.
|
|
34
|
+
const DASH_SANS_STACK = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;
|
|
35
|
+
const DASH_DARK_CHROME_CSS = `
|
|
36
|
+
:root { color-scheme: dark; }
|
|
37
|
+
body {
|
|
38
|
+
--bg: #15181C; --ink: #E7E5DF; --muted: #9A9E95; --line: #2B3036; --card: #1C2126;
|
|
39
|
+
--taught: #5FBE8B; --corpus: #6C93BF; --entail: #D9A554; --alert: #D08070;
|
|
40
|
+
--taught-t1: rgba(95,190,139,.35); --taught-t2: rgba(95,190,139,.65); --taught-t3: rgba(95,190,139,1);
|
|
41
|
+
--corpus-t1: rgba(108,147,191,.35); --corpus-t2: rgba(108,147,191,.65); --corpus-t3: rgba(108,147,191,1);
|
|
42
|
+
--entail-t1: rgba(217,165,84,.35); --entail-t2: rgba(217,165,84,.65); --entail-t3: rgba(217,165,84,1);
|
|
43
|
+
--taught-soft: rgba(95,190,139,.14); --corpus-soft: rgba(108,147,191,.14);
|
|
44
|
+
--entail-soft: rgba(217,165,84,.16); --alert-soft: rgba(208,128,112,.16);
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
28
48
|
/** The human label + short colour key for one source snapshot entry
|
|
29
49
|
* ({ key, band }). A seed band folds to a readable corpus name; the three
|
|
30
50
|
* growth sources get their own words. Pure and `.toString()`-splice safe. */
|
|
@@ -79,7 +99,7 @@ export function loadProgressLine(parts) {
|
|
|
79
99
|
* page can digest a term client-side over its grown store (no TOML parser in
|
|
80
100
|
* the browser); an empty list leaves the digest panel degrading to an honest
|
|
81
101
|
* "no digest available" the same way the node stub does. */
|
|
82
|
-
export function renderResearchHtml({ title = DEFAULT_TITLE, digestStructures = [] } = {}) {
|
|
102
|
+
export function renderResearchHtml({ title = DEFAULT_TITLE, digestStructures = [], seedStamp = "" } = {}) {
|
|
83
103
|
const digestStructuresJson = JSON.stringify(Array.isArray(digestStructures) ? digestStructures : []);
|
|
84
104
|
return `<!doctype html>
|
|
85
105
|
<html lang="en">
|
|
@@ -99,34 +119,52 @@ export function renderResearchHtml({ title = DEFAULT_TITLE, digestStructures = [
|
|
|
99
119
|
-->
|
|
100
120
|
<style>
|
|
101
121
|
${THEME_TOKENS_CSS}
|
|
122
|
+
${DASH_DARK_CHROME_CSS}
|
|
102
123
|
html, body { min-height: 100%; }
|
|
103
|
-
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${
|
|
124
|
+
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${DASH_SANS_STACK}; font-size: 15px; line-height: 1.5; }
|
|
104
125
|
.mono { font-family: ${MONO_STACK}; }
|
|
105
126
|
button { font: inherit; color: inherit; background: none; cursor: pointer; border: none; }
|
|
106
|
-
button:focus-visible, textarea:focus-visible, input:focus-visible, summary:focus-visible { outline: 2px solid var(--
|
|
127
|
+
button:focus-visible, textarea:focus-visible, input:focus-visible, summary:focus-visible { outline: 2px solid var(--corpus); outline-offset: 2px; }
|
|
107
128
|
a { color: var(--corpus); }
|
|
108
129
|
|
|
109
|
-
.wrap { max-width:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
.
|
|
130
|
+
.wrap { max-width: 1200px; margin: 0 auto; padding: 1.1rem 1.1rem 3rem; }
|
|
131
|
+
|
|
132
|
+
/* A brand block on the left and a small stat-readout panel on the right —
|
|
133
|
+
the readout fills the row instead of one status line stranded beside a
|
|
134
|
+
wide gap, and it doubles as the boot-progress display. */
|
|
135
|
+
header.topbar { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.4rem; flex-wrap: wrap; padding: .6rem 0 1rem; border-bottom: 1px solid var(--line); margin-bottom: 1.3rem; }
|
|
136
|
+
.brand { display: flex; flex-direction: column; gap: .3rem; max-width: 640px; }
|
|
137
|
+
.eyebrow { font-family: ${MONO_STACK}; font-size: .72rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
|
|
138
|
+
.subtitle { font-size: .92rem; color: var(--ink); opacity: .82; max-width: 58ch; }
|
|
139
|
+
.statuspanel { display: flex; gap: 1.1rem; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: .5rem .9rem; }
|
|
140
|
+
.statuspanel .stat { display: flex; flex-direction: column; gap: .14rem; min-width: 7rem; }
|
|
141
|
+
.statuspanel .stat-label { font-family: ${MONO_STACK}; font-size: .6rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
|
|
142
|
+
.statuspanel .stat-value { font-family: ${MONO_STACK}; font-size: .82rem; font-variant-numeric: tabular-nums; color: var(--ink); }
|
|
143
|
+
/* the live total is what this page is actually growing, so it carries the
|
|
144
|
+
panel's emphasis — the seed count beside it never moves after boot. */
|
|
145
|
+
.statuspanel .stat-facts { padding-right: 1.1rem; border-right: 1px solid var(--line); }
|
|
146
|
+
.statuspanel .stat-facts .stat-value { font-size: 1.15rem; font-weight: 600; }
|
|
147
|
+
.enginenote { margin: -.5rem 0 1.2rem; padding: .55rem .8rem; border: 1px solid var(--alert); border-radius: 6px; background: var(--alert-soft); color: var(--alert); font-family: ${MONO_STACK}; font-size: .78rem; }
|
|
148
|
+
.enginenote[hidden] { display: none; }
|
|
116
149
|
|
|
117
150
|
h2.band { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); margin: 1.6rem 0 .7rem; border-bottom: 1px solid var(--line); padding-bottom: .35rem; }
|
|
118
151
|
|
|
119
|
-
/* the three ways to grow the graph
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.grow
|
|
152
|
+
/* the three ways to grow the graph, as one control-bar row of panels: a
|
|
153
|
+
hairline grid gap (painted by the shared background) separates them
|
|
154
|
+
instead of individual card borders, and each carries a top accent in
|
|
155
|
+
its own tone color. Stacks on a narrow screen. */
|
|
156
|
+
.grow { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
|
|
157
|
+
.grow .card { background: var(--card); border-top: 2px solid var(--line); padding: .9rem 1rem 1.1rem; display: flex; flex-direction: column; gap: .55rem; min-width: 0; }
|
|
158
|
+
.grow .card.card-research { border-top-color: var(--corpus); }
|
|
159
|
+
.grow .card.card-taught { border-top-color: var(--taught); }
|
|
160
|
+
.grow .card.card-ingest { border-top-color: var(--entail); }
|
|
161
|
+
.grow .card h3 { margin: 0; font-size: .95rem; font-weight: 600; }
|
|
124
162
|
.grow .card .hint { font-size: .76rem; color: var(--muted); margin: 0; }
|
|
125
163
|
.grow .card .tone { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: .35rem; vertical-align: baseline; }
|
|
126
164
|
.tone-taught { background: var(--taught); } .tone-ingest { background: var(--entail); }
|
|
127
165
|
.tone-research { background: var(--corpus); } .tone-seed { background: var(--muted); }
|
|
128
166
|
|
|
129
|
-
.grow input[type="text"], .grow textarea { width: 100%; box-sizing: border-box; font-family: ${MONO_STACK}; font-size: .8rem; background: var(--
|
|
167
|
+
.grow input[type="text"], .grow textarea { width: 100%; box-sizing: border-box; font-family: ${MONO_STACK}; font-size: .8rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: .4rem .55rem; }
|
|
130
168
|
.grow textarea { resize: vertical; min-height: 5.5rem; line-height: 1.5; }
|
|
131
169
|
.grow input::placeholder, .grow textarea::placeholder { color: var(--muted); }
|
|
132
170
|
.row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
|
|
@@ -138,35 +176,45 @@ ${THEME_TOKENS_CSS}
|
|
|
138
176
|
.optionToggle input { margin: 0; accent-color: var(--corpus); }
|
|
139
177
|
.knobs { display: flex; gap: .9rem; align-items: center; flex-wrap: wrap; }
|
|
140
178
|
.knob { display: inline-flex; align-items: center; gap: .4rem; font-family: ${MONO_STACK}; font-size: .68rem; color: var(--muted); }
|
|
141
|
-
.knob input[type="number"] { width: 3.4rem; font-family: ${MONO_STACK}; font-size: .74rem; background: var(--
|
|
179
|
+
.knob input[type="number"] { width: 3.4rem; font-family: ${MONO_STACK}; font-size: .74rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: .25rem .35rem; text-align: right; }
|
|
142
180
|
|
|
143
181
|
/* highlights + ask, two columns */
|
|
144
182
|
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; align-items: start; }
|
|
145
183
|
|
|
146
|
-
.panel { border: 1px solid var(--line); border-radius:
|
|
147
|
-
.panel h3 { margin: 0 0 .6rem; font-size: .9rem; }
|
|
184
|
+
.panel { border: 1px solid var(--line); border-radius: 6px; padding: .9rem 1rem 1rem; background: var(--card); }
|
|
185
|
+
.panel h3 { margin: 0 0 .6rem; font-size: .9rem; font-weight: 600; }
|
|
148
186
|
.panel .empty { color: var(--muted); font-size: .8rem; margin: .3rem 0; }
|
|
149
187
|
|
|
150
|
-
|
|
188
|
+
/* recently-learned: a dense feed of the facts just grounded. */
|
|
189
|
+
.fact { display: grid; grid-template-columns: auto 1fr auto 1fr; gap: .45rem; align-items: baseline; padding: .3rem .3rem; margin: 0 -.3rem; border-radius: 4px; border-bottom: 1px solid var(--line); font-family: ${MONO_STACK}; font-size: .76rem; }
|
|
151
190
|
.fact:last-child { border-bottom: none; }
|
|
191
|
+
.fact:hover { background: var(--line); }
|
|
152
192
|
.fact .dot { width: 7px; height: 7px; border-radius: 50%; align-self: center; }
|
|
153
193
|
.fact .subj { color: var(--ink); word-break: break-word; }
|
|
154
194
|
.fact .pred { color: var(--corpus); white-space: nowrap; }
|
|
155
195
|
.fact .obj { color: var(--ink); word-break: break-word; }
|
|
156
196
|
|
|
157
|
-
|
|
197
|
+
/* best-connected terms: a top-N ranked list (a mini bar chart), styled as
|
|
198
|
+
ledger.html's own predicate.top panel so the two pages read as siblings.
|
|
199
|
+
Each row keeps the "chip tapchip" contract the page's click handling and
|
|
200
|
+
its own e2e coverage rely on; the ranked-bar look rides inside it. */
|
|
201
|
+
.chips { display: flex; flex-direction: column; gap: .2rem; }
|
|
158
202
|
.chip { font-family: ${MONO_STACK}; font-size: .74rem; border: 1px solid var(--line); border-radius: 99px; padding: .2rem .6rem; background: var(--bg); }
|
|
159
|
-
.chip.tapchip { cursor: pointer; }
|
|
160
|
-
.chip.tapchip:hover {
|
|
161
|
-
.chip .
|
|
203
|
+
.chip.tapchip { cursor: pointer; display: grid; grid-template-columns: 1.5rem minmax(0,1fr) 4.6rem 3.6rem; align-items: center; gap: .5rem; width: 100%; text-align: left; border: none; border-radius: 4px; padding: .3rem .4rem; background: none; }
|
|
204
|
+
.chip.tapchip:hover { background: var(--line); }
|
|
205
|
+
.chip .rank { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
206
|
+
.chip .term { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
207
|
+
.chip .track { background: var(--line); border-radius: 99px; height: 5px; overflow: hidden; }
|
|
208
|
+
.chip .fill { display: block; height: 100%; background: var(--ink); opacity: .6; border-radius: 99px; }
|
|
209
|
+
.chip .deg { color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
|
|
162
210
|
|
|
163
211
|
/* the term digest: a narrative card, its sources, and the flat fact list one
|
|
164
212
|
click away behind "show the facts". */
|
|
165
213
|
.digestpanel .hint { font-size: .78rem; color: var(--muted); margin: 0 0 .55rem; }
|
|
166
214
|
.digestout { min-height: 1.4rem; }
|
|
167
215
|
.digestout .empty { color: var(--muted); font-size: .82rem; margin: .2rem 0; }
|
|
168
|
-
.digestout .miss { color: var(--muted); font-size: .88rem; border: 1px dashed var(--line); border-radius:
|
|
169
|
-
.dgcard { border: 1px solid var(--line); border-radius:
|
|
216
|
+
.digestout .miss { color: var(--muted); font-size: .88rem; border: 1px dashed var(--line); border-radius: 6px; padding: .55rem .7rem; }
|
|
217
|
+
.dgcard { border: 1px solid var(--line); border-radius: 6px; padding: .7rem .85rem .8rem; background: var(--bg); }
|
|
170
218
|
.dgterm { font-family: ${MONO_STACK}; font-size: .72rem; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin-bottom: .4rem; }
|
|
171
219
|
.dgcard p { margin: 0 0 .5rem; font-size: .95rem; }
|
|
172
220
|
.dgcard p:last-of-type { margin-bottom: 0; }
|
|
@@ -178,15 +226,19 @@ ${THEME_TOKENS_CSS}
|
|
|
178
226
|
|
|
179
227
|
/* ask, scoped by source */
|
|
180
228
|
.askRow { display: flex; gap: .5rem; margin: .2rem 0 .7rem; }
|
|
181
|
-
.askRow input { flex: 1; min-width: 0; font-family:
|
|
182
|
-
#answer { font-size: .9rem; white-space: pre-wrap; word-break: break-word; padding: .55rem .7rem; border-radius:
|
|
229
|
+
.askRow input { flex: 1; min-width: 0; font-family: inherit; font-size: .92rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: .45rem .7rem; }
|
|
230
|
+
#answer { font-size: .9rem; white-space: pre-wrap; word-break: break-word; padding: .55rem .7rem; border-radius: 6px; background: var(--bg); border: 1px solid var(--line); min-height: 1.4rem; }
|
|
183
231
|
#answer.miss { color: var(--muted); border-style: dashed; }
|
|
184
232
|
.sourcesHead { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; margin: .2rem 0 .5rem; }
|
|
185
233
|
.sourcesHead .toggleAll { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); border: 1px solid var(--line); border-radius: 4px; padding: .12rem .5rem; background: var(--bg); }
|
|
186
234
|
|
|
235
|
+
/* scope by source: a filterable table — a header row names the columns,
|
|
236
|
+
then one row per source with its own checkbox, count, and history. */
|
|
237
|
+
.sourceTableHead { display: flex; justify-content: space-between; padding: 0 .3rem .3rem; font-family: ${MONO_STACK}; font-size: .6rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); border-bottom: 1px solid var(--line); margin-bottom: .2rem; }
|
|
187
238
|
details.source { border-bottom: 1px solid var(--line); }
|
|
188
239
|
details.source:last-of-type { border-bottom: none; }
|
|
189
|
-
details.source > summary { list-style: none; display: flex; align-items: center; gap: .5rem; padding: .35rem 0; cursor: pointer; font-family: ${MONO_STACK}; font-size: .78rem; }
|
|
240
|
+
details.source > summary { list-style: none; display: flex; align-items: center; gap: .5rem; padding: .35rem .3rem; margin: 0 -.3rem; border-radius: 4px; cursor: pointer; font-family: ${MONO_STACK}; font-size: .78rem; }
|
|
241
|
+
details.source > summary:hover { background: var(--line); }
|
|
190
242
|
details.source > summary::-webkit-details-marker { display: none; }
|
|
191
243
|
details.source > summary .srcLabel { flex: 1; display: inline-flex; align-items: center; gap: .4rem; }
|
|
192
244
|
details.source > summary input { margin: 0; accent-color: var(--corpus); }
|
|
@@ -201,23 +253,33 @@ ${THEME_TOKENS_CSS}
|
|
|
201
253
|
@media (max-width: 820px) {
|
|
202
254
|
.grow { grid-template-columns: 1fr; }
|
|
203
255
|
.cols { grid-template-columns: 1fr; }
|
|
256
|
+
.chip.tapchip { grid-template-columns: 1.5rem minmax(0,1fr) 3.2rem; }
|
|
257
|
+
.chip.tapchip .track { display: none; }
|
|
204
258
|
}
|
|
205
259
|
@media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; } }
|
|
260
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
261
|
+
.fact, .chip.tapchip, details.source > summary { transition: background-color .1s ease; }
|
|
262
|
+
}
|
|
206
263
|
</style>
|
|
207
264
|
</head>
|
|
208
265
|
<body>
|
|
209
266
|
<div class="wrap">
|
|
210
267
|
<header class="topbar">
|
|
211
268
|
<div class="brand">
|
|
212
|
-
<span class="eyebrow">the-mechanical-code-talker</span>
|
|
213
|
-
<span class="subtitle">
|
|
269
|
+
<span class="eyebrow">the-mechanical-code-talker · research</span>
|
|
270
|
+
<span class="subtitle">Grow one graph three ways. Watch what it learns, then ask a question scoped to the sources you trust.</span>
|
|
271
|
+
</div>
|
|
272
|
+
<div class="statuspanel" id="statusPanel" aria-live="polite">
|
|
273
|
+
<div class="stat stat-facts"><span class="stat-label">facts in the graph</span><span class="stat-value" id="statFacts">—</span></div>
|
|
274
|
+
<div class="stat"><span class="stat-label">seed</span><span class="stat-value" id="statSeed">—</span></div>
|
|
275
|
+
<div class="stat"><span class="stat-label">engine</span><span class="stat-value" id="statEngine">booting…</span></div>
|
|
214
276
|
</div>
|
|
215
|
-
<span class="status" id="status">loading the engine…</span>
|
|
216
277
|
</header>
|
|
278
|
+
<div class="enginenote" id="engineNote" hidden></div>
|
|
217
279
|
|
|
218
280
|
<h2 class="band">grow the graph</h2>
|
|
219
281
|
<section class="grow">
|
|
220
|
-
<div class="card">
|
|
282
|
+
<div class="card card-research">
|
|
221
283
|
<h3><span class="tone tone-research"></span>research a term</h3>
|
|
222
284
|
<p class="hint">Fetches the topic from Simple English Wikipedia and stores the facts it grounds, then queues the topics its lead section links to. Asking is the consent for these fetches.</p>
|
|
223
285
|
<div class="row">
|
|
@@ -236,24 +298,24 @@ ${THEME_TOKENS_CSS}
|
|
|
236
298
|
</div>
|
|
237
299
|
<p class="note" id="researchNote"></p>
|
|
238
300
|
</div>
|
|
239
|
-
<div class="card">
|
|
301
|
+
<div class="card card-taught">
|
|
240
302
|
<h3><span class="tone tone-taught"></span>teach by telling</h3>
|
|
241
|
-
<p class="hint">Type a plain fact and it
|
|
303
|
+
<p class="hint">Type a plain fact and it's stored if the recognizer can ground it, for example “a beagle is a kind of dog” or “a dog has a tail”. An unrecognized sentence is skipped.</p>
|
|
242
304
|
<div class="row">
|
|
243
305
|
<input id="teachInput" type="text" autocomplete="off" spellcheck="false" placeholder="a beagle is a kind of dog" aria-label="A fact to teach">
|
|
244
306
|
<button type="button" class="btn primary" id="teachGo" disabled>teach</button>
|
|
245
307
|
</div>
|
|
246
308
|
<p class="note" id="teachNote"></p>
|
|
247
309
|
</div>
|
|
248
|
-
<div class="card">
|
|
310
|
+
<div class="card card-ingest">
|
|
249
311
|
<h3><span class="tone tone-ingest"></span>ingest documents</h3>
|
|
250
|
-
<p class="hint">Paste or drop text
|
|
312
|
+
<p class="hint">Paste or drop text. It keeps only the sentences it can ground as facts and skips the rest, using the same recognizer the ingest page runs.</p>
|
|
251
313
|
<textarea id="ingestText" spellcheck="false" placeholder="Paste a paragraph or drop a .txt/.md file here." aria-label="Text to ingest"></textarea>
|
|
252
314
|
<div class="row">
|
|
253
315
|
<button type="button" class="btn primary" id="ingestGo" disabled>ingest</button>
|
|
254
316
|
<button type="button" class="btn" id="ingestBrowse">browse…</button>
|
|
255
317
|
<input type="file" id="ingestFile" accept=".txt,.md,text/plain,text/markdown" hidden>
|
|
256
|
-
<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, tagged optimistic-extract
|
|
318
|
+
<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, tagged optimistic-extract, below every curated source.">
|
|
257
319
|
<input type="checkbox" id="fuzzyToggle"> fuzzy tier
|
|
258
320
|
</label>
|
|
259
321
|
</div>
|
|
@@ -275,7 +337,7 @@ ${THEME_TOKENS_CSS}
|
|
|
275
337
|
|
|
276
338
|
<h2 class="band">read a term back</h2>
|
|
277
339
|
<div class="panel digestpanel">
|
|
278
|
-
<p class="hint">Pick a term the graph knows
|
|
340
|
+
<p class="hint">Pick a term the graph knows. It reads back a short narrative built from the facts it holds, with each source named. The narrative is deterministic and never a guess. The full fact list stays one click away behind “show the facts”.</p>
|
|
279
341
|
<div class="askRow">
|
|
280
342
|
<input id="digestInput" type="text" autocomplete="off" spellcheck="false" placeholder="a term the graph knows, e.g. dog" aria-label="A term to read back as a digest" disabled>
|
|
281
343
|
<button type="button" class="btn primary" id="digestGo" disabled>digest</button>
|
|
@@ -298,6 +360,7 @@ ${THEME_TOKENS_CSS}
|
|
|
298
360
|
<h3 style="margin:0">scope by source</h3>
|
|
299
361
|
<button type="button" class="toggleAll" id="toggleAll">all / none</button>
|
|
300
362
|
</div>
|
|
363
|
+
<div class="sourceTableHead" aria-hidden="true"><span>source</span><span>facts</span></div>
|
|
301
364
|
<div id="sourcesList"><p class="empty">loading sources…</p></div>
|
|
302
365
|
</div>
|
|
303
366
|
</div>
|
|
@@ -326,7 +389,21 @@ ${THEME_TOKENS_CSS}
|
|
|
326
389
|
|
|
327
390
|
if ("serviceWorker" in navigator) navigator.serviceWorker.register("./tmct-sw.js").catch(() => {});
|
|
328
391
|
|
|
329
|
-
|
|
392
|
+
// The header's stat panel replaces one status line: "seed" carries the
|
|
393
|
+
// fact-count readout (boot progress, then the final count), "engine"
|
|
394
|
+
// carries the wink-nlp load result. engineNote is a separate banner for
|
|
395
|
+
// the one case that needs more room than a stat value: the whole engine
|
|
396
|
+
// bundle failing to load.
|
|
397
|
+
// The build's own content hash for the seed. It rides in the URL this page
|
|
398
|
+
// fetches the seed by, so the service worker's cache-first read can only
|
|
399
|
+
// ever return the copy this page asked for. Empty in a build with no seed.
|
|
400
|
+
const SEED_STAMP = ${JSON.stringify(seedStamp)};
|
|
401
|
+
const SEED_QUERY = SEED_STAMP ? "?b=" + SEED_STAMP : "";
|
|
402
|
+
|
|
403
|
+
const statFactsEl = el("statFacts");
|
|
404
|
+
const statSeedEl = el("statSeed");
|
|
405
|
+
const statEngineEl = el("statEngine");
|
|
406
|
+
const engineNoteEl = el("engineNote");
|
|
330
407
|
let session = null;
|
|
331
408
|
let researchQueue = null; // the engine's latest research snapshot, null when no run stands
|
|
332
409
|
const checkedSources = new Set(); // source keys currently checked for the ask
|
|
@@ -337,7 +414,7 @@ ${THEME_TOKENS_CSS}
|
|
|
337
414
|
let progressActive = true;
|
|
338
415
|
function noteProgress(key, loaded, total) {
|
|
339
416
|
progressParts[key] = { loaded: loaded, total: total };
|
|
340
|
-
if (progressActive)
|
|
417
|
+
if (progressActive) statEngineEl.textContent = loadProgressLine(Object.values(progressParts));
|
|
341
418
|
}
|
|
342
419
|
|
|
343
420
|
async function tryLoadWink() {
|
|
@@ -359,7 +436,7 @@ ${THEME_TOKENS_CSS}
|
|
|
359
436
|
let seedFacts = 0;
|
|
360
437
|
async function fetchSeed() {
|
|
361
438
|
try {
|
|
362
|
-
const blob = await fetchWithProgress("./chat-seed.json", (loaded, total) => noteProgress("seed", loaded, total));
|
|
439
|
+
const blob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, (loaded, total) => noteProgress("seed", loaded, total));
|
|
363
440
|
seedPayload = JSON.parse(await blob.text());
|
|
364
441
|
seedFacts = (seedPayload.individuals || []).filter((i) => i.class === "Fact").length;
|
|
365
442
|
} catch (err) {
|
|
@@ -398,15 +475,19 @@ ${THEME_TOKENS_CSS}
|
|
|
398
475
|
|
|
399
476
|
async function boot() {
|
|
400
477
|
if (!window.tmctResearch) {
|
|
401
|
-
|
|
478
|
+
engineNoteEl.hidden = false;
|
|
479
|
+
engineNoteEl.textContent = "The research engine did not load. Run npm run demo:build, then reload this page.";
|
|
480
|
+
statSeedEl.textContent = "—";
|
|
481
|
+
statEngineEl.textContent = "unavailable";
|
|
402
482
|
return;
|
|
403
483
|
}
|
|
404
484
|
const [winkStatus] = await Promise.all([tryLoadWink(), fetchSeed()]);
|
|
405
485
|
progressActive = false;
|
|
406
486
|
window.tmctResearch.registerReferencePackProvider(fetchPackProvider);
|
|
407
487
|
session = newSession();
|
|
408
|
-
const winkPart = winkStatus === "loaded" ? "wink-nlp
|
|
409
|
-
|
|
488
|
+
const winkPart = winkStatus === "loaded" ? "wink-nlp loaded" : "wink-nlp unavailable, curated tiers only";
|
|
489
|
+
statSeedEl.textContent = seedPayload ? seedFacts.toLocaleString() + " facts" : "no seed";
|
|
490
|
+
statEngineEl.textContent = winkPart + " · ready";
|
|
410
491
|
enableInputs();
|
|
411
492
|
await refresh();
|
|
412
493
|
}
|
|
@@ -428,6 +509,8 @@ ${THEME_TOKENS_CSS}
|
|
|
428
509
|
let snap;
|
|
429
510
|
try { snap = await window.tmctResearch.researchSnapshot(session.memoryDir, session.sessionIds); }
|
|
430
511
|
catch (err) { console.warn("tmct research: snapshot failed", err); return; }
|
|
512
|
+
const total = (snap.sources || []).reduce((sum, source) => sum + (Number(source.count) || 0), 0);
|
|
513
|
+
statFactsEl.textContent = total.toLocaleString();
|
|
431
514
|
renderRecent(snap.recent);
|
|
432
515
|
renderHubs(snap.hubs);
|
|
433
516
|
renderSources(snap.sources, snap.history);
|
|
@@ -459,6 +542,11 @@ ${THEME_TOKENS_CSS}
|
|
|
459
542
|
for (const fact of recent) box.appendChild(factRow(fact));
|
|
460
543
|
}
|
|
461
544
|
|
|
545
|
+
// A top-N ranked bar list, styled after ledger.html's own predicate.top
|
|
546
|
+
// panel so the two pages' widgets read as siblings. Each row keeps the
|
|
547
|
+
// page's "chip tapchip" contract (its click-to-digest behaviour and its
|
|
548
|
+
// own e2e coverage key off those two classes); the rank/track/fill bar
|
|
549
|
+
// rides inside it.
|
|
462
550
|
function renderHubs(hubs) {
|
|
463
551
|
const box = el("hubsList");
|
|
464
552
|
box.textContent = "";
|
|
@@ -468,19 +556,24 @@ ${THEME_TOKENS_CSS}
|
|
|
468
556
|
box.appendChild(p);
|
|
469
557
|
return;
|
|
470
558
|
}
|
|
471
|
-
|
|
559
|
+
const max = hubs.reduce((m, h) => Math.max(m, h.degree), 0) || 1;
|
|
560
|
+
hubs.forEach((hub, i) => {
|
|
472
561
|
const chip = document.createElement("button");
|
|
473
562
|
chip.type = "button";
|
|
474
563
|
chip.className = "chip tapchip";
|
|
475
564
|
chip.setAttribute("aria-label", "read a digest of " + hub.term);
|
|
476
|
-
|
|
565
|
+
const rank = document.createElement("span"); rank.className = "rank"; rank.textContent = String(i + 1).padStart(2, "0");
|
|
566
|
+
const term = document.createElement("span"); term.className = "term"; term.textContent = hub.term;
|
|
567
|
+
const track = document.createElement("span"); track.className = "track";
|
|
568
|
+
const fill = document.createElement("span"); fill.className = "fill"; fill.style.width = ((hub.degree / max) * 100).toFixed(1) + "%";
|
|
569
|
+
track.appendChild(fill);
|
|
477
570
|
const deg = document.createElement("span");
|
|
478
571
|
deg.className = "deg";
|
|
479
572
|
deg.textContent = hub.degree + (hub.degree === 1 ? " fact" : " facts");
|
|
480
|
-
chip.appendChild(deg);
|
|
573
|
+
chip.appendChild(rank); chip.appendChild(term); chip.appendChild(track); chip.appendChild(deg);
|
|
481
574
|
chip.addEventListener("click", () => { el("digestInput").value = hub.term; digest(); });
|
|
482
575
|
box.appendChild(chip);
|
|
483
|
-
}
|
|
576
|
+
});
|
|
484
577
|
}
|
|
485
578
|
|
|
486
579
|
function renderSources(sources, history) {
|
|
@@ -499,7 +592,7 @@ ${THEME_TOKENS_CSS}
|
|
|
499
592
|
}
|
|
500
593
|
if (!sources || !sources.length) {
|
|
501
594
|
const p = document.createElement("p"); p.className = "empty";
|
|
502
|
-
p.textContent = "No facts yet
|
|
595
|
+
p.textContent = "No facts yet. Grow the graph above, then scope your question here.";
|
|
503
596
|
box.appendChild(p);
|
|
504
597
|
return;
|
|
505
598
|
}
|
|
@@ -569,7 +662,7 @@ ${THEME_TOKENS_CSS}
|
|
|
569
662
|
const answerEl = el("answer");
|
|
570
663
|
if (boxes.length && checked.length === 0) {
|
|
571
664
|
answerEl.className = "miss";
|
|
572
|
-
answerEl.textContent = "No source is checked
|
|
665
|
+
answerEl.textContent = "No source is checked. Check at least one on the right to ask against it.";
|
|
573
666
|
return;
|
|
574
667
|
}
|
|
575
668
|
answerEl.className = "";
|
|
@@ -605,7 +698,7 @@ ${THEME_TOKENS_CSS}
|
|
|
605
698
|
box.textContent = "";
|
|
606
699
|
if (!view) {
|
|
607
700
|
const p = document.createElement("p"); p.className = "miss";
|
|
608
|
-
p.textContent = 'No grounded digest for "' + term + '"
|
|
701
|
+
p.textContent = 'No grounded digest for "' + term + '". Nothing is stored about it, or nothing the digest could compose. It abstains rather than guess.';
|
|
609
702
|
box.appendChild(p);
|
|
610
703
|
return;
|
|
611
704
|
}
|
|
@@ -659,7 +752,7 @@ ${THEME_TOKENS_CSS}
|
|
|
659
752
|
note.textContent = "stored: " + (res.answer || "remembered.");
|
|
660
753
|
el("teachInput").value = "";
|
|
661
754
|
} else {
|
|
662
|
-
note.textContent = res && res.answer ? res.answer : "not a recognized fact shape
|
|
755
|
+
note.textContent = res && res.answer ? res.answer : "not a recognized fact shape, nothing stored.";
|
|
663
756
|
}
|
|
664
757
|
await refresh();
|
|
665
758
|
}
|
|
@@ -711,9 +804,9 @@ ${THEME_TOKENS_CSS}
|
|
|
711
804
|
const depth = researchQueue.maxDepth || 1;
|
|
712
805
|
const budget = researchQueue.maxTopics || 0;
|
|
713
806
|
const knobs = " (depth " + depth + (budget ? ", budget " + budget : "") + ")";
|
|
714
|
-
const capped = researchQueue.nodeCapReached ? "
|
|
807
|
+
const capped = researchQueue.nodeCapReached ? " · node budget reached" : "";
|
|
715
808
|
if (researchQueue.complete) {
|
|
716
|
-
note.textContent = 'research "' + researchQueue.topic + '" complete
|
|
809
|
+
note.textContent = 'research "' + researchQueue.topic + '" complete · '
|
|
717
810
|
+ researchQueue.done.length + " topic" + (researchQueue.done.length === 1 ? "" : "s") + " grounded" + knobs + capped + ".";
|
|
718
811
|
} else {
|
|
719
812
|
note.textContent = 'research "' + researchQueue.topic + '": '
|
|
@@ -793,7 +886,8 @@ ${THEME_TOKENS_CSS}
|
|
|
793
886
|
|
|
794
887
|
window.tmctResearchReady = boot().catch((err) => {
|
|
795
888
|
console.error("tmct research failed to boot", err);
|
|
796
|
-
|
|
889
|
+
engineNoteEl.hidden = false;
|
|
890
|
+
engineNoteEl.textContent = "The research page failed to start (" + (err && err.message ? err.message : err) + ").";
|
|
797
891
|
});
|
|
798
892
|
})();
|
|
799
893
|
</script>
|
|
@@ -73,7 +73,7 @@ const SPIDER_FLY_TOLD_RE = new RegExp(
|
|
|
73
73
|
const SPIDER_FLY_SEE_RE = /^what (?:does|can) the (spider|fly)(?:-(\d+))?\s+see[.!?\s]*$/i;
|
|
74
74
|
|
|
75
75
|
const WORLD_OPENING_FALLBACK =
|
|
76
|
-
"a spider waits in its web; a fly drifts in from the edge of the board. Neither is yours to move
|
|
76
|
+
"a spider waits in its web; a fly drifts in from the edge of the board. Neither is yours to move. Watch, or address one by name in chat.";
|
|
77
77
|
|
|
78
78
|
// ---- the opening turn: load the shipped board through the worlds pack -------
|
|
79
79
|
|