laminark 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +147 -0
  2. package/package.json +65 -0
  3. package/plugin/.claude-plugin/plugin.json +13 -0
  4. package/plugin/.mcp.json +12 -0
  5. package/plugin/CLAUDE.md +10 -0
  6. package/plugin/commands/recall.md +55 -0
  7. package/plugin/commands/remember.md +34 -0
  8. package/plugin/commands/resume.md +45 -0
  9. package/plugin/commands/stash.md +34 -0
  10. package/plugin/commands/status.md +33 -0
  11. package/plugin/dist/analysis/worker.d.ts +1 -0
  12. package/plugin/dist/analysis/worker.js +233 -0
  13. package/plugin/dist/analysis/worker.js.map +1 -0
  14. package/plugin/dist/config-t8LZeB-u.mjs +90 -0
  15. package/plugin/dist/config-t8LZeB-u.mjs.map +1 -0
  16. package/plugin/dist/hooks/handler.d.ts +286 -0
  17. package/plugin/dist/hooks/handler.d.ts.map +1 -0
  18. package/plugin/dist/hooks/handler.js +2413 -0
  19. package/plugin/dist/hooks/handler.js.map +1 -0
  20. package/plugin/dist/index.d.ts +447 -0
  21. package/plugin/dist/index.d.ts.map +1 -0
  22. package/plugin/dist/index.js +7334 -0
  23. package/plugin/dist/index.js.map +1 -0
  24. package/plugin/dist/observations-CorAAc1A.d.mts +192 -0
  25. package/plugin/dist/observations-CorAAc1A.d.mts.map +1 -0
  26. package/plugin/dist/tool-registry-e710BvXq.mjs +3574 -0
  27. package/plugin/dist/tool-registry-e710BvXq.mjs.map +1 -0
  28. package/plugin/hooks/hooks.json +78 -0
  29. package/plugin/laminark.db +0 -0
  30. package/plugin/package.json +17 -0
  31. package/plugin/scripts/README.md +65 -0
  32. package/plugin/scripts/bump-version.sh +42 -0
  33. package/plugin/scripts/dev-sync.sh +58 -0
  34. package/plugin/scripts/ensure-deps.sh +15 -0
  35. package/plugin/scripts/install.sh +139 -0
  36. package/plugin/scripts/local-install.sh +138 -0
  37. package/plugin/scripts/uninstall.sh +133 -0
  38. package/plugin/scripts/update.sh +39 -0
  39. package/plugin/scripts/verify-install.sh +87 -0
  40. package/plugin/skills/status/SKILL.md +6 -0
  41. package/plugin/ui/activity.js +197 -0
  42. package/plugin/ui/app.js +1612 -0
  43. package/plugin/ui/graph.js +2560 -0
  44. package/plugin/ui/help/activity-feed.png +0 -0
  45. package/plugin/ui/help/analysis-panel.png +0 -0
  46. package/plugin/ui/help/graph-toolbar.png +0 -0
  47. package/plugin/ui/help/graph-view.png +0 -0
  48. package/plugin/ui/help/settings.png +0 -0
  49. package/plugin/ui/help/timeline.png +0 -0
  50. package/plugin/ui/help.js +932 -0
  51. package/plugin/ui/index.html +756 -0
  52. package/plugin/ui/settings.js +1414 -0
  53. package/plugin/ui/styles.css +3856 -0
  54. package/plugin/ui/timeline.js +652 -0
  55. package/plugin/ui/tools.js +826 -0
@@ -0,0 +1,932 @@
1
+ /**
2
+ * Laminark Help tab — comprehensive in-app documentation.
3
+ *
4
+ * Data-driven: all content defined as structured sections, auto-generates
5
+ * table of contents, supports search filtering, and cross-links between
6
+ * sections via smooth-scroll anchors.
7
+ */
8
+
9
+ (function () {
10
+ // -----------------------------------------------------------------------
11
+ // Section data
12
+ // -----------------------------------------------------------------------
13
+
14
+ var SECTIONS = [
15
+ {
16
+ id: 'getting-started',
17
+ title: 'Getting Started',
18
+ blocks: [
19
+ {
20
+ type: 'text',
21
+ content:
22
+ 'Laminark is a persistent adaptive memory system for Claude Code. ' +
23
+ 'It automatically captures observations during your coding sessions ' +
24
+ 'via hooks (PostToolUse, SessionStart, etc.), classifies them with ' +
25
+ 'Haiku AI, extracts entities and relationships into a knowledge graph, ' +
26
+ 'and streams live updates over SSE.',
27
+ },
28
+ {
29
+ type: 'list',
30
+ items: [
31
+ 'Observations are captured automatically — no manual tagging needed.',
32
+ 'Haiku classifies each observation by kind: change, finding, decision, reference, or verification.',
33
+ 'Graph extraction identifies entities (Project, File, Decision, Problem, Solution, Reference) and their relationships.',
34
+ 'Topic detection uses EWMA to identify conversation shifts.',
35
+ 'All data is project-scoped — switch projects via the selector in the nav bar.',
36
+ ],
37
+ },
38
+ {
39
+ type: 'tip',
40
+ content:
41
+ '<strong>Tip:</strong> Use the project selector in the top navigation bar to switch between projects. Each project maintains its own memory, graph, and timeline.',
42
+ },
43
+ {
44
+ type: 'crosslinks',
45
+ links: [
46
+ { target: 'knowledge-graph', label: 'Knowledge Graph' },
47
+ { target: 'mcp-tools', label: 'MCP Tools' },
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ {
53
+ id: 'knowledge-graph',
54
+ title: 'Knowledge Graph',
55
+ blocks: [
56
+ {
57
+ type: 'text',
58
+ content:
59
+ 'The knowledge graph visualizes entities extracted from your observations and ' +
60
+ 'the relationships between them. Nodes are sized by observation count and degree ' +
61
+ '(number of connections). Edges are colored by relationship type.',
62
+ },
63
+ {
64
+ type: 'subsection',
65
+ title: 'Entity Types',
66
+ },
67
+ {
68
+ type: 'cards',
69
+ items: [
70
+ { name: 'Project', desc: 'Represents a codebase or project workspace.', color: '#58a6ff' },
71
+ { name: 'File', desc: 'Source files, configs, or other project files.', color: '#3fb950' },
72
+ { name: 'Decision', desc: 'Architectural or implementation decisions.', color: '#d29922' },
73
+ { name: 'Problem', desc: 'Issues, bugs, or challenges encountered.', color: '#f85149' },
74
+ { name: 'Solution', desc: 'Fixes, workarounds, or approaches that resolved problems.', color: '#a371f7' },
75
+ { name: 'Reference', desc: 'External docs, libraries, APIs, or resources.', color: '#f0883e' },
76
+ ],
77
+ },
78
+ {
79
+ type: 'subsection',
80
+ title: 'Relationship Types',
81
+ },
82
+ {
83
+ type: 'table',
84
+ rows: [
85
+ ['related_to', 'General association between entities'],
86
+ ['solved_by', 'Problem resolved by a Solution'],
87
+ ['caused_by', 'Problem caused by another entity'],
88
+ ['modifies', 'Decision or Solution that modifies a File'],
89
+ ['informed_by', 'Decision informed by a Reference'],
90
+ ['references', 'Entity references another entity'],
91
+ ['verified_by', 'Change verified by a verification observation'],
92
+ ['preceded_by', 'Temporal ordering between entities'],
93
+ ],
94
+ },
95
+ {
96
+ type: 'img',
97
+ src: 'help/graph-view.png',
98
+ caption: 'Knowledge graph showing entities and relationships',
99
+ },
100
+ {
101
+ type: 'tip',
102
+ content:
103
+ '<strong>Tip:</strong> Node size reflects importance — larger nodes have more observations and connections. Hover over any node for a tooltip with details.',
104
+ },
105
+ {
106
+ type: 'crosslinks',
107
+ links: [
108
+ { target: 'graph-interactions', label: 'Graph Interactions' },
109
+ { target: 'graph-toolbar', label: 'Graph Toolbar' },
110
+ ],
111
+ },
112
+ ],
113
+ },
114
+ {
115
+ id: 'graph-interactions',
116
+ title: 'Graph Interactions',
117
+ blocks: [
118
+ {
119
+ type: 'text',
120
+ content:
121
+ 'The graph is fully interactive. Click, drag, pan, and zoom to explore your knowledge.',
122
+ },
123
+ {
124
+ type: 'list',
125
+ items: [
126
+ 'Click a node to select it and open the detail panel with observations and relationships.',
127
+ 'Drag nodes to reposition them. The simulation will adjust.',
128
+ 'Pan by clicking and dragging the background.',
129
+ 'Zoom with the mouse wheel or trackpad.',
130
+ 'Right-click a node for a context menu with Focus, Find Path, and Details options.',
131
+ 'Click a relationship in the detail panel to navigate to the connected node.',
132
+ ],
133
+ },
134
+ {
135
+ type: 'subsection',
136
+ title: 'Detail Panel',
137
+ },
138
+ {
139
+ type: 'text',
140
+ content:
141
+ 'When you click a node, the detail panel slides in from the right showing the entity type, ' +
142
+ 'creation date, a scrollable list of observations, and all relationships. Click any ' +
143
+ 'relationship to navigate to the connected node.',
144
+ },
145
+ {
146
+ type: 'subsection',
147
+ title: 'Focus Mode',
148
+ },
149
+ {
150
+ type: 'text',
151
+ content:
152
+ 'Focus mode zooms into a single node and its immediate neighbors. A breadcrumb bar ' +
153
+ 'appears at the top showing your navigation path. Click any breadcrumb to go back. ' +
154
+ 'Use the focus button in the detail panel or right-click context menu to enter focus mode.',
155
+ },
156
+ {
157
+ type: 'crosslinks',
158
+ links: [
159
+ { target: 'graph-toolbar', label: 'Graph Toolbar' },
160
+ { target: 'keyboard-shortcuts', label: 'Keyboard Shortcuts' },
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ {
166
+ id: 'graph-toolbar',
167
+ title: 'Graph Toolbar',
168
+ blocks: [
169
+ {
170
+ type: 'text',
171
+ content:
172
+ 'The toolbar in the top-right corner of the graph view provides layout and analysis controls.',
173
+ },
174
+ {
175
+ type: 'subsection',
176
+ title: 'Layout Modes',
177
+ },
178
+ {
179
+ type: 'table',
180
+ rows: [
181
+ ['Clustered', 'Force-directed layout grouping related nodes together (default)'],
182
+ ['Hierarchical', 'Top-down tree layout organized by entity relationships'],
183
+ ['Concentric', 'Radial layout with most-connected nodes at the center'],
184
+ ['Communities', 'Detects and separates graph communities into distinct clusters'],
185
+ ],
186
+ },
187
+ {
188
+ type: 'subsection',
189
+ title: 'Toolbar Buttons',
190
+ },
191
+ {
192
+ type: 'list',
193
+ items: [
194
+ 'Edge Labels — Toggle edge relationship labels on/off. Use the dropdown to show/hide specific relationship types.',
195
+ 'Pathfinder — Find the shortest path between two nodes. Click two nodes to see the highlighted path.',
196
+ 'Analysis — Open the analysis panel showing entity type distribution, relationship stats, top entities by degree, and detected clusters.',
197
+ 'Freeze — Pause live graph updates. A stale-data indicator appears when frozen and new data arrives.',
198
+ 'Fit to View — Reset zoom and pan to fit all nodes in the viewport.',
199
+ ],
200
+ },
201
+ {
202
+ type: 'subsection',
203
+ title: 'Pathfinder',
204
+ },
205
+ {
206
+ type: 'text',
207
+ content:
208
+ 'Activate Pathfinder from the toolbar, then click a start node (green ring) and an end node ' +
209
+ '(red ring). The shortest path is highlighted in orange. Non-path elements are dimmed. ' +
210
+ 'Click the Pathfinder button again or press Escape to exit.',
211
+ },
212
+ {
213
+ type: 'img',
214
+ src: 'help/graph-toolbar.png',
215
+ caption: 'Graph toolbar: layout modes, edge labels, pathfinder, analysis, freeze, fit-to-view',
216
+ },
217
+ {
218
+ type: 'img',
219
+ src: 'help/analysis-panel.png',
220
+ caption: 'Analysis panel showing entity types, relationship distribution, top entities, and clusters',
221
+ },
222
+ {
223
+ type: 'crosslinks',
224
+ links: [
225
+ { target: 'graph-interactions', label: 'Graph Interactions' },
226
+ { target: 'search-filtering', label: 'Search & Filtering' },
227
+ ],
228
+ },
229
+ ],
230
+ },
231
+ {
232
+ id: 'search-filtering',
233
+ title: 'Search & Filtering',
234
+ blocks: [
235
+ {
236
+ type: 'text',
237
+ content:
238
+ 'Filter and search your knowledge graph to find exactly what you need.',
239
+ },
240
+ {
241
+ type: 'subsection',
242
+ title: 'Entity Type Filters',
243
+ },
244
+ {
245
+ type: 'text',
246
+ content:
247
+ 'The filter bar below the navigation shows colored pills for each entity type with counts. ' +
248
+ 'Click a pill to toggle that type on/off. Click "All" to show/hide everything. ' +
249
+ 'Active pills are highlighted with their entity color.',
250
+ },
251
+ {
252
+ type: 'subsection',
253
+ title: 'Time Range',
254
+ },
255
+ {
256
+ type: 'text',
257
+ content:
258
+ 'Below the filter bar, time range presets let you focus on recent data: All, Hour, Today, ' +
259
+ 'Week, or Month. For custom ranges, set the From/To date inputs and click Apply. ' +
260
+ 'Presets filter client-side instantly; custom ranges fetch from the server.',
261
+ },
262
+ {
263
+ type: 'subsection',
264
+ title: 'Search Box',
265
+ },
266
+ {
267
+ type: 'list',
268
+ items: [
269
+ 'Type to client-filter — matches are highlighted in the graph as you type.',
270
+ 'Press Enter to trigger a server-side full-text search for deeper results.',
271
+ 'Use Arrow keys to navigate the results dropdown.',
272
+ 'Press Escape to clear the search.',
273
+ 'Click a result to select and center that node in the graph.',
274
+ ],
275
+ },
276
+ {
277
+ type: 'crosslinks',
278
+ links: [
279
+ { target: 'knowledge-graph', label: 'Knowledge Graph' },
280
+ { target: 'keyboard-shortcuts', label: 'Keyboard Shortcuts' },
281
+ ],
282
+ },
283
+ ],
284
+ },
285
+ {
286
+ id: 'timeline',
287
+ title: 'Timeline',
288
+ blocks: [
289
+ {
290
+ type: 'text',
291
+ content:
292
+ 'The Timeline tab shows a chronological view of your coding sessions and observations. ' +
293
+ 'Sessions are grouped as cards along a vertical spine.',
294
+ },
295
+ {
296
+ type: 'list',
297
+ items: [
298
+ 'Each session card shows the start time, duration, and observation count.',
299
+ 'Click the session header to expand/collapse its observations.',
300
+ 'Observations display timestamps, type dots (color-coded by source), and text previews.',
301
+ 'Topic shift markers appear between observations when the AI detects a conversation change, with a confidence percentage.',
302
+ 'Session summaries (italic text) provide an AI-generated overview of the session.',
303
+ 'Infinite scroll pagination loads older sessions as you scroll down.',
304
+ 'The "Jump to Today" button in the bottom-right corner scrolls to the most recent session.',
305
+ ],
306
+ },
307
+ {
308
+ type: 'img',
309
+ src: 'help/timeline.png',
310
+ caption: 'Timeline view with sessions, observations, and topic shift markers',
311
+ },
312
+ {
313
+ type: 'tip',
314
+ content:
315
+ '<strong>Tip:</strong> Active sessions show a pulsing green badge. Completed sessions show a blue badge with the observation count.',
316
+ },
317
+ {
318
+ type: 'crosslinks',
319
+ links: [
320
+ { target: 'activity-feed', label: 'Activity Feed' },
321
+ { target: 'getting-started', label: 'Getting Started' },
322
+ ],
323
+ },
324
+ ],
325
+ },
326
+ {
327
+ id: 'activity-feed',
328
+ title: 'Activity Feed',
329
+ blocks: [
330
+ {
331
+ type: 'text',
332
+ content:
333
+ 'The Activity tab shows a real-time event stream powered by Server-Sent Events (SSE). ' +
334
+ 'Events appear with a slide-in animation as they arrive.',
335
+ },
336
+ {
337
+ type: 'subsection',
338
+ title: 'Event Types',
339
+ },
340
+ {
341
+ type: 'table',
342
+ rows: [
343
+ ['Observation', 'New memory observation captured (blue)'],
344
+ ['Entity', 'Knowledge graph entity created or updated (purple)'],
345
+ ['Topic Shift', 'Conversation topic change detected (orange)'],
346
+ ['Session Start', 'New coding session began (green)'],
347
+ ['Session End', 'Coding session ended (gray)'],
348
+ ],
349
+ },
350
+ {
351
+ type: 'list',
352
+ items: [
353
+ 'Maximum 100 items are retained in the feed.',
354
+ 'Each item shows an icon, event type, description, and timestamp.',
355
+ 'Use the Clear button in the header to reset the feed.',
356
+ 'Events are project-scoped — only events from the selected project appear.',
357
+ ],
358
+ },
359
+ {
360
+ type: 'img',
361
+ src: 'help/activity-feed.png',
362
+ caption: 'Activity feed showing live SSE events',
363
+ },
364
+ {
365
+ type: 'crosslinks',
366
+ links: [
367
+ { target: 'timeline', label: 'Timeline' },
368
+ { target: 'settings', label: 'Settings' },
369
+ ],
370
+ },
371
+ ],
372
+ },
373
+ {
374
+ id: 'settings',
375
+ title: 'Settings',
376
+ blocks: [
377
+ {
378
+ type: 'text',
379
+ content:
380
+ 'The Settings tab provides database statistics, configuration controls, and data management.',
381
+ },
382
+ {
383
+ type: 'subsection',
384
+ title: 'Database Statistics',
385
+ },
386
+ {
387
+ type: 'text',
388
+ content:
389
+ 'A grid of stat cards shows live metrics for the selected project scope:',
390
+ },
391
+ {
392
+ type: 'table',
393
+ rows: [
394
+ ['Observations', 'Total memory observations stored'],
395
+ ['Embeddings', 'Vector embeddings for semantic search'],
396
+ ['Staleness', 'Percentage of observations without embeddings'],
397
+ ['Graph Nodes', 'Total entities in the knowledge graph'],
398
+ ['Graph Edges', 'Total relationships between entities'],
399
+ ['Sessions', 'Coding sessions recorded'],
400
+ ['Stashes', 'Saved context stashes'],
401
+ ['Topic Shifts', 'Detected topic changes'],
402
+ ['Notifications', 'System notifications generated'],
403
+ ['Projects', 'Total projects tracked'],
404
+ ],
405
+ },
406
+ {
407
+ type: 'subsection',
408
+ title: 'Topic Detection Config',
409
+ },
410
+ {
411
+ type: 'text',
412
+ content:
413
+ 'Enable or disable automatic topic detection. Choose sensitivity presets (Low, Medium, High) ' +
414
+ 'or fine-tune EWMA parameters: alpha (smoothing factor), threshold, and window size.',
415
+ },
416
+ {
417
+ type: 'subsection',
418
+ title: 'Graph Extraction Config',
419
+ },
420
+ {
421
+ type: 'text',
422
+ content:
423
+ 'Configure how entities and relationships are extracted: temporal decay rate, fuzzy dedup ' +
424
+ 'similarity threshold, quality gate thresholds for each entity type, and relationship ' +
425
+ 'detector sensitivity.',
426
+ },
427
+ {
428
+ type: 'subsection',
429
+ title: 'Danger Zone',
430
+ },
431
+ {
432
+ type: 'text',
433
+ content:
434
+ 'Choose a scope (global or current project), then use reset operations to clear specific data. ' +
435
+ 'All destructive actions require typing a confirmation phrase.',
436
+ },
437
+ {
438
+ type: 'table',
439
+ rows: [
440
+ ['Reset Observations', 'Delete all observations and embeddings'],
441
+ ['Reset Graph', 'Delete all knowledge graph nodes and edges'],
442
+ ['Reset Sessions', 'Delete all session records and topic shifts'],
443
+ ['Reset Everything', 'Complete data wipe — observations, graph, sessions, and config'],
444
+ ],
445
+ },
446
+ {
447
+ type: 'img',
448
+ src: 'help/settings.png',
449
+ caption: 'Settings view with database statistics, config sections, and danger zone',
450
+ },
451
+ {
452
+ type: 'crosslinks',
453
+ links: [
454
+ { target: 'getting-started', label: 'Getting Started' },
455
+ { target: 'mcp-tools', label: 'MCP Tools' },
456
+ ],
457
+ },
458
+ ],
459
+ },
460
+ {
461
+ id: 'mcp-tools',
462
+ title: 'MCP Tools',
463
+ blocks: [
464
+ {
465
+ type: 'text',
466
+ content:
467
+ 'Laminark exposes tools via the Model Context Protocol (MCP) that Claude Code uses ' +
468
+ 'to save memories, search knowledge, query the graph, manage debug paths, track work branches, and ingest codebase knowledge.',
469
+ },
470
+ {
471
+ type: 'cards',
472
+ items: [
473
+ { name: 'save_memory', desc: 'Save a new memory observation with text content and optional title. Supports kind classification: change, reference, finding, decision, or verification.' },
474
+ { name: 'recall', desc: 'Search, view, purge, or restore memories. Full-text search with detail levels: compact, timeline, or full.' },
475
+ { name: 'topic_context', desc: 'Show recently stashed context threads. Use when asked "where was I?" to resume abandoned work.' },
476
+ { name: 'query_graph', desc: 'Query the knowledge graph for entities and relationships with configurable traversal depth (1-4).' },
477
+ { name: 'graph_stats', desc: 'Get knowledge graph statistics: entity counts, relationship distribution, and health metrics.' },
478
+ { name: 'status', desc: 'Show Laminark system status: connection info, memory count, token estimates, and capabilities.' },
479
+ { name: 'discover_tools', desc: 'Search the tool registry by keyword or description. Supports semantic search across all registered tools.' },
480
+ { name: 'report_available_tools', desc: 'Register all tools available in this session with Laminark for discovery and routing.' },
481
+ { name: 'path_start', desc: 'Start tracking a debug path. Use when actively debugging an issue to record the investigation.' },
482
+ { name: 'path_resolve', desc: 'Resolve the active debug path with a resolution summary. Generates a KISS summary (Problem/Cause/Fix/Prevention).' },
483
+ { name: 'path_show', desc: 'Show a debug path with its waypoints and KISS summary. Omit path ID for the active path.' },
484
+ { name: 'path_list', desc: 'List recent debug paths, optionally filtered by status: active, resolved, or abandoned.' },
485
+ { name: 'query_branches', desc: 'Search and list thought branches — coherent units of work like investigations, bug fixes, or features.' },
486
+ { name: 'show_branch', desc: 'Show detailed view of a thought branch with observation timeline and arc stage annotations.' },
487
+ { name: 'branch_summary', desc: 'Summary of recent work activity grouped by time window. Shows what was investigated, fixed, and built.' },
488
+ { name: 'hygiene', desc: 'Analyze observations for deletion candidates with confidence scoring. Simulate mode for dry-run, purge mode to execute.' },
489
+ { name: 'ingest_knowledge', desc: 'Ingest structured markdown documents into queryable per-project reference memories. Auto-detects .planning/codebase/ (GSD output).' },
490
+ ],
491
+ },
492
+ {
493
+ type: 'crosslinks',
494
+ links: [
495
+ { target: 'getting-started', label: 'Getting Started' },
496
+ { target: 'settings', label: 'Settings' },
497
+ ],
498
+ },
499
+ ],
500
+ },
501
+ {
502
+ id: 'keyboard-shortcuts',
503
+ title: 'Keyboard Shortcuts',
504
+ blocks: [
505
+ {
506
+ type: 'shortcuts',
507
+ items: [
508
+ { keys: ['Ctrl', 'Shift', 'P'], desc: 'Toggle performance overlay (graph view)' },
509
+ { keys: ['Escape'], desc: 'Close detail panel, cancel pathfinder, dismiss dialogs' },
510
+ { keys: ['Enter'], desc: 'Trigger server-side search (in search input)' },
511
+ { keys: ['Arrow Up', 'Arrow Down'], desc: 'Navigate search results dropdown' },
512
+ ],
513
+ },
514
+ {
515
+ type: 'crosslinks',
516
+ links: [
517
+ { target: 'graph-interactions', label: 'Graph Interactions' },
518
+ { target: 'search-filtering', label: 'Search & Filtering' },
519
+ ],
520
+ },
521
+ ],
522
+ },
523
+ ];
524
+
525
+ // -----------------------------------------------------------------------
526
+ // Render engine — sidebar tree layout
527
+ // -----------------------------------------------------------------------
528
+
529
+ /**
530
+ * Extract subsections from a section's blocks (blocks with type 'subsection').
531
+ */
532
+ function getSubsections(section) {
533
+ var subs = [];
534
+ section.blocks.forEach(function (b) {
535
+ if (b.type === 'subsection') {
536
+ subs.push({ title: b.title, id: section.id + '--' + b.title.toLowerCase().replace(/[^a-z0-9]+/g, '-') });
537
+ }
538
+ });
539
+ return subs;
540
+ }
541
+
542
+ var activeSection = null;
543
+ var treeItems = {};
544
+ var contentArea = null;
545
+
546
+ /**
547
+ * Build the sidebar tree from SECTIONS.
548
+ */
549
+ function renderSidebar() {
550
+ var sidebar = document.createElement('aside');
551
+ sidebar.className = 'help-sidebar';
552
+
553
+ // Search
554
+ var searchDiv = document.createElement('div');
555
+ searchDiv.className = 'help-sidebar-search';
556
+ var searchInput = document.createElement('input');
557
+ searchInput.type = 'text';
558
+ searchInput.className = 'help-search-input';
559
+ searchInput.placeholder = 'Search...';
560
+ searchDiv.appendChild(searchInput);
561
+ sidebar.appendChild(searchDiv);
562
+
563
+ // Tree container
564
+ var treeContainer = document.createElement('div');
565
+ treeContainer.className = 'help-tree-container';
566
+ var tree = document.createElement('ul');
567
+ tree.className = 'help-tree';
568
+
569
+ SECTIONS.forEach(function (section) {
570
+ var li = document.createElement('li');
571
+ li.className = 'help-tree-item';
572
+ li.setAttribute('data-section', section.id);
573
+
574
+ var subs = getSubsections(section);
575
+
576
+ // Label
577
+ var label = document.createElement('span');
578
+ label.className = 'help-tree-label';
579
+
580
+ if (subs.length > 0) {
581
+ var toggle = document.createElement('span');
582
+ toggle.className = 'help-tree-toggle';
583
+ toggle.textContent = '\u25B8'; // ▸
584
+ label.appendChild(toggle);
585
+ } else {
586
+ // spacer for alignment
587
+ var spacer = document.createElement('span');
588
+ spacer.style.width = '16px';
589
+ spacer.style.flexShrink = '0';
590
+ label.appendChild(spacer);
591
+ }
592
+
593
+ var text = document.createTextNode(section.title);
594
+ label.appendChild(text);
595
+ li.appendChild(label);
596
+
597
+ // Children (subsections)
598
+ if (subs.length > 0) {
599
+ var childUl = document.createElement('ul');
600
+ childUl.className = 'help-tree-children';
601
+
602
+ subs.forEach(function (sub) {
603
+ var childLi = document.createElement('li');
604
+ var childLabel = document.createElement('span');
605
+ childLabel.className = 'help-tree-child-label';
606
+ childLabel.textContent = sub.title;
607
+ childLabel.addEventListener('click', function (e) {
608
+ e.stopPropagation();
609
+ showSection(section.id);
610
+ // Scroll to subsection after rendering
611
+ setTimeout(function () {
612
+ var el = document.getElementById('help-sub-' + sub.id);
613
+ if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
614
+ }, 50);
615
+ });
616
+ childLi.appendChild(childLabel);
617
+ childUl.appendChild(childLi);
618
+ });
619
+
620
+ li.appendChild(childUl);
621
+ }
622
+
623
+ // Click handler for top-level label
624
+ label.addEventListener('click', function () {
625
+ showSection(section.id);
626
+ });
627
+
628
+ treeItems[section.id] = li;
629
+ tree.appendChild(li);
630
+ });
631
+
632
+ treeContainer.appendChild(tree);
633
+ sidebar.appendChild(treeContainer);
634
+
635
+ // Search filtering
636
+ var debounce = null;
637
+ searchInput.addEventListener('input', function () {
638
+ if (debounce) clearTimeout(debounce);
639
+ debounce = setTimeout(function () {
640
+ var query = searchInput.value.trim().toLowerCase();
641
+ SECTIONS.forEach(function (section) {
642
+ var li = treeItems[section.id];
643
+ if (!query) {
644
+ li.style.display = '';
645
+ return;
646
+ }
647
+ var text = (section.title + ' ' + getBlocksText(section.blocks)).toLowerCase();
648
+ li.style.display = text.indexOf(query) !== -1 ? '' : 'none';
649
+ });
650
+ }, 150);
651
+ });
652
+
653
+ return sidebar;
654
+ }
655
+
656
+ /**
657
+ * Show a specific section in the content area. Updates tree active state.
658
+ */
659
+ function showSection(sectionId) {
660
+ if (!contentArea) return;
661
+
662
+ // Find section data
663
+ var section = null;
664
+ for (var i = 0; i < SECTIONS.length; i++) {
665
+ if (SECTIONS[i].id === sectionId) {
666
+ section = SECTIONS[i];
667
+ break;
668
+ }
669
+ }
670
+ if (!section) return;
671
+
672
+ activeSection = sectionId;
673
+
674
+ // Update tree active states
675
+ Object.keys(treeItems).forEach(function (id) {
676
+ var li = treeItems[id];
677
+ var label = li.querySelector('.help-tree-label');
678
+ if (id === sectionId) {
679
+ label.classList.add('active');
680
+ li.classList.add('expanded');
681
+ } else {
682
+ label.classList.remove('active');
683
+ li.classList.remove('expanded');
684
+ }
685
+ });
686
+
687
+ // Render section content
688
+ contentArea.innerHTML = '';
689
+ contentArea.scrollTop = 0;
690
+ var el = renderSection(section);
691
+ contentArea.appendChild(el);
692
+ }
693
+
694
+ /**
695
+ * Main entry point: builds layout, renders sidebar + content area.
696
+ */
697
+ function renderHelp() {
698
+ var container = document.getElementById('help-content');
699
+ if (!container) return;
700
+
701
+ container.innerHTML = '';
702
+
703
+ // Build sidebar
704
+ var sidebar = renderSidebar();
705
+ container.appendChild(sidebar);
706
+
707
+ // Build content area
708
+ contentArea = document.createElement('div');
709
+ contentArea.className = 'help-content-area';
710
+ container.appendChild(contentArea);
711
+
712
+ // Select first section by default
713
+ if (SECTIONS.length > 0) {
714
+ showSection(SECTIONS[0].id);
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Extract all text content from blocks for search matching.
720
+ */
721
+ function getBlocksText(blocks) {
722
+ var parts = [];
723
+ blocks.forEach(function (b) {
724
+ if (b.type === 'text') parts.push(b.content);
725
+ if (b.type === 'tip') parts.push(b.content);
726
+ if (b.type === 'subsection') parts.push(b.title);
727
+ if (b.type === 'list' && b.items) b.items.forEach(function (i) { parts.push(i); });
728
+ if (b.type === 'cards' && b.items) b.items.forEach(function (c) { parts.push(c.name + ' ' + c.desc); });
729
+ if (b.type === 'table' && b.rows) b.rows.forEach(function (r) { parts.push(r.join(' ')); });
730
+ if (b.type === 'shortcuts' && b.items) b.items.forEach(function (s) { parts.push(s.keys.join('+') + ' ' + s.desc); });
731
+ });
732
+ return parts.join(' ');
733
+ }
734
+
735
+ /**
736
+ * Render a single section with all its blocks.
737
+ */
738
+ function renderSection(section) {
739
+ var el = document.createElement('div');
740
+ el.className = 'help-section';
741
+ el.id = 'help-' + section.id;
742
+
743
+ var title = document.createElement('h2');
744
+ title.className = 'help-section-title';
745
+ title.textContent = section.title;
746
+ el.appendChild(title);
747
+
748
+ section.blocks.forEach(function (block) {
749
+ el.appendChild(renderBlock(block));
750
+ });
751
+
752
+ return el;
753
+ }
754
+
755
+ /**
756
+ * Render a single content block.
757
+ */
758
+ function renderBlock(block) {
759
+ switch (block.type) {
760
+ case 'text': return renderText(block);
761
+ case 'list': return renderList(block);
762
+ case 'cards': return renderCards(block);
763
+ case 'table': return renderTable(block);
764
+ case 'shortcuts': return renderShortcuts(block);
765
+ case 'tip': return renderTip(block);
766
+ case 'subsection': return renderSubsection(block);
767
+ case 'crosslinks': return renderCrosslinks(block);
768
+ case 'img': return renderImage(block);
769
+ default:
770
+ var el = document.createElement('div');
771
+ return el;
772
+ }
773
+ }
774
+
775
+ function renderText(block) {
776
+ var p = document.createElement('p');
777
+ p.className = 'help-intro';
778
+ p.textContent = block.content;
779
+ return p;
780
+ }
781
+
782
+ function renderList(block) {
783
+ var ul = document.createElement('ul');
784
+ ul.className = 'help-list';
785
+ block.items.forEach(function (item) {
786
+ var li = document.createElement('li');
787
+ li.textContent = item;
788
+ ul.appendChild(li);
789
+ });
790
+ return ul;
791
+ }
792
+
793
+ function renderCards(block) {
794
+ var grid = document.createElement('div');
795
+ grid.className = 'help-cards';
796
+ block.items.forEach(function (card) {
797
+ var el = document.createElement('div');
798
+ el.className = 'help-card';
799
+
800
+ var name = document.createElement('div');
801
+ name.className = 'help-card-name';
802
+ name.textContent = card.name;
803
+ if (card.color) {
804
+ name.style.borderLeft = '3px solid ' + card.color;
805
+ name.style.paddingLeft = '8px';
806
+ }
807
+ el.appendChild(name);
808
+
809
+ var desc = document.createElement('div');
810
+ desc.className = 'help-card-desc';
811
+ desc.textContent = card.desc;
812
+ el.appendChild(desc);
813
+
814
+ grid.appendChild(el);
815
+ });
816
+ return grid;
817
+ }
818
+
819
+ function renderTable(block) {
820
+ var table = document.createElement('table');
821
+ table.className = 'help-table';
822
+ block.rows.forEach(function (row) {
823
+ var tr = document.createElement('tr');
824
+ row.forEach(function (cell) {
825
+ var td = document.createElement('td');
826
+ td.textContent = cell;
827
+ tr.appendChild(td);
828
+ });
829
+ table.appendChild(tr);
830
+ });
831
+ return table;
832
+ }
833
+
834
+ function renderShortcuts(block) {
835
+ var table = document.createElement('table');
836
+ table.className = 'help-shortcuts';
837
+ block.items.forEach(function (sc) {
838
+ var row = document.createElement('tr');
839
+
840
+ var keyCell = document.createElement('td');
841
+ sc.keys.forEach(function (key, idx) {
842
+ var kbd = document.createElement('kbd');
843
+ kbd.textContent = key;
844
+ keyCell.appendChild(kbd);
845
+ if (idx < sc.keys.length - 1) {
846
+ keyCell.appendChild(document.createTextNode(' + '));
847
+ }
848
+ });
849
+ row.appendChild(keyCell);
850
+
851
+ var descCell = document.createElement('td');
852
+ descCell.textContent = sc.desc;
853
+ row.appendChild(descCell);
854
+
855
+ table.appendChild(row);
856
+ });
857
+ return table;
858
+ }
859
+
860
+ function renderTip(block) {
861
+ var div = document.createElement('div');
862
+ div.className = 'help-tip';
863
+ div.innerHTML = block.content;
864
+ return div;
865
+ }
866
+
867
+ function renderSubsection(block) {
868
+ var h3 = document.createElement('h3');
869
+ h3.className = 'help-subsection-title';
870
+ h3.textContent = block.title;
871
+ // Generate an anchor ID for subsection scrolling
872
+ if (activeSection) {
873
+ h3.id = 'help-sub-' + activeSection + '--' + block.title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
874
+ }
875
+ return h3;
876
+ }
877
+
878
+ function renderCrosslinks(block) {
879
+ var div = document.createElement('div');
880
+ div.className = 'help-crosslinks';
881
+
882
+ var label = document.createTextNode('See also:');
883
+ div.appendChild(label);
884
+
885
+ block.links.forEach(function (link, idx) {
886
+ var a = document.createElement('a');
887
+ a.className = 'help-crosslink';
888
+ a.textContent = link.label;
889
+ a.addEventListener('click', function (e) {
890
+ e.preventDefault();
891
+ showSection(link.target);
892
+ });
893
+ div.appendChild(a);
894
+
895
+ if (idx < block.links.length - 1) {
896
+ div.appendChild(document.createTextNode(','));
897
+ }
898
+ });
899
+
900
+ return div;
901
+ }
902
+
903
+ function renderImage(block) {
904
+ var wrap = document.createElement('div');
905
+ wrap.className = 'help-screenshot';
906
+
907
+ var img = document.createElement('img');
908
+ img.src = block.src;
909
+ img.alt = block.caption || '';
910
+ img.loading = 'lazy';
911
+ wrap.appendChild(img);
912
+
913
+ if (block.caption) {
914
+ var caption = document.createElement('div');
915
+ caption.className = 'help-screenshot-caption';
916
+ caption.textContent = block.caption;
917
+ wrap.appendChild(caption);
918
+ }
919
+
920
+ return wrap;
921
+ }
922
+
923
+ // -----------------------------------------------------------------------
924
+ // Auto-render
925
+ // -----------------------------------------------------------------------
926
+
927
+ if (document.readyState === 'loading') {
928
+ document.addEventListener('DOMContentLoaded', renderHelp);
929
+ } else {
930
+ renderHelp();
931
+ }
932
+ })();