dsh-notes-board 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +163 -2
  4. package/client.js +1253 -0
  5. package/cordis.patch.yml +6 -0
  6. package/icon.svg +16 -0
  7. package/index.js +28 -0
  8. package/locale/en.json +6 -0
  9. package/locale/zh.json +6 -0
  10. package/package.json +79 -4
  11. package/scripts/build-client.mjs +175 -0
  12. package/scripts/test-board.mjs +202 -0
  13. package/src/core/notes.js +486 -0
  14. package/src/core/tools.js +221 -0
  15. package/src/host/board.js +184 -0
  16. package/src/host/notes.js +250 -0
  17. package/src/host/skills.js +48 -0
  18. package/src/panel.js +294 -0
  19. package/vendor/board.html +1779 -0
  20. package/vendor/write-notes-like-deepseek/README.md +323 -0
  21. package/vendor/write-notes-like-deepseek/package.json +21 -0
  22. package/vendor/write-notes-like-deepseek/scripts/export-assets.sh +40 -0
  23. package/vendor/write-notes-like-deepseek/scripts/import-dsh-notes.ts +125 -0
  24. package/vendor/write-notes-like-deepseek/scripts/test-skill-install.mjs +63 -0
  25. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/SKILL.md +195 -0
  26. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/assets/agent-notes-board.html +1779 -0
  27. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/archiving.md +109 -0
  28. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/classification.md +33 -0
  29. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/note-format.md +69 -0
  30. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/prose-checklist.md +51 -0
  31. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/quality-gate.md +58 -0
  32. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/simplification-checklist.md +26 -0
  33. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/verification.md +34 -0
  34. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/when-to-write.md +83 -0
  35. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/agent-note-tree.ts +75 -0
  36. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/archive-agent-note.ts +167 -0
  37. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/build-board.ts +235 -0
  38. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/check-note-anchors.ts +103 -0
  39. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-format.ts +158 -0
  40. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-tree.ts +56 -0
  41. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-archived-agent-notes.ts +127 -0
  42. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/implemented.md +32 -0
  43. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/proposed.md +38 -0
  44. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/rejected.md +27 -0
  45. package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/verify-notes.yml +38 -0
@@ -0,0 +1,1779 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="generator" content="agent-notes-board">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>工程决策看板</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {
13
+ --bg-base: #F8FAFC;
14
+ --bg-surface: #FFFFFF;
15
+ --bg-surface-raised: #FFFFFF;
16
+ --bg-surface-subtle: #F1F5F9;
17
+ --border-subtle: #F1F5F9;
18
+ --border-normal: #E2E8F0;
19
+ --border-focus: #1D4ED8;
20
+
21
+ --text-pure: #0A0E1A;
22
+ --text-primary: #0F172A;
23
+ --text-secondary: #475569;
24
+ --text-tertiary: #94A3B8;
25
+ --text-mono-faint: #CBD5E1;
26
+
27
+ --brand-blue: #1D4ED8;
28
+ --brand-blue-hover: #2563EB;
29
+ --brand-blue-glow: rgba(29, 78, 216, 0.12);
30
+ --alert-ruby: #DC2626;
31
+ --alert-ruby-subtle: #FEF2F2;
32
+ --amber-core: #D97706;
33
+ --emerald-live: #059669;
34
+ --purple-meta: #7C3AED;
35
+ --teal-prune: #0D9488;
36
+
37
+ --font-display: "Space Grotesk", -apple-system, sans-serif;
38
+ --font-body: "Plus Jakarta Sans", -apple-system, "PingFang SC", sans-serif;
39
+ --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;
40
+
41
+ --radius-sm: 4px;
42
+ --radius-md: 8px;
43
+ --radius-lg: 12px;
44
+ }
45
+
46
+ * { box-sizing: border-box; margin: 0; padding: 0; }
47
+
48
+ /* 瑞士工程图纸微网格背景 */
49
+ body {
50
+ background-color: var(--bg-base);
51
+ background-image:
52
+ linear-gradient(to right, rgba(15, 23, 42, 0.035) 1px, transparent 1px),
53
+ linear-gradient(to bottom, rgba(15, 23, 42, 0.035) 1px, transparent 1px);
54
+ background-size: 28px 28px;
55
+ color: var(--text-primary);
56
+ font: 14px/1.6 var(--font-body);
57
+ -webkit-font-smoothing: antialiased;
58
+ min-height: 100vh;
59
+ display: flex;
60
+ flex-direction: column;
61
+ overflow-x: hidden;
62
+ }
63
+
64
+ /* 顶栏 HUD(磨砂象牙白) */
65
+ header.hud-topbar {
66
+ position: sticky; top: 0; z-index: 50;
67
+ height: 64px; background: rgba(255, 255, 255, 0.88);
68
+ backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
69
+ border-bottom: 1px solid var(--border-normal);
70
+ padding: 0 32px; display: flex; align-items: center; gap: 24px;
71
+ box-shadow: 0 1px 4px rgba(15, 23, 42, 0.03);
72
+ }
73
+
74
+ .brand-group { display: flex; align-items: center; gap: 12px; }
75
+ .brand-logo {
76
+ width: 32px; height: 32px; border-radius: 6px;
77
+ background: #0F172A; border: 1px solid #0F172A;
78
+ display: flex; align-items: center; justify-content: center;
79
+ position: relative; box-shadow: 0 2px 6px rgba(15,23,42,0.15);
80
+ }
81
+ .brand-logo::before {
82
+ content: ''; position: absolute; width: 8px; height: 8px;
83
+ background: #60A5FA; border-radius: 50%;
84
+ }
85
+ .brand-text {
86
+ font-family: var(--font-display); font-size: 16px; font-weight: 700;
87
+ color: #0F172A; letter-spacing: -0.3px; display: flex; align-items: center; gap: 8px;
88
+ }
89
+ .telemetry-chip {
90
+ font-family: var(--font-mono); font-size: 11px; padding: 2px 8px;
91
+ background: #F1F5F9; border: 1px solid var(--border-normal);
92
+ border-radius: var(--radius-sm); color: var(--text-secondary); font-weight: 600;
93
+ }
94
+
95
+ /* 导航控制器 Deck Tabs */
96
+ nav.deck-tabs {
97
+ display: flex; gap: 4px; background: #F1F5F9;
98
+ padding: 3px; border-radius: var(--radius-md); border: 1px solid var(--border-normal);
99
+ }
100
+ .deck-tab {
101
+ border: 0; background: transparent; color: var(--text-secondary);
102
+ font-family: var(--font-display); font-size: 13px; font-weight: 600;
103
+ padding: 6px 16px; border-radius: var(--radius-sm); cursor: pointer;
104
+ transition: all .18s cubic-bezier(0.16, 1, 0.3, 1);
105
+ display: flex; align-items: center; gap: 8px; position: relative;
106
+ }
107
+ .deck-tab:hover { color: #0F172A; background: rgba(255,255,255,0.6); }
108
+ .deck-tab.active {
109
+ color: #0F172A; background: #FFFFFF;
110
+ box-shadow: 0 1px 3px rgba(15,23,42,0.08);
111
+ }
112
+ .deck-tab.active::after {
113
+ content: ''; position: absolute; bottom: -1px; left: 20%; right: 20%;
114
+ height: 2px; background: var(--brand-blue);
115
+ }
116
+
117
+ .hud-grow { flex: 1; }
118
+
119
+ /* 战术全局搜索框 */
120
+ .hud-search { position: relative; width: 260px; }
121
+ .hud-search input {
122
+ width: 100%; height: 36px; background: #FFFFFF;
123
+ border: 1px solid var(--border-normal); border-radius: var(--radius-sm);
124
+ color: #0F172A; font-family: var(--font-mono); font-size: 12.5px;
125
+ padding: 0 34px 0 34px; outline: none; transition: all .2s;
126
+ }
127
+ .hud-search input:focus {
128
+ width: 320px; border-color: var(--brand-blue); background: #FFFFFF;
129
+ box-shadow: 0 0 0 3px var(--brand-blue-glow);
130
+ }
131
+ .hud-search .search-ico {
132
+ position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
133
+ color: var(--text-tertiary); pointer-events: none;
134
+ }
135
+ .hud-search .shortcut-key {
136
+ position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
137
+ font-family: var(--font-mono); font-size: 10px; color: var(--text-tertiary);
138
+ background: #F1F5F9; padding: 1px 5px; border-radius: 3px; border: 1px solid var(--border-normal);
139
+ }
140
+
141
+ /* 即时搜索下拉浮窗 */
142
+ .search-popover {
143
+ position: absolute; top: calc(100% + 8px); left: 0; width: 440px;
144
+ background: #FFFFFF; border: 1px solid var(--border-normal);
145
+ border-radius: var(--radius-md); box-shadow: 0 12px 36px rgba(15,23,42,0.14);
146
+ max-height: 420px; overflow-y: auto; z-index: 100; display: none;
147
+ flex-direction: column; padding: 4px 0;
148
+ }
149
+ .search-popover.open { display: flex; animation: viewIn .15s ease-out; }
150
+ .popover-item {
151
+ padding: 10px 14px; cursor: pointer; display: flex; align-items: center; justify-content: space-between;
152
+ gap: 12px; transition: background .12s; border-bottom: 1px solid #F8FAFC;
153
+ }
154
+ .popover-item:hover, .popover-item.selected { background: #EFF6FF; }
155
+ .popover-item-main { flex: 1; min-width: 0; }
156
+ .popover-title {
157
+ font-size: 13px; font-weight: 600; color: #0F172A;
158
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 2px;
159
+ }
160
+ .popover-sub {
161
+ font-family: var(--font-mono); font-size: 11px; color: var(--text-tertiary);
162
+ display: flex; align-items: center; gap: 8px;
163
+ }
164
+ .popover-empty {
165
+ padding: 24px; text-align: center; color: var(--text-tertiary);
166
+ font-family: var(--font-mono); font-size: 12px;
167
+ }
168
+ .popover-footer {
169
+ padding: 8px 14px; font-family: var(--font-mono); font-size: 11px;
170
+ color: var(--text-tertiary); background: #F8FAFC; border-top: 1px solid var(--border-normal);
171
+ display: flex; justify-content: space-between; align-items: center;
172
+ }
173
+
174
+ /* 本地雷达自动同步指示器 */
175
+ .sync-beacon {
176
+ display: flex; align-items: center; gap: 10px;
177
+ background: #FFFFFF; border: 1px solid var(--border-normal);
178
+ border-radius: var(--radius-sm); padding: 6px 14px; font-size: 12px;
179
+ font-family: var(--font-mono); color: var(--text-secondary); cursor: pointer;
180
+ transition: all .2s; user-select: none; box-shadow: 0 1px 2px rgba(15,23,42,0.04);
181
+ }
182
+ .sync-beacon:hover { border-color: var(--brand-blue); color: #0F172A; box-shadow: 0 0 0 3px var(--brand-blue-glow); }
183
+ .radar-dot {
184
+ width: 8px; height: 8px; border-radius: 50%; background: #94A3B8; position: relative;
185
+ }
186
+ .radar-dot.active { background: var(--emerald-live); }
187
+ .radar-dot.active::after {
188
+ content: ''; position: absolute; inset: -4px; border-radius: 50%;
189
+ border: 1px solid var(--emerald-live); animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
190
+ }
191
+ .radar-dot.syncing { background: var(--amber-core); }
192
+ @keyframes ping { 75%, 100% { transform: scale(2.2); opacity: 0; } }
193
+
194
+ /* 主视口布局 */
195
+ main.mission-canvas {
196
+ max-width: 1440px; width: 100%; margin: 0 auto; padding: 28px 36px 80px;
197
+ flex: 1; display: flex; flex-direction: column; gap: 28px;
198
+ }
199
+
200
+ .canvas-view { display: none; }
201
+ .canvas-view.active { display: block; animation: viewIn .25s ease-out; }
202
+ @keyframes viewIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
203
+
204
+ /* 实体白皮书面板 */
205
+ .hud-panel {
206
+ background: var(--bg-surface); border: 1px solid var(--border-normal);
207
+ border-radius: var(--radius-md); padding: 24px 28px; position: relative;
208
+ box-shadow: 0 1px 3px rgba(15,23,42,0.04), 0 6px 16px rgba(15,23,42,0.03);
209
+ overflow: hidden;
210
+ }
211
+ .panel-masthead {
212
+ display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 20px;
213
+ }
214
+ .panel-headline {
215
+ font-family: var(--font-display); font-size: 16px; font-weight: 700;
216
+ color: #0F172A; letter-spacing: -0.2px; display: flex; align-items: center; gap: 10px;
217
+ }
218
+ .panel-annotation {
219
+ font-family: var(--font-mono); font-size: 11.5px; color: var(--text-tertiary);
220
+ }
221
+
222
+ /* KPI 战术度量仪表阵列 */
223
+ .kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-bottom: 28px; }
224
+ @media (max-width: 960px) { .kpi-row { grid-template-columns: repeat(2, 1fr); } }
225
+ .kpi-instrument {
226
+ background: var(--bg-surface); border: 1px solid var(--border-normal);
227
+ border-radius: var(--radius-md); padding: 20px 24px; cursor: pointer;
228
+ transition: all .2s; position: relative; overflow: hidden;
229
+ box-shadow: 0 1px 3px rgba(15,23,42,0.04), 0 4px 12px rgba(15,23,42,0.02);
230
+ }
231
+ .kpi-instrument:hover {
232
+ border-color: var(--brand-blue); transform: translateY(-2px);
233
+ box-shadow: 0 6px 20px rgba(15,23,42,0.08);
234
+ }
235
+ .kpi-tag {
236
+ font-family: var(--font-mono); font-size: 11.5px; color: var(--text-secondary);
237
+ display: flex; align-items: center; justify-content: space-between; font-weight: 600;
238
+ }
239
+ .kpi-num {
240
+ font-family: var(--font-display); font-size: 38px; font-weight: 700;
241
+ color: #0F172A; line-height: 1.1; margin: 10px 0 6px; letter-spacing: -1px;
242
+ }
243
+ .kpi-caption { font-size: 12.5px; color: var(--text-tertiary); }
244
+ .kpi-accent-bar { position: absolute; bottom: 0; left: 0; right: 0; height: 3px; }
245
+
246
+ /* 核心承重墙网格(Structural Pillars) */
247
+ .pillars-vault { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 16px; margin-bottom: 28px; }
248
+ .pillar-specimen {
249
+ background: #FFFFFF; border: 1px solid var(--border-normal);
250
+ border-radius: var(--radius-sm); padding: 18px; cursor: pointer;
251
+ transition: all .2s cubic-bezier(0.16, 1, 0.3, 1);
252
+ display: flex; flex-direction: column; justify-content: space-between; position: relative;
253
+ box-shadow: 0 1px 2px rgba(15,23,42,0.04);
254
+ }
255
+ .pillar-specimen:hover {
256
+ border-color: var(--brand-blue); transform: translateY(-2px);
257
+ box-shadow: 0 8px 24px rgba(29, 78, 216, 0.08);
258
+ }
259
+ .pillar-specimen::after {
260
+ content: ''; position: absolute; right: 12px; top: 12px;
261
+ width: 6px; height: 6px; border-radius: 50%; background: var(--amber-core);
262
+ }
263
+ .pillar-meta { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
264
+ .pillar-core-title {
265
+ font-family: var(--font-display); font-size: 15px; font-weight: 700;
266
+ color: #0F172A; line-height: 1.35; margin-bottom: 10px;
267
+ }
268
+ .pillar-abstract {
269
+ font-size: 13px; color: var(--text-secondary); line-height: 1.6;
270
+ margin-bottom: 16px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
271
+ }
272
+ .pillar-telemetry {
273
+ display: flex; align-items: center; justify-content: space-between;
274
+ font-family: var(--font-mono); font-size: 11.5px; color: var(--text-tertiary);
275
+ border-top: 1px solid var(--border-subtle); padding-top: 10px; margin-top: auto;
276
+ }
277
+ .weight-gauge {
278
+ color: var(--brand-blue); font-weight: 700; background: #EFF6FF;
279
+ border: 1px solid #BFDBFE; border-radius: 3px; padding: 2px 7px;
280
+ }
281
+
282
+ /* 领域分类矩阵(Domain Matrix) */
283
+ .matrix-chambers {
284
+ display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
285
+ gap: 18px; width: 100%;
286
+ }
287
+ @media (max-width: 1200px) { .matrix-chambers { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
288
+ @media (max-width: 768px) { .matrix-chambers { grid-template-columns: minmax(0, 1fr); } }
289
+ .chamber-pod {
290
+ background: #F8FAFC; border: 1px solid var(--border-normal);
291
+ border-radius: var(--radius-sm); padding: 16px; min-width: 0; overflow: hidden;
292
+ }
293
+ .chamber-header {
294
+ display: flex; align-items: center; justify-content: space-between;
295
+ padding-bottom: 10px; border-bottom: 1px solid var(--border-normal); margin-bottom: 12px;
296
+ }
297
+ .chamber-name {
298
+ font-family: var(--font-display); font-size: 13.5px; font-weight: 700;
299
+ color: #0F172A; display: flex; align-items: center; gap: 8px;
300
+ }
301
+ .chamber-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-tertiary); font-weight: 600; }
302
+ .chamber-stream {
303
+ display: flex; flex-direction: column; gap: 8px; max-height: 420px;
304
+ overflow-y: auto; min-width: 0; width: 100%; padding-right: 4px;
305
+ }
306
+ .chamber-stream::-webkit-scrollbar { width: 4px; }
307
+ .chamber-stream::-webkit-scrollbar-thumb { background: #CBD5E1; border-radius: 4px; }
308
+ .chamber-stream::-webkit-scrollbar-track { background: transparent; }
309
+
310
+ .specimen-row {
311
+ flex-shrink: 0; /* 绝对禁止压扁 */
312
+ min-height: 54px; display: flex; flex-direction: column; justify-content: center;
313
+ background: #FFFFFF; border: 1px solid var(--border-normal);
314
+ border-radius: var(--radius-sm); padding: 10px 12px; cursor: pointer; transition: all .15s;
315
+ box-shadow: 0 1px 2px rgba(15,23,42,0.02); min-width: 0; width: 100%; box-sizing: border-box;
316
+ }
317
+ .specimen-row:hover {
318
+ border-color: var(--brand-blue); transform: translateX(2px);
319
+ box-shadow: 0 2px 8px rgba(15,23,42,0.06);
320
+ }
321
+ .specimen-title {
322
+ font-size: 13px; font-weight: 600; color: #0F172A; line-height: 1.4; margin-bottom: 4px;
323
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%;
324
+ }
325
+ .specimen-info {
326
+ font-family: var(--font-mono); font-size: 11px; color: var(--text-tertiary); line-height: 1.2;
327
+ display: flex; align-items: center; justify-content: space-between; width: 100%;
328
+ }
329
+
330
+ /* 时间线筛选工具栏 */
331
+ .timeline-toolbar {
332
+ display: flex; flex-direction: column; gap: 10px; margin-bottom: 24px;
333
+ background: #F8FAFC; border: 1px solid var(--border-normal);
334
+ border-radius: var(--radius-sm); padding: 14px 18px;
335
+ }
336
+ .filter-row {
337
+ display: flex; align-items: center; gap: 12px; flex-wrap: wrap; width: 100%;
338
+ }
339
+ .filter-group-label {
340
+ font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary);
341
+ font-weight: 700; display: flex; align-items: center; gap: 6px; flex-shrink: 0; min-width: 76px;
342
+ }
343
+ .month-pills, .class-pills {
344
+ display: flex; gap: 6px; overflow-x: auto; max-width: calc(100% - 90px); padding-bottom: 2px;
345
+ }
346
+ .month-pill, .class-pill {
347
+ border: 1px solid var(--border-normal); background: #FFFFFF;
348
+ color: var(--text-secondary); font-family: var(--font-mono); font-size: 11px;
349
+ font-weight: 600; padding: 3px 9px; border-radius: 4px; cursor: pointer;
350
+ transition: all .15s; white-space: nowrap;
351
+ }
352
+ .month-pill:hover, .class-pill:hover { border-color: var(--brand-blue); color: var(--brand-blue); }
353
+ .month-pill.active {
354
+ background: var(--brand-blue); border-color: var(--brand-blue);
355
+ color: #FFFFFF; box-shadow: 0 1px 4px rgba(29,78,216,0.25);
356
+ }
357
+ .class-pill.active {
358
+ background: #0F172A; border-color: #0F172A;
359
+ color: #FFFFFF; box-shadow: 0 1px 4px rgba(15,23,42,0.2);
360
+ }
361
+ .filter-summary-bar {
362
+ display: flex; align-items: center; justify-content: space-between;
363
+ border-top: 1px solid var(--border-subtle); padding-top: 8px; margin-top: 2px;
364
+ font-family: var(--font-mono); font-size: 11px; color: var(--text-tertiary);
365
+ }
366
+
367
+ /* 时间线 Chrono-Track */
368
+ .chrono-track { position: relative; padding-left: 36px; }
369
+ .chrono-track::before {
370
+ content: ''; position: absolute; left: 11px; top: 12px; bottom: 12px; width: 2px;
371
+ background: linear-gradient(180deg, var(--brand-blue) 0%, rgba(15, 23, 42, 0.1) 100%);
372
+ }
373
+ .chrono-node-epoch { margin-bottom: 36px; position: relative; }
374
+ .chrono-epoch-badge {
375
+ font-family: var(--font-mono); font-size: 12px; font-weight: 700;
376
+ background: #FFFFFF; border: 1px solid var(--border-normal); color: var(--brand-blue);
377
+ padding: 4px 14px; border-radius: var(--radius-sm); display: inline-flex; align-items: center; gap: 8px;
378
+ margin-left: -36px; margin-bottom: 16px; box-shadow: 0 2px 6px rgba(15,23,42,0.06);
379
+ }
380
+ .chrono-cards { display: flex; flex-direction: column; gap: 14px; }
381
+ .chrono-card {
382
+ background: #FFFFFF; border: 1px solid var(--border-normal);
383
+ border-radius: var(--radius-sm); padding: 18px 22px; cursor: pointer;
384
+ transition: all .2s; position: relative; box-shadow: 0 1px 3px rgba(15,23,42,0.03);
385
+ }
386
+ .chrono-card:hover {
387
+ border-color: var(--brand-blue); transform: translateX(4px);
388
+ box-shadow: 0 6px 20px rgba(15,23,42,0.06);
389
+ }
390
+ .chrono-pulse {
391
+ position: absolute; left: -31px; top: 24px; width: 10px; height: 10px;
392
+ border-radius: 50%; background: #FFFFFF; border: 2px solid var(--brand-blue);
393
+ }
394
+ .chrono-date-badge {
395
+ font-family: var(--font-mono); font-size: 11.5px; font-weight: 700;
396
+ color: #1D4ED8; background: #EFF6FF; border: 1px solid #BFDBFE;
397
+ padding: 2px 8px; border-radius: 4px; display: inline-flex; align-items: center; gap: 4px;
398
+ flex-shrink: 0;
399
+ }
400
+ .chrono-meta-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
401
+ .chrono-date { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-tertiary); }
402
+ .chrono-heading { font-family: var(--font-display); font-size: 15px; font-weight: 700; color: #0F172A; }
403
+ .chrono-body { font-size: 13.5px; color: var(--text-secondary); line-height: 1.6; }
404
+
405
+ /* 避坑与反思智库(Pitfalls Vault) */
406
+ .alt-controls { display: flex; gap: 14px; margin-bottom: 24px; align-items: center; flex-wrap: wrap; }
407
+ .alt-search-input {
408
+ flex: 1; min-width: 280px; height: 40px; background: #FFFFFF;
409
+ border: 1px solid var(--border-normal); border-radius: var(--radius-sm);
410
+ color: #0F172A; font-family: var(--font-mono); font-size: 13px; padding: 0 16px; outline: none;
411
+ box-shadow: 0 1px 2px rgba(15,23,42,0.03);
412
+ }
413
+ .alt-search-input:focus { border-color: var(--alert-ruby); box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12); }
414
+
415
+ .pitfalls-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 18px; }
416
+ .pitfall-dossier {
417
+ background: #FFFFFF; border: 1px solid var(--border-normal);
418
+ border-radius: var(--radius-sm); padding: 22px; display: flex; flex-direction: column;
419
+ justify-content: space-between; gap: 14px; cursor: pointer; transition: all .2s;
420
+ box-shadow: 0 1px 3px rgba(15,23,42,0.03);
421
+ }
422
+ .pitfall-dossier:hover {
423
+ border-color: var(--alert-ruby); transform: translateY(-2px);
424
+ box-shadow: 0 8px 24px rgba(220, 38, 38, 0.08);
425
+ }
426
+ .hazard-banner {
427
+ background: var(--alert-ruby-subtle); border-left: 3px solid var(--alert-ruby);
428
+ padding: 10px 14px; font-size: 13.5px; font-weight: 600; color: #991B1B;
429
+ }
430
+ .crux-evidence { font-size: 13px; color: var(--text-secondary); line-height: 1.65; }
431
+ .triumphant-verdict {
432
+ background: #ECFDF5; border: 1px solid #A7F3D0;
433
+ border-radius: var(--radius-sm); padding: 8px 12px; font-size: 12px; color: #065F46;
434
+ display: flex; align-items: center; gap: 8px; font-weight: 600;
435
+ }
436
+
437
+ /* 决策档案目录(Directory Matrix) */
438
+ .matrix-toolbar {
439
+ display: flex; align-items: center; gap: 12px; background: #FFFFFF;
440
+ border: 1px solid var(--border-normal); border-radius: var(--radius-sm); padding: 12px 18px; margin-bottom: 16px;
441
+ box-shadow: 0 1px 2px rgba(15,23,42,0.03);
442
+ }
443
+ .matrix-toolbar select {
444
+ height: 34px; background: #F8FAFC; border: 1px solid var(--border-normal);
445
+ border-radius: var(--radius-sm); color: #0F172A; font-family: var(--font-mono); font-size: 12px;
446
+ padding: 0 12px; outline: none; cursor: pointer; font-weight: 500;
447
+ }
448
+ .matrix-board {
449
+ background: #FFFFFF; border: 1px solid var(--border-normal);
450
+ border-radius: var(--radius-md); overflow: hidden;
451
+ box-shadow: 0 1px 3px rgba(15,23,42,0.04), 0 4px 12px rgba(15,23,42,0.02);
452
+ }
453
+ .matrix-row-head, .matrix-row-cell {
454
+ display: grid; grid-template-columns: 80px 110px 1fr 110px 120px 70px;
455
+ gap: 14px; align-items: center; padding: 0 24px;
456
+ }
457
+ .matrix-row-head {
458
+ height: 42px; background: #F8FAFC; font-family: var(--font-mono);
459
+ font-size: 11.5px; color: var(--text-secondary); border-bottom: 1px solid var(--border-normal);
460
+ font-weight: 600;
461
+ }
462
+ .matrix-row-cell {
463
+ min-height: 52px; border-bottom: 1px solid #F1F5F9;
464
+ cursor: pointer; transition: all .15s; font-size: 13.5px;
465
+ }
466
+ .matrix-row-cell:hover { background: #F8FAFC; }
467
+ .cell-index { font-family: var(--font-mono); font-size: 12px; color: var(--text-tertiary); font-weight: 500; }
468
+ .cell-title { font-weight: 600; color: #0F172A; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
469
+ .cell-date { font-family: var(--font-mono); font-size: 12px; color: var(--text-secondary); }
470
+ .cell-cites { font-family: var(--font-mono); font-size: 12px; color: var(--brand-blue); text-align: right; font-weight: 700; }
471
+
472
+ /* 状态与分类战术徽章(瑞士精密标贴风格) */
473
+ .status-badge {
474
+ display: inline-flex; align-items: center; gap: 6px;
475
+ font-family: var(--font-mono); font-size: 11px; font-weight: 600;
476
+ padding: 2px 8px; border-radius: 3px; width: max-content;
477
+ }
478
+ .status-badge.st-implemented { background: #ECFDF5; color: #065F46; border: 1px solid #A7F3D0; }
479
+ .status-badge.st-proposed { background: #FFFBEB; color: #92400E; border: 1px solid #FDE68A; }
480
+ .status-badge.st-rejected { background: #FEF2F2; color: #991B1B; border: 1px solid #FECACA; }
481
+ .status-badge.st-archived { background: #F1F5F9; color: #475569; border: 1px solid #E2E8F0; }
482
+
483
+ .class-badge {
484
+ font-family: var(--font-mono); font-size: 10.5px; padding: 2px 7px;
485
+ border-radius: 3px; font-weight: 600; width: max-content;
486
+ }
487
+ .cls-architecture { background: #FFF7ED; color: #C2410C; border: 1px solid #FED7AA; }
488
+ .cls-feature { background: #EFF6FF; color: #1D4ED8; border: 1px solid #BFDBFE; }
489
+ .cls-bug-fix { background: #ECFDF5; color: #047857; border: 1px solid #A7F3D0; }
490
+ .cls-simplification { background: #F0FDFA; color: #0F766E; border: 1px solid #99F6E4; }
491
+ .cls-process { background: #FAF5FF; color: #7E22CE; border: 1px solid #E9D5FF; }
492
+ .cls-testing { background: #FDF2F8; color: #BE185D; border: 1px solid #FBCFE8; }
493
+
494
+ /* 结构化白皮书控制台抽屉(Dossier Drawer) */
495
+ #drawer-curtain {
496
+ position: fixed; inset: 0; background: rgba(15, 23, 42, 0.45);
497
+ backdrop-filter: blur(4px); z-index: 60; display: none;
498
+ }
499
+ #drawer-curtain.open { display: block; }
500
+ #drawer-console {
501
+ position: fixed; top: 0; right: 0; bottom: 0; width: 70vw; max-width: 860px; min-width: 520px;
502
+ background: #FFFFFF; border-left: 1px solid var(--border-normal); z-index: 61;
503
+ transform: translateX(105%); transition: transform .28s cubic-bezier(0.16, 1, 0.3, 1);
504
+ display: flex; flex-direction: column; box-shadow: -16px 0 48px rgba(15, 23, 42, 0.15);
505
+ }
506
+ #drawer-console.open { transform: none; }
507
+
508
+ .console-header {
509
+ height: 64px; padding: 0 28px; border-bottom: 1px solid var(--border-normal);
510
+ display: flex; align-items: center; justify-content: space-between; gap: 14px;
511
+ background: #F8FAFC;
512
+ }
513
+ .console-exit {
514
+ background: #FFFFFF; border: 1px solid var(--border-normal); color: var(--text-secondary);
515
+ font-size: 16px; cursor: pointer; padding: 6px 10px; border-radius: var(--radius-sm);
516
+ display: flex; align-items: center; justify-content: center;
517
+ }
518
+ .console-exit:hover { color: #0F172A; border-color: #94A3B8; }
519
+
520
+ .console-body { flex: 1; overflow-y: auto; padding: 36px 44px 80px; }
521
+
522
+ /* 抽屉打开时锁住背景滚动,避免内外双层滚动条 */
523
+ body:has(#drawer-console.open),
524
+ html:has(#drawer-console.open) { overflow: hidden; }
525
+
526
+ /* 代码反向注释定位板 */
527
+ .anchor-terminal {
528
+ background: #F8FAFC; border: 1px dashed #BFDBFE;
529
+ border-radius: var(--radius-sm); padding: 12px 18px; margin-bottom: 28px;
530
+ display: flex; align-items: center; justify-content: space-between; gap: 16px;
531
+ }
532
+ .anchor-code {
533
+ font-family: var(--font-mono); font-size: 12.5px; color: #1D4ED8; font-weight: 500;
534
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
535
+ }
536
+ .btn-anchor-copy {
537
+ background: #EFF6FF; border: 1px solid var(--brand-blue);
538
+ color: var(--brand-blue); font-family: var(--font-mono); font-size: 11px; padding: 5px 12px;
539
+ border-radius: var(--radius-sm); cursor: pointer; white-space: nowrap; transition: all .15s;
540
+ font-weight: 600;
541
+ }
542
+ .btn-anchor-copy:hover { background: var(--brand-blue); color: #FFFFFF; box-shadow: 0 2px 6px rgba(29, 78, 216, 0.2); }
543
+
544
+ .console-headline {
545
+ font-family: var(--font-display); font-size: 24px; font-weight: 700;
546
+ color: #0F172A; line-height: 1.35; margin-bottom: 16px; letter-spacing: -0.3px;
547
+ }
548
+ .console-timestamp {
549
+ font-family: var(--font-mono); font-size: 12px; color: var(--text-tertiary);
550
+ display: flex; align-items: center; gap: 10px; margin-bottom: 28px;
551
+ }
552
+
553
+ .quad-section { margin-bottom: 32px; }
554
+ .quad-title {
555
+ font-family: var(--font-mono); font-size: 12px; font-weight: 700; letter-spacing: 0.8px;
556
+ color: var(--text-secondary); margin-bottom: 12px; display: flex; align-items: center; gap: 10px;
557
+ text-transform: uppercase;
558
+ }
559
+ .quad-title::after { content: ''; flex: 1; height: 1px; background: var(--border-normal); }
560
+ .quad-content { font-size: 14.5px; line-height: 1.8; color: #334155; }
561
+ .quad-content code { font-family: var(--font-mono); font-size: 13px; background: #F1F5F9; padding: 2px 6px; border-radius: 3px; color: #1D4ED8; }
562
+ .quad-content pre { background: #0F172A; border: 1px solid #1E293B; border-radius: var(--radius-sm); padding: 16px; overflow-x: auto; margin: 14px 0; }
563
+ .quad-content pre code { background: transparent; padding: 0; color: #F8FAFC; }
564
+
565
+ .lineage-nodes { display: flex; flex-direction: column; gap: 12px; }
566
+ .lineage-group-title { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600; }
567
+ .lineage-chips { display: flex; flex-wrap: wrap; gap: 8px; }
568
+ .lineage-chip {
569
+ background: #F8FAFC; border: 1px solid var(--border-normal);
570
+ border-radius: var(--radius-sm); padding: 6px 12px; font-size: 12.5px; color: #0F172A;
571
+ cursor: pointer; transition: all .15s; display: inline-flex; align-items: center; gap: 6px;
572
+ font-weight: 500;
573
+ }
574
+ .lineage-chip:hover { color: var(--brand-blue); border-color: var(--brand-blue); background: #EFF6FF; }
575
+
576
+ /* 引导开场台 */
577
+ .onboarding-deck {
578
+ background: #FFFFFF; border: 1px dashed var(--border-normal);
579
+ border-radius: var(--radius-md); padding: 56px 40px; text-align: center;
580
+ display: flex; flex-direction: column; align-items: center; gap: 20px;
581
+ box-shadow: 0 4px 20px rgba(15,23,42,0.05);
582
+ }
583
+ .onboarding-deck h2 { font-family: var(--font-display); font-size: 22px; font-weight: 700; color: #0F172A; }
584
+ .onboarding-deck p { color: var(--text-secondary); max-width: 620px; font-size: 14.5px; line-height: 1.7; }
585
+ .action-btn-primary {
586
+ background: var(--brand-blue); color: #FFFFFF; border: 0; border-radius: var(--radius-sm);
587
+ padding: 12px 24px; font-family: var(--font-display); font-size: 14px; font-weight: 700;
588
+ cursor: pointer; box-shadow: 0 2px 8px rgba(29, 78, 216, 0.25); transition: all .2s;
589
+ display: inline-flex; align-items: center; gap: 10px;
590
+ }
591
+ .action-btn-primary:hover { background: var(--brand-blue-hover); }
592
+ .action-btn-secondary {
593
+ background: #FFFFFF; border: 1px solid var(--border-normal); color: var(--text-secondary);
594
+ font-family: var(--font-mono); font-size: 13px; padding: 10px 18px; border-radius: var(--radius-sm);
595
+ cursor: pointer; transition: all .15s; font-weight: 600;
596
+ }
597
+ .action-btn-secondary:hover { color: #0F172A; border-color: #94A3B8; }
598
+ </style>
599
+ </head>
600
+ <body>
601
+
602
+ <!-- 顶栏 HUD -->
603
+ <header class="hud-topbar">
604
+ <div class="brand-group">
605
+ <div class="brand-logo"></div>
606
+ <div class="brand-text">
607
+ <span id="brand-project-title">工程决策看板</span>
608
+ <span class="telemetry-chip" id="chip-total-count">0 项</span>
609
+ </div>
610
+ </div>
611
+
612
+ <nav class="deck-tabs">
613
+ <button class="deck-tab active" data-view="baseline">🏛️ 架构基线</button>
614
+ <button class="deck-tab" data-view="timeline">⏱️ 演进时间线</button>
615
+ <button class="deck-tab" data-view="alternatives">🛡️ 避坑智库</button>
616
+ <button class="deck-tab" data-view="directory">📋 决策清单</button>
617
+ </nav>
618
+
619
+ <div class="hud-grow"></div>
620
+
621
+ <div class="hud-search">
622
+ <svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
623
+ <input id="global-query" type="text" placeholder="快速搜索决策与避坑...">
624
+ <span class="shortcut-key">/</span>
625
+ <div class="search-popover" id="search-popover"></div>
626
+ </div>
627
+
628
+ <div class="sync-beacon" id="beacon-sync" title="点击选择本地 .agents/notes 目录开启纯本地热同步">
629
+ <span class="radar-dot" id="radar-indicator"></span>
630
+ <span id="beacon-status">连接本地目录</span>
631
+ </div>
632
+ </header>
633
+
634
+ <main class="mission-canvas">
635
+
636
+ <!-- 未装载引导 -->
637
+ <div class="onboarding-deck" id="deck-onboarding" style="display:none;">
638
+ <h2>工程决策看板</h2>
639
+ <p>当前尚未挂载笔记数据。本看板支持浏览器端直接授权读取本地 <code>.agents/notes</code> 目录,无需任何后端服务器与 Git 提交,本地新建或修改笔记切回浏览器即刻自动热更新。</p>
640
+ <div style="display:flex; gap:14px; margin-top:8px;">
641
+ <button class="action-btn-primary" id="btn-authorize-dir">
642
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
643
+ 选择本地 .agents/notes 文件夹
644
+ </button>
645
+ <button class="action-btn-secondary" id="btn-mount-demo">载入标准样例演示数据</button>
646
+ </div>
647
+ </div>
648
+
649
+ <!-- 1. 架构基线视图 -->
650
+ <section class="canvas-view active" id="view-baseline">
651
+ <div class="kpi-row" id="kpi-row"></div>
652
+
653
+ <div class="hud-panel" style="margin-bottom: 28px;">
654
+ <div class="panel-masthead">
655
+ <span class="panel-headline">
656
+ <span>🏛️</span>
657
+ <span>系统承重墙</span>
658
+ </span>
659
+ <span class="panel-annotation">基于交叉引用入度权重萃取的系统关键协议</span>
660
+ </div>
661
+ <div class="pillars-vault" id="pillars-vault"></div>
662
+ </div>
663
+
664
+ <div class="hud-panel">
665
+ <div class="panel-masthead">
666
+ <span class="panel-headline">
667
+ <span>🧩</span>
668
+ <span>领域架构分类阵列</span>
669
+ </span>
670
+ <span class="panel-annotation">现行生效的生产事实全景清单</span>
671
+ </div>
672
+ <div class="matrix-chambers" id="matrix-chambers"></div>
673
+ </div>
674
+ </section>
675
+
676
+ <!-- 2. 演进时间线视图 -->
677
+ <section class="canvas-view" id="view-timeline">
678
+ <div class="hud-panel">
679
+ <div class="panel-masthead">
680
+ <span class="panel-headline">
681
+ <span>⏱️</span>
682
+ <span>技术演进时间线</span>
683
+ </span>
684
+ <span class="panel-annotation">记录架构突破与代码裁撤里程碑</span>
685
+ </div>
686
+
687
+ <div class="timeline-toolbar">
688
+ <div class="filter-row">
689
+ <span class="filter-group-label">📅 月份切片:</span>
690
+ <div class="month-pills" id="timeline-month-pills"></div>
691
+ </div>
692
+ <div class="filter-row">
693
+ <span class="filter-group-label">🏷️ 分类切片:</span>
694
+ <div class="class-pills" id="timeline-class-pills"></div>
695
+ </div>
696
+ <div class="filter-summary-bar">
697
+ <span id="timeline-filter-info"></span>
698
+ <button id="btn-reset-timeline-filter" style="background:none;border:0;color:var(--brand-blue);font-family:var(--font-mono);font-size:11px;cursor:pointer;font-weight:600;">重置时间线筛选</button>
699
+ </div>
700
+ </div>
701
+
702
+ <div class="chrono-track" id="chrono-track"></div>
703
+ </div>
704
+ </section>
705
+
706
+ <!-- 3. 避坑智库视图 -->
707
+ <section class="canvas-view" id="view-alternatives">
708
+ <div class="hud-panel">
709
+ <div class="panel-masthead">
710
+ <span class="panel-headline">
711
+ <span>🛡️</span>
712
+ <span>避坑与反思智库</span>
713
+ </span>
714
+ <span class="panel-annotation">记录「为什么放弃了方案 B」与被否决的提案,防止重复踩坑</span>
715
+ </div>
716
+ <div class="alt-controls">
717
+ <input type="text" class="alt-search-input" id="alt-query-input" placeholder="检索被否决方案关键字(如:SQLite、多进程、Redis...)">
718
+ </div>
719
+ <div class="pitfalls-grid" id="pitfalls-grid"></div>
720
+ </div>
721
+ </section>
722
+
723
+ <!-- 4. 决策清单视图 -->
724
+ <section class="canvas-view" id="view-directory">
725
+ <div class="matrix-toolbar">
726
+ <select id="select-status"><option value="">状态:全部</option></select>
727
+ <select id="select-class"><option value="">分类:全部</option></select>
728
+ <select id="select-sort">
729
+ <option value="new">排序:最新优先</option>
730
+ <option value="old">排序:最早优先</option>
731
+ <option value="cites">排序:引用最多</option>
732
+ </select>
733
+ <button id="btn-reset-matrix-filter" style="background:none;border:0;color:var(--brand-blue);font-family:var(--font-mono);font-size:11px;cursor:pointer;font-weight:600;" title="清空全部筛选条件">重置筛选</button>
734
+ <span style="font-family:var(--font-mono); font-size:11px; color:var(--text-tertiary); margin-left:auto;" id="matrix-matched-count"></span>
735
+ </div>
736
+
737
+ <div class="matrix-board">
738
+ <div class="matrix-row-head">
739
+ <span>编号</span>
740
+ <span>分类</span>
741
+ <span>标题</span>
742
+ <span>状态</span>
743
+ <span>日期</span>
744
+ <span style="text-align:right;">引用</span>
745
+ </div>
746
+ <div id="matrix-rows-stream"></div>
747
+ </div>
748
+ </section>
749
+
750
+ <footer style="margin-top: 24px; padding: 20px 0; border-top: 1px solid var(--border-normal); display: flex; align-items: center; justify-content: space-between; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-tertiary); flex-wrap: wrap; gap: 12px;">
751
+ <div>Agent Notes · 工程决策看板 · 遵循 MIT 协议开源</div>
752
+ <div style="display: flex; gap: 16px; align-items: center;">
753
+ <span>友情链接:</span>
754
+ <a href="https://linux.do" target="_blank" rel="noopener noreferrer" style="color: var(--brand-blue); text-decoration: none; font-weight: 600;">LinuxDo</a>
755
+ </div>
756
+ </footer>
757
+
758
+ </main>
759
+
760
+ <!-- 结构化控制台抽屉 -->
761
+ <div id="drawer-curtain"></div>
762
+ <aside id="drawer-console">
763
+ <div class="console-header">
764
+ <span class="status-badge" id="dr-status-badge"></span>
765
+ <span class="class-badge" id="dr-class-badge"></span>
766
+ <div style="flex:1;"></div>
767
+ <button class="console-exit" id="btn-console-close" title="退出 (Esc)">✕</button>
768
+ </div>
769
+ <div class="console-body">
770
+ <div class="anchor-terminal">
771
+ <span class="anchor-code" id="dr-anchor-code">// Note: 见 .agents/notes/...</span>
772
+ <button class="btn-anchor-copy" id="btn-anchor-copy">复制反向代码注释</button>
773
+ </div>
774
+
775
+ <h1 class="console-headline" id="dr-headline"></h1>
776
+ <div class="console-timestamp" id="dr-timestamp-bar"></div>
777
+
778
+ <div class="quad-section" id="quad-sec-problem">
779
+ <div class="quad-title">🎯 01. 背景与痛点</div>
780
+ <div class="quad-content" id="dr-body-problem"></div>
781
+ </div>
782
+
783
+ <div class="quad-section" id="quad-sec-decision">
784
+ <div class="quad-title">⚡ 02. 落地裁定事实</div>
785
+ <div class="quad-content" id="dr-body-decision"></div>
786
+ </div>
787
+
788
+ <div class="quad-section" id="quad-sec-alt">
789
+ <div class="quad-title">🚫 03. 审慎考虑并放弃的备选</div>
790
+ <div class="quad-content" id="dr-body-alt"></div>
791
+ </div>
792
+
793
+ <div class="quad-section" id="quad-sec-consequences">
794
+ <div class="quad-title">⚖️ 04. 收益与工程代价</div>
795
+ <div class="quad-content" id="dr-body-consequences"></div>
796
+ </div>
797
+
798
+ <div class="quad-section">
799
+ <div class="quad-title">🔗 05. 因果与血缘依赖链路</div>
800
+ <div class="lineage-nodes" id="dr-lineage-stream"></div>
801
+ </div>
802
+ </div>
803
+ </aside>
804
+
805
+ <!-- 内联数据插槽 -->
806
+ <script id="embedded-data">
807
+ window.__INLINE_DATA__ = null;
808
+ </script>
809
+
810
+ <script>
811
+ 'use strict';
812
+
813
+ const LIFECYCLES = ['implemented', 'proposed', 'rejected', 'archived'];
814
+ const STATUS_LABELS = { implemented: '已落地', proposed: '待评审', rejected: '已否决', archived: '已归档' };
815
+
816
+ const CLASSES = ['architecture', 'feature', 'bug-fix', 'simplification', 'process', 'testing'];
817
+ const CLASS_LABELS = {
818
+ architecture: '架构设计',
819
+ feature: '功能特性',
820
+ 'bug-fix': '缺陷修复',
821
+ simplification: '化简裁撤',
822
+ process: '流程规范',
823
+ testing: '测试基建'
824
+ };
825
+
826
+ const DB_NAME = 'AgentNotesBoardDB';
827
+ const STORE_NAME = 'handles';
828
+
829
+ const Core = {
830
+ nodes: [],
831
+ byId: new Map(),
832
+ inCount: new Map(),
833
+ outMap: new Map(),
834
+ activeTab: 'baseline',
835
+ dirHandle: null,
836
+ lastSyncTime: 0,
837
+ filterQuery: '',
838
+ stFilter: '',
839
+ clsFilter: '',
840
+ sortMode: 'new',
841
+ altQuery: '',
842
+ timelineMonth: '',
843
+ timelineClass: '',
844
+ drawerId: null,
845
+ };
846
+
847
+ // 全序日期比较器:date 相同时以唯一 id 兜底,保证任意刷新顺序一致
848
+ const byDateDesc = (a, b) => (b.date || '').localeCompare(a.date || '') || a.id.localeCompare(b.id);
849
+ const byDateAsc = (a, b) => (a.date || '').localeCompare(b.date || '') || a.id.localeCompare(b.id);
850
+
851
+ function getDB() {
852
+ return new Promise((resolve, reject) => {
853
+ const req = indexedDB.open(DB_NAME, 1);
854
+ req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME);
855
+ req.onsuccess = () => resolve(req.result);
856
+ req.onerror = () => reject(req.error);
857
+ });
858
+ }
859
+
860
+ async function saveDirHandle(handle) {
861
+ try {
862
+ const db = await getDB();
863
+ const tx = db.transaction(STORE_NAME, 'readwrite');
864
+ tx.objectStore(STORE_NAME).put(handle, 'notesDirHandle');
865
+ await new Promise(r => tx.oncomplete = r);
866
+ } catch (e) { console.warn('Cannot persist dir handle', e); }
867
+ }
868
+
869
+ async function loadSavedDirHandle() {
870
+ try {
871
+ const db = await getDB();
872
+ const tx = db.transaction(STORE_NAME, 'readonly');
873
+ const req = tx.objectStore(STORE_NAME).get('notesDirHandle');
874
+ return new Promise(resolve => {
875
+ req.onsuccess = () => resolve(req.result || null);
876
+ req.onerror = () => resolve(null);
877
+ });
878
+ } catch { return null; }
879
+ }
880
+
881
+ function escapeHtml(str) {
882
+ if (!str) return '';
883
+ return String(str)
884
+ .replace(/&/g, '&amp;')
885
+ .replace(/</g, '&lt;')
886
+ .replace(/>/g, '&gt;')
887
+ .replace(/"/g, '&quot;')
888
+ .replace(/'/g, '&#39;');
889
+ }
890
+
891
+ function parseMarkdown(text) {
892
+ if (!text) return '';
893
+ let s = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
894
+ s = s.replace(/```([\s\S]*?)```/g, (m, c) => `<pre><code>${c.trim()}</code></pre>`);
895
+ s = s.replace(/`([^`\n]+)`/g, '<code>$1</code>');
896
+ s = s.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>');
897
+ s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, linkText, href) => {
898
+ const cleanHref = href.trim();
899
+ if (/^(?:javascript|data|vbscript):/i.test(cleanHref)) {
900
+ return linkText;
901
+ }
902
+ const safeHref = escapeHtml(cleanHref);
903
+ return `<a href="javascript:void(0)" class="md-deep-link" data-deep-link="${safeHref}" style="color:var(--brand-cyan);text-decoration:underline;">${linkText}</a>`;
904
+ });
905
+ s = s.replace(/^- (.*)$/gm, '• $1');
906
+ return s.split(/\n{2,}/).map(p => {
907
+ p = p.trim();
908
+ if (!p) return '';
909
+ if (p.startsWith('<pre>')) return p;
910
+ return `<p style="margin-bottom:10px;">${p.replace(/\n/g, '<br>')}</p>`;
911
+ }).join('');
912
+ }
913
+
914
+ function parseNote(raw, relPath, slugToId = new Map()) {
915
+ const slug = relPath.split('/').pop().replace(/\.md$/, '');
916
+ const parts = relPath.split('/');
917
+ const lifecycle = parts[0];
918
+ const cls = parts[1] || 'architecture';
919
+
920
+ let title = slug;
921
+ let status = lifecycle;
922
+ let date = '';
923
+
924
+ const dMatch = /^(\d{4}-\d{2}-\d{2})/.exec(slug);
925
+ if (dMatch) date = dMatch[1];
926
+
927
+ const h1Match = /^# Agent Note[^::]*[::]\s*(.*)$/m.exec(raw);
928
+ if (h1Match) title = h1Match[1].trim();
929
+
930
+ function extractSection(pattern) {
931
+ const re = new RegExp(`^## (?:${pattern})\\s*\\n+([\\s\\S]*?)(?=^## |\\s*$)`, 'm');
932
+ const m = re.exec(raw);
933
+ return m ? m[1].trim() : '';
934
+ }
935
+
936
+ const problem = extractSection('Problem|问题');
937
+ const decision = extractSection('Decision|Proposal|决策|提案');
938
+ const alternatives = extractSection('Alternatives considered|曾考虑的替代方案|曾考虑的备选');
939
+ const consequences = extractSection('Consequences|后果');
940
+
941
+ const links = [];
942
+ for (const m of raw.matchAll(/\]\(([^)]+\.md)\)/g)) {
943
+ let targetHref = m[1].split('#')[0].trim();
944
+ if (targetHref.includes('://')) continue;
945
+ targetHref = targetHref.replace(/\.zh\.md$/, '.md');
946
+ const segs = `${lifecycle}/${cls}/${targetHref}`.split('/');
947
+ const resolved = [];
948
+ for (const s of segs) {
949
+ if (!s || s === '.') continue;
950
+ if (s === '..') resolved.pop();
951
+ else resolved.push(s);
952
+ }
953
+ const cleanTarget = resolved.join('/');
954
+
955
+ let finalTarget = '';
956
+ if (slugToId.has(cleanTarget)) {
957
+ finalTarget = slugToId.get(cleanTarget);
958
+ } else {
959
+ const targetSlug = targetHref.split('/').pop().replace(/\.md$/, '');
960
+ if (slugToId.has(targetSlug)) {
961
+ finalTarget = slugToId.get(targetSlug);
962
+ }
963
+ }
964
+ if (finalTarget && finalTarget !== relPath) {
965
+ links.push(finalTarget);
966
+ }
967
+ }
968
+
969
+ return {
970
+ id: relPath,
971
+ slug,
972
+ lifecycle,
973
+ cls,
974
+ date,
975
+ title,
976
+ status,
977
+ problem,
978
+ decision,
979
+ alternatives,
980
+ consequences,
981
+ outLinks: [...new Set(links)],
982
+ rawBody: raw,
983
+ };
984
+ }
985
+
986
+ async function scanDirectory(rootHandle) {
987
+ const notesMap = new Map();
988
+ async function walk(dirHandle, currentPath = '') {
989
+ for await (const entry of dirHandle.values()) {
990
+ if (entry.name.startsWith('.')) continue;
991
+ const subPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
992
+ if (entry.kind === 'directory') {
993
+ await walk(entry, subPath);
994
+ } else if (entry.kind === 'file' && entry.name.endsWith('.md')) {
995
+ const isZh = entry.name.endsWith('.zh.md');
996
+ const cleanRel = isZh ? subPath.replace(/\.zh\.md$/, '.md') : subPath;
997
+ const parts = cleanRel.split('/');
998
+ if (parts.length >= 3 && LIFECYCLES.includes(parts[0])) {
999
+ try {
1000
+ const file = await entry.getFile();
1001
+ const text = await file.text();
1002
+ if (!notesMap.has(cleanRel) || isZh) {
1003
+ notesMap.set(cleanRel, text);
1004
+ }
1005
+ } catch (err) {
1006
+ console.warn('Read note error:', subPath, err);
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ let targetHandle = rootHandle;
1014
+ try {
1015
+ const agentsDir = await rootHandle.getDirectoryHandle('.agents');
1016
+ targetHandle = await agentsDir.getDirectoryHandle('notes');
1017
+ } catch {}
1018
+
1019
+ await walk(targetHandle);
1020
+
1021
+ const slugToId = new Map();
1022
+ for (const rel of notesMap.keys()) {
1023
+ const slug = rel.split('/').pop().replace(/\.md$/, '');
1024
+ slugToId.set(slug, rel);
1025
+ slugToId.set(rel, rel);
1026
+ }
1027
+
1028
+ const notes = [];
1029
+ for (const [cleanRel, text] of notesMap.entries()) {
1030
+ notes.push(parseNote(text, cleanRel, slugToId));
1031
+ }
1032
+ return notes;
1033
+ }
1034
+
1035
+ function ingestDataset(notes) {
1036
+ Core.nodes = notes;
1037
+ Core.byId.clear();
1038
+ Core.inCount.clear();
1039
+ Core.outMap.clear();
1040
+
1041
+ notes.forEach(n => {
1042
+ Core.byId.set(n.id, n);
1043
+ Core.outMap.set(n.id, n.outLinks);
1044
+ });
1045
+
1046
+ notes.forEach(n => {
1047
+ n.outLinks.forEach(target => {
1048
+ if (Core.byId.has(target)) {
1049
+ Core.inCount.set(target, (Core.inCount.get(target) || 0) + 1);
1050
+ }
1051
+ });
1052
+ });
1053
+
1054
+ Core.nodes.sort(byDateDesc);
1055
+
1056
+ document.getElementById('chip-total-count').textContent = `${notes.length} 项`;
1057
+ document.getElementById('deck-onboarding').style.display = notes.length ? 'none' : 'flex';
1058
+
1059
+ renderInterface();
1060
+ }
1061
+
1062
+ function renderInterface() {
1063
+ renderBaseline();
1064
+ renderTimeline();
1065
+ renderAlternatives();
1066
+ renderDirectory();
1067
+ }
1068
+
1069
+ function renderBaseline() {
1070
+ const nodes = Core.nodes;
1071
+ const implemented = nodes.filter(n => n.lifecycle === 'implemented');
1072
+ const proposed = nodes.filter(n => n.lifecycle === 'proposed');
1073
+ const rejected = nodes.filter(n => n.lifecycle === 'rejected');
1074
+ const simplifications = nodes.filter(n => n.cls === 'simplification');
1075
+
1076
+ const kpis = [
1077
+ { label: '现行落地', count: implemented.length, desc: '生产已交付生效', color: '#10B981', filter: 'implemented' },
1078
+ { label: '待评审提案', count: proposed.length, desc: '待团队确认', color: '#F59E0B', filter: 'proposed' },
1079
+ { label: '避坑否决', count: rejected.length, desc: '防重犯事实存档', color: '#FF3366', filter: 'rejected' },
1080
+ { label: '化简裁撤', count: simplifications.length, desc: '代码只减不增', color: '#14B8A6', filter: 'simplification' },
1081
+ ];
1082
+
1083
+ document.getElementById('kpi-row').innerHTML = kpis.map(k => `
1084
+ <div class="kpi-instrument" onclick="routeToListFilter('${escapeHtml(k.filter)}')">
1085
+ <div class="kpi-tag">
1086
+ <span>${escapeHtml(k.label)}</span>
1087
+ <span style="width:6px;height:6px;border-radius:50%;background:${k.color}"></span>
1088
+ </div>
1089
+ <div class="kpi-num">${k.count}</div>
1090
+ <div class="kpi-caption">${escapeHtml(k.desc)}</div>
1091
+ <div class="kpi-accent-bar" style="background:${k.color}"></div>
1092
+ </div>
1093
+ `).join('');
1094
+
1095
+ const pillars = [...nodes]
1096
+ .filter(n => n.lifecycle === 'implemented')
1097
+ .sort((a, b) => (Core.inCount.get(b.id) || 0) - (Core.inCount.get(a.id) || 0))
1098
+ .slice(0, 6);
1099
+
1100
+ document.getElementById('pillars-vault').innerHTML = pillars.map(n => {
1101
+ const cites = Core.inCount.get(n.id) || 0;
1102
+ const title = escapeHtml(n.title);
1103
+ const plain = (n.decision || n.problem || '系统核心架构协议')
1104
+ .replace(/<[^>]+>/g, '')
1105
+ .replace(/^#{1,6}\s+/gm, '')
1106
+ .replace(/\[(.*?)\]\([^)]*\)/g, '$1')
1107
+ .replace(/[*`>]/g, '')
1108
+ .replace(/\s+/g, ' ')
1109
+ .trim();
1110
+ const desc = escapeHtml(plain);
1111
+ const safeCls = escapeHtml(n.cls);
1112
+ const clsLabel = escapeHtml(CLASS_LABELS[n.cls] || n.cls);
1113
+ const safeDate = escapeHtml(n.date || '进行中');
1114
+ const safeSlug = escapeHtml(n.slug);
1115
+ const safeId = escapeHtml(n.id);
1116
+ return `
1117
+ <div class="pillar-specimen" data-node-id="${safeId}">
1118
+ <div>
1119
+ <div class="pillar-meta">
1120
+ <span class="class-badge cls-${safeCls}">${clsLabel}</span>
1121
+ <span class="weight-gauge">被引用 × ${cites}</span>
1122
+ </div>
1123
+ <div class="pillar-core-title">${title}</div>
1124
+ <div class="pillar-abstract">${desc}</div>
1125
+ </div>
1126
+ <div class="pillar-telemetry">
1127
+ <span>${safeDate}</span>
1128
+ <span>#${safeSlug}</span>
1129
+ </div>
1130
+ </div>
1131
+ `;
1132
+ }).join('');
1133
+
1134
+ const activeNodes = nodes.filter(n => n.lifecycle === 'implemented' || n.lifecycle === 'proposed');
1135
+ document.getElementById('matrix-chambers').innerHTML = CLASSES.map(c => {
1136
+ const colNodes = activeNodes.filter(n => n.cls === c);
1137
+ const clsLabel = escapeHtml(CLASS_LABELS[c] || c);
1138
+ return `
1139
+ <div class="chamber-pod">
1140
+ <div class="chamber-header">
1141
+ <span class="chamber-name">
1142
+ <span style="width:6px;height:6px;background:var(--brand-cyan);border-radius:1px;"></span>
1143
+ ${clsLabel}
1144
+ </span>
1145
+ <span class="chamber-count">${colNodes.length}</span>
1146
+ </div>
1147
+ <div class="chamber-stream">
1148
+ ${colNodes.length ? colNodes.map(n => `
1149
+ <div class="specimen-row" data-node-id="${escapeHtml(n.id)}">
1150
+ <div class="specimen-title">${escapeHtml(n.title)}</div>
1151
+ <div class="specimen-info">
1152
+ <span>${escapeHtml(n.date || '—')}</span>
1153
+ ${(Core.inCount.get(n.id) || 0) ? `<span style="color:var(--brand-blue)">↗${Core.inCount.get(n.id)}</span>` : ''}
1154
+ </div>
1155
+ </div>
1156
+ `).join('') : '<div style="color:var(--text-tertiary);font-family:var(--font-mono);font-size:11px;padding:8px;">// 暂无该类活跃决策</div>'}
1157
+ </div>
1158
+ </div>
1159
+ `;
1160
+ }).join('');
1161
+ }
1162
+
1163
+ function renderTimeline() {
1164
+ const allDatedNodes = [...Core.nodes].filter(n => n.date).sort(byDateDesc);
1165
+
1166
+ // 1. 统计全部月份
1167
+ const monthsMap = new Map();
1168
+ allDatedNodes.forEach(n => {
1169
+ const mo = n.date.slice(0, 7);
1170
+ if (!monthsMap.has(mo)) monthsMap.set(mo, []);
1171
+ monthsMap.get(mo).push(n);
1172
+ });
1173
+
1174
+ // 2. 渲染月份筛选胶囊
1175
+ const monthPillsContainer = document.getElementById('timeline-month-pills');
1176
+ if (monthPillsContainer) {
1177
+ const allPill = `<button class="month-pill ${!Core.timelineMonth ? 'active' : ''}" data-mo="">全部 (${allDatedNodes.length})</button>`;
1178
+ const moPills = [...monthsMap.entries()].map(([mo, items]) => `
1179
+ <button class="month-pill ${Core.timelineMonth === mo ? 'active' : ''}" data-mo="${mo}">${mo} (${items.length})</button>
1180
+ `).join('');
1181
+ monthPillsContainer.innerHTML = allPill + moPills;
1182
+
1183
+ monthPillsContainer.querySelectorAll('.month-pill').forEach(btn => {
1184
+ btn.addEventListener('click', () => {
1185
+ Core.timelineMonth = btn.dataset.mo;
1186
+ renderTimeline();
1187
+ });
1188
+ });
1189
+ }
1190
+
1191
+ // 3. 渲染大类分类筛选胶囊
1192
+ const classPillsContainer = document.getElementById('timeline-class-pills');
1193
+ if (classPillsContainer) {
1194
+ const allClsPill = `<button class="class-pill ${!Core.timelineClass ? 'active' : ''}" data-cls="">全部分类 (${allDatedNodes.length})</button>`;
1195
+ const clsPills = CLASSES.map(c => {
1196
+ const cnt = allDatedNodes.filter(n => n.cls === c).length;
1197
+ return `<button class="class-pill ${Core.timelineClass === c ? 'active' : ''}" data-cls="${c}">${CLASS_LABELS[c]} (${cnt})</button>`;
1198
+ }).join('');
1199
+ classPillsContainer.innerHTML = allClsPill + clsPills;
1200
+
1201
+ classPillsContainer.querySelectorAll('.class-pill').forEach(btn => {
1202
+ btn.addEventListener('click', () => {
1203
+ Core.timelineClass = btn.dataset.cls;
1204
+ renderTimeline();
1205
+ });
1206
+ });
1207
+ }
1208
+
1209
+ // 4. 重置筛选按钮
1210
+ const btnReset = document.getElementById('btn-reset-timeline-filter');
1211
+ if (btnReset) {
1212
+ btnReset.onclick = () => {
1213
+ Core.timelineMonth = '';
1214
+ Core.timelineClass = '';
1215
+ renderTimeline();
1216
+ };
1217
+ }
1218
+
1219
+ // 5. 正交组合过滤
1220
+ let filteredNodes = allDatedNodes;
1221
+ if (Core.timelineMonth) {
1222
+ filteredNodes = filteredNodes.filter(n => n.date.startsWith(Core.timelineMonth));
1223
+ }
1224
+ if (Core.timelineClass) {
1225
+ filteredNodes = filteredNodes.filter(n => n.cls === Core.timelineClass);
1226
+ }
1227
+
1228
+ // 6. 更新信息条
1229
+ const infoEl = document.getElementById('timeline-filter-info');
1230
+ if (infoEl) {
1231
+ const parts = [];
1232
+ if (Core.timelineMonth) parts.push(Core.timelineMonth);
1233
+ if (Core.timelineClass) parts.push(CLASS_LABELS[Core.timelineClass] || Core.timelineClass);
1234
+ infoEl.textContent = parts.length
1235
+ ? `已切片 [${parts.join(' · ')}] 对应 ${filteredNodes.length} 条记录`
1236
+ : `全周期全分类共 ${filteredNodes.length} 条演化记录`;
1237
+ }
1238
+
1239
+ // 7. 按月份分组展示
1240
+ const filteredMonthsMap = new Map();
1241
+ filteredNodes.forEach(n => {
1242
+ const mo = n.date.slice(0, 7);
1243
+ if (!filteredMonthsMap.has(mo)) filteredMonthsMap.set(mo, []);
1244
+ filteredMonthsMap.get(mo).push(n);
1245
+ });
1246
+
1247
+ let html = '';
1248
+ for (const [mo, items] of filteredMonthsMap.entries()) {
1249
+ html += `
1250
+ <div class="chrono-node-epoch">
1251
+ <div class="chrono-epoch-badge">${escapeHtml(mo)} · ${items.length} 条</div>
1252
+ <div class="chrono-cards">
1253
+ ${items.map(n => `
1254
+ <div class="chrono-card" data-node-id="${escapeHtml(n.id)}">
1255
+ <div class="chrono-pulse"></div>
1256
+ <div class="chrono-meta-row">
1257
+ <span class="chrono-date-badge">📅 ${escapeHtml(n.date || '进行中')}</span>
1258
+ <span class="class-badge cls-${escapeHtml(n.cls)}">${escapeHtml(CLASS_LABELS[n.cls] || n.cls)}</span>
1259
+ <span class="status-badge st-${escapeHtml(n.lifecycle)}">${escapeHtml(STATUS_LABELS[n.lifecycle] || n.lifecycle)}</span>
1260
+ <span class="chrono-heading">${escapeHtml(n.title)}</span>
1261
+ </div>
1262
+ <div class="chrono-body">${escapeHtml((n.problem || n.decision || '').slice(0, 180))}...</div>
1263
+ </div>
1264
+ `).join('')}
1265
+ </div>
1266
+ </div>
1267
+ `;
1268
+ }
1269
+ document.getElementById('chrono-track').innerHTML = html || '<div style="color:var(--text-tertiary);padding:36px;font-family:var(--font-mono);text-align:center;">// 未找到符合此切片组合的演化记录</div>';
1270
+ }
1271
+
1272
+ function renderAlternatives() {
1273
+ const q = (Core.altQuery || '').toLowerCase();
1274
+ const altItems = [];
1275
+
1276
+ Core.nodes.forEach(n => {
1277
+ const title = n.title;
1278
+ const altContent = n.alternatives;
1279
+
1280
+ if (n.lifecycle === 'rejected') {
1281
+ const crux = (n.rawBody || '').split('\n')[2]?.replace(/Status:\s*rejected\s*[—–-]/, '').trim() || n.problem || '审慎否决,防止重犯。';
1282
+ altItems.push({
1283
+ nodeId: n.id,
1284
+ title,
1285
+ cls: n.cls,
1286
+ discarded: `[已否决提案] ${title}`,
1287
+ crux,
1288
+ winner: '提案直接否决'
1289
+ });
1290
+ }
1291
+ if (altContent) {
1292
+ const parts = altContent.split(/\n(?=###? |- )/);
1293
+ parts.forEach(p => {
1294
+ const clean = p.replace(/^[-#* ]+/, '').trim()
1295
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
1296
+ .replace(/\*\*/g, '')
1297
+ .replace(/__([^_]+)__/g, '$1')
1298
+ .replace(/`([^`]*)`/g, '$1')
1299
+ .replace(/\[(.*?)\]\([^)]*\)/g, '$1');
1300
+ if (clean.length > 5) {
1301
+ const colonIdx = clean.indexOf(':') > -1 ? clean.indexOf(':') : clean.indexOf(':');
1302
+ let heading = clean.slice(0, 45);
1303
+ let reason = clean;
1304
+ if (colonIdx > 0 && colonIdx < 35) {
1305
+ heading = clean.slice(0, colonIdx);
1306
+ reason = clean.slice(colonIdx + 1);
1307
+ }
1308
+ altItems.push({
1309
+ nodeId: n.id,
1310
+ title,
1311
+ cls: n.cls,
1312
+ discarded: heading,
1313
+ crux: reason,
1314
+ winner: `采纳决策: ${title}`
1315
+ });
1316
+ }
1317
+ });
1318
+ }
1319
+ });
1320
+
1321
+ const filtered = altItems.filter(item => {
1322
+ if (!q) return true;
1323
+ return item.discarded.toLowerCase().includes(q) || item.crux.toLowerCase().includes(q) || item.title.toLowerCase().includes(q);
1324
+ });
1325
+
1326
+ document.getElementById('pitfalls-grid').innerHTML = filtered.length ? filtered.map(item => `
1327
+ <div class="pitfall-dossier" data-node-id="${escapeHtml(item.nodeId)}">
1328
+ <div>
1329
+ <div style="display:flex; justify-content:space-between; margin-bottom:10px;">
1330
+ <span class="class-badge cls-${escapeHtml(item.cls)}">${escapeHtml(CLASS_LABELS[item.cls] || item.cls)}</span>
1331
+ <span style="font-family:var(--font-mono);font-size:11px;color:var(--text-tertiary);">#${escapeHtml(item.nodeId.split('/').pop().replace('.md',''))}</span>
1332
+ </div>
1333
+ <div class="hazard-banner">🚫 放弃备选:${escapeHtml(item.discarded)}</div>
1334
+ <div class="crux-evidence" style="margin-top:12px;">💡 权衡依据:${escapeHtml(item.crux)}</div>
1335
+ </div>
1336
+ <div class="triumphant-verdict">✅ ${escapeHtml(item.winner)}</div>
1337
+ </div>
1338
+ `).join('') : `<div style="grid-column:1/-1;color:var(--text-tertiary);text-align:center;padding:48px;font-family:var(--font-mono);">// 未检索到相关避坑记录</div>`;
1339
+ }
1340
+
1341
+ function renderDirectory() {
1342
+ const q = (Core.filterQuery || '').toLowerCase();
1343
+ const st = Core.stFilter;
1344
+ const cls = Core.clsFilter;
1345
+
1346
+ let list = Core.nodes.filter(n => {
1347
+ if (st && n.lifecycle !== st) return false;
1348
+ if (cls && n.cls !== cls) return false;
1349
+ if (q) {
1350
+ const hay = `${n.title} ${n.slug} ${n.problem || ''} ${n.decision || ''}`.toLowerCase();
1351
+ if (!hay.includes(q)) return false;
1352
+ }
1353
+ return true;
1354
+ });
1355
+
1356
+ if (Core.sortMode === 'new') list.sort(byDateDesc);
1357
+ else if (Core.sortMode === 'old') list.sort(byDateAsc);
1358
+ else if (Core.sortMode === 'cites') list.sort((a, b) => (Core.inCount.get(b.id) || 0) - (Core.inCount.get(a.id) || 0));
1359
+
1360
+ document.getElementById('matrix-matched-count').textContent = `匹配 ${list.length} 条`;
1361
+
1362
+ document.getElementById('matrix-rows-stream').innerHTML = list.length ? list.map((n, idx) => {
1363
+ const cites = Core.inCount.get(n.id) || 0;
1364
+ return `
1365
+ <div class="matrix-row-cell" data-node-id="${escapeHtml(n.id)}">
1366
+ <span class="cell-index">#${String(idx + 1).padStart(3, '0')}</span>
1367
+ <span><span class="class-badge cls-${escapeHtml(n.cls)}">${escapeHtml(CLASS_LABELS[n.cls] || n.cls)}</span></span>
1368
+ <span class="cell-title">${escapeHtml(n.title)}</span>
1369
+ <span><span class="status-badge st-${escapeHtml(n.lifecycle)}">${escapeHtml(STATUS_LABELS[n.lifecycle] || n.lifecycle)}</span></span>
1370
+ <span class="cell-date">${escapeHtml(n.date || '—')}</span>
1371
+ <span class="cell-cites">${cites ? `↗ ${cites}` : ''}</span>
1372
+ </div>
1373
+ `;
1374
+ }).join('') : '<div style="padding:40px;text-align:center;color:var(--text-tertiary);font-family:var(--font-mono);">// 没有匹配的决策记录</div>';
1375
+ }
1376
+
1377
+ function openConsole(id) {
1378
+ const n = Core.byId.get(id);
1379
+ if (!n) return;
1380
+
1381
+ Core.drawerId = id;
1382
+
1383
+ const stBadge = document.getElementById('dr-status-badge');
1384
+ stBadge.className = `status-badge st-${n.lifecycle}`;
1385
+ stBadge.textContent = STATUS_LABELS[n.lifecycle] || n.lifecycle;
1386
+
1387
+ const clsBadge = document.getElementById('dr-class-badge');
1388
+ clsBadge.className = `class-badge cls-${n.cls}`;
1389
+ clsBadge.textContent = CLASS_LABELS[n.cls] || n.cls;
1390
+
1391
+ const anchor = `// Note: 见 .agents/notes/${n.id}`;
1392
+ document.getElementById('dr-anchor-code').textContent = anchor;
1393
+ document.getElementById('btn-anchor-copy').onclick = () => {
1394
+ navigator.clipboard.writeText(anchor);
1395
+ const btn = document.getElementById('btn-anchor-copy');
1396
+ const old = btn.textContent;
1397
+ btn.textContent = '已复制';
1398
+ setTimeout(() => btn.textContent = old, 1500);
1399
+ };
1400
+
1401
+ document.getElementById('dr-headline').textContent = n.title;
1402
+ document.getElementById('dr-timestamp-bar').innerHTML = `
1403
+ <span>${escapeHtml(n.date || '无日期')}</span>
1404
+ <span>·</span>
1405
+ <span>${escapeHtml(n.slug)}</span>
1406
+ ${(Core.inCount.get(n.id) || 0) ? `<span>·</span><span style="color:var(--brand-blue);font-weight:700;">被引用 ${Core.inCount.get(n.id)} 次</span>` : ''}
1407
+ `;
1408
+
1409
+ document.getElementById('dr-body-problem').innerHTML = parseMarkdown(n.problem) || '<span style="color:var(--text-tertiary);">(未独立分节说明问题)</span>';
1410
+ document.getElementById('dr-body-decision').innerHTML = parseMarkdown(n.decision) || '<span style="color:var(--text-tertiary);">(未记录具体裁定事实)</span>';
1411
+ document.getElementById('dr-body-alt').innerHTML = parseMarkdown(n.alternatives) || '<span style="color:var(--text-tertiary);">(无曾考虑的备选对比)</span>';
1412
+ document.getElementById('dr-body-consequences').innerHTML = parseMarkdown(n.consequences) || '<span style="color:var(--text-tertiary);">(未详述工程后果)</span>';
1413
+
1414
+ const incoming = Core.nodes.filter(x => x.outLinks.includes(n.id));
1415
+ const outgoing = n.outLinks.map(t => Core.byId.get(t)).filter(Boolean);
1416
+
1417
+ let lineageHtml = '';
1418
+ if (outgoing.length) {
1419
+ lineageHtml += `
1420
+ <div>
1421
+ <div class="lineage-group-title">前置先决依据:</div>
1422
+ <div class="lineage-chips">
1423
+ ${outgoing.map(o => `<div class="lineage-chip" data-node-id="${escapeHtml(o.id)}">↖ ${escapeHtml(o.title)}</div>`).join('')}
1424
+ </div>
1425
+ </div>
1426
+ `;
1427
+ }
1428
+ if (incoming.length) {
1429
+ lineageHtml += `
1430
+ <div style="margin-top:10px;">
1431
+ <div class="lineage-group-title">派生制约影响:</div>
1432
+ <div class="lineage-chips">
1433
+ ${incoming.map(i => `<div class="lineage-chip" data-node-id="${escapeHtml(i.id)}">↘ ${escapeHtml(i.title)}</div>`).join('')}
1434
+ </div>
1435
+ </div>
1436
+ `;
1437
+ }
1438
+ if (!incoming.length && !outgoing.length) {
1439
+ lineageHtml = '<span style="font-family:var(--font-mono);font-size:11px;color:var(--text-tertiary);">独立节点,暂无交叉引用</span>';
1440
+ }
1441
+ document.getElementById('dr-lineage-stream').innerHTML = lineageHtml;
1442
+
1443
+ // 打开新笔记前重置滚动位置,避免从上一篇的底部开始显示
1444
+ document.querySelector('#drawer-console .console-body').scrollTop = 0;
1445
+
1446
+ document.getElementById('drawer-curtain').classList.add('open');
1447
+ document.getElementById('drawer-console').classList.add('open');
1448
+ history.replaceState(null, '', '#note=' + encodeURIComponent(id));
1449
+ }
1450
+
1451
+ function closeConsole() {
1452
+ document.getElementById('drawer-curtain').classList.remove('open');
1453
+ document.getElementById('drawer-console').classList.remove('open');
1454
+ history.replaceState(null, '', '#' + Core.activeTab);
1455
+ }
1456
+
1457
+ function handleDeepLink(link) {
1458
+ for (const n of Core.nodes) {
1459
+ if (link.includes(n.slug)) {
1460
+ openConsole(n.id);
1461
+ return;
1462
+ }
1463
+ }
1464
+ }
1465
+
1466
+ function routeToListFilter(filterVal) {
1467
+ switchDeckTab('directory');
1468
+ // 彻底清空可能残留的旧筛选项与搜索词,确保列表与点击的卡片完全对应
1469
+ Core.stFilter = '';
1470
+ Core.clsFilter = '';
1471
+ Core.filterQuery = '';
1472
+ const gq = document.getElementById('global-query');
1473
+ if (gq) gq.value = '';
1474
+ document.getElementById('select-status').value = '';
1475
+ document.getElementById('select-class').value = '';
1476
+
1477
+ if (['implemented', 'proposed', 'rejected', 'archived'].includes(filterVal)) {
1478
+ Core.stFilter = filterVal;
1479
+ document.getElementById('select-status').value = filterVal;
1480
+ } else if (CLASSES.includes(filterVal)) {
1481
+ Core.clsFilter = filterVal;
1482
+ document.getElementById('select-class').value = filterVal;
1483
+ }
1484
+ renderDirectory();
1485
+ }
1486
+
1487
+ function switchDeckTab(viewId) {
1488
+ if (viewId.startsWith('note=')) {
1489
+ const id = decodeURIComponent(viewId.slice('note='.length));
1490
+ openConsole(id);
1491
+ return;
1492
+ }
1493
+ if (!['baseline', 'timeline', 'alternatives', 'directory'].includes(viewId)) viewId = 'baseline';
1494
+ Core.activeTab = viewId;
1495
+ document.querySelectorAll('.deck-tab').forEach(t => t.classList.toggle('active', t.dataset.view === viewId));
1496
+ document.querySelectorAll('.canvas-view').forEach(p => p.classList.toggle('active', p.id === `view-${viewId}`));
1497
+ if (location.hash.slice(1) !== viewId && !location.hash.includes('note=')) {
1498
+ history.replaceState(null, '', '#' + viewId);
1499
+ }
1500
+ }
1501
+
1502
+ async function triggerLiveSync(silent = false) {
1503
+ if (!Core.dirHandle) return;
1504
+ const radar = document.getElementById('radar-indicator');
1505
+ const beaconText = document.getElementById('beacon-status');
1506
+ radar.className = 'radar-dot syncing';
1507
+ beaconText.textContent = '正在同步…';
1508
+
1509
+ try {
1510
+ const notes = await scanDirectory(Core.dirHandle);
1511
+ ingestDataset(notes);
1512
+ radar.className = 'radar-dot active';
1513
+ beaconText.textContent = `已连接本地目录(${notes.length} 篇)`;
1514
+ Core.lastSyncTime = Date.now();
1515
+ } catch (err) {
1516
+ console.error('Directory sync fault:', err);
1517
+ radar.className = 'radar-dot';
1518
+ beaconText.textContent = '重新授权目录';
1519
+ }
1520
+ }
1521
+
1522
+ async function startMission() {
1523
+ document.querySelectorAll('.deck-tab').forEach(tab => {
1524
+ tab.addEventListener('click', () => switchDeckTab(tab.dataset.view));
1525
+ });
1526
+
1527
+ window.addEventListener('hashchange', () => {
1528
+ const h = location.hash.slice(1);
1529
+ if (h) switchDeckTab(h);
1530
+ });
1531
+
1532
+ const queryInput = document.getElementById('global-query');
1533
+ const searchPopover = document.getElementById('search-popover');
1534
+ let selectedSearchIdx = -1;
1535
+
1536
+ function renderSearchPopover(q) {
1537
+ if (!q) {
1538
+ searchPopover.classList.remove('open');
1539
+ searchPopover.innerHTML = '';
1540
+ selectedSearchIdx = -1;
1541
+ return;
1542
+ }
1543
+
1544
+ const matches = [];
1545
+ for (const n of Core.nodes) {
1546
+ const hay = `${n.title} ${n.titleZh || ''} ${n.slug} ${n.problem || ''} ${n.problemZh || ''} ${n.decision || ''} ${n.decisionZh || ''}`.toLowerCase();
1547
+ if (hay.includes(q)) matches.push(n);
1548
+ }
1549
+
1550
+ const topMatches = matches.slice(0, 7);
1551
+ selectedSearchIdx = -1;
1552
+
1553
+ if (!topMatches.length) {
1554
+ searchPopover.innerHTML = `<div class="popover-empty">// 未找到与 "${escapeHtml(q)}" 匹配的决策记录</div>`;
1555
+ } else {
1556
+ searchPopover.innerHTML = topMatches.map((n, idx) => {
1557
+ const title = escapeHtml(n.title);
1558
+ const cites = Core.inCount.get(n.id) || 0;
1559
+ return `
1560
+ <div class="popover-item" data-id="${escapeHtml(n.id)}" data-idx="${idx}">
1561
+ <div class="popover-item-main">
1562
+ <div class="popover-title">${title}</div>
1563
+ <div class="popover-sub">
1564
+ <span class="class-badge cls-${escapeHtml(n.cls)}">${escapeHtml(CLASS_LABELS[n.cls] || n.cls)}</span>
1565
+ <span>${escapeHtml(n.date || '进行中')}</span>
1566
+ <span>#${escapeHtml(n.slug)}</span>
1567
+ </div>
1568
+ </div>
1569
+ ${cites ? `<span class="weight-gauge">↗${cites}</span>` : ''}
1570
+ </div>
1571
+ `;
1572
+ }).join('') + `
1573
+ <div class="popover-footer">
1574
+ <span>共找到 <b>${matches.length}</b> 项决策</span>
1575
+ <span>按 ↵ 回车在全量清单中查看全部</span>
1576
+ </div>
1577
+ `;
1578
+
1579
+ searchPopover.querySelectorAll('.popover-item').forEach(item => {
1580
+ item.addEventListener('click', () => {
1581
+ openConsole(item.dataset.id);
1582
+ searchPopover.classList.remove('open');
1583
+ });
1584
+ });
1585
+ }
1586
+
1587
+ searchPopover.classList.add('open');
1588
+ }
1589
+
1590
+ queryInput.addEventListener('input', e => {
1591
+ const q = e.target.value.trim().toLowerCase();
1592
+ Core.filterQuery = q;
1593
+ Core.altQuery = q;
1594
+ renderDirectory();
1595
+ renderAlternatives();
1596
+ renderSearchPopover(q);
1597
+ });
1598
+
1599
+ queryInput.addEventListener('keydown', e => {
1600
+ const items = searchPopover.querySelectorAll('.popover-item');
1601
+ if (e.key === 'ArrowDown') {
1602
+ e.preventDefault();
1603
+ if (!items.length) return;
1604
+ selectedSearchIdx = (selectedSearchIdx + 1) % items.length;
1605
+ items.forEach((it, i) => it.classList.toggle('selected', i === selectedSearchIdx));
1606
+ items[selectedSearchIdx]?.scrollIntoView({ block: 'nearest' });
1607
+ } else if (e.key === 'ArrowUp') {
1608
+ e.preventDefault();
1609
+ if (!items.length) return;
1610
+ selectedSearchIdx = (selectedSearchIdx - 1 + items.length) % items.length;
1611
+ items.forEach((it, i) => it.classList.toggle('selected', i === selectedSearchIdx));
1612
+ items[selectedSearchIdx]?.scrollIntoView({ block: 'nearest' });
1613
+ } else if (e.key === 'Enter') {
1614
+ e.preventDefault();
1615
+ if (selectedSearchIdx >= 0 && items[selectedSearchIdx]) {
1616
+ openConsole(items[selectedSearchIdx].dataset.id);
1617
+ searchPopover.classList.remove('open');
1618
+ } else {
1619
+ switchDeckTab('directory');
1620
+ searchPopover.classList.remove('open');
1621
+ }
1622
+ } else if (e.key === 'Escape') {
1623
+ searchPopover.classList.remove('open');
1624
+ }
1625
+ });
1626
+
1627
+ document.addEventListener('click', e => {
1628
+ const nodeTrigger = e.target.closest('[data-node-id]');
1629
+ if (nodeTrigger) {
1630
+ const id = nodeTrigger.getAttribute('data-node-id');
1631
+ if (id) {
1632
+ openConsole(id);
1633
+ return;
1634
+ }
1635
+ }
1636
+ const deepLink = e.target.closest('.md-deep-link');
1637
+ if (deepLink) {
1638
+ e.preventDefault();
1639
+ const target = deepLink.getAttribute('data-deep-link');
1640
+ if (target) handleDeepLink(target);
1641
+ return;
1642
+ }
1643
+ if (!e.target.closest('.hud-search')) {
1644
+ searchPopover.classList.remove('open');
1645
+ }
1646
+ });
1647
+
1648
+ window.addEventListener('keydown', e => {
1649
+ if (e.key === '/' && document.activeElement !== queryInput) {
1650
+ e.preventDefault();
1651
+ queryInput.focus();
1652
+ }
1653
+ if (e.key === 'Escape') closeConsole();
1654
+ });
1655
+
1656
+ document.getElementById('alt-query-input').addEventListener('input', e => {
1657
+ Core.altQuery = e.target.value;
1658
+ renderAlternatives();
1659
+ });
1660
+
1661
+ const selSt = document.getElementById('select-status');
1662
+ LIFECYCLES.forEach(s => selSt.innerHTML += `<option value="${s}">状态:${STATUS_LABELS[s]}</option>`);
1663
+ selSt.addEventListener('change', e => { Core.stFilter = e.target.value; renderDirectory(); });
1664
+
1665
+ const selCls = document.getElementById('select-class');
1666
+ CLASSES.forEach(c => selCls.innerHTML += `<option value="${c}">分类:${CLASS_LABELS[c]}</option>`);
1667
+ selCls.addEventListener('change', e => { Core.clsFilter = e.target.value; renderDirectory(); });
1668
+
1669
+ document.getElementById('select-sort').addEventListener('change', e => { Core.sortMode = e.target.value; renderDirectory(); });
1670
+
1671
+ const btnResetMatrix = document.getElementById('btn-reset-matrix-filter');
1672
+ if (btnResetMatrix) {
1673
+ btnResetMatrix.addEventListener('click', () => {
1674
+ Core.stFilter = '';
1675
+ Core.clsFilter = '';
1676
+ Core.filterQuery = '';
1677
+ const gq = document.getElementById('global-query');
1678
+ if (gq) gq.value = '';
1679
+ document.getElementById('select-status').value = '';
1680
+ document.getElementById('select-class').value = '';
1681
+ renderDirectory();
1682
+ });
1683
+ }
1684
+
1685
+ document.getElementById('btn-console-close').addEventListener('click', closeConsole);
1686
+ document.getElementById('drawer-curtain').addEventListener('click', closeConsole);
1687
+
1688
+ async function requestDirectoryAccess() {
1689
+ if (!('showDirectoryPicker' in window)) {
1690
+ alert('请使用 Chrome / Edge 浏览器体验 File System Access 本地无感同步。');
1691
+ return;
1692
+ }
1693
+ try {
1694
+ const handle = await window.showDirectoryPicker({ mode: 'read' });
1695
+ Core.dirHandle = handle;
1696
+ await saveDirHandle(handle);
1697
+ await triggerLiveSync();
1698
+ } catch (err) {
1699
+ if (err.name !== 'AbortError') console.error('Directory request fault:', err);
1700
+ }
1701
+ }
1702
+
1703
+ document.getElementById('beacon-sync').addEventListener('click', requestDirectoryAccess);
1704
+ document.getElementById('btn-authorize-dir').addEventListener('click', requestDirectoryAccess);
1705
+ document.getElementById('btn-mount-demo').addEventListener('click', () => loadSampleDataset());
1706
+
1707
+ if (window.__INLINE_DATA__ && window.__INLINE_DATA__.length) {
1708
+ ingestDataset(window.__INLINE_DATA__);
1709
+ } else {
1710
+ const saved = await loadSavedDirHandle();
1711
+ if (saved) {
1712
+ Core.dirHandle = saved;
1713
+ try {
1714
+ if ((await saved.queryPermission({ mode: 'read' })) === 'granted') {
1715
+ await triggerLiveSync();
1716
+ } else {
1717
+ document.getElementById('beacon-status').textContent = '点击确认授权';
1718
+ }
1719
+ } catch {
1720
+ document.getElementById('deck-onboarding').style.display = 'flex';
1721
+ }
1722
+ } else {
1723
+ document.getElementById('deck-onboarding').style.display = 'flex';
1724
+ }
1725
+ }
1726
+
1727
+ window.addEventListener('focus', () => {
1728
+ if (Core.dirHandle && Date.now() - Core.lastSyncTime > 2500) {
1729
+ triggerLiveSync(true);
1730
+ }
1731
+ });
1732
+
1733
+ setInterval(() => {
1734
+ if (Core.dirHandle && document.hasFocus() && Date.now() - Core.lastSyncTime > 4000) {
1735
+ triggerLiveSync(true);
1736
+ }
1737
+ }, 5000);
1738
+
1739
+ if (location.hash.slice(1)) {
1740
+ switchDeckTab(location.hash.slice(1));
1741
+ }
1742
+ }
1743
+
1744
+ function loadSampleDataset() {
1745
+ const sample = [
1746
+ {
1747
+ id: 'implemented/architecture/2026-06-13-capability-seams.md',
1748
+ slug: '2026-06-13-capability-seams',
1749
+ lifecycle: 'implemented',
1750
+ cls: 'architecture',
1751
+ date: '2026-06-13',
1752
+ title: '能力 Seam:Service Definition / Provider / Consumer 三权分立',
1753
+ problem: '各项能力关注点以不同速率变化。把实现与面向模型的工具契约绑在一起,会导致换沙箱实现时工具 Schema 频发破坏性变更。',
1754
+ decision: '拆分为 Service Definition(Cordis 抽象服务声明)、Service Provider(本地或沙箱实现插件)与 Consumer(面向模型的 Schema 工具),实现三者独立演进。',
1755
+ alternatives: '### 始终合并各角色\n否决,重新耦合了独立变化的关注点。\n### @cordisjs/plugin-capability\n这是权限安全服务,并非实现替换机制。',
1756
+ consequences: '增加包数量与胶水样板;换来沙箱后端替换时彻底零破坏面向模型的工具约定。',
1757
+ outLinks: [],
1758
+ },
1759
+ {
1760
+ id: 'implemented/architecture/2026-07-20-canonical-tool-output-contract.md',
1761
+ slug: '2026-07-20-canonical-tool-output-contract',
1762
+ lifecycle: 'implemented',
1763
+ cls: 'architecture',
1764
+ date: '2026-07-20',
1765
+ title: '规范化工具输出契约 (Canonical Output Contract)',
1766
+ problem: '工具直接产出 model-facing 的 ContentBlock 文本,导致编程式工具调用(PTC)无法拿到稳定的强类型领域对象。',
1767
+ decision: '每个工具声明强制的 Canonical Output:执行期输出强类型 DTO,并通过纯函数式 render() 投影成持久化和模型可读文本。',
1768
+ alternatives: '### 双轨制返回值\n否决,维护双重契约增加心智负担。',
1769
+ consequences: '执行期拥有强类型无损数据;PTC 模式无需反向解析字符串。',
1770
+ outLinks: ['implemented/architecture/2026-06-13-capability-seams.md'],
1771
+ }
1772
+ ];
1773
+ ingestDataset(sample);
1774
+ }
1775
+
1776
+ document.addEventListener('DOMContentLoaded', startMission);
1777
+ </script>
1778
+ </body>
1779
+ </html>