@joekytc/dsh-swarm 0.1.2 → 0.2.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 (567) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +3 -3
  3. package/client/BoardCard.tsx +24 -2
  4. package/client/ConfigSection.tsx +71 -0
  5. package/client/ConfigSelect.tsx +69 -0
  6. package/client/KanbanBoard.tsx +3 -0
  7. package/client/KanbanTab.tsx +4 -3
  8. package/client/TaskDrawer.tsx +2 -26
  9. package/client/WorkflowRail.tsx +3 -1
  10. package/client/config-store.ts +51 -0
  11. package/client/config.css +110 -0
  12. package/client/index.ts +39 -4
  13. package/client/kanban.css +19 -2
  14. package/client/session-bridge.ts +39 -0
  15. package/client/timeline-model.ts +10 -1
  16. package/client/workflow-model.ts +3 -1
  17. package/lib/client.js +358 -53
  18. package/lib/config.d.ts +8 -1
  19. package/lib/config.js +6 -1
  20. package/lib/dispatcher/agent-runner.d.ts +28 -6
  21. package/lib/dispatcher/agent-runner.js +175 -62
  22. package/lib/dispatcher/chain-auditor.js +4 -1
  23. package/lib/dispatcher/dispatcher.d.ts +62 -3
  24. package/lib/dispatcher/dispatcher.js +243 -20
  25. package/lib/dispatcher/session-events.d.ts +11 -15
  26. package/lib/dispatcher/session-events.js +17 -5
  27. package/lib/dispatcher/v-orchestrator.d.ts +42 -9
  28. package/lib/dispatcher/v-orchestrator.js +236 -28
  29. package/lib/domain/config-override.d.ts +38 -0
  30. package/lib/domain/config-override.js +101 -0
  31. package/lib/domain/delivery-contract.d.ts +3 -2
  32. package/lib/domain/delivery-contract.js +28 -16
  33. package/lib/domain/gate-policy.d.ts +26 -0
  34. package/lib/domain/gate-policy.js +44 -0
  35. package/lib/domain/kanban-service.d.ts +16 -3
  36. package/lib/domain/kanban-service.js +41 -6
  37. package/lib/domain/permissions.js +1 -1
  38. package/lib/domain/planning-checklist.d.ts +5 -0
  39. package/lib/domain/planning-checklist.js +80 -6
  40. package/lib/domain/projection.js +2 -1
  41. package/lib/domain/review-evidence.js +1 -1
  42. package/lib/domain/state-machine.js +2 -1
  43. package/lib/domain/types.d.ts +2 -2
  44. package/lib/index.js +19 -21
  45. package/lib/roles/clean-fs-tools.d.ts +22 -0
  46. package/lib/roles/clean-fs-tools.js +118 -0
  47. package/lib/roles/skill-installer.d.ts +6 -0
  48. package/lib/roles/skill-installer.js +32 -0
  49. package/lib/roles/toolsets.d.ts +13 -1
  50. package/lib/roles/toolsets.js +163 -31
  51. package/lib/roles/wiki-worker.d.ts +1 -0
  52. package/lib/roles/wiki-worker.js +10 -2
  53. package/lib/routes/kanban-http.d.ts +5 -2
  54. package/lib/routes/kanban-http.js +36 -3
  55. package/lib/routes/planning-driver.d.ts +3 -1
  56. package/lib/routes/planning-driver.js +10 -6
  57. package/lib/routes/prefix-router.d.ts +16 -0
  58. package/lib/routes/prefix-router.js +37 -2
  59. package/lib/services/config-provider.d.ts +42 -0
  60. package/lib/services/config-provider.js +94 -0
  61. package/lib/services/gate-runner.d.ts +24 -0
  62. package/lib/services/gate-runner.js +87 -0
  63. package/lib/services/kanban-provider.d.ts +5 -1
  64. package/lib/services/kanban-provider.js +40 -2
  65. package/lib/services/llm-catalog.d.ts +35 -0
  66. package/lib/services/llm-catalog.js +19 -0
  67. package/lib/tools/kanban-tools.js +1 -0
  68. package/lib/tools/main-session-tools.d.ts +17 -2
  69. package/lib/tools/main-session-tools.js +88 -21
  70. package/lib/tools/planning-tools.d.ts +4 -0
  71. package/lib/tools/planning-tools.js +39 -12
  72. package/lib/tools/prefetch-tools.js +1 -0
  73. package/lib/tools/spec-card-tools.js +1 -0
  74. package/lib/tools/wiki-tools.js +5 -4
  75. package/lib/wiki/kb-linkage.d.ts +9 -2
  76. package/lib/wiki/kb-linkage.js +3 -3
  77. package/lib/wiki/local-kb-client.d.ts +14 -0
  78. package/lib/wiki/local-kb-client.js +101 -0
  79. package/lib/wiki/local-kb.d.ts +6 -0
  80. package/lib/wiki/local-kb.js +25 -0
  81. package/lib/wiki/memory-recall.d.ts +14 -6
  82. package/lib/wiki/memory-recall.js +17 -13
  83. package/lib/wiki/page-path.d.ts +9 -3
  84. package/lib/wiki/page-path.js +29 -11
  85. package/lib/wiki/wiki-vault-client.d.ts +3 -3
  86. package/lib/wiki/wiki-vault-client.js +6 -6
  87. package/package.json +16 -11
  88. package/personas/kanban-d/preset.yml +1 -1
  89. package/personas/kanban-dt/agent.cordis.yml +2 -2
  90. package/personas/kanban-dt/preset.yml +1 -1
  91. package/personas/kanban-p/agent.cordis.yml +16 -0
  92. package/personas/kanban-p/preset.yml +1 -1
  93. package/personas/kanban-pt/agent.cordis.yml +38 -8
  94. package/personas/kanban-pt/preset.yml +1 -1
  95. package/personas/kanban-w/agent.cordis.yml +35 -16
  96. package/personas/kanban-w/preset.yml +1 -1
  97. package/personas/persona-dt.md +1 -1
  98. package/personas/persona-p.md +4 -1
  99. package/personas/persona-pt.md +13 -6
  100. package/personas/persona-w.md +11 -9
  101. package/skills/grill-me/SKILL.md +71 -0
  102. package/skills/llm-wiki-skill/.mise.toml +2 -0
  103. package/skills/llm-wiki-skill/.nvmrc +1 -0
  104. package/skills/llm-wiki-skill/AGENTS.md +173 -0
  105. package/skills/llm-wiki-skill/CHANGELOG.md +548 -0
  106. package/skills/llm-wiki-skill/CLAUDE.md +152 -0
  107. package/skills/llm-wiki-skill/HERMES.md +44 -0
  108. package/skills/llm-wiki-skill/README.en.md +261 -0
  109. package/skills/llm-wiki-skill/README.md +291 -0
  110. package/skills/llm-wiki-skill/SKILL.md +1133 -0
  111. package/skills/llm-wiki-skill/assets/graph-demo.gif +0 -0
  112. package/skills/llm-wiki-skill/assets/graph-preview.png +0 -0
  113. package/skills/llm-wiki-skill/deps/LICENSE-d3.txt +13 -0
  114. package/skills/llm-wiki-skill/deps/LICENSE-marked.txt +44 -0
  115. package/skills/llm-wiki-skill/deps/LICENSE-purify.txt +568 -0
  116. package/skills/llm-wiki-skill/deps/LICENSE-roughjs.txt +21 -0
  117. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/SKILL.md +261 -0
  118. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/references/config/first-time-setup.md +106 -0
  119. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/cdp.ts +179 -0
  120. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/constants.ts +13 -0
  121. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/defuddle-converter.ts +58 -0
  122. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/html-to-markdown.ts +135 -0
  123. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/legacy-converter.ts +629 -0
  124. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/main.ts +314 -0
  125. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/markdown-conversion-shared.ts +305 -0
  126. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/media-localizer.ts +317 -0
  127. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/package.json +14 -0
  128. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/paths.ts +29 -0
  129. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/package.json +9 -0
  130. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/src/index.test.ts +307 -0
  131. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/src/index.ts +523 -0
  132. package/skills/llm-wiki-skill/deps/d3.min.js +2 -0
  133. package/skills/llm-wiki-skill/deps/marked.min.js +6 -0
  134. package/skills/llm-wiki-skill/deps/purify.min.js +3 -0
  135. package/skills/llm-wiki-skill/deps/rough.min.js +7 -0
  136. package/skills/llm-wiki-skill/deps/youtube-transcript/SKILL.md +47 -0
  137. package/skills/llm-wiki-skill/deps/youtube-transcript/scripts/get_transcript.py +72 -0
  138. package/skills/llm-wiki-skill/designs/graph-edge-legibility/graph-edge-treatments.html +480 -0
  139. package/skills/llm-wiki-skill/docs/design/oriental-atlas/DESIGN.md +183 -0
  140. package/skills/llm-wiki-skill/docs/design/oriental-atlas/README.md +10 -0
  141. package/skills/llm-wiki-skill/docs/design/oriental-atlas/design-brief.md +170 -0
  142. package/skills/llm-wiki-skill/docs/design/oriental-atlas/oriental-editorial-atlas.html +1908 -0
  143. package/skills/llm-wiki-skill/docs/design/oriental-atlas/reviews/result-review.md +68 -0
  144. package/skills/llm-wiki-skill/docs/graph/2026-06-16-interaction-risk-audit.md +88 -0
  145. package/skills/llm-wiki-skill/docs/graph/2026-06-16-interaction-test-coverage-map.md +212 -0
  146. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-0-2-baseline.md +86 -0
  147. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-1-3-dom-svg-large-baseline.md +85 -0
  148. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-1-4-disposable-validation-gate.md +97 -0
  149. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-2-4-global-renderer-trial-matrix.md +168 -0
  150. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-1-sigma-graphology-trial.md +71 -0
  151. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-2-vis-network-trial.md +78 -0
  152. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-3-aggregation-fallback-trial.md +72 -0
  153. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-4-global-renderer-route-decision.md +94 -0
  154. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-sigma-global-production-integration-result.md +102 -0
  155. package/skills/llm-wiki-skill/docs/graph/performance/artifacts/2026-06-18-phase-0-2/stage-4.5-offline-dense-wheel.json +9 -0
  156. package/skills/llm-wiki-skill/docs/graph/performance/artifacts/2026-06-18-phase-0-2/stage-4.5-offline-dense-wheel.png +0 -0
  157. package/skills/llm-wiki-skill/docs/graph/performance/artifacts/2026-06-18-phase-0-2/stage-4.5-offline-navigation.png +0 -0
  158. package/skills/llm-wiki-skill/docs/spark/2026-06-15-omp-tool-status-events-design.md +174 -0
  159. package/skills/llm-wiki-skill/docs/spark/2026-06-16-graph-interaction-geometry-design.md +832 -0
  160. package/skills/llm-wiki-skill/docs/spark/2026-06-16-graph-six-layer-architecture-design.md +577 -0
  161. package/skills/llm-wiki-skill/docs/spark/2026-06-17-static-renderer-coordination-split-design.md +172 -0
  162. package/skills/llm-wiki-skill/docs/spark/2026-06-18-large-graph-global-community-design.md +909 -0
  163. package/skills/llm-wiki-skill/docs/spark/2026-06-19-sigma-global-graph-renderer-design.md +360 -0
  164. package/skills/llm-wiki-skill/docs/spark/2026-06-20-paper-ui-port-design.md +162 -0
  165. package/skills/llm-wiki-skill/docs/spark/2026-06-20-paper-ui-v2-shell-alignment-design.md +384 -0
  166. package/skills/llm-wiki-skill/docs/spark/2026-06-21-graph-experience-completion-design.md +534 -0
  167. package/skills/llm-wiki-skill/docs/spark/2026-06-22-chat-auto-scroll-design.md +185 -0
  168. package/skills/llm-wiki-skill/docs/spark/2026-06-22-graph-community-node-visual-design.md +266 -0
  169. package/skills/llm-wiki-skill/docs/spark/2026-06-25-sigma-overlay-refactor-perf-design.md +170 -0
  170. package/skills/llm-wiki-skill/docs/spark/2026-06-26-global-sigma-community-spotlight-design.md +393 -0
  171. package/skills/llm-wiki-skill/docs/spark/mockups/2026-06-18-large-graph-global-community-flow.html +746 -0
  172. package/skills/llm-wiki-skill/install.ps1 +87 -0
  173. package/skills/llm-wiki-skill/install.sh +722 -0
  174. package/skills/llm-wiki-skill/package-lock.json +11171 -0
  175. package/skills/llm-wiki-skill/package.json +24 -0
  176. package/skills/llm-wiki-skill/packages/graph-engine/package.json +37 -0
  177. package/skills/llm-wiki-skill/packages/graph-engine/src/anim/.gitkeep +1 -0
  178. package/skills/llm-wiki-skill/packages/graph-engine/src/anim/index.ts +179 -0
  179. package/skills/llm-wiki-skill/packages/graph-engine/src/architecture.ts +79 -0
  180. package/skills/llm-wiki-skill/packages/graph-engine/src/diff.ts +121 -0
  181. package/skills/llm-wiki-skill/packages/graph-engine/src/facade.ts +1406 -0
  182. package/skills/llm-wiki-skill/packages/graph-engine/src/graph-node.ts +37 -0
  183. package/skills/llm-wiki-skill/packages/graph-engine/src/index.ts +25 -0
  184. package/skills/llm-wiki-skill/packages/graph-engine/src/layout/edge-geometry.ts +17 -0
  185. package/skills/llm-wiki-skill/packages/graph-engine/src/layout/index.ts +16 -0
  186. package/skills/llm-wiki-skill/packages/graph-engine/src/layout/spatial-index.ts +509 -0
  187. package/skills/llm-wiki-skill/packages/graph-engine/src/model/atlas.ts +18 -0
  188. package/skills/llm-wiki-skill/packages/graph-engine/src/model/index.ts +10 -0
  189. package/skills/llm-wiki-skill/packages/graph-engine/src/model/labels.ts +11 -0
  190. package/skills/llm-wiki-skill/packages/graph-engine/src/model/layout.ts +81 -0
  191. package/skills/llm-wiki-skill/packages/graph-engine/src/model/learning.ts +8 -0
  192. package/skills/llm-wiki-skill/packages/graph-engine/src/model/legacy-helpers.ts +1379 -0
  193. package/skills/llm-wiki-skill/packages/graph-engine/src/model/queue.ts +7 -0
  194. package/skills/llm-wiki-skill/packages/graph-engine/src/model/storage.ts +4 -0
  195. package/skills/llm-wiki-skill/packages/graph-engine/src/model/visibility.ts +9 -0
  196. package/skills/llm-wiki-skill/packages/graph-engine/src/render/.gitkeep +1 -0
  197. package/skills/llm-wiki-skill/packages/graph-engine/src/render/adapter.ts +443 -0
  198. package/skills/llm-wiki-skill/packages/graph-engine/src/render/aggregation-containers.ts +64 -0
  199. package/skills/llm-wiki-skill/packages/graph-engine/src/render/community-cloud-geometry.ts +178 -0
  200. package/skills/llm-wiki-skill/packages/graph-engine/src/render/community-wash.ts +238 -0
  201. package/skills/llm-wiki-skill/packages/graph-engine/src/render/community-washes.ts +19 -0
  202. package/skills/llm-wiki-skill/packages/graph-engine/src/render/compat-contract.ts +14 -0
  203. package/skills/llm-wiki-skill/packages/graph-engine/src/render/controller.ts +705 -0
  204. package/skills/llm-wiki-skill/packages/graph-engine/src/render/controls.ts +288 -0
  205. package/skills/llm-wiki-skill/packages/graph-engine/src/render/dom-svg-renderer.ts +180 -0
  206. package/skills/llm-wiki-skill/packages/graph-engine/src/render/edges.ts +51 -0
  207. package/skills/llm-wiki-skill/packages/graph-engine/src/render/geometry.ts +380 -0
  208. package/skills/llm-wiki-skill/packages/graph-engine/src/render/gestures.ts +673 -0
  209. package/skills/llm-wiki-skill/packages/graph-engine/src/render/graph-renderer-root.ts +387 -0
  210. package/skills/llm-wiki-skill/packages/graph-engine/src/render/hit-testing.ts +56 -0
  211. package/skills/llm-wiki-skill/packages/graph-engine/src/render/host-dom.ts +14 -0
  212. package/skills/llm-wiki-skill/packages/graph-engine/src/render/hover-card.ts +37 -0
  213. package/skills/llm-wiki-skill/packages/graph-engine/src/render/index.ts +213 -0
  214. package/skills/llm-wiki-skill/packages/graph-engine/src/render/keyboard.ts +48 -0
  215. package/skills/llm-wiki-skill/packages/graph-engine/src/render/legend.ts +25 -0
  216. package/skills/llm-wiki-skill/packages/graph-engine/src/render/minimap.ts +45 -0
  217. package/skills/llm-wiki-skill/packages/graph-engine/src/render/model.ts +1339 -0
  218. package/skills/llm-wiki-skill/packages/graph-engine/src/render/node-drag-lifecycle.ts +94 -0
  219. package/skills/llm-wiki-skill/packages/graph-engine/src/render/nodes.ts +103 -0
  220. package/skills/llm-wiki-skill/packages/graph-engine/src/render/offline-reader.ts +209 -0
  221. package/skills/llm-wiki-skill/packages/graph-engine/src/render/overlays-presenter.ts +192 -0
  222. package/skills/llm-wiki-skill/packages/graph-engine/src/render/overlays.ts +73 -0
  223. package/skills/llm-wiki-skill/packages/graph-engine/src/render/pin-position.ts +51 -0
  224. package/skills/llm-wiki-skill/packages/graph-engine/src/render/preview.ts +65 -0
  225. package/skills/llm-wiki-skill/packages/graph-engine/src/render/relation-focus.ts +91 -0
  226. package/skills/llm-wiki-skill/packages/graph-engine/src/render/render-context.ts +85 -0
  227. package/skills/llm-wiki-skill/packages/graph-engine/src/render/render-pipeline.ts +871 -0
  228. package/skills/llm-wiki-skill/packages/graph-engine/src/render/render-styles.ts +1405 -0
  229. package/skills/llm-wiki-skill/packages/graph-engine/src/render/renderer-surface.ts +75 -0
  230. package/skills/llm-wiki-skill/packages/graph-engine/src/render/search.ts +57 -0
  231. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-coordinates.ts +44 -0
  232. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-global-drag.ts +246 -0
  233. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-global-renderer.ts +1425 -0
  234. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-overlay-svg.ts +121 -0
  235. package/skills/llm-wiki-skill/packages/graph-engine/src/render/simulation-bridge.ts +59 -0
  236. package/skills/llm-wiki-skill/packages/graph-engine/src/render/state.ts +262 -0
  237. package/skills/llm-wiki-skill/packages/graph-engine/src/render/toolbar.ts +40 -0
  238. package/skills/llm-wiki-skill/packages/graph-engine/src/render/viewport.ts +336 -0
  239. package/skills/llm-wiki-skill/packages/graph-engine/src/select/.gitkeep +1 -0
  240. package/skills/llm-wiki-skill/packages/graph-engine/src/select/index.ts +189 -0
  241. package/skills/llm-wiki-skill/packages/graph-engine/src/sim/index.ts +372 -0
  242. package/skills/llm-wiki-skill/packages/graph-engine/src/sim/pins.ts +84 -0
  243. package/skills/llm-wiki-skill/packages/graph-engine/src/summary/index.ts +447 -0
  244. package/skills/llm-wiki-skill/packages/graph-engine/src/themes/.gitkeep +1 -0
  245. package/skills/llm-wiki-skill/packages/graph-engine/src/themes/index.ts +8 -0
  246. package/skills/llm-wiki-skill/packages/graph-engine/src/themes/tokens.ts +117 -0
  247. package/skills/llm-wiki-skill/packages/graph-engine/src/types/d3-quadtree.d.ts +22 -0
  248. package/skills/llm-wiki-skill/packages/graph-engine/src/types.ts +531 -0
  249. package/skills/llm-wiki-skill/packages/graph-engine/test/aggregation-fallback-trial-adapter.test.ts +115 -0
  250. package/skills/llm-wiki-skill/packages/graph-engine/test/aggregation-fallback-trial-adapter.ts +222 -0
  251. package/skills/llm-wiki-skill/packages/graph-engine/test/anim.test.ts +121 -0
  252. package/skills/llm-wiki-skill/packages/graph-engine/test/architecture.test.ts +63 -0
  253. package/skills/llm-wiki-skill/packages/graph-engine/test/community-cloud-geometry.test.ts +86 -0
  254. package/skills/llm-wiki-skill/packages/graph-engine/test/community-wash.test.ts +97 -0
  255. package/skills/llm-wiki-skill/packages/graph-engine/test/diff.test.ts +133 -0
  256. package/skills/llm-wiki-skill/packages/graph-engine/test/facade.test.ts +1369 -0
  257. package/skills/llm-wiki-skill/packages/graph-engine/test/geometry.test.ts +147 -0
  258. package/skills/llm-wiki-skill/packages/graph-engine/test/gestures.test.ts +768 -0
  259. package/skills/llm-wiki-skill/packages/graph-engine/test/graph-layout.test.ts +110 -0
  260. package/skills/llm-wiki-skill/packages/graph-engine/test/helpers.test.ts +287 -0
  261. package/skills/llm-wiki-skill/packages/graph-engine/test/interaction-contract.test.ts +106 -0
  262. package/skills/llm-wiki-skill/packages/graph-engine/test/keyboard.test.ts +59 -0
  263. package/skills/llm-wiki-skill/packages/graph-engine/test/large-fixtures.test.ts +47 -0
  264. package/skills/llm-wiki-skill/packages/graph-engine/test/large-graph-fixtures.ts +420 -0
  265. package/skills/llm-wiki-skill/packages/graph-engine/test/learning.test.ts +259 -0
  266. package/skills/llm-wiki-skill/packages/graph-engine/test/node-drag-lifecycle.test.ts +166 -0
  267. package/skills/llm-wiki-skill/packages/graph-engine/test/overlays.test.ts +115 -0
  268. package/skills/llm-wiki-skill/packages/graph-engine/test/preview.test.ts +56 -0
  269. package/skills/llm-wiki-skill/packages/graph-engine/test/queue.test.ts +114 -0
  270. package/skills/llm-wiki-skill/packages/graph-engine/test/relation-focus.test.ts +115 -0
  271. package/skills/llm-wiki-skill/packages/graph-engine/test/render-model.test.ts +1030 -0
  272. package/skills/llm-wiki-skill/packages/graph-engine/test/renderer-adapter-contract.test.ts +271 -0
  273. package/skills/llm-wiki-skill/packages/graph-engine/test/renderer-boundary.test.ts +618 -0
  274. package/skills/llm-wiki-skill/packages/graph-engine/test/renderer-lifecycle.test.ts +1711 -0
  275. package/skills/llm-wiki-skill/packages/graph-engine/test/route-continuity.test.ts +284 -0
  276. package/skills/llm-wiki-skill/packages/graph-engine/test/runtime-state.test.ts +281 -0
  277. package/skills/llm-wiki-skill/packages/graph-engine/test/search-and-legend.test.ts +124 -0
  278. package/skills/llm-wiki-skill/packages/graph-engine/test/select.test.ts +168 -0
  279. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-coordinates.test.ts +50 -0
  280. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-global-renderer.test.ts +2210 -0
  281. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-trial-adapter.test.ts +82 -0
  282. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-trial-adapter.ts +271 -0
  283. package/skills/llm-wiki-skill/packages/graph-engine/test/sim.test.ts +207 -0
  284. package/skills/llm-wiki-skill/packages/graph-engine/test/simulation-bridge.test.ts +96 -0
  285. package/skills/llm-wiki-skill/packages/graph-engine/test/spatial-index.test.ts +222 -0
  286. package/skills/llm-wiki-skill/packages/graph-engine/test/state.test.ts +189 -0
  287. package/skills/llm-wiki-skill/packages/graph-engine/test/summary-contract.test.ts +422 -0
  288. package/skills/llm-wiki-skill/packages/graph-engine/test/themes.test.ts +62 -0
  289. package/skills/llm-wiki-skill/packages/graph-engine/test/toolbar.test.ts +64 -0
  290. package/skills/llm-wiki-skill/packages/graph-engine/test/viewport.test.ts +213 -0
  291. package/skills/llm-wiki-skill/packages/graph-engine/test/vis-network-trial-adapter.test.ts +82 -0
  292. package/skills/llm-wiki-skill/packages/graph-engine/test/vis-network-trial-adapter.ts +186 -0
  293. package/skills/llm-wiki-skill/packages/graph-engine/tsconfig.json +17 -0
  294. package/skills/llm-wiki-skill/packages/graph-engine/vite.config.ts +15 -0
  295. package/skills/llm-wiki-skill/platforms/claude/CLAUDE.md +37 -0
  296. package/skills/llm-wiki-skill/platforms/claude/companions/llm-wiki-upgrade/SKILL.md +95 -0
  297. package/skills/llm-wiki-skill/platforms/codex/AGENTS.md +23 -0
  298. package/skills/llm-wiki-skill/platforms/hermes/README.md +31 -0
  299. package/skills/llm-wiki-skill/platforms/openclaw/README.md +31 -0
  300. package/skills/llm-wiki-skill/scripts/adapter-state.sh +424 -0
  301. package/skills/llm-wiki-skill/scripts/build-graph-data.sh +395 -0
  302. package/skills/llm-wiki-skill/scripts/build-graph-html.sh +459 -0
  303. package/skills/llm-wiki-skill/scripts/cache.sh +352 -0
  304. package/skills/llm-wiki-skill/scripts/create-source-page.sh +100 -0
  305. package/skills/llm-wiki-skill/scripts/delete-helper.sh +68 -0
  306. package/skills/llm-wiki-skill/scripts/graph-analysis.js +732 -0
  307. package/skills/llm-wiki-skill/scripts/hook-session-start.sh +46 -0
  308. package/skills/llm-wiki-skill/scripts/init-wiki.sh +95 -0
  309. package/skills/llm-wiki-skill/scripts/lib/source-signal-eligibility.js +158 -0
  310. package/skills/llm-wiki-skill/scripts/lint-fix.sh +114 -0
  311. package/skills/llm-wiki-skill/scripts/lint-runner.sh +217 -0
  312. package/skills/llm-wiki-skill/scripts/runtime-context.sh +77 -0
  313. package/skills/llm-wiki-skill/scripts/shared-config.sh +83 -0
  314. package/skills/llm-wiki-skill/scripts/source-record-contract.tsv +10 -0
  315. package/skills/llm-wiki-skill/scripts/source-registry.sh +349 -0
  316. package/skills/llm-wiki-skill/scripts/source-registry.tsv +10 -0
  317. package/skills/llm-wiki-skill/scripts/source-signal-coverage.js +83 -0
  318. package/skills/llm-wiki-skill/scripts/validate-step1.sh +144 -0
  319. package/skills/llm-wiki-skill/scripts/wiki-compat.sh +267 -0
  320. package/skills/llm-wiki-skill/setup.sh +8 -0
  321. package/skills/llm-wiki-skill/templates/entity-template.md +30 -0
  322. package/skills/llm-wiki-skill/templates/graph-styles/wash/footer.html +27 -0
  323. package/skills/llm-wiki-skill/templates/graph-styles/wash/graph-wash-helpers.js +1326 -0
  324. package/skills/llm-wiki-skill/templates/graph-styles/wash/graph-wash.js +1009 -0
  325. package/skills/llm-wiki-skill/templates/graph-styles/wash/header.html +2002 -0
  326. package/skills/llm-wiki-skill/templates/index-en-template.md +51 -0
  327. package/skills/llm-wiki-skill/templates/index-template.md +51 -0
  328. package/skills/llm-wiki-skill/templates/log-en-template.md +11 -0
  329. package/skills/llm-wiki-skill/templates/log-template.md +11 -0
  330. package/skills/llm-wiki-skill/templates/overview-en-template.md +41 -0
  331. package/skills/llm-wiki-skill/templates/overview-template.md +41 -0
  332. package/skills/llm-wiki-skill/templates/purpose-en-template.md +16 -0
  333. package/skills/llm-wiki-skill/templates/purpose-template.md +16 -0
  334. package/skills/llm-wiki-skill/templates/query-template.md +19 -0
  335. package/skills/llm-wiki-skill/templates/schema-template.md +185 -0
  336. package/skills/llm-wiki-skill/templates/source-template.md +51 -0
  337. package/skills/llm-wiki-skill/templates/synthesis-template.md +25 -0
  338. package/skills/llm-wiki-skill/templates/topic-template.md +35 -0
  339. package/skills/llm-wiki-skill/tests/adapter-state.sh +338 -0
  340. package/skills/llm-wiki-skill/tests/browser/graph-aggregation-fallback-trial.ts +737 -0
  341. package/skills/llm-wiki-skill/tests/browser/graph-community-node-map.mjs +255 -0
  342. package/skills/llm-wiki-skill/tests/browser/graph-community-wash-interactions.mjs +170 -0
  343. package/skills/llm-wiki-skill/tests/browser/graph-default-behavior.mjs +630 -0
  344. package/skills/llm-wiki-skill/tests/browser/graph-density-preview.mjs +53 -0
  345. package/skills/llm-wiki-skill/tests/browser/graph-html-insights.mjs +41 -0
  346. package/skills/llm-wiki-skill/tests/browser/graph-large-performance.ts +689 -0
  347. package/skills/llm-wiki-skill/tests/browser/graph-node-slim.mjs +75 -0
  348. package/skills/llm-wiki-skill/tests/browser/graph-offline-phase-6.mjs +660 -0
  349. package/skills/llm-wiki-skill/tests/browser/graph-renderer-trial-shared.ts +291 -0
  350. package/skills/llm-wiki-skill/tests/browser/graph-sigma-global-production.ts +1437 -0
  351. package/skills/llm-wiki-skill/tests/browser/graph-sigma-graphology-trial.ts +949 -0
  352. package/skills/llm-wiki-skill/tests/browser/graph-stage-4-5.mjs +815 -0
  353. package/skills/llm-wiki-skill/tests/browser/graph-vis-network-trial.ts +762 -0
  354. package/skills/llm-wiki-skill/tests/browser/graph-workbench-interactions.mjs +730 -0
  355. package/skills/llm-wiki-skill/tests/browser/validate-graph-trial-result.mjs +149 -0
  356. package/skills/llm-wiki-skill/tests/expected/graph-data-empty.json +60 -0
  357. package/skills/llm-wiki-skill/tests/expected/graph-data-sample.json +396 -0
  358. package/skills/llm-wiki-skill/tests/expected/graph-interactive-basic.html +1736 -0
  359. package/skills/llm-wiki-skill/tests/expected/lint-output.txt +44 -0
  360. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/index.md +1 -0
  361. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Alpha.md +10 -0
  362. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Beta.md +7 -0
  363. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Delta.md +8 -0
  364. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Gamma.md +8 -0
  365. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/queries/20260422-abc.md +10 -0
  366. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/synthesis/Crystal.md +8 -0
  367. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/topics/Overview.md +9 -0
  368. package/skills/llm-wiki-skill/tests/fixtures/graph-data-empty-wiki/wiki/entities/.gitkeep +0 -0
  369. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/purpose.md +3 -0
  370. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Attention.md +7 -0
  371. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Decoder.md +7 -0
  372. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Encoder.md +7 -0
  373. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/GPT.md +7 -0
  374. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Transformer.md +13 -0
  375. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/sources/paper.md +9 -0
  376. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/topics/arch.md +12 -0
  377. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/topics/finetune.md +9 -0
  378. package/skills/llm-wiki-skill/tests/fixtures/graph-interactive-basic/wiki/graph-data.json +40 -0
  379. package/skills/llm-wiki-skill/tests/fixtures/graph-interactive-dense/wiki/graph-data.json +3965 -0
  380. package/skills/llm-wiki-skill/tests/fixtures/graph-interactive-multicomm/wiki/graph-data.json +212 -0
  381. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/index.md +14 -0
  382. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/C++.md +6 -0
  383. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/Lonely.md +4 -0
  384. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/Other.md +8 -0
  385. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/Real.md +7 -0
  386. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/sources/2026-01-01-test-source.md +15 -0
  387. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/sources/2026-02-01-inline-img.md +12 -0
  388. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/topics/AIbasic.md +3 -0
  389. package/skills/llm-wiki-skill/tests/graph-aggregation-fallback-trial.regression-1.sh +46 -0
  390. package/skills/llm-wiki-skill/tests/graph-analysis-helper.regression-1.sh +237 -0
  391. package/skills/llm-wiki-skill/tests/graph-browser-large-performance.regression-1.sh +39 -0
  392. package/skills/llm-wiki-skill/tests/graph-browser-stage-4-5.regression-1.sh +163 -0
  393. package/skills/llm-wiki-skill/tests/graph-build-failures.regression-1.sh +91 -0
  394. package/skills/llm-wiki-skill/tests/graph-community-node-map.regression-1.sh +107 -0
  395. package/skills/llm-wiki-skill/tests/graph-community-wash-interactions.regression-1.sh +35 -0
  396. package/skills/llm-wiki-skill/tests/graph-data-confidence-merge.regression-1.sh +309 -0
  397. package/skills/llm-wiki-skill/tests/graph-data-source-paths.regression-1.sh +49 -0
  398. package/skills/llm-wiki-skill/tests/graph-default-behavior.regression-1.sh +35 -0
  399. package/skills/llm-wiki-skill/tests/graph-html-a11y.regression-1.sh +50 -0
  400. package/skills/llm-wiki-skill/tests/graph-html-brand-link.regression-1.sh +47 -0
  401. package/skills/llm-wiki-skill/tests/graph-html-density.regression-1.sh +228 -0
  402. package/skills/llm-wiki-skill/tests/graph-html-drawer-neighbors.regression-1.sh +47 -0
  403. package/skills/llm-wiki-skill/tests/graph-html-insights.regression-1.sh +80 -0
  404. package/skills/llm-wiki-skill/tests/graph-html-long-label.regression-1.sh +92 -0
  405. package/skills/llm-wiki-skill/tests/graph-html-minimap.regression-1.sh +51 -0
  406. package/skills/llm-wiki-skill/tests/graph-html-mobile.regression-1.sh +48 -0
  407. package/skills/llm-wiki-skill/tests/graph-html-oriental-atlas-contract.regression-1.sh +94 -0
  408. package/skills/llm-wiki-skill/tests/graph-html-oriental-design-contract.regression-1.sh +82 -0
  409. package/skills/llm-wiki-skill/tests/graph-html-search.regression-1.sh +52 -0
  410. package/skills/llm-wiki-skill/tests/graph-html-styles.regression-1.sh +66 -0
  411. package/skills/llm-wiki-skill/tests/graph-html-toolbar.regression-1.sh +62 -0
  412. package/skills/llm-wiki-skill/tests/graph-offline-phase-6.regression-1.sh +51 -0
  413. package/skills/llm-wiki-skill/tests/graph-sigma-global-production.regression-1.sh +64 -0
  414. package/skills/llm-wiki-skill/tests/graph-sigma-graphology-trial.regression-1.sh +46 -0
  415. package/skills/llm-wiki-skill/tests/graph-vis-network-trial.regression-1.sh +46 -0
  416. package/skills/llm-wiki-skill/tests/graph-workbench-interactions.regression-1.sh +151 -0
  417. package/skills/llm-wiki-skill/tests/js/graph-wash-bootstrap.test.js +110 -0
  418. package/skills/llm-wiki-skill/tests/js/graph-wash-helpers.test.js +296 -0
  419. package/skills/llm-wiki-skill/tests/js/graph-wash-learning.test.js +260 -0
  420. package/skills/llm-wiki-skill/tests/js/graph-wash-queue.test.js +115 -0
  421. package/skills/llm-wiki-skill/tests/js/graph-wash-runtime-state.test.js +282 -0
  422. package/skills/llm-wiki-skill/tests/js/source-signal-coverage.test.js +48 -0
  423. package/skills/llm-wiki-skill/tests/js/source-signal-eligibility.test.js +202 -0
  424. package/skills/llm-wiki-skill/tests/lib/graph-html-engine-helpers.sh +110 -0
  425. package/skills/llm-wiki-skill/tests/lint-output.regression-1.sh +32 -0
  426. package/skills/llm-wiki-skill/tests/regression.sh +1754 -0
  427. package/skills/llm-wiki-skill/workbench/AGENTS.md +110 -0
  428. package/skills/llm-wiki-skill/workbench/CLAUDE.md +55 -0
  429. package/skills/llm-wiki-skill/workbench/LICENSE +21 -0
  430. package/skills/llm-wiki-skill/workbench/PRODUCT.md +1353 -0
  431. package/skills/llm-wiki-skill/workbench/README.md +30 -0
  432. package/skills/llm-wiki-skill/workbench/docs/current-kb-retrieval-design.md +587 -0
  433. package/skills/llm-wiki-skill/workbench/docs/graph-evolution-1-design.md +210 -0
  434. package/skills/llm-wiki-skill/workbench/docs/stage-2-design.md +1139 -0
  435. package/skills/llm-wiki-skill/workbench/docs/stage-3-design.md +1269 -0
  436. package/skills/llm-wiki-skill/workbench/docs/stage-3.5-design.md +1164 -0
  437. package/skills/llm-wiki-skill/workbench/docs/stage-4-design.md +504 -0
  438. package/skills/llm-wiki-skill/workbench/docs/stage-4.5-design.md +195 -0
  439. package/skills/llm-wiki-skill/workbench/docs/superpowers/specs/2026-05-28-resizable-preview-layout-design.md +165 -0
  440. package/skills/llm-wiki-skill/workbench/docs/superpowers/specs/2026-05-28-settings-panel-scroll-fix.md +50 -0
  441. package/skills/llm-wiki-skill/workbench/server/package.json +31 -0
  442. package/skills/llm-wiki-skill/workbench/server/src/agent.ts +457 -0
  443. package/skills/llm-wiki-skill/workbench/server/src/artifacts.ts +248 -0
  444. package/skills/llm-wiki-skill/workbench/server/src/auth.ts +164 -0
  445. package/skills/llm-wiki-skill/workbench/server/src/config.ts +130 -0
  446. package/skills/llm-wiki-skill/workbench/server/src/conversations.test.ts +159 -0
  447. package/skills/llm-wiki-skill/workbench/server/src/conversations.ts +270 -0
  448. package/skills/llm-wiki-skill/workbench/server/src/digest/batch.ts +189 -0
  449. package/skills/llm-wiki-skill/workbench/server/src/digest/concurrency.test.ts +36 -0
  450. package/skills/llm-wiki-skill/workbench/server/src/digest/concurrency.ts +31 -0
  451. package/skills/llm-wiki-skill/workbench/server/src/digest/subagent.ts +91 -0
  452. package/skills/llm-wiki-skill/workbench/server/src/extensions/artifacts.ts +81 -0
  453. package/skills/llm-wiki-skill/workbench/server/src/extensions/knowledge-base.ts +263 -0
  454. package/skills/llm-wiki-skill/workbench/server/src/extensions/new-wiki.ts +42 -0
  455. package/skills/llm-wiki-skill/workbench/server/src/extensions/synthesis.ts +172 -0
  456. package/skills/llm-wiki-skill/workbench/server/src/graph-watcher.test.ts +196 -0
  457. package/skills/llm-wiki-skill/workbench/server/src/graph.test.ts +113 -0
  458. package/skills/llm-wiki-skill/workbench/server/src/graph.ts +351 -0
  459. package/skills/llm-wiki-skill/workbench/server/src/index.ts +1148 -0
  460. package/skills/llm-wiki-skill/workbench/server/src/knowledge-bases.test.ts +30 -0
  461. package/skills/llm-wiki-skill/workbench/server/src/knowledge-bases.ts +321 -0
  462. package/skills/llm-wiki-skill/workbench/server/src/pages.test.ts +41 -0
  463. package/skills/llm-wiki-skill/workbench/server/src/pages.ts +160 -0
  464. package/skills/llm-wiki-skill/workbench/server/src/retrieval.test.ts +151 -0
  465. package/skills/llm-wiki-skill/workbench/server/src/retrieval.ts +427 -0
  466. package/skills/llm-wiki-skill/workbench/server/src/tool-status-events.test.ts +363 -0
  467. package/skills/llm-wiki-skill/workbench/server/src/tool-status-events.ts +616 -0
  468. package/skills/llm-wiki-skill/workbench/server/src/wiki-init.test.ts +19 -0
  469. package/skills/llm-wiki-skill/workbench/server/src/wiki-init.ts +188 -0
  470. package/skills/llm-wiki-skill/workbench/server/tsconfig.json +10 -0
  471. package/skills/llm-wiki-skill/workbench/tsconfig.json +20 -0
  472. package/skills/llm-wiki-skill/workbench/web/README.md +73 -0
  473. package/skills/llm-wiki-skill/workbench/web/components.json +21 -0
  474. package/skills/llm-wiki-skill/workbench/web/eslint.config.js +22 -0
  475. package/skills/llm-wiki-skill/workbench/web/index.html +16 -0
  476. package/skills/llm-wiki-skill/workbench/web/package.json +54 -0
  477. package/skills/llm-wiki-skill/workbench/web/src/App.tsx +1195 -0
  478. package/skills/llm-wiki-skill/workbench/web/src/components/AddExternalDialog.tsx +375 -0
  479. package/skills/llm-wiki-skill/workbench/web/src/components/AppearancePanel.tsx +158 -0
  480. package/skills/llm-wiki-skill/workbench/web/src/components/ArtifactView.tsx +15 -0
  481. package/skills/llm-wiki-skill/workbench/web/src/components/BatchDigestPanel.tsx +117 -0
  482. package/skills/llm-wiki-skill/workbench/web/src/components/ChatPanel.tsx +890 -0
  483. package/skills/llm-wiki-skill/workbench/web/src/components/CommandMenu.tsx +75 -0
  484. package/skills/llm-wiki-skill/workbench/web/src/components/ExportButtons.tsx +58 -0
  485. package/skills/llm-wiki-skill/workbench/web/src/components/GraphPanel.tsx +800 -0
  486. package/skills/llm-wiki-skill/workbench/web/src/components/GraphReader.tsx +52 -0
  487. package/skills/llm-wiki-skill/workbench/web/src/components/GraphSelection.tsx +100 -0
  488. package/skills/llm-wiki-skill/workbench/web/src/components/GraphSummaryDrawer.tsx +269 -0
  489. package/skills/llm-wiki-skill/workbench/web/src/components/MainViewTabs.tsx +69 -0
  490. package/skills/llm-wiki-skill/workbench/web/src/components/MarkdownView.tsx +106 -0
  491. package/skills/llm-wiki-skill/workbench/web/src/components/NewWikiDialog.tsx +103 -0
  492. package/skills/llm-wiki-skill/workbench/web/src/components/RefMenu.tsx +44 -0
  493. package/skills/llm-wiki-skill/workbench/web/src/components/RightDrawer.tsx +298 -0
  494. package/skills/llm-wiki-skill/workbench/web/src/components/SearchPanel.tsx +165 -0
  495. package/skills/llm-wiki-skill/workbench/web/src/components/SettingsPanel.tsx +320 -0
  496. package/skills/llm-wiki-skill/workbench/web/src/components/Sidebar.tsx +354 -0
  497. package/skills/llm-wiki-skill/workbench/web/src/components/ToolHistorySummary.tsx +111 -0
  498. package/skills/llm-wiki-skill/workbench/web/src/components/ToolStatusRunway.tsx +70 -0
  499. package/skills/llm-wiki-skill/workbench/web/src/components/TopBar.tsx +348 -0
  500. package/skills/llm-wiki-skill/workbench/web/src/components/renderers/DownloadOnlyRenderer.tsx +88 -0
  501. package/skills/llm-wiki-skill/workbench/web/src/components/renderers/HtmlRenderer.tsx +92 -0
  502. package/skills/llm-wiki-skill/workbench/web/src/components/ui/button.tsx +52 -0
  503. package/skills/llm-wiki-skill/workbench/web/src/components/ui/command.tsx +50 -0
  504. package/skills/llm-wiki-skill/workbench/web/src/components/ui/dialog.tsx +156 -0
  505. package/skills/llm-wiki-skill/workbench/web/src/components/ui/input.tsx +21 -0
  506. package/skills/llm-wiki-skill/workbench/web/src/components/ui/tooltip.tsx +57 -0
  507. package/skills/llm-wiki-skill/workbench/web/src/index.css +4204 -0
  508. package/skills/llm-wiki-skill/workbench/web/src/lib/api.ts +648 -0
  509. package/skills/llm-wiki-skill/workbench/web/src/lib/appearance.ts +114 -0
  510. package/skills/llm-wiki-skill/workbench/web/src/lib/drawer-layout.ts +41 -0
  511. package/skills/llm-wiki-skill/workbench/web/src/lib/drawer-state.ts +168 -0
  512. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-reader.ts +25 -0
  513. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-selection-drawer.ts +35 -0
  514. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-selection.ts +96 -0
  515. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-summary-actions.ts +184 -0
  516. package/skills/llm-wiki-skill/workbench/web/src/lib/legacy-tool-status.ts +113 -0
  517. package/skills/llm-wiki-skill/workbench/web/src/lib/model-roles.ts +20 -0
  518. package/skills/llm-wiki-skill/workbench/web/src/lib/search-filter.ts +104 -0
  519. package/skills/llm-wiki-skill/workbench/web/src/lib/sse.ts +72 -0
  520. package/skills/llm-wiki-skill/workbench/web/src/lib/tool-status-format.ts +170 -0
  521. package/skills/llm-wiki-skill/workbench/web/src/lib/tool-status-model.ts +392 -0
  522. package/skills/llm-wiki-skill/workbench/web/src/lib/utils.ts +10 -0
  523. package/skills/llm-wiki-skill/workbench/web/src/lib/view-status.ts +16 -0
  524. package/skills/llm-wiki-skill/workbench/web/src/lib/wiki-links.ts +32 -0
  525. package/skills/llm-wiki-skill/workbench/web/src/main.tsx +10 -0
  526. package/skills/llm-wiki-skill/workbench/web/test/api.test.ts +61 -0
  527. package/skills/llm-wiki-skill/workbench/web/test/app-shell-drawer-layout.test.tsx +48 -0
  528. package/skills/llm-wiki-skill/workbench/web/test/appearance-panel.test.tsx +121 -0
  529. package/skills/llm-wiki-skill/workbench/web/test/appearance.test.ts +262 -0
  530. package/skills/llm-wiki-skill/workbench/web/test/chat-host-source.test.tsx +22 -0
  531. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-auto-scroll.test.tsx +311 -0
  532. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-bubbles.test.tsx +48 -0
  533. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-composer.test.tsx +86 -0
  534. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-tool-status.test.tsx +26 -0
  535. package/skills/llm-wiki-skill/workbench/web/test/export-batch-paper.test.tsx +101 -0
  536. package/skills/llm-wiki-skill/workbench/web/test/graph-drawer-state.test.ts +257 -0
  537. package/skills/llm-wiki-skill/workbench/web/test/graph-panel-paper.test.tsx +200 -0
  538. package/skills/llm-wiki-skill/workbench/web/test/graph-reader-request.test.ts +32 -0
  539. package/skills/llm-wiki-skill/workbench/web/test/graph-reader.test.ts +62 -0
  540. package/skills/llm-wiki-skill/workbench/web/test/graph-selection-drawer.test.ts +56 -0
  541. package/skills/llm-wiki-skill/workbench/web/test/graph-selection.test.ts +83 -0
  542. package/skills/llm-wiki-skill/workbench/web/test/graph-summary-actions.test.ts +188 -0
  543. package/skills/llm-wiki-skill/workbench/web/test/main-view-tabs.test.tsx +34 -0
  544. package/skills/llm-wiki-skill/workbench/web/test/markdown-view.test.tsx +34 -0
  545. package/skills/llm-wiki-skill/workbench/web/test/popup-menus.test.tsx +85 -0
  546. package/skills/llm-wiki-skill/workbench/web/test/render.test.tsx +30 -0
  547. package/skills/llm-wiki-skill/workbench/web/test/render.tsx +38 -0
  548. package/skills/llm-wiki-skill/workbench/web/test/right-drawer-graph-summary.test.tsx +287 -0
  549. package/skills/llm-wiki-skill/workbench/web/test/right-drawer-interactions.test.tsx +182 -0
  550. package/skills/llm-wiki-skill/workbench/web/test/search-filter.test.ts +65 -0
  551. package/skills/llm-wiki-skill/workbench/web/test/search-panel.test.tsx +155 -0
  552. package/skills/llm-wiki-skill/workbench/web/test/setup-dom.ts +66 -0
  553. package/skills/llm-wiki-skill/workbench/web/test/sidebar-v2-source.test.ts +17 -0
  554. package/skills/llm-wiki-skill/workbench/web/test/sidebar.test.tsx +110 -0
  555. package/skills/llm-wiki-skill/workbench/web/test/tool-history-summary.test.tsx +150 -0
  556. package/skills/llm-wiki-skill/workbench/web/test/tool-status-format.test.ts +107 -0
  557. package/skills/llm-wiki-skill/workbench/web/test/tool-status-model.test.ts +324 -0
  558. package/skills/llm-wiki-skill/workbench/web/test/tool-status-runway.test.ts +159 -0
  559. package/skills/llm-wiki-skill/workbench/web/test/topbar.test.tsx +248 -0
  560. package/skills/llm-wiki-skill/workbench/web/test/visual/paper-ui.ts +898 -0
  561. package/skills/llm-wiki-skill/workbench/web/test/wiki-links.test.ts +23 -0
  562. package/skills/llm-wiki-skill/workbench/web/test-results/paper-ui/actual/.gitkeep +1 -0
  563. package/skills/llm-wiki-skill/workbench/web/test-results/paper-ui/baseline/.gitkeep +1 -0
  564. package/skills/llm-wiki-skill/workbench/web/tsconfig.app.json +30 -0
  565. package/skills/llm-wiki-skill/workbench/web/tsconfig.json +7 -0
  566. package/skills/llm-wiki-skill/workbench/web/tsconfig.node.json +24 -0
  567. package/skills/llm-wiki-skill/workbench/web/vite.config.ts +28 -0
@@ -0,0 +1,1437 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import http from "node:http";
3
+ import { createRequire } from "node:module";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ import { buildCommunityAggregationMarkers } from "../../packages/graph-engine/src";
9
+ import {
10
+ generateLargeGraphFixture,
11
+ type LargeGraphFixtureMetadata
12
+ } from "../../packages/graph-engine/test/large-graph-fixtures";
13
+ import {
14
+ FRAME_P95_CEILING_MS,
15
+ FPS_FLOOR,
16
+ NAME_HELPER_INIT_SCRIPT,
17
+ TRIAL_SCHEMA_VERSION,
18
+ actionThresholds,
19
+ DURATION_GATED_ACTIONS,
20
+ durationFailureClass,
21
+ durationLimitMs,
22
+ frameSampleFailureClass,
23
+ memoryGrowthFailureClass,
24
+ memoryGrowthFailureDetail,
25
+ validateTrialResults,
26
+ waitForAnimationFrames
27
+ } from "./graph-renderer-trial-shared";
28
+
29
+ const require = createRequire(import.meta.url);
30
+ const { chromium } = require("playwright");
31
+
32
+ const repoRoot = path.resolve(import.meta.dirname, "../..");
33
+ const artifactDir = process.env.GRAPH_SIGMA_PRODUCTION_ARTIFACT_DIR || path.join(os.tmpdir(), `llm-wiki-sigma-global-production-${Date.now()}`);
34
+ const executablePath = process.env.GRAPH_SIGMA_PRODUCTION_CHROME_EXECUTABLE || "";
35
+ const DEFAULT_SIGMA_PRODUCTION_SHAPES: LargeGraphFixtureId[] = [
36
+ "real-snapshot-proxy",
37
+ "nodes-1000-sparse",
38
+ "nodes-1000-dense"
39
+ ];
40
+ const SIGMA_GLOBAL_NODE_LIMIT = 2000;
41
+ const requestedShapes = parseSigmaProductionShapes(process.env.GRAPH_SIGMA_PRODUCTION_SHAPES);
42
+ const resultPath = path.join(artifactDir, "sigma-global-production-results.json");
43
+ const buildCommit = readBuildCommit();
44
+ const rendererName = "sigma-global-production";
45
+ const productionPath = true;
46
+ const engineDistDir = path.join(repoRoot, "packages/graph-engine/dist");
47
+ const sigmaVersion = "3.0.3";
48
+ const graphologyVersion = "0.26.0";
49
+ let capturedBrowserVersion = "unknown";
50
+ const runContext = {
51
+ run_started_at: "",
52
+ run_finished_at: "",
53
+ browser: "unknown",
54
+ build_commit: buildCommit
55
+ };
56
+
57
+ main().catch((error) => {
58
+ console.error(error);
59
+ process.exitCode = 1;
60
+ });
61
+
62
+ function readBuildCommit(): string {
63
+ try {
64
+ return execFileSync("git", ["-C", repoRoot, "rev-parse", "--short", "HEAD"], { encoding: "utf8" }).trim();
65
+ } catch {
66
+ return "unknown";
67
+ }
68
+ }
69
+
70
+ function parseSigmaProductionShapes(value: string | undefined): LargeGraphFixtureId[] {
71
+ return (value || DEFAULT_SIGMA_PRODUCTION_SHAPES.join(","))
72
+ .split(",")
73
+ .map((item) => item.trim())
74
+ .filter(Boolean) as LargeGraphFixtureId[];
75
+ }
76
+
77
+ async function main(): Promise<void> {
78
+ await fs.mkdir(artifactDir, { recursive: true });
79
+ const records: PerformanceRecord[] = [];
80
+ const errors: string[] = [];
81
+ const browser = await chromium.launch({
82
+ ...(executablePath ? { executablePath } : {}),
83
+ args: ["--js-flags=--expose-gc"]
84
+ });
85
+ const staticServer = await startStaticServer();
86
+ runContext.run_started_at = new Date().toISOString();
87
+ try {
88
+ capturedBrowserVersion = await browser.version();
89
+ runContext.browser = capturedBrowserVersion;
90
+ } catch {
91
+ runContext.browser = capturedBrowserVersion;
92
+ }
93
+
94
+ try {
95
+ for (const shape of requestedShapes) {
96
+ const fixture = generateLargeGraphFixture(shape);
97
+ if (fixture.metadata.nodes > SIGMA_GLOBAL_NODE_LIMIT) {
98
+ errors.push(`${shape}: skipped because Phase 1 routes graphs over ${SIGMA_GLOBAL_NODE_LIMIT} nodes to over-limit notice`);
99
+ continue;
100
+ }
101
+ const searchResultIds = fixture.data.nodes.filter((node) => node.label.includes("needle")).map((node) => node.id);
102
+ const selectedNodeIds = fixture.data.nodes.slice(0, Math.min(8, fixture.data.nodes.length)).map((node) => node.id);
103
+ const aggregationMarkers = buildCommunityAggregationMarkers(fixture.data, {
104
+ pins: fixture.pins,
105
+ searchResultIds,
106
+ selectedNodeIds,
107
+ minCommunitySize: 80
108
+ });
109
+ const html = await writeProductionHtml(shape, staticServer.origin, {
110
+ data: fixture.data,
111
+ pins: fixture.pins,
112
+ aggregationMarkers
113
+ });
114
+ try {
115
+ const shapeRecords = await measureShape(browser, fixture.metadata, staticServer.artifactUrl(html));
116
+ records.push(...shapeRecords);
117
+ } catch (error) {
118
+ errors.push(`${shape}: ${errorDetail(error)}`);
119
+ records.push(failedRecord(fixture.metadata, {
120
+ action: "fixture_load_or_action",
121
+ failure_class: classifyError(error),
122
+ failure_detail: errorDetail(error),
123
+ artifact_path: resultPath
124
+ }));
125
+ } finally {
126
+ await writeResult(records, errors);
127
+ }
128
+ }
129
+ } finally {
130
+ await browser.close().catch(() => undefined);
131
+ await staticServer.close();
132
+ }
133
+
134
+ runContext.run_finished_at = new Date().toISOString();
135
+ for (const record of records) record.run_finished_at = runContext.run_finished_at;
136
+ await writeResult(records, errors);
137
+
138
+ validateTrialResults({
139
+ renderer: "Sigma global production",
140
+ requestedShapes,
141
+ records,
142
+ errors,
143
+ resultPath
144
+ });
145
+ console.log(`Wrote ${records.length} production Sigma global records to ${resultPath}`);
146
+ }
147
+
148
+ async function writeResult(records: PerformanceRecord[], errors: string[]): Promise<void> {
149
+ runContext.run_finished_at = new Date().toISOString();
150
+ await fs.writeFile(resultPath, `${JSON.stringify({
151
+ schema_version: TRIAL_SCHEMA_VERSION,
152
+ run_started_at: runContext.run_started_at,
153
+ run_finished_at: runContext.run_finished_at,
154
+ renderer: rendererName,
155
+ production_path: productionPath,
156
+ browser: runContext.browser,
157
+ build_commit: runContext.build_commit,
158
+ candidate: {
159
+ sigma: sigmaVersion,
160
+ graphology: graphologyVersion,
161
+ production_path_switched: true
162
+ },
163
+ artifact_dir: artifactDir,
164
+ shapes: requestedShapes,
165
+ records,
166
+ errors
167
+ }, null, 2)}\n`);
168
+ }
169
+
170
+ async function startStaticServer(): Promise<{
171
+ origin: string;
172
+ artifactUrl(file: string): string;
173
+ close(): Promise<void>;
174
+ }> {
175
+ const server = http.createServer(async (request, response) => {
176
+ try {
177
+ const requestUrl = new URL(request.url || "/", "http://127.0.0.1");
178
+ const pathname = decodeURIComponent(requestUrl.pathname);
179
+ if (pathname.startsWith("/artifact/")) {
180
+ const file = path.basename(pathname.slice("/artifact/".length));
181
+ await serveFile(response, path.join(artifactDir, file));
182
+ return;
183
+ }
184
+ if (pathname.startsWith("/graph-engine-dist/")) {
185
+ const relative = pathname.slice("/graph-engine-dist/".length);
186
+ if (relative.includes("..")) {
187
+ response.writeHead(400).end("bad path");
188
+ return;
189
+ }
190
+ await serveFile(response, path.join(engineDistDir, relative));
191
+ return;
192
+ }
193
+ response.writeHead(404).end("not found");
194
+ } catch (error) {
195
+ response.writeHead(500).end(errorDetail(error));
196
+ }
197
+ });
198
+
199
+ await new Promise<void>((resolve, reject) => {
200
+ server.once("error", reject);
201
+ server.listen(0, "127.0.0.1", () => {
202
+ server.off("error", reject);
203
+ resolve();
204
+ });
205
+ });
206
+ const address = server.address();
207
+ if (!address || typeof address === "string") throw new Error("failed to start production regression static server");
208
+ const origin = `http://127.0.0.1:${address.port}`;
209
+ return {
210
+ origin,
211
+ artifactUrl(file) {
212
+ return `${origin}/artifact/${encodeURIComponent(path.basename(file))}`;
213
+ },
214
+ close() {
215
+ return new Promise((resolve) => server.close(() => resolve()));
216
+ }
217
+ };
218
+ }
219
+
220
+ async function serveFile(response: http.ServerResponse, file: string): Promise<void> {
221
+ const content = await fs.readFile(file);
222
+ response.writeHead(200, {
223
+ "content-type": contentType(file),
224
+ "cache-control": file.startsWith(engineDistDir) ? "public, max-age=3600" : "no-store"
225
+ });
226
+ response.end(content);
227
+ }
228
+
229
+ function contentType(file: string): string {
230
+ if (file.endsWith(".html")) return "text/html; charset=utf-8";
231
+ if (file.endsWith(".js")) return "text/javascript; charset=utf-8";
232
+ if (file.endsWith(".json")) return "application/json; charset=utf-8";
233
+ if (file.endsWith(".map")) return "application/json; charset=utf-8";
234
+ return "application/octet-stream";
235
+ }
236
+
237
+ async function writeProductionHtml(
238
+ shape: string,
239
+ origin: string,
240
+ input: { data: unknown; pins: unknown; aggregationMarkers: unknown }
241
+ ): Promise<string> {
242
+ const file = path.join(artifactDir, `${shape}-production.html`);
243
+ await fs.writeFile(file, `<!doctype html>
244
+ <html>
245
+ <head>
246
+ <meta charset="utf-8" />
247
+ <title>Sigma Global Production ${escapeHtml(shape)}</title>
248
+ <style>
249
+ html, body, #stage { width: 100%; height: 100%; margin: 0; overflow: hidden; }
250
+ body { background: #f8fafc; font-family: system-ui, sans-serif; }
251
+ #stage { position: relative; }
252
+ .sigma-global-route, .sigma-global-renderer { width: 100%; height: 100%; }
253
+ #drawer { position: absolute; right: 0; top: 0; width: 320px; height: 100%; background: white; border-left: 1px solid #e5e7eb; padding: 16px; box-sizing: border-box; display: none; z-index: 20; }
254
+ #drawer[data-open="true"] { display: block; }
255
+ .summary-card { display: flex; flex-direction: column; gap: 8px; }
256
+ .summary-kicker { font-size: 12px; color: #64748b; text-transform: uppercase; letter-spacing: 0.04em; }
257
+ .summary-title { font-size: 18px; margin: 0; }
258
+ .summary-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
259
+ .summary-fact { display: flex; justify-content: space-between; font-size: 13px; color: #334155; border-bottom: 1px solid #f1f5f9; padding: 2px 0; }
260
+ .summary-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
261
+ .summary-item { font-size: 13px; color: #1e293b; padding: 4px 6px; background: #f8fafc; border-radius: 4px; }
262
+ </style>
263
+ </head>
264
+ <body>
265
+ <div id="stage"></div>
266
+ <aside id="drawer"></aside>
267
+ <script type="module">
268
+ import { createGraphEngine, buildCommunityAggregationMarkers } from "${origin}/graph-engine-dist/engine.esm.js";
269
+ (async () => {
270
+ const graphData = ${JSON.stringify(input.data)};
271
+ const initialPins = ${JSON.stringify(input.pins)};
272
+ const initialAggregationMarkers = ${JSON.stringify(input.aggregationMarkers)};
273
+ const stage = document.getElementById("stage");
274
+ const drawer = document.getElementById("drawer");
275
+ const firstNodeId = graphData.nodes[0]?.id || null;
276
+ const firstCommunityId = graphData.nodes.find((node) => node.community)?.community || null;
277
+ const productionStart = performance.now();
278
+ let loadingStateSeenAtMs = null;
279
+ let currentPins = initialPins;
280
+ let searchResultIds = [];
281
+ let selectedContainerId = null;
282
+ let selectedNodeId = null;
283
+ let lastSelectionKind = null;
284
+ let lastSelection = null;
285
+ let lastVisibility = {
286
+ searchQuery: "",
287
+ searchResultIds: [],
288
+ typeFilters: {},
289
+ temporaryObject: null
290
+ };
291
+ stage.dataset.loadingState = "sigma-global-loading";
292
+ loadingStateSeenAtMs = performance.now() - productionStart;
293
+ stage.addEventListener("click", (event) => {
294
+ const target = event.target instanceof Element ? event.target : null;
295
+ if (target?.closest(".sigma-global-node-hit-target")) lastSelectionKind = "node";
296
+ if (target?.closest(".sigma-global-community-region")) lastSelectionKind = "community";
297
+ }, true);
298
+
299
+ try {
300
+ const engine = createGraphEngine(stage, {
301
+ data: graphData,
302
+ pins: currentPins,
303
+ theme: "shan-shui",
304
+ aggregationMarkers: [],
305
+ capabilities: {
306
+ persistPins(pins) {
307
+ currentPins = pins;
308
+ return Promise.resolve();
309
+ },
310
+ onSelectionChange(selection) {
311
+ lastSelection = selection;
312
+ if (lastSelectionKind === "node") {
313
+ selectedNodeId = selection?.nodeIds?.length === 1 ? selection.nodeIds[0] : null;
314
+ selectedContainerId = null;
315
+ } else if (lastSelectionKind === "community") {
316
+ selectedNodeId = null;
317
+ selectedContainerId = selection?.communityIds?.length ? selection.communityIds[0] : null;
318
+ }
319
+ },
320
+ onSelectionClear() {
321
+ lastSelection = null;
322
+ selectedNodeId = null;
323
+ selectedContainerId = null;
324
+ lastSelectionKind = null;
325
+ },
326
+ onVisibilityStateChange(state) {
327
+ lastVisibility = state;
328
+ searchResultIds = state.searchResultIds || [];
329
+ }
330
+ }
331
+ });
332
+ function hasProductionSigma() {
333
+ return Boolean(document.querySelector(".sigma-global-renderer[data-renderer='sigma-global']"));
334
+ }
335
+
336
+ function routeId() {
337
+ return document.querySelector(".sigma-global-route[data-route]")?.dataset.route ||
338
+ document.querySelector(".graph-over-limit-notice-view[data-route]")?.dataset.route ||
339
+ (domSvgGraphRoot() ? "dom-svg-community" : "") ||
340
+ "unknown";
341
+ }
342
+
343
+ function domSvgGraphRoot() {
344
+ return Array.from(document.querySelectorAll(".llm-wiki-graph-engine"))
345
+ .find((element) => !element.closest(".sigma-global-route")) || null;
346
+ }
347
+
348
+ function fallbackRouteElement() {
349
+ return document.querySelector(".graph-over-limit-notice-view[data-route]") || domSvgGraphRoot();
350
+ }
351
+
352
+ function waitForProductionSigma(timeoutMs = 10000) {
353
+ const started = performance.now();
354
+ return new Promise((resolve, reject) => {
355
+ if (hasProductionSigma()) {
356
+ resolve(productionProbe());
357
+ return;
358
+ }
359
+ let observer = null;
360
+ let interval = null;
361
+ let timeout = null;
362
+ function cleanup() {
363
+ if (observer) observer.disconnect();
364
+ if (interval) window.clearInterval(interval);
365
+ if (timeout) window.clearTimeout(timeout);
366
+ }
367
+ function finish() {
368
+ cleanup();
369
+ resolve(productionProbe());
370
+ }
371
+ function fail(error) {
372
+ cleanup();
373
+ reject(error);
374
+ }
375
+ observer = new MutationObserver(() => {
376
+ if (hasProductionSigma()) finish();
377
+ });
378
+ observer.observe(document.body, { childList: true, subtree: true });
379
+ interval = window.setInterval(() => {
380
+ if (hasProductionSigma()) {
381
+ finish();
382
+ return;
383
+ }
384
+ const fallback = fallbackRouteElement();
385
+ if (fallback && !document.querySelector(".sigma-global-route[data-route='sigma-global']")) {
386
+ fail(new Error("production Sigma route fell back before renderer became ready"));
387
+ }
388
+ }, 10);
389
+ timeout = window.setTimeout(() => {
390
+ fail(new Error("timed out waiting for production Sigma renderer"));
391
+ }, timeoutMs);
392
+ function tick() {
393
+ if (hasProductionSigma()) {
394
+ finish();
395
+ return;
396
+ }
397
+ const fallback = fallbackRouteElement();
398
+ if (fallback && !document.querySelector(".sigma-global-route[data-route='sigma-global']")) {
399
+ fail(new Error("production Sigma route fell back before renderer became ready"));
400
+ return;
401
+ }
402
+ if (performance.now() - started > timeoutMs) {
403
+ fail(new Error("timed out waiting for production Sigma renderer"));
404
+ return;
405
+ }
406
+ requestAnimationFrame(tick);
407
+ }
408
+ });
409
+ }
410
+
411
+ function waitForSelectionState(kind, id, timeoutMs = 10000) {
412
+ const started = performance.now();
413
+ return new Promise((resolve, reject) => {
414
+ function selectedCommunityRegionReady() {
415
+ if (kind !== "community") return true;
416
+ return Array.from(document.querySelectorAll(".sigma-global-community-region"))
417
+ .some((element) => element.dataset.communityId === id && element.dataset.selected === "true");
418
+ }
419
+ function done() {
420
+ if (kind === "community") {
421
+ return routeId() === "sigma-global"
422
+ && lastSelectionKind === "community"
423
+ && selectedContainerId === id
424
+ && selectedCommunityRegionReady();
425
+ }
426
+ if (kind === "node") {
427
+ return lastSelectionKind === "node" && selectedNodeId === id;
428
+ }
429
+ return false;
430
+ }
431
+ function tick() {
432
+ if (done()) {
433
+ resolve(productionProbe({ canvasSignal: false }));
434
+ return;
435
+ }
436
+ if (performance.now() - started > timeoutMs) {
437
+ reject(new Error("timed out waiting for " + kind + " selection " + id));
438
+ return;
439
+ }
440
+ requestAnimationFrame(tick);
441
+ }
442
+ tick();
443
+ });
444
+ }
445
+
446
+ function productionProbe(options = {}) {
447
+ const includeCanvasSignal = options.canvasSignal === true;
448
+ const sigmaRoot = document.querySelector(".sigma-global-renderer[data-renderer='sigma-global']");
449
+ const canvasSignal = sigmaRoot && includeCanvasSignal ? sigmaCanvasSignal(sigmaRoot) : { nonblank: null, sampleCount: 0 };
450
+ const hitTargetCount = document.querySelectorAll(".sigma-global-node-hit-target, .sigma-global-community-region").length;
451
+ return {
452
+ productionPath: Boolean(sigmaRoot),
453
+ route: routeId(),
454
+ canvasCount: sigmaRoot ? sigmaRoot.querySelectorAll("canvas").length : 0,
455
+ canvasNonBlank: canvasSignal.nonblank,
456
+ canvasPixelSampleCount: canvasSignal.sampleCount,
457
+ visibleSignal: Boolean(sigmaRoot) && hitTargetCount > 0,
458
+ hitTargetCount,
459
+ sigmaRendererCount: document.querySelectorAll(".sigma-global-renderer[data-renderer='sigma-global']").length,
460
+ fallbackCount: document.querySelectorAll(".graph-over-limit-notice-view").length + (domSvgGraphRoot() ? 1 : 0)
461
+ };
462
+ }
463
+
464
+ function sigmaCanvasSignal(root) {
465
+ let sampleCount = 0;
466
+ for (const canvas of Array.from(root.querySelectorAll("canvas"))) {
467
+ if (!canvas.width || !canvas.height) continue;
468
+ try {
469
+ const sampleSize = 16;
470
+ const sampler = document.createElement("canvas");
471
+ sampler.width = sampleSize;
472
+ sampler.height = sampleSize;
473
+ const context = sampler.getContext("2d", { willReadFrequently: true });
474
+ if (!context) continue;
475
+ context.drawImage(canvas, 0, 0, sampleSize, sampleSize);
476
+ const data = context.getImageData(0, 0, sampleSize, sampleSize).data;
477
+ for (let index = 0; index < data.length; index += 4) {
478
+ const alpha = data[index + 3];
479
+ const luminance = data[index] + data[index + 1] + data[index + 2];
480
+ if (alpha > 0 && luminance > 0) sampleCount += 1;
481
+ }
482
+ if (sampleCount > 0) return { nonblank: true, sampleCount };
483
+ } catch {
484
+ continue;
485
+ }
486
+ }
487
+ return { nonblank: false, sampleCount };
488
+ }
489
+
490
+ function firstSearchHitId() {
491
+ return searchResultIds[0] || lastVisibility.searchResultIds?.[0] || null;
492
+ }
493
+
494
+ function overlayCenter(selector) {
495
+ const element = document.querySelector(selector);
496
+ if (!element) return null;
497
+ const rect = element.getBoundingClientRect();
498
+ return {
499
+ x: rect.left + rect.width / 2,
500
+ y: rect.top + rect.height / 2,
501
+ width: rect.width,
502
+ height: rect.height,
503
+ id: element.dataset.id || element.dataset.nodeId || element.dataset.communityId || element.dataset.aggregationId || null,
504
+ communityId: element.dataset.communityId || null,
505
+ aggregationId: element.dataset.aggregationId || null
506
+ };
507
+ }
508
+
509
+ function readableCommunityId() {
510
+ const counts = new Map();
511
+ for (const node of graphData.nodes) {
512
+ if (!node.community) continue;
513
+ counts.set(node.community, (counts.get(node.community) || 0) + 1);
514
+ }
515
+ for (const [id, count] of counts) {
516
+ if (count <= 500) return id;
517
+ }
518
+ return firstCommunityId;
519
+ }
520
+
521
+ function largestCommunitySize() {
522
+ const counts = new Map();
523
+ for (const node of graphData.nodes) {
524
+ if (!node.community) continue;
525
+ counts.set(node.community, (counts.get(node.community) || 0) + 1);
526
+ }
527
+ return Math.max(0, ...counts.values());
528
+ }
529
+
530
+ function markersFor(ids, selectedIds = []) {
531
+ return buildCommunityAggregationMarkers(graphData, {
532
+ pins: currentPins,
533
+ searchResultIds: ids,
534
+ selectedNodeIds: selectedIds,
535
+ minCommunitySize: 80
536
+ });
537
+ }
538
+
539
+ function searchHighlight(query) {
540
+ const input = document.querySelector(".graph-search-input");
541
+ if (!input) throw new Error("Sigma search input not found");
542
+ input.focus();
543
+ input.value = String(query || "");
544
+ input.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: String(query || "") }));
545
+ return {
546
+ hits: searchResultIds.length,
547
+ selectedCount: 0,
548
+ inputValue: input.value,
549
+ visibilityHits: lastVisibility.searchResultIds?.length || 0,
550
+ production: productionProbe({ canvasSignal: false })
551
+ };
552
+ }
553
+
554
+ function nodeHitTarget() {
555
+ const id = firstSearchHitId() || firstNodeId;
556
+ if (!id) return null;
557
+ return overlayCenter('.sigma-global-node-hit-target[data-node-id="' + CSS.escape(id) + '"]') ||
558
+ overlayCenter(".sigma-global-node-hit-target[data-search-hit='true']") ||
559
+ overlayCenter(".sigma-global-node-hit-target");
560
+ }
561
+
562
+ function containerHitTarget(id) {
563
+ if (!id) return null;
564
+ return overlayCenter('.sigma-global-community-region[data-community-id="' + CSS.escape(id) + '"]') ||
565
+ overlayCenter(".sigma-global-community-region");
566
+ }
567
+
568
+ function summaryPayload() {
569
+ if (selectedContainerId) return engine.summarizeCommunity(selectedContainerId, { searchResultIds });
570
+ if (selectedNodeId) return engine.summarizeNode(selectedNodeId, { searchResultIds });
571
+ return engine.summarizeGlobal({ searchResultIds });
572
+ }
573
+
574
+ function openDrawer() {
575
+ const payload = summaryPayload();
576
+ drawer.innerHTML = "";
577
+ const card = document.createElement("article");
578
+ card.className = "summary-card";
579
+ const kicker = document.createElement("div");
580
+ kicker.className = "summary-kicker";
581
+ kicker.textContent = payload.kind || "summary";
582
+ const title = document.createElement("h2");
583
+ title.className = "summary-title";
584
+ title.textContent = payload.label || payload.title || payload.nodeId || payload.communityId || "Graph";
585
+ const facts = document.createElement("div");
586
+ facts.className = "summary-facts";
587
+ const factEntries = [
588
+ ["nodes", payload.nodeCount ?? payload.totalNodes ?? payload.connectionCount ?? graphData.nodes.length],
589
+ ["edges", payload.edgeCount ?? payload.totalEdges ?? graphData.edges.length],
590
+ ["search", payload.searchResultIds?.length ?? Number(Boolean(payload.searchHit))],
591
+ ["commands", payload.commands?.length ?? 0]
592
+ ];
593
+ for (const [label, value] of factEntries) {
594
+ const row = document.createElement("div");
595
+ row.className = "summary-fact";
596
+ const lab = document.createElement("span");
597
+ lab.className = "summary-fact-label";
598
+ lab.textContent = label;
599
+ const val = document.createElement("span");
600
+ val.className = "summary-fact-value";
601
+ val.textContent = String(value ?? 0);
602
+ row.append(lab, val);
603
+ facts.append(row);
604
+ }
605
+ card.append(kicker, title, facts);
606
+ const items = payload.strongestRelations || payload.topCommunities || payload.bridgeRelations || payload.commands || [];
607
+ if (items.length) {
608
+ const list = document.createElement("ul");
609
+ list.className = "summary-list";
610
+ for (const item of items.slice(0, 8)) {
611
+ const li = document.createElement("li");
612
+ li.className = "summary-item";
613
+ li.textContent = item.label || item.kind || item.toNodeId || item.communityId || "item";
614
+ list.append(li);
615
+ }
616
+ card.append(list);
617
+ }
618
+ drawer.append(card);
619
+ drawer.dataset.open = "true";
620
+ return {
621
+ open: true,
622
+ kind: payload.kind,
623
+ facts: facts.children.length,
624
+ items: drawer.querySelectorAll(".summary-item").length,
625
+ production: productionProbe({ canvasSignal: false })
626
+ };
627
+ }
628
+
629
+ async function enterCommunity(id) {
630
+ if (!id) return { selectedContainerId: null, route: routeId() };
631
+ if (largestCommunitySize() > 500) {
632
+ if (selectedContainerId !== id) {
633
+ const selection = engine.select({ kind: "community", id });
634
+ lastSelection = selection;
635
+ lastSelectionKind = "community";
636
+ selectedContainerId = id;
637
+ selectedNodeId = null;
638
+ engine.setAggregationMarkers(markersFor(searchResultIds, selection?.nodeIds || []));
639
+ }
640
+ await waitForSelectionState("community", id, 10000);
641
+ return { selectedContainerId, route: routeId(), aggregationPath: true };
642
+ }
643
+ selectedContainerId = id;
644
+ lastSelectionKind = "community";
645
+ lastSelection = engine.focusCommunity(id);
646
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
647
+ return { selectedContainerId: id, route: routeId(), domCommunity: Boolean(domSvgGraphRoot()) };
648
+ }
649
+
650
+ async function returnGlobal(waitForReady = true) {
651
+ if (largestCommunitySize() > 500 && hasProductionSigma()) {
652
+ engine.resetView();
653
+ selectedContainerId = null;
654
+ selectedNodeId = null;
655
+ lastSelection = null;
656
+ lastSelectionKind = null;
657
+ if (!waitForReady) return { selectedContainerId, route: routeId(), production: productionProbe({ canvasSignal: false }) };
658
+ return { selectedContainerId, route: routeId(), production: productionProbe({ canvasSignal: false }) };
659
+ }
660
+ engine.resetView();
661
+ selectedContainerId = null;
662
+ selectedNodeId = null;
663
+ lastSelection = null;
664
+ lastSelectionKind = null;
665
+ if (!waitForReady) return { selectedContainerId, route: routeId(), production: productionProbe({ canvasSignal: false }) };
666
+ const production = await waitForProductionSigma(10000);
667
+ return { selectedContainerId, route: routeId(), production };
668
+ }
669
+
670
+ await Promise.resolve();
671
+ await new Promise((resolve) => window.setTimeout(resolve, 20));
672
+ await waitForProductionSigma(10000);
673
+ stage.dataset.loadingState = "sigma-global-ready";
674
+ window.__sigmaProductionRenderStartedAt = productionStart;
675
+ window.__sigmaProductionRenderFinishedAt = performance.now();
676
+ window.__sigmaProductionLoadingStateSeenAtMs = loadingStateSeenAtMs;
677
+ window.__sigmaProduction = {
678
+ ready: true,
679
+ engine,
680
+ firstNodeId,
681
+ firstCommunityId: readableCommunityId(),
682
+ searchHighlight,
683
+ nodeHitTarget,
684
+ containerHitTarget,
685
+ openDrawer,
686
+ enterCommunity,
687
+ returnGlobal,
688
+ productionProbe,
689
+ counts(options = {}) {
690
+ const probe = productionProbe({ canvasSignal: options.canvasSignal === true });
691
+ return {
692
+ nodes: graphData.nodes.length,
693
+ edges: graphData.edges.length,
694
+ route: routeId(),
695
+ selectedNodeId,
696
+ selectedContainerId,
697
+ lastSelectionKind,
698
+ searchResultCount: searchResultIds.length,
699
+ lastSelectionId: lastSelection?.id || null,
700
+ lastSelectionNodeIds: lastSelection?.nodeIds || [],
701
+ lastSelectionCommunityIds: lastSelection?.communityIds || [],
702
+ visibilitySearchResultCount: lastVisibility.searchResultIds?.length ?? 0,
703
+ domNodeCount: document.querySelectorAll("*").length,
704
+ visibleCardCount: drawer.querySelectorAll(".summary-card").length,
705
+ productionPath: probe.productionPath,
706
+ topLevelProductionPath: probe.productionPath && probe.route === "sigma-global" && probe.sigmaRendererCount === 1 && probe.fallbackCount === 0,
707
+ canvasCount: probe.canvasCount,
708
+ canvasNonBlank: probe.canvasNonBlank,
709
+ canvasPixelSampleCount: probe.canvasPixelSampleCount,
710
+ visibleSignal: probe.visibleSignal,
711
+ hitTargetCount: probe.hitTargetCount,
712
+ sigmaRendererCount: probe.sigmaRendererCount,
713
+ loadingState: stage.dataset.loadingState || "",
714
+ loadingStateSeenAtMs
715
+ };
716
+ }
717
+ };
718
+ } catch (error) {
719
+ window.__sigmaProductionError = error instanceof Error ? error.message : String(error);
720
+ throw error;
721
+ }
722
+ })();
723
+ </script>
724
+ </body>
725
+ </html>
726
+ `);
727
+ return file;
728
+ }
729
+
730
+ async function measureShape(browser: BrowserLike, metadata: LargeGraphFixtureMetadata, url: string): Promise<PerformanceRecord[]> {
731
+ const page = await browser.newPage({ viewport: { width: 1440, height: 960 } });
732
+ const diagnostics: string[] = [];
733
+ page.on?.("console", (message: { type(): string; text(): string }) => {
734
+ diagnostics.push(`console.${message.type()}: ${message.text()}`);
735
+ });
736
+ page.on?.("pageerror", (error: Error) => {
737
+ diagnostics.push(`pageerror: ${error.message}`);
738
+ });
739
+ page.on?.("requestfailed", (request: { url(): string; failure(): { errorText?: string } | null }) => {
740
+ diagnostics.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ""}`.trim());
741
+ });
742
+ page.on?.("response", (response: { status(): number; url(): string }) => {
743
+ if (response.status() >= 400) diagnostics.push(`response.${response.status()}: ${response.url()}`);
744
+ });
745
+ page.setDefaultTimeout(timeoutFor(metadata));
746
+ page.setDefaultNavigationTimeout(45_000);
747
+ await page.addInitScript(NAME_HELPER_INIT_SCRIPT);
748
+ const records: PerformanceRecord[] = [];
749
+ try {
750
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: navigationTimeoutFor(metadata) });
751
+ try {
752
+ await page.waitForFunction(() => Boolean((window as any).__sigmaProduction?.ready || (window as any).__sigmaProductionError));
753
+ } catch (error) {
754
+ throw new Error(`${errorDetail(error)}; diagnostics=${diagnostics.slice(-12).join(" | ") || "none"}`);
755
+ }
756
+ const pageError = await page.evaluate(() => (window as any).__sigmaProductionError || null);
757
+ if (pageError) throw new Error(`${String(pageError)}; diagnostics=${diagnostics.slice(-12).join(" | ") || "none"}`);
758
+ const timing = await page.evaluate(() => ({
759
+ started: (window as any).__sigmaProductionRenderStartedAt,
760
+ finished: (window as any).__sigmaProductionRenderFinishedAt,
761
+ loadingStateSeenAtMs: (window as any).__sigmaProductionLoadingStateSeenAtMs
762
+ }));
763
+ const duration = typeof timing.started === "number" && typeof timing.finished === "number"
764
+ ? timing.finished - timing.started
765
+ : 0;
766
+ const initialRecord = await recordFromPage(page, metadata, {
767
+ action: "initial_render",
768
+ duration_ms: duration,
769
+ pass: true,
770
+ artifact_path: resultPath
771
+ });
772
+ initialRecord.loading_state_seen_at_ms = typeof timing.loadingStateSeenAtMs === "number"
773
+ ? round(timing.loadingStateSeenAtMs)
774
+ : null;
775
+ if (metadata.nodes >= 10000 && (initialRecord.loading_state_seen_at_ms == null || initialRecord.loading_state_seen_at_ms > 250)) {
776
+ initialRecord.pass = false;
777
+ initialRecord.failure_class = "loading_state_late";
778
+ initialRecord.failure_detail = `loading_state_seen_at_ms=${initialRecord.loading_state_seen_at_ms ?? "null"}; ceiling=250`;
779
+ }
780
+ records.push(initialRecord);
781
+ for (const action of [
782
+ () => measureWheelZoom(page, metadata),
783
+ () => measureDrag(page, metadata),
784
+ () => measureSearch(page, metadata),
785
+ () => measurePointSelect(page, metadata),
786
+ () => measureContainerSelect(page, metadata),
787
+ () => measureDrawerOpen(page, metadata),
788
+ () => measureEnterCommunity(page, metadata),
789
+ () => measureReturnGlobal(page, metadata)
790
+ ]) {
791
+ records.push(await safeMeasure(page, metadata, action));
792
+ }
793
+ records.push(await safeMeasure(page, metadata, () => measureRepeatedCycles(page, metadata)));
794
+ } finally {
795
+ await page.close().catch(() => undefined);
796
+ }
797
+ return records;
798
+ }
799
+
800
+ async function safeMeasure(page: PageLike, metadata: LargeGraphFixtureMetadata, action: () => Promise<PerformanceRecord>): Promise<PerformanceRecord> {
801
+ try {
802
+ return await action();
803
+ } catch (error) {
804
+ return failedRecord(metadata, {
805
+ action: inferActionName(error),
806
+ failure_class: classifyError(error),
807
+ failure_detail: errorDetail(error),
808
+ artifact_path: resultPath
809
+ });
810
+ }
811
+ }
812
+
813
+ async function measureWheelZoom(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
814
+ await driveWheel(page, 500);
815
+ const runs: { fps: number; p95: number; durationMs: number }[] = [];
816
+ for (let i = 0; i < 3; i += 1) {
817
+ const samplePromise = sampleAnimationFrames(page, 900);
818
+ await driveWheel(page, 900);
819
+ runs.push(await samplePromise);
820
+ }
821
+ return frameSampleRecord(page, metadata, { action: "wheel_zoom", runs });
822
+ }
823
+
824
+ async function measureDrag(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
825
+ await driveDrag(page, 500);
826
+ const runs: { fps: number; p95: number; durationMs: number }[] = [];
827
+ for (let i = 0; i < 3; i += 1) {
828
+ const samplePromise = sampleAnimationFrames(page, 900);
829
+ await driveDrag(page, 900);
830
+ runs.push(await samplePromise);
831
+ }
832
+ return frameSampleRecord(page, metadata, { action: "drag", runs });
833
+ }
834
+
835
+ async function measureSearch(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
836
+ const started = performance.now();
837
+ const result = await page.evaluate(() => (window as any).__sigmaProduction.searchHighlight("needle"));
838
+ await page.waitForFunction(
839
+ (expected: number) => ((window as any).__sigmaProduction?.counts?.().visibilitySearchResultCount ?? 0) === expected,
840
+ metadata.search_hits,
841
+ { timeout: 4000 }
842
+ );
843
+ await waitForAnimationFrames(page, 3);
844
+ const counts = await page.evaluate(() => (window as any).__sigmaProduction.counts());
845
+ const hits = (counts as { visibilitySearchResultCount: number }).visibilitySearchResultCount;
846
+ return recordFromPage(page, metadata, {
847
+ action: "search_highlight",
848
+ duration_ms: performance.now() - started,
849
+ pass: hits === metadata.search_hits,
850
+ failure_class: hits === metadata.search_hits ? null : "search_hit_mismatch",
851
+ failure_detail: hits === metadata.search_hits ? null : `expected=${metadata.search_hits}; actual=${hits}`,
852
+ artifact_path: resultPath
853
+ });
854
+ }
855
+
856
+ async function measurePointSelect(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
857
+ await ensureSearchReady(page, metadata);
858
+ const target = await page.evaluate(() => (window as any).__sigmaProduction.nodeHitTarget());
859
+ const started = performance.now();
860
+ if (!target) throw new Error("measurePointSelect: no Sigma node hit target");
861
+ await clickPoint(page, target as PointerTarget);
862
+ await page.waitForFunction(
863
+ () => {
864
+ const counts = (window as any).__sigmaProduction?.counts?.();
865
+ return counts?.lastSelectionKind === "node" && (counts.lastSelectionNodeIds ?? []).length > 0;
866
+ },
867
+ undefined,
868
+ { timeout: 4000 }
869
+ );
870
+ await waitForAnimationFrames(page, 3);
871
+ const counts = await page.evaluate(() => (window as any).__sigmaProduction.counts());
872
+ const actual = (counts as { selectedNodeId: string | null; lastSelectionNodeIds?: string[] }).selectedNodeId;
873
+ const nodeIds = (counts as { lastSelectionKind?: string | null; lastSelectionNodeIds?: string[] }).lastSelectionNodeIds ?? [];
874
+ const selectedNodeId = (counts as { lastSelectionKind?: string | null; lastSelectionNodeIds?: string[] }).lastSelectionKind === "node"
875
+ ? nodeIds[0] ?? null
876
+ : null;
877
+ return recordFromPage(page, metadata, {
878
+ action: "point_select",
879
+ duration_ms: performance.now() - started,
880
+ pass: Boolean(selectedNodeId),
881
+ failure_class: Boolean(selectedNodeId) ? null : "selected_node_mismatch",
882
+ failure_detail: Boolean(selectedNodeId) ? null : `actual=${actual ?? "null"}; nodeIds=${nodeIds.join(",")}`,
883
+ artifact_path: resultPath
884
+ });
885
+ }
886
+
887
+ async function measureContainerSelect(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
888
+ await ensureSearchReady(page, metadata);
889
+ const target = await page.evaluate(() => {
890
+ const trial = (window as any).__sigmaProduction;
891
+ return trial.containerHitTarget(trial.firstCommunityId);
892
+ });
893
+ const started = performance.now();
894
+ if (!target) throw new Error("measureContainerSelect: no Sigma container hit target");
895
+ await clickPoint(page, target as PointerTarget);
896
+ await page.waitForFunction(
897
+ () => {
898
+ const counts = (window as any).__sigmaProduction?.counts?.();
899
+ return counts?.lastSelectionKind === "community" && (counts.lastSelectionCommunityIds ?? []).length > 0;
900
+ },
901
+ undefined,
902
+ { timeout: 4000 }
903
+ );
904
+ await waitForAnimationFrames(page, 3);
905
+ const counts = await page.evaluate(() => (window as any).__sigmaProduction.counts());
906
+ const actual = (counts as { selectedContainerId: string | null; lastSelectionCommunityIds?: string[] }).selectedContainerId;
907
+ const communityIds = (counts as { lastSelectionKind?: string | null; lastSelectionCommunityIds?: string[] }).lastSelectionCommunityIds ?? [];
908
+ const selectedCommunityId = clickedCommunityFromCounts(counts as { lastSelectionKind?: string | null; lastSelectionCommunityIds?: string[] });
909
+ return recordFromPage(page, metadata, {
910
+ action: "container_select",
911
+ duration_ms: performance.now() - started,
912
+ pass: Boolean(selectedCommunityId),
913
+ failure_class: Boolean(selectedCommunityId) ? null : "selected_container_mismatch",
914
+ failure_detail: Boolean(selectedCommunityId) ? null : `actual=${actual ?? "null"}; communityIds=${communityIds.join(",")}`,
915
+ artifact_path: resultPath
916
+ });
917
+ }
918
+
919
+ async function measureDrawerOpen(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
920
+ const started = performance.now();
921
+ const result = await page.evaluate(() => (window as any).__sigmaProduction.openDrawer());
922
+ await waitForAnimationFrames(page, 1);
923
+ const card = await page.evaluate(() => {
924
+ const drawer = document.getElementById("drawer");
925
+ return {
926
+ open: drawer?.dataset.open === "true",
927
+ cards: drawer?.querySelectorAll(".summary-card").length ?? 0,
928
+ facts: drawer?.querySelectorAll(".summary-fact").length ?? 0,
929
+ items: drawer?.querySelectorAll(".summary-item").length ?? 0
930
+ };
931
+ });
932
+ const opened = Boolean((result as { open: boolean }).open) && Boolean(card.open);
933
+ const rendered = opened && (card.cards ?? 0) > 0 && (card.facts ?? 0) > 0;
934
+ return recordFromPage(page, metadata, {
935
+ action: "drawer_open",
936
+ duration_ms: performance.now() - started,
937
+ pass: rendered,
938
+ failure_class: rendered ? null : "drawer_not_opened",
939
+ failure_detail: rendered ? null : `cards=${card.cards}; facts=${card.facts}; items=${card.items}`,
940
+ artifact_path: resultPath
941
+ });
942
+ }
943
+
944
+ async function measureEnterCommunity(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
945
+ const started = performance.now();
946
+ const result = await page.evaluate(() => {
947
+ const trial = (window as any).__sigmaProduction;
948
+ return trial.enterCommunity(trial.firstCommunityId);
949
+ });
950
+ await waitForAnimationFrames(page);
951
+ const route = (result as { route?: string }).route;
952
+ return recordFromPage(page, metadata, {
953
+ action: "enter_community",
954
+ duration_ms: performance.now() - started,
955
+ pass: route === "unknown" ? false : true,
956
+ failure_class: route === "unknown" ? "community_route_unknown" : null,
957
+ failure_detail: route === "unknown" ? "route=unknown" : null,
958
+ artifact_path: resultPath
959
+ });
960
+ }
961
+
962
+ async function measureReturnGlobal(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
963
+ const started = performance.now();
964
+ const result = await page.evaluate(() => (window as any).__sigmaProduction.returnGlobal(false));
965
+ const duration = performance.now() - started;
966
+ const probe = (result as { production?: { productionPath?: boolean }; selectedContainerId?: string | null }).production;
967
+ const selectedContainerId = (result as { selectedContainerId?: string | null }).selectedContainerId;
968
+ const readyProbe = await page.evaluate(() => (window as any).__sigmaProduction.productionProbe({ canvasSignal: false }));
969
+ return recordFromPage(page, metadata, {
970
+ action: "return_global",
971
+ duration_ms: duration,
972
+ pass: Boolean((readyProbe as { productionPath?: boolean }).productionPath) && selectedContainerId == null,
973
+ failure_class: Boolean((readyProbe as { productionPath?: boolean }).productionPath) && selectedContainerId == null ? null : "global_return_incomplete",
974
+ failure_detail: Boolean((readyProbe as { productionPath?: boolean }).productionPath) && selectedContainerId == null ? null : `productionPath=${probe?.productionPath}; readyProductionPath=${(readyProbe as { productionPath?: boolean }).productionPath}; selectedContainerId=${selectedContainerId ?? "null"}`,
975
+ artifact_path: resultPath
976
+ });
977
+ }
978
+
979
+ function allowsNonSigmaRouteForAction(action: string): boolean {
980
+ return action === "enter_community";
981
+ }
982
+
983
+ function clickedCommunityFromCounts(counts: { lastSelectionKind?: string | null; lastSelectionCommunityIds?: string[] }): string | null {
984
+ return counts.lastSelectionKind === "community" ? counts.lastSelectionCommunityIds?.[0] ?? null : null;
985
+ }
986
+
987
+ async function measureRepeatedCycles(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<PerformanceRecord> {
988
+ const cycleCount = metadata.nodes >= 10000 ? 6 : 3;
989
+ await settleMemory(page);
990
+ const before = await memoryMb(page);
991
+ const started = performance.now();
992
+ for (let index = 0; index < cycleCount; index += 1) {
993
+ await ensureSearchReady(page, metadata);
994
+ const nodeTarget = await page.evaluate(() => (window as any).__sigmaProduction.nodeHitTarget());
995
+ if (!nodeTarget) throw new Error("measureRepeatedCycles: no Sigma node hit target");
996
+ await clickPoint(page, nodeTarget as PointerTarget);
997
+ await waitForAnimationFrames(page, 2);
998
+ const containerTarget = await page.evaluate(() => {
999
+ const trial = (window as any).__sigmaProduction;
1000
+ return trial.containerHitTarget(trial.firstCommunityId);
1001
+ });
1002
+ if (!containerTarget) throw new Error("measureRepeatedCycles: no Sigma container hit target");
1003
+ await clickPoint(page, containerTarget as PointerTarget);
1004
+ await page.evaluate(() => (window as any).__sigmaProduction.openDrawer());
1005
+ await page.evaluate(() => (window as any).__sigmaProduction.returnGlobal());
1006
+ await waitForAnimationFrames(page, 2);
1007
+ }
1008
+ await settleMemory(page);
1009
+ const after = await memoryMb(page);
1010
+ const memoryGrowth = before == null || after == null ? null : round(after - before);
1011
+ const failureClass = memoryGrowthFailureClass(memoryGrowth, metadata);
1012
+ const record = await recordFromPage(page, metadata, {
1013
+ action: "repeated_search_community_drawer_cycles",
1014
+ duration_ms: performance.now() - started,
1015
+ pass: failureClass == null,
1016
+ failure_class: failureClass,
1017
+ failure_detail: memoryGrowthFailureDetail(memoryGrowth, metadata),
1018
+ artifact_path: resultPath
1019
+ });
1020
+ record.memory_after_cycles_mb = after;
1021
+ record.memory_growth_mb = memoryGrowth;
1022
+ return record;
1023
+ }
1024
+
1025
+ async function ensureSearchReady(page: PageLike, metadata: LargeGraphFixtureMetadata): Promise<void> {
1026
+ const count = await page.evaluate(() => (window as any).__sigmaProduction?.counts?.().visibilitySearchResultCount ?? 0);
1027
+ if (count === metadata.search_hits) return;
1028
+ await page.evaluate(() => (window as any).__sigmaProduction.searchHighlight("needle"));
1029
+ await page.waitForFunction(
1030
+ (expected: number) => ((window as any).__sigmaProduction?.counts?.().visibilitySearchResultCount ?? 0) === expected,
1031
+ metadata.search_hits,
1032
+ { timeout: 4000 }
1033
+ );
1034
+ await waitForAnimationFrames(page, 3);
1035
+ }
1036
+
1037
+ async function clickPoint(page: PageLike, target: PointerTarget): Promise<void> {
1038
+ if (!Number.isFinite(target.x) || !Number.isFinite(target.y)) {
1039
+ throw new Error(`invalid pointer target: x=${target.x}; y=${target.y}`);
1040
+ }
1041
+ await page.mouse.click(target.x, target.y);
1042
+ }
1043
+
1044
+ async function recordFromPage(
1045
+ page: PageLike,
1046
+ metadata: LargeGraphFixtureMetadata,
1047
+ input: Partial<PerformanceRecord> & { action: string; artifact_path: string }
1048
+ ): Promise<PerformanceRecord> {
1049
+ const counts = await page.evaluate(() => {
1050
+ const trial = (window as any).__sigmaProduction;
1051
+ const trialCounts = trial?.counts?.({ canvasSignal: true }) ?? {};
1052
+ return {
1053
+ dom_node_count: trialCounts.domNodeCount ?? document.querySelectorAll("*").length,
1054
+ visible_node_count: trialCounts.nodes ?? null,
1055
+ visible_edge_count: trialCounts.edges ?? null,
1056
+ visible_label_count: 0,
1057
+ visible_card_count: trialCounts.visibleCardCount ?? 0,
1058
+ memory_peak_mb: typeof performance !== "undefined" && "memory" in performance
1059
+ ? Math.round((((performance as any).memory?.usedJSHeapSize || 0) / 1024 / 1024) * 10) / 10
1060
+ : null,
1061
+ long_task_count: performance.getEntriesByType ? performance.getEntriesByType("longtask").length : null,
1062
+ interaction_mode: trialCounts.productionPath ? "production-sigma-global" : "production-route-missing",
1063
+ interaction_updated_objects: trialCounts.nodes ?? null,
1064
+ interaction_hidden_objects: 0,
1065
+ interaction_preserved_nodes: trialCounts.nodes ?? null,
1066
+ interaction_max_updates: trialCounts.nodes ?? null,
1067
+ production_route: trialCounts.route ?? null,
1068
+ loading_state: trialCounts.loadingState || (trialCounts.productionPath ? "sigma-global-ready" : "sigma-global-not-ready"),
1069
+ loading_state_seen_at_ms: typeof trialCounts.loadingStateSeenAtMs === "number" ? Math.round(trialCounts.loadingStateSeenAtMs * 10) / 10 : null,
1070
+ production_path: Boolean(trialCounts.topLevelProductionPath),
1071
+ sigma_canvas_count: trialCounts.canvasCount ?? 0,
1072
+ sigma_canvas_nonblank: trialCounts.canvasNonBlank ?? false,
1073
+ sigma_canvas_pixel_sample_count: trialCounts.canvasPixelSampleCount ?? 0,
1074
+ sigma_visible_signal: trialCounts.visibleSignal ?? false,
1075
+ sigma_hit_target_count: trialCounts.hitTargetCount ?? 0
1076
+ };
1077
+ });
1078
+ const record: PerformanceRecord = {
1079
+ ...baseRecord(metadata, input.action, input.artifact_path),
1080
+ ...counts,
1081
+ duration_ms: round(input.duration_ms ?? 0),
1082
+ fps: input.fps == null ? null : round(input.fps),
1083
+ frame_p95_ms: input.frame_p95_ms == null ? null : round(input.frame_p95_ms),
1084
+ pass: input.pass ?? true,
1085
+ failure_class: input.failure_class ?? null,
1086
+ failure_detail: input.failure_detail ?? null
1087
+ };
1088
+ if (allowsNonSigmaRouteForAction(record.action) && record.production_route === "dom-svg-community") {
1089
+ record.production_path = true;
1090
+ }
1091
+ if (!record.production_path && !record.failure_class && !allowsNonSigmaRouteForAction(record.action)) {
1092
+ record.pass = false;
1093
+ record.failure_class = "production_path_missing";
1094
+ record.failure_detail = productionSignalFailureDetail(record);
1095
+ }
1096
+ if (record.production_path && !record.failure_class && !allowsNonSigmaRouteForAction(record.action)) {
1097
+ const signalFailure = productionSignalFailureClass(record);
1098
+ if (signalFailure) {
1099
+ record.pass = false;
1100
+ record.failure_class = signalFailure;
1101
+ record.failure_detail = productionSignalFailureDetail(record);
1102
+ }
1103
+ }
1104
+ return applyDurationGate(metadata, record);
1105
+ }
1106
+
1107
+ function productionSignalFailureClass(record: PerformanceRecord): string | null {
1108
+ if ((record.sigma_canvas_count ?? 0) < 1) return "sigma_canvas_missing";
1109
+ if (record.sigma_canvas_nonblank !== true && record.sigma_visible_signal !== true) return "sigma_canvas_blank";
1110
+ return null;
1111
+ }
1112
+
1113
+ function productionSignalFailureDetail(record: PerformanceRecord): string {
1114
+ return `route=${record.production_route ?? "unknown"}; sigma_canvas_count=${record.sigma_canvas_count ?? "null"}; sigma_canvas_nonblank=${String(record.sigma_canvas_nonblank)}; sigma_visible_signal=${String(record.sigma_visible_signal)}; sigma_hit_target_count=${record.sigma_hit_target_count ?? "null"}`;
1115
+ }
1116
+
1117
+ function applyDurationGate(metadata: LargeGraphFixtureMetadata, record: PerformanceRecord): PerformanceRecord {
1118
+ if (!DURATION_GATED_ACTIONS.has(record.action) || record.failure_class) return record;
1119
+ const failure = durationFailureClass({ duration_ms: record.duration_ms }, metadata, record.action);
1120
+ if (!failure) return record;
1121
+ const limit = durationLimitMs(metadata, record.action);
1122
+ return {
1123
+ ...record,
1124
+ pass: false,
1125
+ failure_class: failure,
1126
+ failure_detail: `duration_ms=${record.duration_ms}; ceiling=${limit}`
1127
+ };
1128
+ }
1129
+
1130
+ function failedRecord(
1131
+ metadata: LargeGraphFixtureMetadata,
1132
+ input: { action: string; failure_class: string; failure_detail?: string; artifact_path: string }
1133
+ ): PerformanceRecord {
1134
+ return {
1135
+ ...baseRecord(metadata, input.action, input.artifact_path),
1136
+ production_path: false,
1137
+ production_route: "unknown",
1138
+ loading_state: "not-run",
1139
+ failure_class: input.failure_class,
1140
+ failure_detail: input.failure_detail ?? null
1141
+ };
1142
+ }
1143
+
1144
+ function baseRecord(metadata: LargeGraphFixtureMetadata, action: string, artifactPath: string): PerformanceRecord {
1145
+ return {
1146
+ schema_version: TRIAL_SCHEMA_VERSION,
1147
+ renderer: rendererName,
1148
+ production_path: productionPath,
1149
+ graph_shape: metadata.id,
1150
+ nodes: metadata.nodes,
1151
+ edges: metadata.edges,
1152
+ communities: metadata.communities,
1153
+ largest_community: metadata.largest_community,
1154
+ largest_connected_density: metadata.largest_connected_density,
1155
+ search_hits: metadata.search_hits,
1156
+ pin_count: metadata.pin_count,
1157
+ oversized_community: metadata.oversized_community,
1158
+ action,
1159
+ duration_ms: null,
1160
+ fps: null,
1161
+ frame_p95_ms: null,
1162
+ long_task_count: null,
1163
+ dom_node_count: null,
1164
+ visible_node_count: null,
1165
+ visible_edge_count: null,
1166
+ visible_label_count: null,
1167
+ visible_card_count: null,
1168
+ interaction_mode: null,
1169
+ interaction_updated_objects: null,
1170
+ interaction_hidden_objects: null,
1171
+ interaction_preserved_nodes: null,
1172
+ interaction_max_updates: null,
1173
+ memory_peak_mb: null,
1174
+ memory_after_cycles_mb: null,
1175
+ memory_growth_mb: null,
1176
+ thresholds: actionThresholds(metadata, action),
1177
+ browser: runContext.browser,
1178
+ build_commit: runContext.build_commit,
1179
+ run_started_at: runContext.run_started_at,
1180
+ run_finished_at: runContext.run_finished_at,
1181
+ production_route: null,
1182
+ loading_state: null,
1183
+ loading_state_seen_at_ms: null,
1184
+ sigma_canvas_count: null,
1185
+ sigma_canvas_nonblank: null,
1186
+ sigma_canvas_pixel_sample_count: null,
1187
+ sigma_visible_signal: null,
1188
+ sigma_hit_target_count: null,
1189
+ warmup_runs: undefined,
1190
+ median_fps: undefined,
1191
+ worst_run_fps: undefined,
1192
+ worst_run_frame_p95_ms: undefined,
1193
+ pass: false,
1194
+ failure_class: null,
1195
+ failure_detail: null,
1196
+ artifact_path: artifactPath,
1197
+ measured_at: new Date().toISOString()
1198
+ };
1199
+ }
1200
+
1201
+ async function settleMemory(page: PageLike): Promise<void> {
1202
+ try {
1203
+ await page.evaluate(() => {
1204
+ const maybeGc = (globalThis as unknown as { gc?: () => void }).gc;
1205
+ if (typeof maybeGc === "function") maybeGc();
1206
+ });
1207
+ } catch {
1208
+ // Best effort only; Chromium exposes performance.memory even without GC.
1209
+ }
1210
+ await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve(undefined)))));
1211
+ }
1212
+
1213
+ async function memoryMb(page: PageLike): Promise<number | null> {
1214
+ return page.evaluate(() => {
1215
+ if (typeof performance === "undefined" || !("memory" in performance)) return null;
1216
+ const used = (performance as any).memory?.usedJSHeapSize;
1217
+ return typeof used === "number" ? Math.round((used / 1024 / 1024) * 10) / 10 : null;
1218
+ });
1219
+ }
1220
+
1221
+ async function driveWheel(page: PageLike, durationMs: number): Promise<void> {
1222
+ const end = performance.now() + durationMs;
1223
+ while (performance.now() < end) {
1224
+ await page.mouse.move(720, 480);
1225
+ await page.mouse.wheel(0, -240);
1226
+ await page.waitForTimeout(60);
1227
+ }
1228
+ }
1229
+
1230
+ async function driveDrag(page: PageLike, durationMs: number): Promise<void> {
1231
+ await page.mouse.move(640, 400);
1232
+ await page.mouse.down();
1233
+ const end = performance.now() + durationMs;
1234
+ let dx = 640;
1235
+ let dy = 400;
1236
+ while (performance.now() < end) {
1237
+ dx += 16;
1238
+ dy += 12;
1239
+ if (dx > 1260) dx = 580;
1240
+ if (dy > 820) dy = 380;
1241
+ await page.mouse.move(dx, dy);
1242
+ await page.waitForTimeout(55);
1243
+ }
1244
+ await page.mouse.up();
1245
+ }
1246
+
1247
+ async function frameSampleRecord(
1248
+ page: PageLike,
1249
+ metadata: LargeGraphFixtureMetadata,
1250
+ input: { action: "wheel_zoom" | "drag"; runs: { fps: number; p95: number; durationMs: number }[] }
1251
+ ): Promise<PerformanceRecord> {
1252
+ const byFps = [...input.runs].sort((a, b) => a.fps - b.fps);
1253
+ const byP95 = [...input.runs].sort((a, b) => a.p95 - b.p95);
1254
+ const median = (arr: { fps: number; p95: number }[], key: "fps" | "p95") => {
1255
+ if (!arr.length) return 0;
1256
+ const mid = Math.floor(arr.length / 2);
1257
+ return arr.length % 2 ? arr[mid][key] : (arr[mid - 1][key] + arr[mid][key]) / 2;
1258
+ };
1259
+ const fps = median(byFps, "fps");
1260
+ const p95 = median(byP95, "p95");
1261
+ const worst = byFps[0];
1262
+ const probe = await page.evaluate(() => (window as any).__sigmaProduction.productionProbe({ canvasSignal: false }));
1263
+ const frameFailure = frameSampleFailureClass({ fps, frame_p95_ms: p95 });
1264
+ const productionFailure = (probe as { productionPath?: boolean }).productionPath ? null : "production_path_missing";
1265
+ const failureClass = productionFailure || frameFailure;
1266
+ const record = await recordFromPage(page, metadata, {
1267
+ action: input.action,
1268
+ duration_ms: input.runs.reduce((sum, run) => sum + run.durationMs, 0),
1269
+ fps,
1270
+ frame_p95_ms: p95,
1271
+ pass: failureClass == null,
1272
+ failure_class: failureClass,
1273
+ failure_detail: failureClass ? `median_fps=${fps}; median_frame_p95_ms=${p95}; floor=${FPS_FLOOR}; ceiling=${FRAME_P95_CEILING_MS}; production_path=${(probe as { productionPath?: boolean }).productionPath}` : null,
1274
+ artifact_path: resultPath
1275
+ });
1276
+ record.warmup_runs = input.runs.length;
1277
+ record.median_fps = fps;
1278
+ record.worst_run_fps = worst ? worst.fps : null;
1279
+ record.worst_run_frame_p95_ms = worst ? worst.p95 : null;
1280
+ return record;
1281
+ }
1282
+
1283
+ async function sampleAnimationFrames(page: PageLike, durationMs: number): Promise<{ durationMs: number; fps: number; p95: number }> {
1284
+ return page.evaluate(`(() => new Promise((resolve) => {
1285
+ const durationMs = ${JSON.stringify(durationMs)};
1286
+ const started = performance.now();
1287
+ const deltas = [];
1288
+ let last = started;
1289
+ function tick(now) {
1290
+ deltas.push(now - last);
1291
+ last = now;
1292
+ const elapsed = now - started;
1293
+ if (elapsed >= durationMs) {
1294
+ const sorted = [...deltas].sort((a, b) => a - b);
1295
+ const p95 = sorted[Math.max(0, Math.floor(sorted.length * 0.95) - 1)] || 0;
1296
+ resolve({ durationMs: elapsed, fps: deltas.length / (elapsed / 1000), p95 });
1297
+ return;
1298
+ }
1299
+ requestAnimationFrame(tick);
1300
+ }
1301
+ requestAnimationFrame(tick);
1302
+ }))()`) as Promise<{ durationMs: number; fps: number; p95: number }>;
1303
+ }
1304
+
1305
+ function classifyError(error: unknown): string {
1306
+ const message = error instanceof Error ? error.message : String(error);
1307
+ if (/Timeout|timed out/i.test(message)) return "timeout";
1308
+ if (/WebGL|webgl/i.test(message)) return "webgl_unavailable";
1309
+ if (/Target page|browser has been closed/i.test(message)) return "browser_closed";
1310
+ if (/JavaScript heap|out of memory/i.test(message)) return "memory";
1311
+ return "exception";
1312
+ }
1313
+
1314
+ function errorDetail(error: unknown): string {
1315
+ const message = error instanceof Error ? error.message : String(error);
1316
+ return message.replace(/\s+/g, " ").slice(0, 500);
1317
+ }
1318
+
1319
+ function inferActionName(error: unknown): string {
1320
+ const stack = error instanceof Error ? error.stack || error.message : String(error);
1321
+ const match = stack.match(/measure[A-Z][A-Za-z0-9_]*/);
1322
+ if (!match) return "unknown_action";
1323
+ return match[0].replace(/^measure/, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
1324
+ }
1325
+
1326
+ function timeoutFor(metadata: LargeGraphFixtureMetadata): number {
1327
+ if (metadata.nodes >= 10000) return 25_000;
1328
+ if (metadata.nodes >= 5000) return 18_000;
1329
+ return 12_000;
1330
+ }
1331
+
1332
+ function navigationTimeoutFor(metadata: LargeGraphFixtureMetadata): number {
1333
+ if (metadata.nodes >= 10000) return 45_000;
1334
+ if (metadata.nodes >= 5000) return 30_000;
1335
+ return 20_000;
1336
+ }
1337
+
1338
+ function round(value: number): number {
1339
+ return Math.round(value * 10) / 10;
1340
+ }
1341
+
1342
+ function escapeHtml(value: string): string {
1343
+ return value.replace(/[&<>"']/g, (char) => ({
1344
+ "&": "&amp;",
1345
+ "<": "&lt;",
1346
+ ">": "&gt;",
1347
+ '"': "&quot;",
1348
+ "'": "&#39;"
1349
+ }[char] ?? char));
1350
+ }
1351
+
1352
+ interface BrowserLike {
1353
+ newPage(options: { viewport: { width: number; height: number } }): Promise<PageLike>;
1354
+ close(): Promise<void>;
1355
+ }
1356
+
1357
+ interface PointerTarget {
1358
+ x: number;
1359
+ y: number;
1360
+ width: number;
1361
+ height: number;
1362
+ id: string | null;
1363
+ }
1364
+
1365
+ interface PageLike {
1366
+ on?: (event: "console" | "pageerror" | "requestfailed", listener: (...args: any[]) => void) => void;
1367
+ addInitScript(script: string): Promise<void>;
1368
+ setDefaultTimeout(timeout: number): void;
1369
+ setDefaultNavigationTimeout(timeout: number): void;
1370
+ goto(url: string, options?: unknown): Promise<unknown>;
1371
+ waitForFunction(fn: Function | string, arg?: unknown, options?: unknown): Promise<unknown>;
1372
+ waitForTimeout(timeout: number): Promise<void>;
1373
+ evaluate<T>(fn: Function | string, arg?: unknown): Promise<T>;
1374
+ mouse: {
1375
+ move(x: number, y: number, options?: { steps?: number }): Promise<void>;
1376
+ click(x: number, y: number): Promise<void>;
1377
+ down(): Promise<void>;
1378
+ up(): Promise<void>;
1379
+ wheel(deltaX: number, deltaY: number): Promise<void>;
1380
+ };
1381
+ close(): Promise<void>;
1382
+ }
1383
+
1384
+ interface PerformanceRecord {
1385
+ schema_version: string;
1386
+ renderer: string;
1387
+ production_path: boolean;
1388
+ graph_shape: string;
1389
+ nodes: number;
1390
+ edges: number;
1391
+ communities: number;
1392
+ largest_community: number;
1393
+ largest_connected_density: number;
1394
+ search_hits: number;
1395
+ pin_count: number;
1396
+ oversized_community: boolean;
1397
+ action: string;
1398
+ duration_ms: number | null;
1399
+ fps: number | null;
1400
+ frame_p95_ms: number | null;
1401
+ long_task_count: number | null;
1402
+ dom_node_count: number | null;
1403
+ visible_node_count: number | null;
1404
+ visible_edge_count: number | null;
1405
+ visible_label_count: number | null;
1406
+ visible_card_count: number | null;
1407
+ interaction_mode: string | null;
1408
+ interaction_updated_objects: number | null;
1409
+ interaction_hidden_objects: number | null;
1410
+ interaction_preserved_nodes: number | null;
1411
+ interaction_max_updates: number | null;
1412
+ memory_peak_mb: number | null;
1413
+ memory_after_cycles_mb: number | null;
1414
+ memory_growth_mb: number | null;
1415
+ thresholds: Record<string, number>;
1416
+ browser: string;
1417
+ build_commit: string;
1418
+ run_started_at: string;
1419
+ run_finished_at: string;
1420
+ production_route: string | null;
1421
+ loading_state: string | null;
1422
+ loading_state_seen_at_ms: number | null;
1423
+ sigma_canvas_count: number | null;
1424
+ sigma_canvas_nonblank: boolean | null;
1425
+ sigma_canvas_pixel_sample_count: number | null;
1426
+ sigma_visible_signal: boolean | null;
1427
+ sigma_hit_target_count: number | null;
1428
+ warmup_runs?: number;
1429
+ median_fps?: number | null;
1430
+ worst_run_fps?: number | null;
1431
+ worst_run_frame_p95_ms?: number | null;
1432
+ pass: boolean;
1433
+ failure_class: string | null;
1434
+ failure_detail?: string | null;
1435
+ artifact_path: string;
1436
+ measured_at: string;
1437
+ }