@ara-commons/ara-skills 0.3.0 → 0.4.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.
- package/README.md +3 -2
- package/package.json +3 -2
- package/skills/compiler/SKILL.md +63 -11
- package/skills/compiler/references/ara-schema.md +94 -22
- package/skills/compiler/references/validation-checklist.md +32 -8
- package/skills/research-manager/SKILL.md +33 -8
- package/skills/research-manager/references/event-taxonomy.md +1 -1
- package/skills/research-visualizer/SKILL.md +172 -0
- package/skills/research-visualizer/references/binding.md +245 -0
- package/skills/research-visualizer/references/parsing.md +211 -0
- package/skills/research-visualizer/references/trajectory-template.html +804 -0
- package/skills/rigor-reviewer/SKILL.md +1 -0
- package/skills/rigor-reviewer/references/review-dimensions.md +1 -0
- package/src/index.js +1 -1
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>ARA Trajectory</title>
|
|
7
|
+
<!--
|
|
8
|
+
ARA Trajectory Viewer — canonical self-contained scaffold for the `research-visualizer` skill.
|
|
9
|
+
|
|
10
|
+
HOW THE SKILL USES THIS FILE
|
|
11
|
+
----------------------------
|
|
12
|
+
The rendering logic below is FIXED. The agent does NOT rewrite it. The agent only:
|
|
13
|
+
1. Parses the ARA into one ARA_DATA object (schema in references/binding.md).
|
|
14
|
+
2. base64-inlines every referenced figure .png into node.result.figures[].img.
|
|
15
|
+
3. Replaces ONLY the JSON between the BEGIN/END markers in the <script id="ara-data"> block.
|
|
16
|
+
4. Writes the result to <ara>/trajectory.html (or --output).
|
|
17
|
+
Everything is inline (no CDN, no fetch, no server) so the output is one portable file.
|
|
18
|
+
|
|
19
|
+
INJECTION CONTRACT
|
|
20
|
+
------------------
|
|
21
|
+
- Replace exactly the lines between __ARA_DATA_BEGIN__ and __ARA_DATA_END__.
|
|
22
|
+
- The payload MUST be valid JSON (it is read with JSON.parse).
|
|
23
|
+
- JSON must not contain the literal substring "</script>"; escape any "<" in inlined
|
|
24
|
+
markdown/text as "<" to be safe.
|
|
25
|
+
- The demo payload shipped here renders a 3-node sample so the bare template opens and
|
|
26
|
+
shows every node state (experiment, dead_end, isolated). The skill overwrites it.
|
|
27
|
+
-->
|
|
28
|
+
<style>
|
|
29
|
+
/* Research Visualizer — "field notebook of a machine scientist".
|
|
30
|
+
Cool archival vellum + ink; monospace is the machine-readout voice for
|
|
31
|
+
structure / IDs / data, a humanist serif carries the reasoning prose. The
|
|
32
|
+
signature is a traced trajectory spine that forks at branches and is struck
|
|
33
|
+
through at dead ends. Type is read from glyph + label; colour is reserved
|
|
34
|
+
for true epistemic status (teal = chrome, green = supported, amber =
|
|
35
|
+
tentative, clay-red = dead end) — never decoration. */
|
|
36
|
+
:root{
|
|
37
|
+
/* paper & ink — deliberately cool vellum, NOT warm cream */
|
|
38
|
+
--bg:#e9ece4; --panel:#f4f6f0; --panel2:#e3e7dd; --line:#ccd3c7; --ink:#1b2420;
|
|
39
|
+
--muted:#67726a; --faint:#8a948b; --accent:#0e6b62; --accent-ink:#0a4f48; --chip:#e6eae0;
|
|
40
|
+
--ok:#3a6a39; --warn:#a6402c; --tentative:#8a6310; --glyph-bg:#e0e5da; --glyph-ink:#4a544c;
|
|
41
|
+
--sel-bg:#e3ede7; --code-bg:#eaeee6; --reason-bg:#f4f6f0;
|
|
42
|
+
--iso-line:#b7c0ae; --iso-bg:#e5eadf; --iso-ink:#5f6b58;
|
|
43
|
+
--scrim:rgba(20,28,24,.42); --shadow:rgba(20,28,24,.16);
|
|
44
|
+
--add-bg:#e0ebdb; --add-ink:#2f5a2e; --del-bg:#f1e0da; --del-ink:#8c3623; --hunk:#67726a;
|
|
45
|
+
--spine:#c3ccbd; --grid:rgba(27,36,32,.05);
|
|
46
|
+
/* legacy type vars retained so any leftover hooks resolve */
|
|
47
|
+
--q:var(--glyph-bg); --exp:var(--glyph-bg); --dec:var(--glyph-bg); --piv:var(--glyph-bg); --ins:var(--glyph-bg); --def:var(--glyph-bg); --dead:var(--warn);
|
|
48
|
+
/* type roles: a serif reads the prose, monospace reads the machine */
|
|
49
|
+
--serif:ui-serif,"New York","Iowan Old Style",Palatino,Georgia,serif;
|
|
50
|
+
--sans:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
51
|
+
--mono:ui-monospace,"SF Mono","JetBrains Mono","Cascadia Code",Menlo,Consolas,monospace;
|
|
52
|
+
}
|
|
53
|
+
*{box-sizing:border-box}
|
|
54
|
+
html,body{margin:0;height:100%}
|
|
55
|
+
body{background:var(--bg);color:var(--ink);font:14px/1.65 var(--sans);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
|
56
|
+
code,pre,.mono{font-family:var(--mono)}
|
|
57
|
+
a{color:var(--accent);text-decoration:none}
|
|
58
|
+
a:hover{text-decoration:underline}
|
|
59
|
+
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
|
|
60
|
+
|
|
61
|
+
/* ---- masthead ---- */
|
|
62
|
+
header{padding:20px 24px 14px;border-bottom:1px solid var(--line);background:var(--panel)}
|
|
63
|
+
header .title{font-family:var(--serif);font-size:23px;font-weight:600;margin:0;letter-spacing:-.012em}
|
|
64
|
+
header .sub{font-family:var(--mono);color:var(--muted);font-size:11.5px;margin-top:5px;letter-spacing:.01em}
|
|
65
|
+
header details{margin-top:10px;color:var(--ink)}
|
|
66
|
+
header summary{cursor:pointer;color:var(--accent-ink);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.08em}
|
|
67
|
+
.toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:16px}
|
|
68
|
+
.toolbar input[type=search],.toolbar select{background:var(--bg);border:1px solid var(--line);color:var(--ink);border-radius:6px;padding:6px 10px;font-size:13px;font-family:var(--sans)}
|
|
69
|
+
.toolbar input[type=search]{min-width:230px}
|
|
70
|
+
.toolbar input[type=search]:focus,.toolbar select:focus{border-color:var(--accent);outline:none}
|
|
71
|
+
.toolbar label{color:var(--muted);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;display:flex;gap:6px;align-items:center}
|
|
72
|
+
.btn{background:var(--bg);border:1px solid var(--line);color:var(--ink);border-radius:6px;padding:6px 11px;font-size:12.5px;font-family:var(--mono);cursor:pointer;transition:border-color .12s,color .12s,background .12s}
|
|
73
|
+
.btn:hover{border-color:var(--accent);color:var(--accent-ink)}
|
|
74
|
+
.btn.primary{border-color:var(--accent);color:#fff;background:var(--accent)}
|
|
75
|
+
.btn.primary:hover{background:var(--accent-ink)}
|
|
76
|
+
.spacer{flex:1}
|
|
77
|
+
.count{color:var(--muted);font-family:var(--mono);font-size:11px;letter-spacing:.04em;padding-left:10px;border-left:1px solid var(--line)}
|
|
78
|
+
|
|
79
|
+
main{display:grid;grid-template-columns:minmax(300px,36%) 1fr;height:calc(100% - 0px);min-height:0}
|
|
80
|
+
#map{overflow:auto;border-right:1px solid var(--line);padding:18px 12px 56px;background:var(--bg);
|
|
81
|
+
background-image:linear-gradient(var(--grid) 1px,transparent 1px),linear-gradient(90deg,var(--grid) 1px,transparent 1px);
|
|
82
|
+
background-size:24px 24px;background-position:-1px -1px}
|
|
83
|
+
#detail{overflow:auto;padding:26px 32px 80px;background:var(--bg)}
|
|
84
|
+
|
|
85
|
+
/* ---- process map: the trajectory spine is the signature ---- */
|
|
86
|
+
.node{position:relative;display:flex;gap:10px;align-items:flex-start;padding:7px 9px;border-radius:6px;cursor:pointer;border:1px solid transparent}
|
|
87
|
+
.node:hover{background:var(--panel2)}
|
|
88
|
+
.node.sel{background:var(--sel-bg);border-color:var(--accent)}
|
|
89
|
+
.node.deptarget{background:var(--sel-bg);outline:1px dashed var(--accent);outline-offset:-1px}
|
|
90
|
+
.node.dim{opacity:.32}
|
|
91
|
+
.node .glyph{flex:none;width:20px;height:20px;border-radius:5px;display:grid;place-items:center;font-family:var(--mono);font-size:11px;font-weight:700;color:var(--glyph-ink);background:var(--glyph-bg);border:1px solid var(--line);margin-top:1px}
|
|
92
|
+
.node.sel .glyph{border-color:var(--accent);color:var(--accent-ink)}
|
|
93
|
+
.node .nid{color:var(--muted);font-size:10.5px;font-family:var(--mono);letter-spacing:.02em}
|
|
94
|
+
.node .ntitle{font-size:13px;line-height:1.45}
|
|
95
|
+
.node.dead .ntitle{color:var(--warn);text-decoration:line-through;text-decoration-color:rgba(166,64,44,.45)}
|
|
96
|
+
.node .meta{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
|
97
|
+
/* the spine + branch connectors: nested steps trace back to their parent */
|
|
98
|
+
.kid{margin-left:19px;border-left:2px solid var(--spine);padding-left:14px}
|
|
99
|
+
.kid>.node::before{content:"";position:absolute;left:-14px;top:14px;width:10px;height:2px;background:var(--spine)}
|
|
100
|
+
.kid>.node.sel::before{background:var(--accent)}
|
|
101
|
+
.isobox{border:1px dashed var(--iso-line);border-radius:8px;margin:12px 2px;padding:6px 4px;background:var(--iso-bg)}
|
|
102
|
+
.isobox .isohdr{color:var(--iso-ink);font-family:var(--mono);font-size:10px;padding:3px 8px 7px;text-transform:uppercase;letter-spacing:.1em}
|
|
103
|
+
.dep{color:var(--muted);font-size:10px;font-family:var(--mono);border:1px solid var(--line);border-radius:4px;padding:0 5px}
|
|
104
|
+
|
|
105
|
+
.glyph.dead_end{background:var(--warn);color:#fff;border-color:var(--warn)}
|
|
106
|
+
|
|
107
|
+
/* ---- detail pane ---- */
|
|
108
|
+
.dhead{display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-bottom:2px}
|
|
109
|
+
.badge{font-family:var(--mono);font-size:10px;font-weight:700;border-radius:4px;padding:3px 9px;color:var(--glyph-ink);background:var(--glyph-bg);border:1px solid var(--line);text-transform:uppercase;letter-spacing:.08em}
|
|
110
|
+
.badge.dead_end{background:var(--warn);color:#fff;border-color:var(--warn)}
|
|
111
|
+
.pill{font-family:var(--mono);font-size:10px;border:1px solid var(--line);border-radius:20px;padding:2px 9px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
|
|
112
|
+
.pill.explicit{border-color:var(--ok);color:var(--ok)} .pill.inferred{border-color:var(--tentative);color:var(--tentative)}
|
|
113
|
+
.pill.iso{border-color:var(--iso-line);color:var(--iso-ink)}
|
|
114
|
+
.dtitle{font-family:var(--serif);font-size:25px;font-weight:600;margin:12px 0 6px;letter-spacing:-.012em;line-height:1.28}
|
|
115
|
+
.did{color:var(--muted);font-family:var(--mono);font-size:11.5px}
|
|
116
|
+
|
|
117
|
+
.block{border:1px solid var(--line);border-radius:7px;margin:15px 0;background:var(--panel)}
|
|
118
|
+
.block>summary{cursor:pointer;padding:11px 15px;font-family:var(--mono);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--muted);list-style:none;display:flex;align-items:center;gap:8px}
|
|
119
|
+
.block>summary::-webkit-details-marker{display:none}
|
|
120
|
+
.block>summary:hover{color:var(--accent-ink)}
|
|
121
|
+
.block>summary .tag{margin-left:auto;color:var(--faint);font-size:10.5px;font-weight:400;font-family:var(--mono);text-transform:none;letter-spacing:0}
|
|
122
|
+
.block .body{padding:4px 16px 16px}
|
|
123
|
+
/* the primary reasoning block: the agent's thinking, set as typeset prose */
|
|
124
|
+
.block.reason{border-left:3px solid var(--accent);background:var(--reason-bg)}
|
|
125
|
+
.block.reason>summary{color:var(--accent-ink)}
|
|
126
|
+
.block.reason .lead{font-family:var(--serif);font-size:17px;line-height:1.7;color:var(--ink)}
|
|
127
|
+
.didwhat{color:var(--muted);font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.09em;margin:16px 0 4px}
|
|
128
|
+
.lead{font-family:var(--serif);color:#2a322c;font-size:16px;line-height:1.7}
|
|
129
|
+
.chips{display:flex;gap:6px;flex-wrap:wrap;margin-top:11px}
|
|
130
|
+
.chip{background:var(--chip);border:1px solid var(--line);border-radius:4px;padding:2px 8px;font-size:11px;color:#46504a;font-family:var(--mono)}
|
|
131
|
+
.chip.ext{color:var(--muted)}
|
|
132
|
+
|
|
133
|
+
.claim{border:1px solid var(--line);border-left:2px solid var(--accent);border-radius:0 6px 6px 0;padding:11px 13px;margin:11px 0;background:var(--panel2)}
|
|
134
|
+
.claim h4{margin:0 0 6px;font-family:var(--mono);font-size:12px}
|
|
135
|
+
.claim .kv{font-size:13px;line-height:1.6;margin:4px 0;color:#3a423c}
|
|
136
|
+
.claim .kv b{color:var(--muted);font-weight:600;font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.05em}
|
|
137
|
+
.status{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.05em;border-radius:4px;padding:1px 7px;border:1px solid var(--line)}
|
|
138
|
+
.status.supported{color:var(--ok);border-color:var(--ok)}
|
|
139
|
+
.status.refuted{color:var(--warn);border-color:var(--warn)}
|
|
140
|
+
.status.hypothesis{color:var(--tentative);border-color:var(--tentative)}
|
|
141
|
+
.prov{font-size:11px;color:var(--iso-ink);font-family:var(--mono)}
|
|
142
|
+
|
|
143
|
+
.quote{border-left:3px solid var(--accent);background:var(--panel);border-radius:0 6px 6px 0;padding:9px 13px;margin:9px 0;font-size:13.5px;line-height:1.6;color:#2a322c}
|
|
144
|
+
.quote .ref{color:var(--muted);font-size:10.5px;font-family:var(--mono);margin-top:5px}
|
|
145
|
+
figure{margin:14px 0}
|
|
146
|
+
figure img{max-width:100%;border:1px solid var(--line);border-radius:5px;background:#fff;padding:6px}
|
|
147
|
+
figcaption{color:var(--muted);font-family:var(--mono);font-size:11px;margin-top:7px}
|
|
148
|
+
|
|
149
|
+
table.md{border-collapse:collapse;width:100%;font-family:var(--mono);font-size:12px;margin:10px 0}
|
|
150
|
+
table.md th,table.md td{border:1px solid var(--line);padding:5px 9px;text-align:left}
|
|
151
|
+
table.md th{background:var(--panel2);color:var(--ink);font-weight:700;text-transform:uppercase;font-size:10.5px;letter-spacing:.04em}
|
|
152
|
+
pre.snip{background:var(--code-bg);border:1px solid var(--line);border-radius:6px;padding:10px 12px;overflow:auto;font-size:12px;color:var(--ink);white-space:pre-wrap}
|
|
153
|
+
|
|
154
|
+
/* changed-code diff block */
|
|
155
|
+
pre.diff{background:var(--code-bg);border:1px solid var(--line);border-radius:6px;padding:8px 0;overflow:auto;font-size:12px;margin:11px 0;white-space:pre}
|
|
156
|
+
pre.diff .dl{display:block;padding:0 12px;border-left:3px solid transparent}
|
|
157
|
+
pre.diff .dl.add{background:var(--add-bg);color:var(--add-ink);border-left-color:var(--ok)}
|
|
158
|
+
pre.diff .dl.del{background:var(--del-bg);color:var(--del-ink);border-left-color:var(--warn)}
|
|
159
|
+
pre.diff .dl.hunk{color:var(--hunk);background:var(--panel2)}
|
|
160
|
+
pre.diff .dl.meta{color:var(--muted)}
|
|
161
|
+
pre.diff .dl.ctx{color:#3a423c}
|
|
162
|
+
|
|
163
|
+
.ptr{font-size:13px;margin:7px 0}
|
|
164
|
+
.ptr .path{font-family:var(--mono);color:#46504a}
|
|
165
|
+
.truncated{color:var(--muted);font-size:11.5px;font-style:italic}
|
|
166
|
+
.empty{color:var(--muted);font-size:13px}
|
|
167
|
+
.none{color:var(--muted);padding:40px 24px;text-align:center;font-family:var(--mono);font-size:12px;letter-spacing:.02em}
|
|
168
|
+
kbd{background:var(--panel2);border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:0 5px;font-family:var(--mono);font-size:11px}
|
|
169
|
+
|
|
170
|
+
/* ---- enrichment layers (all inert unless the data is present) ---- */
|
|
171
|
+
.layerbar{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}
|
|
172
|
+
.lbtn .cnt{color:var(--muted);font-size:10.5px}
|
|
173
|
+
.chiplabel{color:var(--muted);font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.08em;margin:12px 0 3px}
|
|
174
|
+
.chip.jump{cursor:pointer}
|
|
175
|
+
.chip.jump:hover{border-color:var(--accent);color:var(--accent-ink)}
|
|
176
|
+
.chip.dangle{color:#9aa49b;text-decoration:line-through;cursor:not-allowed}
|
|
177
|
+
.chip.infer{border-style:dashed}
|
|
178
|
+
.chip-built{border-color:var(--ok);color:var(--ok)}
|
|
179
|
+
.chip-rej{border-color:var(--warn);color:var(--warn)}
|
|
180
|
+
.chip-recipe{border-color:var(--accent);color:var(--accent-ink)}
|
|
181
|
+
.nmark{color:var(--muted);font-size:10px;margin-left:5px;font-family:var(--mono)}
|
|
182
|
+
.rel{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.05em;border-radius:4px;padding:2px 8px;color:var(--glyph-ink);background:var(--glyph-bg);font-weight:700}
|
|
183
|
+
.rel.bounds,.rel.refutes{background:var(--warn);color:#fff}
|
|
184
|
+
.rel.neutral{background:var(--chip);color:var(--ink);border:1px solid var(--line);font-weight:400}
|
|
185
|
+
.ribbon{border-left:3px solid var(--accent);background:var(--panel2);border-radius:0 6px 6px 0;padding:9px 13px;margin:9px 0;color:#3a423c;font-size:13px;line-height:1.6}
|
|
186
|
+
.warn{border-left:3px solid var(--warn);background:var(--del-bg);border-radius:0 6px 6px 0;padding:9px 13px;margin:9px 0;font-size:13px;line-height:1.6}
|
|
187
|
+
.muted{color:var(--muted);font-size:11.5px}
|
|
188
|
+
.math{font-family:var(--serif);font-style:italic}
|
|
189
|
+
.gterm{border-bottom:1px dashed var(--accent);cursor:help}
|
|
190
|
+
#gpop{position:fixed;max-width:360px;background:var(--panel);border:1px solid var(--line);border-radius:7px;padding:11px 13px;z-index:60;box-shadow:0 8px 28px var(--shadow);font-size:13px;line-height:1.55;display:none}
|
|
191
|
+
#gpop h5{margin:0 0 5px;font-family:var(--mono);font-size:12px;text-transform:uppercase;letter-spacing:.05em}
|
|
192
|
+
#gpop .gp-open{color:var(--accent);cursor:pointer;font-family:var(--mono);font-size:11px;margin-top:7px;display:inline-block}
|
|
193
|
+
.ov{position:fixed;inset:0;z-index:50}
|
|
194
|
+
.ov[hidden]{display:none}
|
|
195
|
+
.ov-scrim{position:absolute;inset:0;background:var(--scrim)}
|
|
196
|
+
.ov-sheet{position:relative;margin:5vh auto;max-width:920px;max-height:86vh;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 16px 48px var(--shadow)}
|
|
197
|
+
.ov-head{position:sticky;top:0;display:flex;gap:10px;align-items:center;padding:13px 17px;border-bottom:1px solid var(--line);background:var(--panel);z-index:1}
|
|
198
|
+
.ov-head #ov-title{font-family:var(--mono);font-weight:700;font-size:12px;text-transform:uppercase;letter-spacing:.09em;color:var(--accent-ink)}
|
|
199
|
+
.ov-head input[type=search]{flex:1;max-width:260px;background:var(--bg);border:1px solid var(--line);color:var(--ink);border-radius:6px;padding:5px 10px;font-size:13px}
|
|
200
|
+
.ov-body{padding:10px 17px 26px}
|
|
201
|
+
.ov-body .spacer{flex:1}
|
|
202
|
+
.card{border:1px solid var(--line);border-radius:6px;padding:11px 13px;margin:11px 0;background:var(--panel2)}
|
|
203
|
+
.card h4{margin:0 0 6px;font-family:var(--mono);font-size:12px}
|
|
204
|
+
.card .sub{color:var(--muted);font-family:var(--mono);font-size:10.5px}
|
|
205
|
+
.grey{opacity:.5}
|
|
206
|
+
|
|
207
|
+
/* responsive: stack the two panes on a narrow screen */
|
|
208
|
+
@media (max-width:720px){
|
|
209
|
+
main{grid-template-columns:1fr;grid-template-rows:42vh 1fr}
|
|
210
|
+
#map{border-right:none;border-bottom:1px solid var(--line)}
|
|
211
|
+
#detail{padding:20px 18px 64px}
|
|
212
|
+
.dtitle{font-size:21px}
|
|
213
|
+
}
|
|
214
|
+
/* honour reduced-motion */
|
|
215
|
+
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
<header>
|
|
220
|
+
<h1 class="title" id="h-title">ARA Trajectory</h1>
|
|
221
|
+
<div class="sub" id="h-sub"></div>
|
|
222
|
+
<details id="h-abstract-wrap"><summary>Abstract</summary><div class="lead" id="h-abstract"></div></details>
|
|
223
|
+
<div class="toolbar">
|
|
224
|
+
<input type="search" id="q" placeholder="search steps…" autocomplete="off" />
|
|
225
|
+
<label>type
|
|
226
|
+
<select id="ftype"><option value="">all</option></select>
|
|
227
|
+
</label>
|
|
228
|
+
<label><input type="checkbox" id="fdead" /> dead ends only</label>
|
|
229
|
+
<span class="spacer"></span>
|
|
230
|
+
<button class="btn" id="rprev" title="previous (←)">‹</button>
|
|
231
|
+
<button class="btn primary" id="rplay">▶ Replay</button>
|
|
232
|
+
<button class="btn" id="rnext" title="next (→)">›</button>
|
|
233
|
+
<span class="count" id="rstat"></span>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="layerbar" id="layerbar">
|
|
236
|
+
<button class="btn lbtn" data-panel="context" hidden>◧ Context</button>
|
|
237
|
+
<button class="btn lbtn" data-panel="glossary" hidden>▤ Glossary <span class="cnt"></span></button>
|
|
238
|
+
<button class="btn lbtn" data-panel="dependencies" hidden>⇄ Dependencies <span class="cnt"></span></button>
|
|
239
|
+
<button class="btn lbtn" data-panel="recipes" hidden>▦ Recipes <span class="cnt"></span></button>
|
|
240
|
+
</div>
|
|
241
|
+
</header>
|
|
242
|
+
|
|
243
|
+
<main>
|
|
244
|
+
<aside id="map"></aside>
|
|
245
|
+
<section id="detail"><div class="none">Select a step on the left, or press <kbd>▶ Replay</kbd>.</div></section>
|
|
246
|
+
</main>
|
|
247
|
+
|
|
248
|
+
<!-- v1.1: one shared modal overlay for the Context/Glossary/Dependencies/Recipes panels, + a glossary popover. Both inert unless their data is present. -->
|
|
249
|
+
<div id="overlay" class="ov" hidden role="dialog" aria-modal="true">
|
|
250
|
+
<div class="ov-scrim" data-close></div>
|
|
251
|
+
<div class="ov-sheet">
|
|
252
|
+
<div class="ov-head"><span id="ov-title"></span>
|
|
253
|
+
<input type="search" id="ov-q" placeholder="filter…" hidden autocomplete="off" />
|
|
254
|
+
<span class="spacer"></span>
|
|
255
|
+
<button class="btn" id="ov-close" data-close>✕ <kbd>Esc</kbd></button>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="ov-body" id="ov-body"></div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div id="gpop"></div>
|
|
261
|
+
|
|
262
|
+
<!-- The skill replaces ONLY the JSON between the two markers below. -->
|
|
263
|
+
<script id="ara-data" type="application/json">
|
|
264
|
+
/* __ARA_DATA_BEGIN__ */
|
|
265
|
+
{
|
|
266
|
+
"meta": {
|
|
267
|
+
"title": "Research Visualizer — template demo",
|
|
268
|
+
"authors": ["(demo data — replaced by the research-visualizer skill)"],
|
|
269
|
+
"year": "", "venue": "", "ara_dir": "",
|
|
270
|
+
"abstract": "This bare template ships with demo nodes so it opens standalone and shows the reasoning-first layout, a changed-code diff, an experiment, a dead end, and an isolated subtree. Running /research-visualizer on a real ARA overwrites this block."
|
|
271
|
+
},
|
|
272
|
+
"order": ["N01", "N02", "N03", "NV1"],
|
|
273
|
+
"artifacts": [
|
|
274
|
+
{ "id":"A01","name":"encode.py @ baseline","path":"src/encode.py","sha256":"0000…base","original_location":"repo@main:src/encode.py" },
|
|
275
|
+
{ "id":"A02","name":"encode.py @ variant","path":"src/encode.py","sha256":"1111…var","original_location":"repo@feat:src/encode.py" }
|
|
276
|
+
],
|
|
277
|
+
"nodes": [
|
|
278
|
+
{ "id":"N01","type":"question","parent":null,"title":"Is this template wired correctly end to end?","body":"","thinking":"Before trusting the renderer on real data, prove every node state (reasoning, diff, result, dead end, isolated) shows up from one fixed scaffold.","support_level":"explicit","isolated":false,"depends_on":[],"source_refs":["notes.md:1-4"],
|
|
279
|
+
"why":[], "result":{"sources":[],"figures":[],"tables":[],"data":[]}, "verified_by":[], "artifact":[] },
|
|
280
|
+
{ "id":"N02","type":"experiment","parent":"N01","title":"Precompute the field encoder once per type","body":"Replaced the per-call json.dumps path with a cached per-type encoder; output byte-identical.","thinking":"The profile said serialization dominates, and the dead end showed per-field reflection is the trap — so keep the hand-written encoding but pay its setup cost once, not per request.","support_level":"explicit","isolated":false,"depends_on":[],"source_refs":["notes.md:10-22"],
|
|
281
|
+
"code_change":{ "base_artifact":"A01","variant_artifact":"A02","lang":"python","diff":"@@ -1,4 +1,5 @@\n def encode(rec):\n- return json.dumps(rec)\n+ enc = encoder_for(type(rec))\n+ return enc(rec)" },
|
|
282
|
+
"why":[{"id":"C01","statement":"Paying per-type setup once instead of per call removes the hot-path cost without changing output.","status":"supported","conditions":"Holds when the type set is small and stable across calls.","falsification":"If amortized setup ever exceeds the per-call cost it replaces.","provenance":"ai-executed","dependencies":[]}],
|
|
283
|
+
"result":{
|
|
284
|
+
"sources":[{"quote":"p99 down 38%; output byte-identical to the baseline","ref":"figures/demo.md:14"}],
|
|
285
|
+
"figures":[{"id":"F1","caption":"Demo figure (1×1 transparent placeholder)","kind":"quantitative_plot","img":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="}],
|
|
286
|
+
"tables":[{"id":"T1","caption":"Demo table","markdown":"| path | p99 |\n|---|---|\n| baseline | 100% |\n| cached encoder | 62% |"}],
|
|
287
|
+
"data":[{"id":"D1","path":"evidence/data/demo.json","note":"raw data pointer"}]
|
|
288
|
+
},
|
|
289
|
+
"verified_by":[{"id":"E01","title":"Replay under load","run":"src/artifacts.md → demo store","setup":"n/a","metrics":"p99 latency"}],
|
|
290
|
+
"artifact":[{"name":"demo family","pointer":"data/runs/demo (N runs)","what":"pointer index entry (not resolved in v1)"}] },
|
|
291
|
+
{ "id":"N03","type":"dead_end","parent":"N01","title":"Reflection-based serializer","body":"","thinking":"I assumed a library would beat the hand-rolled path. It removed code but ran 1.4x slower — reflection moved work onto the hot path. 'Less code' is not 'less work'.","support_level":"inferred","isolated":false,"depends_on":[],"source_refs":[],
|
|
292
|
+
"why":[], "result":{"sources":[{"quote":"1.4x slower under load","ref":"notes.md:30"}],"figures":[],"tables":[],"data":[]}, "verified_by":[], "artifact":[] },
|
|
293
|
+
{ "id":"NV1","type":"experiment","parent":null,"title":"Side quest: HTTP/2 multiplexing","body":"","thinking":"Curiosity branch, kept off the main line because it doesn't bear on the serialization hotspot.","support_level":"explicit","isolated":true,"depends_on":["N02"],"source_refs":[],
|
|
294
|
+
"why":[], "result":{"sources":[],"figures":[],"tables":[],"data":[]}, "verified_by":[], "artifact":[] }
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
/* __ARA_DATA_END__ */
|
|
298
|
+
</script>
|
|
299
|
+
|
|
300
|
+
<script>
|
|
301
|
+
"use strict";
|
|
302
|
+
(function () {
|
|
303
|
+
const GLYPH = { question:"Q", experiment:"✦", decision:"→", dead_end:"✗", pivot:"↻", insight:"!", default:"•" };
|
|
304
|
+
const TYPES = ["question","experiment","decision","dead_end","pivot","insight"];
|
|
305
|
+
|
|
306
|
+
let DATA;
|
|
307
|
+
try {
|
|
308
|
+
// The payload sits between the BEGIN/END marker comments (kept in-file so the
|
|
309
|
+
// skill can re-inject). Strip ONLY those two named markers before parsing; the
|
|
310
|
+
// rest of textContent is pure JSON. (Marker text is not written literally here,
|
|
311
|
+
// so it stays a single injection site.)
|
|
312
|
+
var raw = document.getElementById("ara-data").textContent
|
|
313
|
+
.replace(/\/\*\s*__ARA_DATA_(?:BEGIN|END)__\s*\*\//g, "");
|
|
314
|
+
DATA = JSON.parse(raw);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
document.getElementById("detail").innerHTML =
|
|
317
|
+
'<div class="none">Could not parse embedded ARA data: ' + String(e) + '</div>';
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const nodes = DATA.nodes || [];
|
|
322
|
+
const byId = new Map(nodes.map(n => [n.id, n]));
|
|
323
|
+
const artifactById = new Map((DATA.artifacts||[]).map(a => [a.id, a]));
|
|
324
|
+
const kids = new Map();
|
|
325
|
+
nodes.forEach(n => kids.set(n.id, []));
|
|
326
|
+
let roots = [];
|
|
327
|
+
nodes.forEach(n => {
|
|
328
|
+
if (n.parent && byId.has(n.parent)) kids.get(n.parent).push(n.id);
|
|
329
|
+
else roots.push(n.id);
|
|
330
|
+
});
|
|
331
|
+
// Traversal order: explicit DATA.order if given, else DFS from roots.
|
|
332
|
+
let order = Array.isArray(DATA.order) && DATA.order.length ? DATA.order.filter(id => byId.has(id)) : [];
|
|
333
|
+
if (!order.length) { const seen=new Set(); const dfs=id=>{if(seen.has(id))return;seen.add(id);order.push(id);(kids.get(id)||[]).forEach(dfs)}; roots.forEach(dfs); }
|
|
334
|
+
|
|
335
|
+
const esc = s => String(s==null?"":s).replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">",'"':"""}[c]));
|
|
336
|
+
const glyph = t => GLYPH[t] || GLYPH.default;
|
|
337
|
+
const cls = t => TYPES.includes(t) ? t : "default";
|
|
338
|
+
|
|
339
|
+
// ---- header ----
|
|
340
|
+
const m = DATA.meta || {};
|
|
341
|
+
document.getElementById("h-title").textContent = m.title || "ARA Trajectory";
|
|
342
|
+
document.getElementById("h-sub").textContent =
|
|
343
|
+
[ (m.authors||[]).join(", "), m.venue, m.year ].filter(Boolean).join(" · ");
|
|
344
|
+
if (m.abstract) document.getElementById("h-abstract").textContent = m.abstract;
|
|
345
|
+
else document.getElementById("h-abstract-wrap").style.display = "none";
|
|
346
|
+
|
|
347
|
+
const ftype = document.getElementById("ftype");
|
|
348
|
+
const present = [...new Set(nodes.map(n => n.type))];
|
|
349
|
+
TYPES.concat(present.filter(t=>!TYPES.includes(t))).filter(t=>present.includes(t))
|
|
350
|
+
.forEach(t => { const o=document.createElement("option"); o.value=t; o.textContent=t; ftype.appendChild(o); });
|
|
351
|
+
|
|
352
|
+
// ---- minimal markdown-table renderer ----
|
|
353
|
+
function renderMd(md){
|
|
354
|
+
if(!md) return "";
|
|
355
|
+
const lines = String(md).split("\n");
|
|
356
|
+
let out=[], i=0;
|
|
357
|
+
while(i<lines.length){
|
|
358
|
+
if(/^\s*\|/.test(lines[i]) && i+1<lines.length && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i+1])){
|
|
359
|
+
const row = l => l.replace(/^\s*\|/,"").replace(/\|\s*$/,"").split("|").map(c=>c.trim());
|
|
360
|
+
const head = row(lines[i]); i+=2; const body=[];
|
|
361
|
+
while(i<lines.length && /^\s*\|/.test(lines[i])){ body.push(row(lines[i])); i++; }
|
|
362
|
+
out.push('<table class="md"><thead><tr>'+head.map(h=>"<th>"+esc(h)+"</th>").join("")+
|
|
363
|
+
'</tr></thead><tbody>'+body.map(r=>"<tr>"+r.map(c=>"<td>"+esc(c)+"</td>").join("")+"</tr>").join("")+"</tbody></table>");
|
|
364
|
+
} else { if(lines[i].trim()) out.push("<div>"+esc(lines[i])+"</div>"); i++; }
|
|
365
|
+
}
|
|
366
|
+
return out.join("");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ---- detail pane ----
|
|
370
|
+
function block(title, tag, inner, open, cls){
|
|
371
|
+
return '<details class="block'+(cls?" "+cls:"")+'"'+(open?" open":"")+'><summary>'+esc(title)+
|
|
372
|
+
(tag?'<span class="tag">'+esc(tag)+'</span>':'')+'</summary><div class="body">'+inner+'</div></details>';
|
|
373
|
+
}
|
|
374
|
+
function chips(arr, klass){ return arr&&arr.length ? '<div class="chips">'+arr.map(x=>'<span class="chip '+(klass||"")+'">'+esc(x)+'</span>').join("")+'</div>' : ""; }
|
|
375
|
+
|
|
376
|
+
// resolve an artifact id (src/artifacts.md) to a label + a shown-not-resolved pointer tooltip
|
|
377
|
+
function artLabel(id){ const a=artifactById.get(id); return a ? (a.name||a.path||id) : id; }
|
|
378
|
+
function artTip(id){ const a=artifactById.get(id); return a ? [a.path,a.sha256,a.original_location||a.original_path].filter(Boolean).join(" · ") : ""; }
|
|
379
|
+
function renderDiff(cc){
|
|
380
|
+
const head=[];
|
|
381
|
+
if(cc.base_artifact) head.push('<span class="chip" title="'+esc(artTip(cc.base_artifact))+'">base: '+esc(artLabel(cc.base_artifact))+'</span>');
|
|
382
|
+
if(cc.variant_artifact) head.push('<span class="chip" title="'+esc(artTip(cc.variant_artifact))+'">variant: '+esc(artLabel(cc.variant_artifact))+'</span>');
|
|
383
|
+
if(cc.lang) head.push('<span class="chip ext">'+esc(cc.lang)+'</span>');
|
|
384
|
+
const chiprow = head.length?'<div class="chips">'+head.join("")+'</div>':"";
|
|
385
|
+
if(cc.diff){
|
|
386
|
+
const lc = L => /^(\+\+\+|---|diff |index )/.test(L) ? "meta" : (L[0]==="+" ? "add" : (L[0]==="-" ? "del" : (/^@@/.test(L) ? "hunk" : "ctx")));
|
|
387
|
+
const body = String(cc.diff).split("\n").map(L=>'<span class="dl '+lc(L)+'">'+esc(L||" ")+'</span>').join("");
|
|
388
|
+
return chiprow+'<pre class="diff">'+body+'</pre>';
|
|
389
|
+
}
|
|
390
|
+
return chiprow+'<div class="empty">'+esc(cc.note||"Diff not available in this checkout — base/variant scripts not present; pointers only.")+'</div>';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function renderDetail(n){
|
|
394
|
+
const d = document.getElementById("detail");
|
|
395
|
+
let h = '<div class="dhead"><span class="badge '+cls(n.type)+'">'+esc(n.type)+'</span>'+
|
|
396
|
+
'<span class="did">'+esc(n.id)+'</span>'+
|
|
397
|
+
(n.support_level?'<span class="pill '+esc(n.support_level)+'">'+esc(n.support_level)+'</span>':'')+
|
|
398
|
+
(n.isolated?'<span class="pill iso">isolated</span>':'')+'</div>';
|
|
399
|
+
h += '<div class="dtitle">'+esc(n.title||"(untitled)")+'</div>';
|
|
400
|
+
|
|
401
|
+
// reasoning (primary, always open) — the agent's thinking IS the content of a step
|
|
402
|
+
let reason="";
|
|
403
|
+
if(n.thinking){
|
|
404
|
+
reason += '<div class="lead">'+esc(n.thinking)+'</div>';
|
|
405
|
+
if(n.body) reason += '<div class="didwhat">what it did</div><div>'+esc(n.body)+'</div>';
|
|
406
|
+
} else {
|
|
407
|
+
reason += n.body ? '<div class="lead">'+esc(n.body)+'</div>' : '<div class="empty">No description.</div>';
|
|
408
|
+
}
|
|
409
|
+
if(n.depends_on&&n.depends_on.length) reason += chips(n.depends_on.map(x=>"⇠ depends on "+x));
|
|
410
|
+
if(n.source_refs&&n.source_refs.length) reason += chips(n.source_refs, "ext");
|
|
411
|
+
if(n.built_on&&n.built_on.length) reason += chipRow("built on", n.built_on, "chip-built");
|
|
412
|
+
if(n.rejected_here&&n.rejected_here.length) reason += chipRow("rejected here", n.rejected_here, "chip-rej");
|
|
413
|
+
if(n.recipe_refs&&n.recipe_refs.length) reason += recipeChips(n.recipe_refs);
|
|
414
|
+
h += block(n.thinking?"reasoning":"what", n.type, reason, true, "reason");
|
|
415
|
+
|
|
416
|
+
// changed code (unified diff) — open when present
|
|
417
|
+
if(n.code_change && (n.code_change.diff || n.code_change.base_artifact || n.code_change.variant_artifact || n.code_change.note)){
|
|
418
|
+
h += block("changed code", n.code_change.lang||"diff", renderDiff(n.code_change), true);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// result (the grounded payload) — open
|
|
422
|
+
const r = n.result||{};
|
|
423
|
+
if((r.sources&&r.sources.length)||(r.figures&&r.figures.length)||(r.tables&&r.tables.length)||(r.data&&r.data.length)){
|
|
424
|
+
let s="";
|
|
425
|
+
(r.figures||[]).forEach(f=>{ s+='<figure><img src="'+esc(f.img)+'" alt="'+esc(f.caption||f.id)+'"/>'+
|
|
426
|
+
(f.caption?'<figcaption>'+esc(f.caption)+'</figcaption>':'')+'</figure>'; });
|
|
427
|
+
(r.tables||[]).forEach(t=>{ s+=(t.caption?'<div class="empty">'+esc(t.caption)+'</div>':'')+renderMd(t.markdown); });
|
|
428
|
+
(r.sources||[]).forEach(q=>{ s+='<div class="quote">'+esc(q.quote)+(q.ref?'<div class="ref">← '+esc(q.ref)+'</div>':'')+'</div>'; });
|
|
429
|
+
(r.data||[]).forEach(x=>{ s+='<div class="ptr">⛁ <span class="path">'+esc(x.path)+'</span>'+(x.note?' — '+esc(x.note):'')+'</div>'; });
|
|
430
|
+
h += block("result", "", s, true);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// why (claims) — supporting detail, collapsed
|
|
434
|
+
if(n.why&&n.why.length){
|
|
435
|
+
const w = n.why.map(c=>{
|
|
436
|
+
let s='<div class="claim"><h4>'+esc(c.id)+(c.title?' — '+esc(c.title):'')+'</h4>';
|
|
437
|
+
if(c.statement) s+='<div class="kv">'+esc(c.statement)+'</div>';
|
|
438
|
+
if(c.status) s+='<div class="kv"><b>status</b> <span class="status '+esc(String(c.status).toLowerCase())+'">'+esc(c.status)+'</span></div>';
|
|
439
|
+
if(c.conditions) s+='<div class="kv"><b>conditions</b> '+esc(c.conditions)+'</div>';
|
|
440
|
+
if(c.falsification) s+='<div class="kv"><b>falsified if</b> '+esc(c.falsification)+'</div>';
|
|
441
|
+
if(c.dependencies&&c.dependencies.length) s+='<div class="kv"><b>depends on</b> '+esc(c.dependencies.join(", "))+'</div>';
|
|
442
|
+
if(c.provenance) s+='<div class="kv"><span class="prov">provenance: '+esc(c.provenance)+'</span></div>';
|
|
443
|
+
return s+'</div>';
|
|
444
|
+
}).join("");
|
|
445
|
+
h += block("why", n.why.map(c=>c.id).join(" · "), w);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// how verified (experiments) — supporting detail, collapsed
|
|
449
|
+
if(n.verified_by&&n.verified_by.length){
|
|
450
|
+
const v = n.verified_by.map(e=>'<div class="claim"><h4>'+esc(e.id)+(e.title?' — '+esc(e.title):'')+'</h4>'+
|
|
451
|
+
(e.run?'<div class="kv"><b>run</b> <span class="path mono">'+esc(e.run)+'</span></div>':'')+
|
|
452
|
+
(e.setup?'<div class="kv"><b>setup</b> '+esc(e.setup)+'</div>':'')+
|
|
453
|
+
(e.metrics?'<div class="kv"><b>metrics</b> '+esc(e.metrics)+'</div>':'')+'</div>').join("");
|
|
454
|
+
h += block("how verified", n.verified_by.map(e=>e.id).join(" · "), v);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// artifact pointers (src/artifacts.md) — collapsed
|
|
458
|
+
if(n.artifact&&n.artifact.length){
|
|
459
|
+
const a = n.artifact.map(x=>'<div class="ptr">▸ <b>'+esc(x.name)+'</b> — <span class="path">'+esc(x.pointer)+'</span>'+
|
|
460
|
+
(x.what?'<div class="empty">'+esc(x.what)+'</div>':'')+'</div>').join("");
|
|
461
|
+
h += block("artifact", "pointer", a);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
d.innerHTML = h;
|
|
465
|
+
if(typeof LEX!=="undefined" && LEX) linkifyConcepts(d);
|
|
466
|
+
d.scrollTop = 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ---- map ----
|
|
470
|
+
let selected = null;
|
|
471
|
+
function selectNode(id){
|
|
472
|
+
if(!byId.has(id)) return;
|
|
473
|
+
selected = id;
|
|
474
|
+
document.querySelectorAll(".node").forEach(el=>el.classList.toggle("sel", el.dataset.id===id));
|
|
475
|
+
const el = document.querySelector('.node[data-id="'+CSS.escape(id)+'"]');
|
|
476
|
+
if(el) el.scrollIntoView({block:"nearest"});
|
|
477
|
+
renderDetail(byId.get(id));
|
|
478
|
+
rstat();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function nodeRow(n){
|
|
482
|
+
const row = document.createElement("div");
|
|
483
|
+
row.className = "node "+(n.type==="dead_end"?"dead":"");
|
|
484
|
+
row.dataset.id = n.id; row.dataset.type = n.type;
|
|
485
|
+
// calmer than before: one glyph + id + (quiet) cross-edge marker; the ⊕/⊘ count soup is gone.
|
|
486
|
+
row.innerHTML = '<span class="glyph '+cls(n.type)+'">'+glyph(n.type)+'</span>'+
|
|
487
|
+
'<span><span class="meta"><span class="nid">'+esc(n.id)+'</span>'+
|
|
488
|
+
(n.depends_on&&n.depends_on.length?'<span class="dep" title="depends on '+esc(n.depends_on.join(", "))+'">⇠ '+esc(n.depends_on.join(","))+'</span>':'')+
|
|
489
|
+
'</span><div class="ntitle">'+esc(n.title||n.body||"(untitled)")+'</div></span>';
|
|
490
|
+
row.addEventListener("click", ()=>selectNode(n.id));
|
|
491
|
+
// hover a node to highlight its cross-edge targets (gentler than always-on connectors)
|
|
492
|
+
if(n.depends_on&&n.depends_on.length){
|
|
493
|
+
row.addEventListener("mouseenter", ()=>n.depends_on.forEach(t=>{ const el=document.querySelector('.node[data-id="'+CSS.escape(t)+'"]'); if(el) el.classList.add("deptarget"); }));
|
|
494
|
+
row.addEventListener("mouseleave", ()=>document.querySelectorAll(".node.deptarget").forEach(el=>el.classList.remove("deptarget")));
|
|
495
|
+
}
|
|
496
|
+
return row;
|
|
497
|
+
}
|
|
498
|
+
function renderSubtree(id, container){
|
|
499
|
+
const n = byId.get(id); if(!n) return;
|
|
500
|
+
container.appendChild(nodeRow(n));
|
|
501
|
+
const cs = kids.get(id)||[];
|
|
502
|
+
if(cs.length){ const wrap=document.createElement("div"); wrap.className="kid"; cs.forEach(c=>renderSubtree(c,wrap)); container.appendChild(wrap); }
|
|
503
|
+
}
|
|
504
|
+
function renderMap(){
|
|
505
|
+
const map = document.getElementById("map"); map.innerHTML="";
|
|
506
|
+
const normalRoots = roots.filter(id=>!byId.get(id).isolated);
|
|
507
|
+
const isoRoots = roots.filter(id=>byId.get(id).isolated);
|
|
508
|
+
normalRoots.forEach(id=>renderSubtree(id, map));
|
|
509
|
+
if(isoRoots.length){
|
|
510
|
+
const box=document.createElement("div"); box.className="isobox";
|
|
511
|
+
const hdr=document.createElement("div"); hdr.className="isohdr"; hdr.textContent="isolated subtree"; box.appendChild(hdr);
|
|
512
|
+
isoRoots.forEach(id=>renderSubtree(id, box)); map.appendChild(box);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ---- filter / search ----
|
|
517
|
+
function applyFilters(){
|
|
518
|
+
const q = document.getElementById("q").value.trim().toLowerCase();
|
|
519
|
+
const ty = document.getElementById("ftype").value;
|
|
520
|
+
const deadOnly = document.getElementById("fdead").checked;
|
|
521
|
+
let shown=0;
|
|
522
|
+
document.querySelectorAll(".node").forEach(el=>{
|
|
523
|
+
const n = byId.get(el.dataset.id);
|
|
524
|
+
const text = (n.id+" "+(n.title||"")+" "+(n.body||"")).toLowerCase();
|
|
525
|
+
const ok = (!q||text.includes(q)) && (!ty||n.type===ty) && (!deadOnly||n.type==="dead_end");
|
|
526
|
+
el.classList.toggle("dim", !ok);
|
|
527
|
+
if(ok) shown++;
|
|
528
|
+
});
|
|
529
|
+
document.getElementById("rstat").textContent = shown+" / "+nodes.length+" steps";
|
|
530
|
+
}
|
|
531
|
+
function rstat(){
|
|
532
|
+
const i = selected?order.indexOf(selected):-1;
|
|
533
|
+
document.getElementById("rstat").textContent = (i>=0?("step "+(i+1)+" / "+order.length):(nodes.length+" steps"));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ---- replay ----
|
|
537
|
+
let timer=null;
|
|
538
|
+
function step(delta){
|
|
539
|
+
let i = selected?order.indexOf(selected):-1;
|
|
540
|
+
i = Math.max(0, Math.min(order.length-1, i+delta));
|
|
541
|
+
selectNode(order[i]);
|
|
542
|
+
}
|
|
543
|
+
function play(){
|
|
544
|
+
if(timer){ stop(); return; }
|
|
545
|
+
if(!selected) selectNode(order[0]);
|
|
546
|
+
document.getElementById("rplay").textContent="⏸ Pause";
|
|
547
|
+
timer = setInterval(()=>{
|
|
548
|
+
const i = selected?order.indexOf(selected):-1;
|
|
549
|
+
if(i>=order.length-1){ stop(); return; }
|
|
550
|
+
step(1);
|
|
551
|
+
}, 1300);
|
|
552
|
+
}
|
|
553
|
+
function stop(){ if(timer){clearInterval(timer);timer=null;} document.getElementById("rplay").textContent="▶ Replay"; }
|
|
554
|
+
|
|
555
|
+
// ======================= v1.1 enrichment layers (all inert unless present) =======================
|
|
556
|
+
const CTX = DATA.context, GLO = DATA.glossary, DEP = DATA.dependencies, REC = DATA.recipes;
|
|
557
|
+
const LAYERS = { context:CTX, glossary:GLO, dependencies:DEP, recipes:REC };
|
|
558
|
+
|
|
559
|
+
// ---- indexes (built once) ----
|
|
560
|
+
const claimToNodes = new Map();
|
|
561
|
+
nodes.forEach(n => (n.why||[]).forEach(c => { if(c&&c.id){ if(!claimToNodes.has(c.id)) claimToNodes.set(c.id,[]); claimToNodes.get(c.id).push(n.id); } }));
|
|
562
|
+
const firstNodeForClaim = id => (claimToNodes.get(id)||[])[0] || null;
|
|
563
|
+
const rwById = new Map(((DEP&&DEP.entries)||[]).map(e => [e.rw_id, e]));
|
|
564
|
+
const termById = new Map(((GLO&&GLO.terms)||[]).map(t => [t.term_id, t]));
|
|
565
|
+
// concept "used by" (inferred) from each node's name-matched concepts[]
|
|
566
|
+
if(GLO){
|
|
567
|
+
const nameToTerm = new Map();
|
|
568
|
+
(GLO.terms||[]).forEach(t => { if(t.name) nameToTerm.set(String(t.name).toLowerCase(), t); (t.aliases||[]).forEach(a=>nameToTerm.set(String(a).toLowerCase(), t)); });
|
|
569
|
+
nodes.forEach(n => (n.concepts||[]).forEach(cn => { const t=nameToTerm.get(String(cn).toLowerCase()); if(t){ (t._used=t._used||[]).push(n.id); } }));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// ---- tiny inline-math fallback (no CDN; operates on already-escaped text, adds only safe tags) ----
|
|
573
|
+
const GK={alpha:"α",beta:"β",gamma:"γ",delta:"δ",epsilon:"ε",eta:"η",theta:"θ",kappa:"κ",lambda:"λ",mu:"μ",nu:"ν",pi:"π",rho:"ρ",sigma:"σ",tau:"τ",phi:"φ",chi:"χ",psi:"ψ",omega:"ω",Delta:"Δ",Sigma:"Σ",Omega:"Ω",Phi:"Φ",Theta:"Θ",Lambda:"Λ",Gamma:"Γ",nabla:"∇",partial:"∂",times:"×",cdot:"·",infty:"∞",approx:"≈",leq:"≤",geq:"≥",neq:"≠",to:"→",in:"∈",sum:"Σ",prod:"Π",sqrt:"√",mathbb:"",mathcal:"",mathbf:"",mathrm:"",left:"",right:""};
|
|
574
|
+
function texInner(s){
|
|
575
|
+
s=s.replace(/\\(?:mathbb|mathcal|mathbf|mathrm)\{([^{}]*)\}/g,"$1");
|
|
576
|
+
s=s.replace(/\\frac\{([^{}]*)\}\{([^{}]*)\}/g,"($1)/($2)");
|
|
577
|
+
s=s.replace(/\\([A-Za-z]+)/g,(m,w)=>GK[w]!==undefined?GK[w]:m);
|
|
578
|
+
s=s.replace(/\^\{([^{}]*)\}/g,"<sup>$1</sup>").replace(/\^([\w()+\-])/g,"<sup>$1</sup>");
|
|
579
|
+
s=s.replace(/_\{([^{}]*)\}/g,"<sub>$1</sub>").replace(/_([\w()+\-])/g,"<sub>$1</sub>");
|
|
580
|
+
return s;
|
|
581
|
+
}
|
|
582
|
+
function mathPass(escaped){
|
|
583
|
+
if(escaped.indexOf("$")<0) return escaped;
|
|
584
|
+
return escaped.replace(/\$\$([^$]+)\$\$/g,(m,x)=>'<span class="math">'+texInner(x)+'</span>')
|
|
585
|
+
.replace(/\$([^$]+)\$/g,(m,x)=>'<span class="math">'+texInner(x)+'</span>');
|
|
586
|
+
}
|
|
587
|
+
function mtext(v){ const e=esc(v); try{ return mathPass(e); }catch(_){ return e; } }
|
|
588
|
+
|
|
589
|
+
// ---- typed-ref rendering (off-ARA pointers stay inert; in-ARA targets become jump chips) ----
|
|
590
|
+
function refUrl(rf){
|
|
591
|
+
const raw=rf.raw||"";
|
|
592
|
+
if(rf.kind==="url"){ const m=raw.match(/https?:\/\/\S+/); return m?m[0]:""; }
|
|
593
|
+
if(rf.kind==="arxiv"){ const m=raw.match(/(\d{4}\.\d{4,5})/); return m?("https://arxiv.org/abs/"+m[1]):""; }
|
|
594
|
+
if(rf.kind==="doi"){ const m=raw.match(/(10\.\d{4,}\/\S+)/); return m?("https://doi.org/"+m[1]):""; }
|
|
595
|
+
return "";
|
|
596
|
+
}
|
|
597
|
+
function refChip(rf){
|
|
598
|
+
const raw=esc(rf.raw||""), url=refUrl(rf);
|
|
599
|
+
if(url) return '<a class="chip ext" href="'+esc(url)+'" target="_blank" rel="noopener">'+raw+'</a>';
|
|
600
|
+
if(rf.target){
|
|
601
|
+
if(rf.kind==="claim") return '<span class="chip jump" data-jump-claim="'+esc(rf.target)+'">'+raw+'</span>';
|
|
602
|
+
if(rf.kind==="node") return '<span class="chip jump" data-jump-node="'+esc(rf.target)+'">'+raw+'</span>';
|
|
603
|
+
if(rf.kind==="concept") return '<span class="chip jump" data-open-term="'+esc(rf.target)+'">'+raw+'</span>';
|
|
604
|
+
if(rf.kind==="related_work") return '<span class="chip jump" data-open-panel="dependencies" data-anchor="ov-'+esc(rf.target)+'">'+raw+'</span>';
|
|
605
|
+
return '<span class="chip jump" data-scroll="ov-'+esc(rf.target)+'">'+raw+'</span>';
|
|
606
|
+
}
|
|
607
|
+
const dangle=(rf.kind==="claim"||rf.kind==="related_work"||rf.kind==="concept");
|
|
608
|
+
return '<span class="chip '+(dangle?'dangle" title="referenced but not found in this ARA':'ext')+'">'+raw+'</span>';
|
|
609
|
+
}
|
|
610
|
+
function refRow(refs){ return refs&&refs.length ? '<div class="chips">'+refs.map(refChip).join("")+'</div>' : ""; }
|
|
611
|
+
|
|
612
|
+
// ---- per-node provenance chips (consumed by renderDetail) ----
|
|
613
|
+
function chipRow(label, arr, klass){
|
|
614
|
+
const cs=arr.map(x=>{
|
|
615
|
+
const txt=[x.rw_id,x.name].filter(Boolean).join(" · ")||"?";
|
|
616
|
+
const tip=x.relation_raw?(' title="'+esc(x.relation_raw)+'"'):'';
|
|
617
|
+
const op =x.rw_id?(' data-open-panel="dependencies" data-anchor="ov-'+esc(x.rw_id)+'"'):'';
|
|
618
|
+
return '<span class="chip '+klass+(x.rw_id?' jump':'')+'"'+tip+op+'>'+esc(txt)+'</span>';
|
|
619
|
+
}).join("");
|
|
620
|
+
return '<div class="chiplabel">'+esc(label)+'</div><div class="chips">'+cs+'</div>';
|
|
621
|
+
}
|
|
622
|
+
function recipeChips(arr){
|
|
623
|
+
const cs=arr.map(x=>{
|
|
624
|
+
const anchor="ov-sec-"+(x.file||"")+"-"+(x.sec_id||"");
|
|
625
|
+
return '<span class="chip chip-recipe jump" data-open-panel="recipes" data-anchor="'+esc(anchor)+'" title="'+esc((x.file||"")+(x.role?(" · "+x.role):""))+'">▦ '+esc(x.heading||x.file||"recipe")+'</span>';
|
|
626
|
+
}).join("");
|
|
627
|
+
return '<div class="chiplabel">recipe</div><div class="chips">'+cs+'</div>';
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ---- the four overlay renderers (reuse block/chips/renderMd) ----
|
|
631
|
+
function renderContext(){
|
|
632
|
+
let h=""; if(CTX.summary) h+='<div class="ribbon">'+esc(CTX.summary)+'</div>';
|
|
633
|
+
(CTX.sections||[]).forEach(sec=>{
|
|
634
|
+
let inner="";
|
|
635
|
+
if(sec.present===false){ inner='<div class="muted grey">(not specified in this ARA)</div>'; }
|
|
636
|
+
else (sec.entries||[]).forEach(en=>{
|
|
637
|
+
let c='<div class="card" id="ov-'+esc(en.ent_id||"")+'"><h4>'+(en.ent_id?'<span class="did">'+esc(en.ent_id)+'</span> ':'')+esc(en.ent_title||"")+(en.inferred_id?' <span class="sub">(pos.)</span>':'')+'</h4>';
|
|
638
|
+
if(!en.unstructured && en.fields && en.fields.length) en.fields.forEach(f=>c+='<div class="kv"><b>'+esc(f.label)+'</b> '+mtext(f.value)+'</div>');
|
|
639
|
+
else if(en.text) c+='<div class="kv">'+mtext(en.text)+'</div>';
|
|
640
|
+
if(en.unstructured) c+='<div class="sub">unstructured</div>';
|
|
641
|
+
c+=refRow(en.refs)+'</div>'; inner+=c;
|
|
642
|
+
});
|
|
643
|
+
h+=block(sec.heading||sec.role||"section", sec.role||"", inner, sec.role==="insight");
|
|
644
|
+
});
|
|
645
|
+
return h || '<div class="muted">No problem context.</div>';
|
|
646
|
+
}
|
|
647
|
+
function renderGlossary(filter){
|
|
648
|
+
const f=(filter||"").trim().toLowerCase();
|
|
649
|
+
const match=t=>!f||((t.name||"")+" "+(t.aliases||[]).join(" ")+" "+(t.definition||"")).toLowerCase().includes(f);
|
|
650
|
+
function card(t){
|
|
651
|
+
let c='<div class="card" id="ov-'+esc(t.term_id)+'"><h4>'+esc(t.name)+(t.group?' <span class="sub">'+esc(t.group)+'</span>':'')+'</h4>';
|
|
652
|
+
if(t.aliases&&t.aliases.length) c+='<div class="chips">'+t.aliases.map(a=>'<span class="chip">'+esc(a)+'</span>').join("")+'</div>';
|
|
653
|
+
if(t.definition) c+='<div class="kv">'+mtext(t.definition)+'</div>';
|
|
654
|
+
(t.fields||[]).forEach(fl=>c+='<div class="kv"><b>'+esc(fl.label)+'</b> '+mtext(fl.value)+'</div>');
|
|
655
|
+
if(t.related&&t.related.length) c+='<div class="kv"><b>related</b> '+t.related.map(r=>{const rt=termById.get(r);return '<span class="chip jump" data-open-term="'+esc(r)+'">'+esc(rt?rt.name:r)+'</span>';}).join(" ")+'</div>';
|
|
656
|
+
if(t._used&&t._used.length) c+='<div class="kv"><b>mentions</b> '+[...new Set(t._used)].map(nid=>'<span class="chip jump" data-jump-node="'+esc(nid)+'">'+esc(nid)+'</span>').join(" ")+' <span class="sub">(inferred)</span></div>';
|
|
657
|
+
return c+refRow(t.refs)+'</div>';
|
|
658
|
+
}
|
|
659
|
+
const groups=GLO.groups&&GLO.groups.length?GLO.groups:null; let h="";
|
|
660
|
+
if(groups){
|
|
661
|
+
const grouped=new Set();
|
|
662
|
+
groups.forEach(g=>{ (g.term_ids||[]).forEach(id=>grouped.add(id)); const ts=(g.term_ids||[]).map(id=>termById.get(id)).filter(Boolean).filter(match); if(ts.length) h+=block(g.name||"group", String(ts.length), ts.map(card).join(""), true); });
|
|
663
|
+
const rest=(GLO.terms||[]).filter(t=>!grouped.has(t.term_id)).filter(match);
|
|
664
|
+
if(rest.length) h+=block("other", String(rest.length), rest.map(card).join(""), true);
|
|
665
|
+
} else { const ts=(GLO.terms||[]).filter(match); h=ts.map(card).join(""); }
|
|
666
|
+
return h || '<div class="muted">No matching terms.</div>';
|
|
667
|
+
}
|
|
668
|
+
function renderDeps(filter){
|
|
669
|
+
const f=(filter||"").trim().toLowerCase(); let h="";
|
|
670
|
+
if(DEP.preamble) h+='<div class="muted">'+esc(DEP.preamble)+'</div>';
|
|
671
|
+
if(DEP.attribution) h+='<div class="ribbon">'+esc(DEP.attribution)+'</div>';
|
|
672
|
+
const ents=(DEP.entries||[]).filter(e=>!e.is_footprint).filter(e=>!f||(((e.rw_id||"")+" "+(e.name||"")+" "+(e.relation_raw||"")+" "+(e.delta||"")).toLowerCase().includes(f)));
|
|
673
|
+
ents.forEach(e=>{
|
|
674
|
+
const rn=e.relation_norm, relc=["baseline","imports","extends","bounds","refutes"].includes(rn)?rn:"neutral";
|
|
675
|
+
let c='<div class="card" id="ov-'+esc(e.rw_id||"")+'"><h4>'+(e.rw_id?'<span class="did">'+esc(e.rw_id)+'</span> ':'')+esc(e.name||"")+'</h4>';
|
|
676
|
+
c+='<div class="chips"><span class="rel '+relc+'">'+esc(e.relation_raw||rn||"related")+'</span>'+(e.cross_agent?'<span class="chip" title="other-agent provenance">↔ other-agent</span>':'')+'</div>';
|
|
677
|
+
if(e.delta) c+='<div class="kv">'+mtext(e.delta)+'</div>';
|
|
678
|
+
if(e.adopted) c+='<div class="kv"><b>adopted</b> '+esc(e.adopted)+'</div>';
|
|
679
|
+
let prov=(e.grounding||[]).map(refChip).join("");
|
|
680
|
+
(e.claims||[]).filter(id=>claimToNodes.has(id)).forEach(id=>prov+='<span class="chip jump" data-jump-claim="'+esc(id)+'">'+esc(id)+'</span>');
|
|
681
|
+
c+= prov?('<div class="kv"><b>grounding</b> '+prov+'</div>'):'<div class="muted">ungrounded</div>';
|
|
682
|
+
h+=c+'</div>';
|
|
683
|
+
});
|
|
684
|
+
if(!ents.length) h+='<div class="muted">No matching dependencies.</div>';
|
|
685
|
+
const fp=DEP.footprint||[];
|
|
686
|
+
if(fp.length) h+=block("Citation footprint", String(fp.length), fp.map(x=>'<div class="ptr"><span class="muted">'+esc(x.ref_id||"")+'</span> '+esc(x.citation||"")+'</div>').join(""), false);
|
|
687
|
+
return h;
|
|
688
|
+
}
|
|
689
|
+
function renderRecipes(){
|
|
690
|
+
let h="";
|
|
691
|
+
(REC.files||[]).forEach(file=>{
|
|
692
|
+
let inner="";
|
|
693
|
+
(file.sections||[]).forEach(sec=>{
|
|
694
|
+
let c='<div class="card" id="ov-sec-'+esc((file.file||"")+"-"+(sec.sec_id||""))+'"><h4>'+(sec.sec_id?'<span class="did">'+esc(sec.sec_id)+'</span> ':'')+esc(sec.heading||"")+'</h4>';
|
|
695
|
+
if(sec.kind==="table"&&sec.markdown) c+=renderMd(sec.markdown);
|
|
696
|
+
else if(sec.kind==="steps"&&sec.steps&&sec.steps.length) c+='<ol>'+sec.steps.map(s=>'<li>'+mtext(String(s).replace(/^\s*\d+\.\s*/,""))+'</li>').join("")+'</ol>';
|
|
697
|
+
else if(sec.kind==="kv"&&sec.fields&&sec.fields.length) sec.fields.forEach(fl=>c+='<div class="kv"><b>'+esc(fl.label)+'</b> '+mtext(fl.value)+'</div>');
|
|
698
|
+
else if(sec.kind==="code"&&sec.code) c+='<pre class="snip">'+esc(sec.code)+'</pre>';
|
|
699
|
+
else if(sec.kind==="math"&&sec.code) c+='<div class="kv math">'+mtext(sec.code)+'</div>';
|
|
700
|
+
else if(sec.text) c+='<div class="kv">'+mtext(sec.text)+'</div>';
|
|
701
|
+
if(sec.warn) c+='<div class="warn">'+esc(sec.warn)+'</div>';
|
|
702
|
+
inner+=c+refRow(sec.refs)+'</div>';
|
|
703
|
+
});
|
|
704
|
+
h+=block((file.file_title||file.file||"recipe")+(file.role?(" · "+file.role):""), String((file.sections||[]).length), inner, true);
|
|
705
|
+
});
|
|
706
|
+
return h || '<div class="muted">No recipe content.</div>';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ---- overlay open/close + layerbar wiring ----
|
|
710
|
+
const ov=document.getElementById("overlay");
|
|
711
|
+
let lastTrigger=null;
|
|
712
|
+
const PANEL_RENDER={ context:renderContext, glossary:()=>renderGlossary(""), dependencies:()=>renderDeps(""), recipes:renderRecipes };
|
|
713
|
+
function hasContent(name){ const L=LAYERS[name]; if(!L) return false;
|
|
714
|
+
if(name==="context") return (L.sections||[]).length>0;
|
|
715
|
+
if(name==="glossary") return (L.terms||[]).length>0;
|
|
716
|
+
if(name==="dependencies") return (L.entries||[]).length>0||(L.footprint||[]).length>0;
|
|
717
|
+
if(name==="recipes") return (L.files||[]).length>0; return false; }
|
|
718
|
+
function panelTitle(name){ const L=LAYERS[name]||{}; return L.title || ({context:"Context",glossary:"Concepts",dependencies:"Dependencies",recipes:"Solution"}[name]); }
|
|
719
|
+
function panelCount(name){ const L=LAYERS[name]||{};
|
|
720
|
+
if(name==="glossary") return (L.terms||[]).length;
|
|
721
|
+
if(name==="dependencies") return (L.entries||[]).length;
|
|
722
|
+
if(name==="recipes") return (L.files||[]).reduce((a,f)=>a+(f.sections||[]).length,0); return 0; }
|
|
723
|
+
function openPanel(name, anchor){
|
|
724
|
+
if(!hasContent(name)) return;
|
|
725
|
+
stop(); lastTrigger=document.activeElement;
|
|
726
|
+
document.getElementById("ov-title").textContent=panelTitle(name);
|
|
727
|
+
const body=document.getElementById("ov-body"); body.innerHTML=PANEL_RENDER[name]();
|
|
728
|
+
if(LEX) linkifyConcepts(body);
|
|
729
|
+
const q=document.getElementById("ov-q");
|
|
730
|
+
if(name==="glossary"||name==="dependencies"){ q.hidden=false; q.value=""; q.dataset.panel=name; } else { q.hidden=true; }
|
|
731
|
+
ov.hidden=false;
|
|
732
|
+
if(anchor){ const el=document.getElementById(anchor); if(el) el.scrollIntoView({block:"center"}); } else body.scrollTop=0;
|
|
733
|
+
(q.hidden?document.getElementById("ov-close"):q).focus();
|
|
734
|
+
}
|
|
735
|
+
function closePanel(){ ov.hidden=true; if(lastTrigger&&lastTrigger.focus) lastTrigger.focus(); }
|
|
736
|
+
document.querySelectorAll(".lbtn").forEach(b=>{ const name=b.dataset.panel;
|
|
737
|
+
if(hasContent(name)){ b.hidden=false; const c=b.querySelector(".cnt"); if(c){ const n=panelCount(name); if(n) c.textContent=n; } b.addEventListener("click",()=>openPanel(name)); } });
|
|
738
|
+
document.getElementById("ov-q").addEventListener("input", e=>{ const p=e.target.dataset.panel, body=document.getElementById("ov-body");
|
|
739
|
+
body.innerHTML = p==="glossary"?renderGlossary(e.target.value):renderDeps(e.target.value); if(LEX) linkifyConcepts(body); });
|
|
740
|
+
|
|
741
|
+
// ---- glossary lexicon + popover ----
|
|
742
|
+
const LEX=(GLO&&GLO.lexicon&&Object.keys(GLO.lexicon).length)?(function(){
|
|
743
|
+
const surf=Object.keys(GLO.lexicon).filter(s=>s.length>=3).sort((a,b)=>b.length-a.length);
|
|
744
|
+
if(!surf.length) return null;
|
|
745
|
+
const re=s=>s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
|
|
746
|
+
return { re:new RegExp("\\b("+surf.map(re).join("|")+")\\b","i"), map:GLO.lexicon };
|
|
747
|
+
})():null;
|
|
748
|
+
const SKIP_TAGS={A:1,CODE:1,PRE:1,BUTTON:1,SCRIPT:1,STYLE:1,H4:1,H5:1,SUMMARY:1};
|
|
749
|
+
function linkifyConcepts(root){
|
|
750
|
+
if(!LEX) return;
|
|
751
|
+
const walker=document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(t){
|
|
752
|
+
if(!t.nodeValue||!t.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
|
|
753
|
+
let p=t.parentNode; while(p&&p!==root){ if(p.classList&&p.classList.contains("gterm")) return NodeFilter.FILTER_REJECT; if(SKIP_TAGS[p.tagName]) return NodeFilter.FILTER_REJECT; p=p.parentNode; }
|
|
754
|
+
return NodeFilter.FILTER_ACCEPT; } });
|
|
755
|
+
const seen=new Set(), hits=[]; let t;
|
|
756
|
+
while((t=walker.nextNode())){ const m=LEX.re.exec(t.nodeValue); if(!m) continue; const tid=LEX.map[m[1].toLowerCase()]; if(!tid||seen.has(tid)) continue; seen.add(tid); hits.push({node:t,index:m.index,len:m[1].length,tid}); }
|
|
757
|
+
hits.forEach(hp=>{ const tn=hp.node, txt=tn.nodeValue, par=tn.parentNode; if(!par) return;
|
|
758
|
+
const span=document.createElement("span"); span.className="gterm"; span.dataset.term=hp.tid; span.tabIndex=0; span.textContent=txt.slice(hp.index,hp.index+hp.len);
|
|
759
|
+
const after=document.createTextNode(txt.slice(hp.index+hp.len));
|
|
760
|
+
par.replaceChild(after,tn); par.insertBefore(span,after); par.insertBefore(document.createTextNode(txt.slice(0,hp.index)),span); });
|
|
761
|
+
}
|
|
762
|
+
const gpop=document.getElementById("gpop");
|
|
763
|
+
function gpopShow(span){ const t=termById.get(span.dataset.term); if(!t) return;
|
|
764
|
+
const def=t.definition||""; gpop.innerHTML='<h5>'+esc(t.name)+'</h5><div>'+mtext(def.slice(0,240))+(def.length>240?'…':'')+'</div><span class="gp-open" data-open-term="'+esc(t.term_id)+'">open in glossary →</span>';
|
|
765
|
+
gpop.style.display="block"; const r=span.getBoundingClientRect();
|
|
766
|
+
gpop.style.left=Math.max(6,Math.min(r.left, window.innerWidth-372))+"px"; gpop.style.top=(r.bottom+6)+"px"; }
|
|
767
|
+
function gpopHide(){ gpop.style.display="none"; }
|
|
768
|
+
|
|
769
|
+
// ---- delegated interactions for the new layers ----
|
|
770
|
+
document.addEventListener("click", e=>{
|
|
771
|
+
const el=e.target.closest&&e.target.closest("[data-close],[data-jump-node],[data-jump-claim],[data-open-panel],[data-open-term],[data-scroll]");
|
|
772
|
+
if(!el) return;
|
|
773
|
+
if(el.hasAttribute("data-close")){ closePanel(); return; }
|
|
774
|
+
if(el.dataset.jumpNode){ closePanel(); selectNode(el.dataset.jumpNode); return; }
|
|
775
|
+
if(el.dataset.jumpClaim){ const nid=firstNodeForClaim(el.dataset.jumpClaim); if(nid){ closePanel(); selectNode(nid); } return; }
|
|
776
|
+
if(el.dataset.openTerm){ openPanel("glossary","ov-"+el.dataset.openTerm); return; }
|
|
777
|
+
if(el.hasAttribute("data-open-panel")){ openPanel(el.dataset.openPanel, el.dataset.anchor||null); return; }
|
|
778
|
+
if(el.dataset.scroll){ const t=document.getElementById(el.dataset.scroll); if(t) t.scrollIntoView({block:"center"}); return; }
|
|
779
|
+
});
|
|
780
|
+
document.addEventListener("mouseover", e=>{ const g=e.target.closest&&e.target.closest(".gterm"); if(g) gpopShow(g); });
|
|
781
|
+
document.addEventListener("mouseout", e=>{ const g=e.target.closest&&e.target.closest(".gterm"); if(g) gpopHide(); });
|
|
782
|
+
document.addEventListener("focusin", e=>{ const g=e.target.closest&&e.target.closest(".gterm"); if(g) gpopShow(g); });
|
|
783
|
+
document.addEventListener("focusout", e=>{ const g=e.target.closest&&e.target.closest(".gterm"); if(g) gpopHide(); });
|
|
784
|
+
window.addEventListener("scroll", gpopHide, true);
|
|
785
|
+
|
|
786
|
+
document.getElementById("q").addEventListener("input", applyFilters);
|
|
787
|
+
document.getElementById("ftype").addEventListener("change", applyFilters);
|
|
788
|
+
document.getElementById("fdead").addEventListener("change", applyFilters);
|
|
789
|
+
document.getElementById("rplay").addEventListener("click", play);
|
|
790
|
+
document.getElementById("rprev").addEventListener("click", ()=>{stop();step(-1);});
|
|
791
|
+
document.getElementById("rnext").addEventListener("click", ()=>{stop();step(1);});
|
|
792
|
+
document.addEventListener("keydown", e=>{
|
|
793
|
+
if(e.key==="Escape"){ if(!ov.hidden){ closePanel(); return; } gpopHide(); }
|
|
794
|
+
if(e.target.tagName==="INPUT"||e.target.tagName==="SELECT") return;
|
|
795
|
+
if(e.key==="ArrowLeft"){stop();step(-1);} else if(e.key==="ArrowRight"){stop();step(1);}
|
|
796
|
+
else if(e.key==="c"||e.key==="g"||e.key==="d"||e.key==="r"){ const p={c:"context",g:"glossary",d:"dependencies",r:"recipes"}[e.key]; if(hasContent(p)) openPanel(p); }
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
renderMap();
|
|
800
|
+
rstat();
|
|
801
|
+
})();
|
|
802
|
+
</script>
|
|
803
|
+
</body>
|
|
804
|
+
</html>
|