synabun 2.0.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (679) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +68 -4
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/launcher.mjs +9 -3
  10. package/lib/claude-hooks.js +172 -18
  11. package/lib/client-config-repair.js +17 -8
  12. package/lib/desktop-app.js +7 -1
  13. package/lib/external-tools.js +1 -1
  14. package/lib/first-launch-protection.js +57 -12
  15. package/lib/mcp-dist.js +176 -0
  16. package/lib/release-npm-ci.mjs +138 -0
  17. package/lib/release-publish.mjs +91 -0
  18. package/lib/release-scan.mjs +50 -0
  19. package/lib/server-crash-record.js +123 -0
  20. package/lib/server-exit.js +318 -0
  21. package/lib/server-supervisor.js +508 -0
  22. package/lib/start-launcher.js +43 -5
  23. package/lib/tools-cli.js +45 -0
  24. package/lib/update-snapshot.js +228 -0
  25. package/lib/virustotal.mjs +192 -0
  26. package/lib/windows-crash-event.js +269 -0
  27. package/mcp-server/dist/http.d.ts +20 -1
  28. package/mcp-server/dist/http.d.ts.map +1 -1
  29. package/mcp-server/dist/http.js +59 -12
  30. package/mcp-server/dist/http.js.map +1 -1
  31. package/mcp-server/dist/index.d.ts +33 -27
  32. package/mcp-server/dist/index.d.ts.map +1 -1
  33. package/mcp-server/dist/index.js +128 -143
  34. package/mcp-server/dist/index.js.map +1 -1
  35. package/mcp-server/dist/preload.js +1 -1
  36. package/mcp-server/dist/preload.js.map +1 -1
  37. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  38. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  39. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  40. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  42. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  44. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  45. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  46. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  47. package/mcp-server/dist/services/neural-interface.js +67 -11
  48. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  49. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  50. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  51. package/mcp-server/dist/services/server-instructions.js +20 -62
  52. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  53. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  54. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  55. package/mcp-server/dist/services/tool-catalog.js +339 -0
  56. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  60. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  61. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  62. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  63. package/mcp-server/dist/services/tool-list.js +334 -0
  64. package/mcp-server/dist/services/tool-list.js.map +1 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  66. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  67. package/mcp-server/dist/services/tool-runtime.js +400 -0
  68. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  70. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  71. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  72. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  73. package/mcp-server/dist/tools/agents.d.ts +10 -10
  74. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  75. package/mcp-server/dist/tools/agents.js +22 -13
  76. package/mcp-server/dist/tools/agents.js.map +1 -1
  77. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  78. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  79. package/mcp-server/dist/tools/image-tools.js +1 -1
  80. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  81. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  82. package/mcp-server/dist/tools/skill-tools.js +639 -0
  83. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  84. package/mcp-server/dist/tools/skill.d.ts +7 -0
  85. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  86. package/mcp-server/dist/tools/skill.js +11 -0
  87. package/mcp-server/dist/tools/skill.js.map +1 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  89. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  90. package/mcp-server/dist/tools/tool-groups.js +147 -0
  91. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  92. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  93. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  94. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  96. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  97. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  98. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  99. package/mcp-server/package-lock.json +2 -2
  100. package/mcp-server/package.json +1 -1
  101. package/mcp-server/src/http.ts +75 -12
  102. package/mcp-server/src/index.ts +147 -154
  103. package/mcp-server/src/preload.ts +12 -12
  104. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  105. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  106. package/mcp-server/src/services/neural-interface.ts +79 -20
  107. package/mcp-server/src/services/server-instructions.ts +27 -63
  108. package/mcp-server/src/services/tool-catalog.ts +403 -0
  109. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  110. package/mcp-server/src/services/tool-list.ts +410 -0
  111. package/mcp-server/src/services/tool-runtime.ts +457 -0
  112. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  113. package/mcp-server/src/tools/agents.ts +22 -14
  114. package/mcp-server/src/tools/image-tools.ts +1 -1
  115. package/mcp-server/src/tools/skill-tools.ts +589 -0
  116. package/mcp-server/src/tools/skill.ts +13 -0
  117. package/mcp-server/src/tools/tool-groups.ts +164 -0
  118. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  119. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  120. package/neural-interface/i18n/en.json +3680 -206
  121. package/neural-interface/i18n/pt-BR.json +3628 -157
  122. package/neural-interface/lib/account-usage.js +5 -3
  123. package/neural-interface/lib/app-controls-api.js +161 -0
  124. package/neural-interface/lib/assistant-api.js +148 -35
  125. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  126. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  127. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  128. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  129. package/neural-interface/lib/assistant-brains/opencode.js +122 -44
  130. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +59 -0
  131. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +97 -0
  132. package/neural-interface/lib/assistant-catalog.js +23 -9
  133. package/neural-interface/lib/assistant-dispatch.js +874 -156
  134. package/neural-interface/lib/assistant-envelope.js +22 -2
  135. package/neural-interface/lib/assistant-persona.js +80 -11
  136. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  137. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  138. package/neural-interface/lib/assistant-route-gate.js +6 -4
  139. package/neural-interface/lib/assistant-router.js +13 -8
  140. package/neural-interface/lib/assistant-runtime.js +183 -26
  141. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  142. package/neural-interface/lib/assistant-tools.js +51 -0
  143. package/neural-interface/lib/backup-service.js +620 -3
  144. package/neural-interface/lib/backup-zip-reader.js +10 -5
  145. package/neural-interface/lib/backup-zip-writer.js +257 -0
  146. package/neural-interface/lib/browser-execution.js +10 -2
  147. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  148. package/neural-interface/lib/browser-network-guard.js +284 -0
  149. package/neural-interface/lib/browser-policy-roots.js +64 -0
  150. package/neural-interface/lib/browser-tool-policy.js +186 -31
  151. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  152. package/neural-interface/lib/claude-hooks-status.js +103 -0
  153. package/neural-interface/lib/claude-model-catalog.js +64 -14
  154. package/neural-interface/lib/claude-temporary.js +5 -11
  155. package/neural-interface/lib/cli-install.js +18 -138
  156. package/neural-interface/lib/cli-resolver.js +4 -1
  157. package/neural-interface/lib/cli-update-planner.js +32 -31
  158. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  159. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  160. package/neural-interface/lib/codex-settings-schema.js +109 -47
  161. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  162. package/neural-interface/lib/confined-fs.js +30 -3
  163. package/neural-interface/lib/crash-notice-api.js +98 -0
  164. package/neural-interface/lib/editor-completion-opencode.js +164 -67
  165. package/neural-interface/lib/exec-loop-command.js +4 -1
  166. package/neural-interface/lib/http-guards.js +8 -2
  167. package/neural-interface/lib/install-details.js +4 -1
  168. package/neural-interface/lib/launch-cache.js +293 -0
  169. package/neural-interface/lib/login-start.js +524 -0
  170. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  171. package/neural-interface/lib/mcp-client-config.js +0 -29
  172. package/neural-interface/lib/mcp-external-servers.js +323 -0
  173. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  174. package/neural-interface/lib/morelogin.js +30 -0
  175. package/neural-interface/lib/native-loop-providers.js +565 -50
  176. package/neural-interface/lib/native-loop-runtime.js +287 -41
  177. package/neural-interface/lib/opencode-config.js +557 -0
  178. package/neural-interface/lib/opencode-db-schema.js +58 -0
  179. package/neural-interface/lib/opencode-history.js +49 -204
  180. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  181. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  182. package/neural-interface/lib/opencode-protocol.js +310 -0
  183. package/neural-interface/lib/opencode-provider-view.js +81 -0
  184. package/neural-interface/lib/opencode-runtime-config.js +32 -15
  185. package/neural-interface/lib/opencode-serve-records.js +624 -0
  186. package/neural-interface/lib/opencode-v2-client.js +53 -131
  187. package/neural-interface/lib/opencode-v2-ws-requests.js +35 -195
  188. package/neural-interface/lib/opencode-worker-activity.js +229 -0
  189. package/neural-interface/lib/opencode-worker-data.js +27 -0
  190. package/neural-interface/lib/opencode2-client.js +796 -0
  191. package/neural-interface/lib/opencode2-proxy.js +76 -0
  192. package/neural-interface/lib/opencode2-translate.js +1057 -0
  193. package/neural-interface/lib/owner-authority.js +249 -0
  194. package/neural-interface/lib/page-session.js +275 -0
  195. package/neural-interface/lib/peer-identity.js +763 -0
  196. package/neural-interface/lib/process-identity.js +361 -0
  197. package/neural-interface/lib/request-gate.js +17 -2
  198. package/neural-interface/lib/rulesets/api.js +1 -1
  199. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  200. package/neural-interface/lib/rulesets/installer.js +92 -27
  201. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  202. package/neural-interface/lib/session-cache-builder.js +18 -10
  203. package/neural-interface/lib/session-title-generator.js +58 -18
  204. package/neural-interface/lib/skills/api-v2.js +495 -0
  205. package/neural-interface/lib/skills/bundle.js +418 -0
  206. package/neural-interface/lib/skills/catalog.js +294 -0
  207. package/neural-interface/lib/skills/creation-api.js +191 -0
  208. package/neural-interface/lib/skills/creation-brief.js +247 -0
  209. package/neural-interface/lib/skills/creation-sources.js +267 -0
  210. package/neural-interface/lib/skills/creation.js +843 -0
  211. package/neural-interface/lib/skills/deploy.js +564 -0
  212. package/neural-interface/lib/skills/digest.js +119 -0
  213. package/neural-interface/lib/skills/discover.js +111 -0
  214. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  215. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  216. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  217. package/neural-interface/lib/skills/eval-trace.js +119 -0
  218. package/neural-interface/lib/skills/evals-api.js +212 -0
  219. package/neural-interface/lib/skills/evals.js +1573 -0
  220. package/neural-interface/lib/skills/export.js +200 -0
  221. package/neural-interface/lib/skills/frontmatter.js +64 -0
  222. package/neural-interface/lib/skills/health.js +75 -0
  223. package/neural-interface/lib/skills/json-edit.js +165 -0
  224. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  225. package/neural-interface/lib/skills/known-bridges.js +96 -0
  226. package/neural-interface/lib/skills/library.js +823 -0
  227. package/neural-interface/lib/skills/local-store.js +21 -0
  228. package/neural-interface/lib/skills/migration.js +433 -0
  229. package/neural-interface/lib/skills/operations.js +45 -0
  230. package/neural-interface/lib/skills/ownership.js +427 -0
  231. package/neural-interface/lib/skills/packages.js +88 -0
  232. package/neural-interface/lib/skills/reviews.js +212 -0
  233. package/neural-interface/lib/skills/revision.js +80 -0
  234. package/neural-interface/lib/skills/roots.js +262 -0
  235. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  236. package/neural-interface/lib/skills/scan-assets.js +252 -0
  237. package/neural-interface/lib/skills/scan-context.js +26 -0
  238. package/neural-interface/lib/skills/scan-shell.js +324 -0
  239. package/neural-interface/lib/skills/scan.js +364 -0
  240. package/neural-interface/lib/skills/scanners.js +54 -0
  241. package/neural-interface/lib/skills/service.js +522 -0
  242. package/neural-interface/lib/skills/telemetry.js +113 -0
  243. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  244. package/neural-interface/lib/style-guide/api.js +4 -3
  245. package/neural-interface/lib/tool-lists.js +124 -0
  246. package/neural-interface/lib/untrusted-file-headers.js +27 -0
  247. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  248. package/neural-interface/lib/worker-confinement.js +1192 -0
  249. package/neural-interface/lib/worker-guard-setting.js +56 -0
  250. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  251. package/neural-interface/lib/worker-relay-ports.js +227 -0
  252. package/neural-interface/lib/worker-resume.js +229 -0
  253. package/neural-interface/lib/worker-sandbox.js +299 -0
  254. package/neural-interface/package-lock.json +1354 -50
  255. package/neural-interface/package.json +6 -2
  256. package/neural-interface/public/index.html +5 -3
  257. package/neural-interface/public/index2d.html +19 -11
  258. package/neural-interface/public/onboarding.html +2 -2
  259. package/neural-interface/public/shared/account-menu.js +8 -5
  260. package/neural-interface/public/shared/agent-runtime-options.js +76 -16
  261. package/neural-interface/public/shared/api.js +1527 -1336
  262. package/neural-interface/public/shared/app-frame.js +192 -0
  263. package/neural-interface/public/shared/assistant/asst-agents-dock.js +205 -19
  264. package/neural-interface/public/shared/assistant/asst-api.js +11 -3
  265. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  266. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  267. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  268. package/neural-interface/public/shared/assistant/asst-brain-picker.js +241 -112
  269. package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
  270. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  271. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  272. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  273. package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
  274. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  275. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  276. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  277. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  278. package/neural-interface/public/shared/assistant/asst-menu.js +84 -12
  279. package/neural-interface/public/shared/assistant/asst-panel.js +303 -44
  280. package/neural-interface/public/shared/assistant/asst-rack-styles.js +93 -58
  281. package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
  282. package/neural-interface/public/shared/assistant/asst-render.js +770 -100
  283. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  284. package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
  285. package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
  286. package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
  287. package/neural-interface/public/shared/assistant/asst-scenes.js +530 -0
  288. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
  289. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  290. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  291. package/neural-interface/public/shared/assistant/asst-state.js +25 -15
  292. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  293. package/neural-interface/public/shared/assistant/asst-styles.js +102 -21
  294. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +165 -13
  295. package/neural-interface/public/shared/automation-context.js +5 -2
  296. package/neural-interface/public/shared/cc-dropdown.js +550 -0
  297. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  298. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  299. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  300. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  301. package/neural-interface/public/shared/cdx/cdx-panel.js +58 -150
  302. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  303. package/neural-interface/public/shared/cdx/cdx-render.js +62 -32
  304. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  305. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  306. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  307. package/neural-interface/public/shared/cdx/cdx-tabs.js +201 -74
  308. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  309. package/neural-interface/public/shared/cli-install-view.js +4 -8
  310. package/neural-interface/public/shared/cli-status.js +2 -2
  311. package/neural-interface/public/shared/constants.js +2 -2
  312. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  313. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  314. package/neural-interface/public/shared/context-menu-model.js +366 -0
  315. package/neural-interface/public/shared/context-menu.js +372 -0
  316. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  317. package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
  318. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  319. package/neural-interface/public/shared/data-cache.js +223 -0
  320. package/neural-interface/public/shared/desktop-bridge.js +11 -0
  321. package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
  322. package/neural-interface/public/shared/desktop-titlebar.js +214 -28
  323. package/neural-interface/public/shared/desktop-update-view.js +35 -5
  324. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  325. package/neural-interface/public/shared/file-links.js +73 -8
  326. package/neural-interface/public/shared/html-shell.js +78 -86
  327. package/neural-interface/public/shared/i18n.js +2 -0
  328. package/neural-interface/public/shared/launch-prefetch.js +59 -0
  329. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  330. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  331. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
  332. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  333. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +9 -2
  334. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  335. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  336. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  337. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  338. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  339. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  340. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
  341. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  342. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +140 -254
  343. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  344. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +97 -203
  345. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
  346. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
  347. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -22
  348. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
  349. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +20 -13
  350. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  351. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +53 -82
  352. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  353. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  354. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  355. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  356. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -40
  357. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  358. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +205 -62
  359. package/neural-interface/public/shared/opencode-providers.js +39 -0
  360. package/neural-interface/public/shared/page-session.js +96 -0
  361. package/neural-interface/public/shared/registry.js +5 -3
  362. package/neural-interface/public/shared/retired-2d.js +3 -0
  363. package/neural-interface/public/shared/schedules-view.js +452 -0
  364. package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
  365. package/neural-interface/public/shared/settings/settings-app.js +243 -0
  366. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  367. package/neural-interface/public/shared/settings/settings-data.js +90 -0
  368. package/neural-interface/public/shared/settings/settings-ia.js +23 -6
  369. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  370. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  371. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  372. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  373. package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
  374. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  375. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  376. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  377. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  378. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  379. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  380. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  381. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  382. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  383. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  384. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  385. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  386. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  387. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  388. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  389. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  390. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  391. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  392. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  393. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  394. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  395. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  396. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  397. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  398. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  399. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  400. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  401. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  402. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  403. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  404. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  405. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  406. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  407. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  408. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  409. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  410. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  411. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  412. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  413. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  414. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  415. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  416. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  417. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  418. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  419. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  420. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  421. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  422. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  423. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  424. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  425. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  426. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  427. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  428. package/neural-interface/public/shared/styles.css +2065 -2150
  429. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  430. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  431. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  432. package/neural-interface/public/shared/tool-list-view.js +112 -0
  433. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  434. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  435. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  436. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  437. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  438. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  439. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  440. package/neural-interface/public/shared/tour/tour.css +144 -0
  441. package/neural-interface/public/shared/ui-assistant.js +27 -5
  442. package/neural-interface/public/shared/ui-automation-studio.js +155 -152
  443. package/neural-interface/public/shared/ui-bookmarks.js +2 -1
  444. package/neural-interface/public/shared/ui-claude-panel.js +153 -13
  445. package/neural-interface/public/shared/ui-codex-panel.js +1 -0
  446. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  447. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  448. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  449. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  450. package/neural-interface/public/shared/ui-detail.js +21 -9
  451. package/neural-interface/public/shared/ui-file-links.js +1 -0
  452. package/neural-interface/public/shared/ui-image-gallery.js +56 -52
  453. package/neural-interface/public/shared/ui-invite.js +18 -2
  454. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  455. package/neural-interface/public/shared/ui-link.js +9 -9
  456. package/neural-interface/public/shared/ui-menubar.js +229 -40
  457. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  458. package/neural-interface/public/shared/ui-native-loop-router.js +9 -1
  459. package/neural-interface/public/shared/ui-navbar.js +4 -24
  460. package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
  461. package/neural-interface/public/shared/ui-panels.js +96 -65
  462. package/neural-interface/public/shared/ui-schedules-studio.js +2267 -953
  463. package/neural-interface/public/shared/ui-sessions.js +20 -7
  464. package/neural-interface/public/shared/ui-settings.js +679 -496
  465. package/neural-interface/public/shared/ui-sidebar.js +4 -4
  466. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
  467. package/neural-interface/public/shared/ui-sidepanel-sessions.js +56 -5
  468. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  469. package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
  470. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  471. package/neural-interface/public/shared/ui-skills.js +42 -2781
  472. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  473. package/neural-interface/public/shared/ui-sync.js +22 -5
  474. package/neural-interface/public/shared/ui-terminal.js +159 -199
  475. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  476. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  477. package/neural-interface/public/shared/ui-trash.js +14 -5
  478. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  479. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  480. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -61
  481. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  482. package/neural-interface/public/shared/ui-tutorial.js +285 -499
  483. package/neural-interface/public/shared/ui-update.js +121 -16
  484. package/neural-interface/public/shared/ui-whiteboard.js +313 -59
  485. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  486. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  487. package/neural-interface/public/shared/view-variant.js +66 -0
  488. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  489. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  490. package/neural-interface/public/shared/worker-end.js +10 -3
  491. package/neural-interface/public/shared/worker-restart.js +12 -0
  492. package/neural-interface/public/sidepanel-session.html +1 -1
  493. package/neural-interface/public/variant/2d/background.js +2 -1
  494. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  495. package/neural-interface/public/variant/2d/graph.js +42 -20
  496. package/neural-interface/public/variant/2d/hulls.js +14 -9
  497. package/neural-interface/public/variant/2d/lasso.js +11 -7
  498. package/neural-interface/public/variant/2d/main.js +36 -2
  499. package/neural-interface/public/variant/2d/minimap.js +11 -13
  500. package/neural-interface/public/variant/3d/main.js +72 -6
  501. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  502. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  503. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  504. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  505. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  506. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  507. package/neural-interface/public/vendor/highlight/LICENSE +29 -0
  508. package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
  509. package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
  510. package/neural-interface/public/vendor/highlight/manifest.json +5 -0
  511. package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
  512. package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
  513. package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
  514. package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
  515. package/neural-interface/public/vendor/marked/manifest.json +4 -0
  516. package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
  517. package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
  518. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
  519. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
  520. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
  521. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
  522. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
  523. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
  524. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
  525. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
  526. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
  527. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
  528. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
  529. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
  530. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
  531. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
  532. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
  533. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
  534. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
  535. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
  536. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
  537. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
  538. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
  539. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
  540. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
  541. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
  542. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
  543. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
  544. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
  545. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
  546. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
  547. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
  548. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
  549. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
  550. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
  551. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
  552. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
  553. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
  554. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
  555. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
  556. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
  557. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
  558. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
  559. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
  560. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
  561. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
  562. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
  563. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
  564. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
  565. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
  566. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
  567. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
  568. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
  569. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
  570. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
  571. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
  572. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
  573. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
  574. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
  575. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
  576. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
  577. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
  578. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
  579. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
  580. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
  581. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
  582. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
  583. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
  584. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
  585. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
  586. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
  587. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
  588. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
  589. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
  590. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
  591. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
  592. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
  593. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
  594. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
  595. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
  596. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
  597. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
  598. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
  599. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
  600. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
  601. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
  602. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
  603. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
  604. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
  605. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
  606. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
  607. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
  608. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
  609. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
  610. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
  611. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
  612. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
  613. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
  614. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
  615. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
  616. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
  617. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
  618. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
  619. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
  620. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
  621. package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
  622. package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
  623. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  624. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  625. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  626. package/neural-interface/scripts/build-vendor.mjs +116 -3
  627. package/neural-interface/server.js +4834 -2541
  628. package/neural-interface/templates/rulesets/core.md +1 -1
  629. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  630. package/neural-interface/templates/rulesets/manifest.json +8 -8
  631. package/npm-shrinkwrap.json +18 -12
  632. package/package.json +16 -3
  633. package/postinstall.js +9 -4
  634. package/preuninstall.js +1 -1
  635. package/setup.js +97 -322
  636. package/skills/skill-forge/SKILL.md +177 -0
  637. package/skills/skill-forge/evals/evals.json +54 -0
  638. package/skills/skill-forge/references/package.md +105 -0
  639. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  640. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  641. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  642. package/skills/synabun/SKILL.md +10 -7
  643. package/skills/synabun/modules/audit.md +1 -5
  644. package/skills/synabun/modules/changelog.md +1 -5
  645. package/skills/synabun/modules/idea.md +1 -1
  646. package/skills/synabun/modules/memorize.md +6 -9
  647. package/skills/synabun/modules/schedule.md +5 -5
  648. package/mcp-server/dist/services/profiles.d.ts +0 -120
  649. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  650. package/mcp-server/dist/services/profiles.js +0 -387
  651. package/mcp-server/dist/services/profiles.js.map +0 -1
  652. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  653. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  654. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  655. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  656. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  657. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  658. package/mcp-server/dist/tools/leonardo.js +0 -21
  659. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  660. package/mcp-server/dist/tools/profile.d.ts +0 -17
  661. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  662. package/mcp-server/dist/tools/profile.js +0 -114
  663. package/mcp-server/dist/tools/profile.js.map +0 -1
  664. package/mcp-server/src/services/profiles.ts +0 -428
  665. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  666. package/mcp-server/src/tools/leonardo.ts +0 -30
  667. package/mcp-server/src/tools/profile.ts +0 -122
  668. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  669. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  670. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  671. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  672. package/skills/leonardo/SKILL.md +0 -168
  673. package/skills/leonardo/icon.svg +0 -1
  674. package/skills/leonardo/modules/browser-guide.md +0 -378
  675. package/skills/leonardo/modules/image-prompter.md +0 -283
  676. package/skills/leonardo/modules/model-advisor.md +0 -100
  677. package/skills/leonardo/modules/prompt-library.md +0 -110
  678. package/skills/leonardo/modules/style-guide.md +0 -186
  679. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -0,0 +1,884 @@
1
+ // The Workspace tour's engine. Beats are data (tour-beats.js); the engine
2
+ // plays one at a time over three actors: the mascot (the empty state's real
3
+ // rig, in its real box: tour-actor.js), the board (hand-drawn ink over the
4
+ // panel, and the one highlight ring on the page: tour-stage.js) and the
5
+ // caption (the narration, laid out whole and revealed a word at a time, said
6
+ // once to the panel's role=status announcer). A beat plays itself out, then
7
+ // waits for the user: tap the board, → / Enter / Space, or Continue. A press
8
+ // mid-beat hurries it (what is in flight runs fast, what is left is placed),
9
+ // ← goes back, Esc ends it with the mascot flying home. Every change starts
10
+ // from what is on screen: the mascot keeps its speed, the ring morphs, the
11
+ // caption morphs (FLIP), the board wipes. Nothing here starts a session, sends
12
+ // a message or changes a setting: the panel's controls are only pointed at.
13
+ import { createHighlight, createStage, EASE, sparkle } from './tour-stage.js';
14
+ import { createActor } from './tour-actor.js';
15
+ import { BEATS, CHAPTERS } from './tour-beats.js';
16
+ import { ellipsePoints, seededRand, softBoxPath } from './tour-ink.js';
17
+ import { unionRect, visibleRect } from '../ui-tutorial-layout.js';
18
+
19
+ const WORD_MS = 34; // the narration's pace
20
+ const PUNCT_MS = 140; // a breath after a comma or a full stop
21
+ const MORPH_MS = 320; // --asst-dur-slow: the caption changing size or place
22
+ const ARROW_MIN_PX = 14; // an arrow shorter than two heads (tour-stage.js arrow head 7) is left out: a stub reads as a blot
23
+ const ICON_NEXT = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h13M13 6l6 6-6 6"/></svg>';
24
+ const ICON_BACK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H6M11 6l-6 6 6 6"/></svg>';
25
+ const ICON_CARET = '<svg viewBox="0 0 10 10" aria-hidden="true"><path d="M2.5 4l2.5 2.5L7.5 4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>';
26
+
27
+ const node = (tag, cls, text) => {
28
+ const el = document.createElement(tag);
29
+ if (cls) el.className = cls;
30
+ if (text != null) el.textContent = text;
31
+ return el;
32
+ };
33
+
34
+ /**
35
+ * createTour({ host, tr, … }) → { play(i), next(), prev(), close(reason), openList(), index, ready }
36
+ * host: { root, transcript, announcer, bar, renderer, director, composer, live } (the panel the tour
37
+ * presents on; ui-tutorial.js gets it from the sidepanel or a session-free preview).
38
+ * tr(key, params): the strings (tour.*). env: { computer } (what this workspace has).
39
+ * onBeat(beat): a beat started (the saved cursor). onComplete(): the hand-off was reached.
40
+ * onClose(reason): 'done' | 'skip' | 'sent' | 'gone', after everything is put back.
41
+ * onExplore(): the chapter list's "Explore a project" row (the setup wizard), when given.
42
+ */
43
+ export function createTour({
44
+ host, tr, beats = BEATS, chapters = CHAPTERS, reduced = () => false, env = {},
45
+ pageHost = document.body, onBeat, onComplete, onClose, onExplore, resumeAt = -1,
46
+ } = {}) {
47
+ const { root, transcript, renderer } = host;
48
+ const announcer = host.announcer || root.querySelector('.asst-announce');
49
+ const panel = root.closest('.assistant-panel');
50
+ const scopes = [root, host.bar, panel, document].filter(Boolean);
51
+ const text = (key, params) => { const v = tr(key, params); return v && v !== key ? v : ''; };
52
+
53
+ let fast = false;
54
+ const quick = () => reduced() || fast;
55
+ const board = createStage(root, { reduced: quick });
56
+ const page = createStage(pageHost, { fixed: true, reduced: quick });
57
+ // The page layer rings controls in the panel too: it sits just above the panel, whatever its stacking.
58
+ try { page.el.style.zIndex = String(Math.max(11001, (parseInt(getComputedStyle(panel || root).zIndex, 10) || 0) + 1)); } catch { /* the stylesheet's */ }
59
+ const ring = createHighlight(page, { reduced });
60
+ const target = renderer.mascot?.();
61
+ const hero = target?.kind === 'hero' ? target : null;
62
+ // The mascot is the top-most thing on the tour: both ink layers keep a hole where it is, every frame it moves.
63
+ const actor = hero ? createActor(hero, { reduced, onMove: (r) => { board.setKeepOut(r); page.setKeepOut(r); } }) : null;
64
+ const scrollTop = transcript.scrollTop;
65
+ hero?.hold?.(true);
66
+ host.director?.present?.(true);
67
+ const previousFocus = document.activeElement;
68
+
69
+ // ── The caption: the narration, the chapter it is in, the way on ──
70
+ const caption = node('div', 'tour-caption');
71
+ caption.dataset.place = 'foot';
72
+ caption.innerHTML = `
73
+ <div class="tour-caption-bg" aria-hidden="true"></div>
74
+ <div class="tour-caption-body">
75
+ <div class="tour-caption-meta">
76
+ <button type="button" class="tour-chapter-btn" aria-haspopup="true" aria-expanded="false"><span class="tour-chapter-name"></span>${ICON_CARET}</button>
77
+ <span class="tour-dots" aria-hidden="true"></span>
78
+ </div>
79
+ <div class="tour-line" aria-hidden="true"></div>
80
+ <div class="tour-list" role="group" hidden></div>
81
+ <div class="tour-actions">
82
+ <span class="tour-hint"></span>
83
+ <button type="button" class="tour-skip"></button>
84
+ <button type="button" class="tour-back" hidden></button>
85
+ <button type="button" class="tour-next"></button>
86
+ </div>
87
+ </div>`;
88
+ const bg = caption.querySelector('.tour-caption-bg');
89
+ const body = caption.querySelector('.tour-caption-body');
90
+ const chapterBtn = caption.querySelector('.tour-chapter-btn');
91
+ const chapterName = caption.querySelector('.tour-chapter-name');
92
+ const dots = caption.querySelector('.tour-dots');
93
+ const line = caption.querySelector('.tour-line');
94
+ const list = caption.querySelector('.tour-list');
95
+ const hint = caption.querySelector('.tour-hint');
96
+ const skipBtn = caption.querySelector('.tour-skip');
97
+ const backBtn = caption.querySelector('.tour-back');
98
+ const nextBtn = caption.querySelector('.tour-next');
99
+ dots.innerHTML = chapters.map(() => '<span class="tour-dot"><span class="tour-dot-fill"></span></span>').join('');
100
+ skipBtn.textContent = text('tour.ui.skip');
101
+ backBtn.innerHTML = ICON_BACK;
102
+ backBtn.setAttribute('aria-label', text('tour.ui.back'));
103
+ backBtn.dataset.tooltip = text('tour.ui.back');
104
+ list.setAttribute('aria-label', text('tour.ui.chapters'));
105
+ transcript.appendChild(caption);
106
+ root.classList.add('tour-on');
107
+ transcript.scrollTop = 0;
108
+
109
+ let index = -1;
110
+ let gen = 0;
111
+ let ready = false;
112
+ let done = false;
113
+ let listOpen = false;
114
+ let lastPlayed = null; // the beat whose ink is on the board
115
+ let ringTarget = null; // the spec the ring and the arms point at
116
+ let place = null; // the beat's travel place, for a replay after a resize
117
+ const timers = new Map(); // timeout id → the resolve of the wait it ends
118
+ const sleep = (ms, my) => new Promise((resolve) => {
119
+ if (my !== gen || quick()) { resolve(); return; }
120
+ const id = setTimeout(() => { timers.delete(id); resolve(); }, ms);
121
+ timers.set(id, resolve);
122
+ });
123
+ // A wait cut short ends at once (a hurried beat goes on; a replaced one stops at its generation check).
124
+ const clearTimers = () => { for (const [id, resolve] of timers) { clearTimeout(id); resolve(); } timers.clear(); };
125
+
126
+ // ── Geometry: board units (0-100 across the board), places, targets ──
127
+ const boardRect = () => {
128
+ const tb = transcript.getBoundingClientRect();
129
+ const cap = caption.getBoundingClientRect();
130
+ const atTop = caption.dataset.place === 'head';
131
+ const top = (atTop ? cap.bottom : tb.top) + 12;
132
+ const bottom = atTop ? tb.bottom - 12 : cap.top - 10;
133
+ return { left: tb.left + 14, top, right: tb.right - 14, bottom, width: tb.width - 28, height: Math.max(0, bottom - top) };
134
+ };
135
+ const u = (x, y, b = boardRect()) => { const k = b.width / 100; return { x: b.left + x * k, y: b.top + y * k }; };
136
+ const uRect = ([x, y, w, h]) => { const b = boardRect(); const a = u(x, y, b); const c = u(x + w, y + h, b); return { left: a.x, top: a.y, right: c.x, bottom: c.y, width: c.x - a.x, height: c.y - a.y }; };
137
+ const shownEl = (el) => {
138
+ if (!el?.isConnected) return false;
139
+ if (el.checkVisibility && !el.checkVisibility({ checkOpacity: false, checkVisibilityCSS: true })) return false;
140
+ return visibleRect(el.getBoundingClientRect(), innerWidth, innerHeight);
141
+ };
142
+ /** The control(s) a target names: the first selector on screen (all of them for a union). */
143
+ function resolve(spec) {
144
+ if (!spec?.sel) return null;
145
+ const found = [];
146
+ for (const sel of spec.sel) {
147
+ for (const scope of scopes) {
148
+ let el = null;
149
+ try { el = scope.querySelector(sel); } catch { el = null; }
150
+ if (el && shownEl(el)) { found.push(el); break; }
151
+ }
152
+ if (found.length && !spec.union) break;
153
+ }
154
+ return found.length ? found : null;
155
+ }
156
+ const rectOfEls = (els) => unionRect(els.map(e => e.getBoundingClientRect()));
157
+ const targetRect = (spec) => { const els = resolve(spec); return els ? rectOfEls(els) : null; };
158
+ /**
159
+ * The ring's shape round a control: its gaps (a beat's `pad` / `padY`, else 7 round and 5 above and below), the
160
+ * control's own corner, the ring's largest radius and how far its pen-tail bows out (`tail`, tour-stage.js).
161
+ */
162
+ function ringShapeFor(spec) {
163
+ const els = resolve(spec);
164
+ let corner = 6;
165
+ try { if (els?.[0]) corner = parseFloat(getComputedStyle(els[0]).borderTopLeftRadius) || 6; } catch { /* the default */ }
166
+ return { pad: spec.pad ?? 7, padY: spec.padY ?? spec.pad ?? 5, corner, radius: spec.radius ?? 999, tail: spec.tail };
167
+ }
168
+ /** How far short of a ringed control the fingertip stops: outside the ring, with room for a poke. */
169
+ const ringGap = spec => Math.max(spec?.pad ?? 7, spec?.padY ?? spec?.pad ?? 5) + 9;
170
+ const places = {
171
+ home: () => null,
172
+ corner: () => ({ point: u(17, 12), scale: 0.8 }),
173
+ top: () => ({ point: u(50, 13), scale: 1 }),
174
+ topright: () => ({ point: u(80, 12), scale: 0.8 }),
175
+ composer: () => {
176
+ const b = boardRect();
177
+ const tb = transcript.getBoundingClientRect();
178
+ const m = targetRect({ sel: ['.asst-dd-model', '.asst-composer'] });
179
+ const x = Math.max(b.left + 56, Math.min(b.right - 70, m ? m.left + m.width / 2 : b.left + b.width / 2));
180
+ // High enough that the arm, reaching down at the pill, ends inside the board (the transcript clips).
181
+ return { point: { x, y: Math.min(b.bottom - 8, tb.bottom - 58) }, scale: 0.8 };
182
+ },
183
+ };
184
+ /**
185
+ * Beside a control, never on it: left of it (or right, when there is no room), level with
186
+ * its top, inside the board. The mascot lands there to point with a short arm.
187
+ */
188
+ function besidePlace(spec, scale = 0.8) {
189
+ const r = targetRect(spec);
190
+ if (!r) return null;
191
+ const b = boardRect();
192
+ const half = 60 * scale; // half the face's width at this scale
193
+ let x = r.left - half - 22;
194
+ if (x - half < b.left) x = r.right + half + 22;
195
+ x = Math.max(b.left + half, Math.min(b.right - half, x));
196
+ const y = Math.max(b.top + 30 * scale, Math.min(b.bottom - 30 * scale, r.top - 18));
197
+ return { point: { x, y }, scale, avoid: [r] };
198
+ }
199
+
200
+ // ── Layout: what the mascot, the ring and the writing keep clear of ──
201
+ const hits = (a, b, m = 0) => a.left < b.right + m && a.right > b.left - m && a.top < b.bottom + m && a.bottom > b.top - m;
202
+ const inside = (a, b, m = 0) => a.left >= b.left - m && a.right <= b.right + m && a.top >= b.top - m && a.bottom <= b.bottom + m;
203
+ const grow = (r, m) => (r ? { left: r.left - m, top: r.top - m, right: r.right + m, bottom: r.bottom + m } : null);
204
+ const overlap = (a, b) => Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left)) * Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
205
+ /** The panel's own words on screen that ink must never cover: the composer's first line (its placeholder or the draft). */
206
+ function panelText() {
207
+ const out = [];
208
+ const input = root.querySelector('.asst-composer textarea, .asst-composer .asst-input');
209
+ if (input) {
210
+ const r = input.getBoundingClientRect();
211
+ const cs = getComputedStyle(input);
212
+ const top = r.top + (parseFloat(cs.paddingTop) || 0);
213
+ const lh = parseFloat(cs.lineHeight) || (parseFloat(cs.fontSize) || 14) * 1.4;
214
+ if (r.width) out.push({ left: r.left + (parseFloat(cs.paddingLeft) || 0), top, right: r.right - (parseFloat(cs.paddingRight) || 0), bottom: top + lh });
215
+ }
216
+ return out;
217
+ }
218
+ /** Everything a new piece of writing (or a landing) keeps clear of, as client rects. */
219
+ function obstacles({ mascot = true, ink = true } = {}) {
220
+ const out = [];
221
+ const cap = caption.getBoundingClientRect();
222
+ if (cap.width) out.push(grow(cap, 4));
223
+ if (mascot && actor) { const k = actor.keepOut(); if (k) out.push(k); }
224
+ if (mascot && actor) { const a = actor.rig.armExtent?.(); if (a) out.push(grow(a, 3)); }
225
+ const rb = ring.box;
226
+ if (rb) out.push(grow(rb, 3));
227
+ if (ringTarget) { const t = targetRect(ringTarget); if (t) out.push(grow(t, 2)); }
228
+ if (ink) out.push(...board.boxes().map(b => grow(b, 3)));
229
+ out.push(...panelText());
230
+ return out.filter(Boolean);
231
+ }
232
+ /**
233
+ * A place for the mascot clear of the board's ink, the caption and the ring: the place asked for, or
234
+ * the nearest free one round it (steps of 10 px), inside the board. The face's keep-out decides.
235
+ */
236
+ function fitPlace(p) {
237
+ if (!p?.point || !actor) return p;
238
+ const b = boardRect();
239
+ const avoid = [...obstacles({ mascot: false }), ...(p.avoid || []).map(r => grow(r, 8))];
240
+ const ok = (pt) => { const k = actor.keepOutAt(pt, p.scale); return k && inside(k, b, 2) && !avoid.some(o => hits(k, o)); };
241
+ if (ok(p.point)) return p;
242
+ for (let d = 10; d <= 160; d += 10) {
243
+ for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [-1, 1], [1, -1], [-1, -1]]) {
244
+ const pt = { x: p.point.x + dx * d, y: p.point.y + dy * d };
245
+ if (ok(pt)) return { ...p, point: pt };
246
+ }
247
+ }
248
+ return p;
249
+ }
250
+
251
+ /**
252
+ * Where a piece of writing goes: the first candidate (client baseline anchors, in order of preference)
253
+ * whose box stays inside the board and clear of the mascot, its arm, the ring and its control, the
254
+ * caption, the panel's words and the ink already there; else the one that overlaps least.
255
+ */
256
+ function chooseSpot(cands, size) {
257
+ const b = boardRect();
258
+ const avoid = obstacles();
259
+ const boxAt = (c) => {
260
+ const left = c.anchor === 'middle' ? c.x - size.w / 2 : c.anchor === 'end' ? c.x - size.w : c.x;
261
+ return { left, right: left + size.w, top: c.y + size.top, bottom: c.y + size.bottom };
262
+ };
263
+ let best = null;
264
+ for (const c of cands) {
265
+ const r = boxAt(c);
266
+ const cost = (inside(r, b, 1) ? 0 : 1e6) + avoid.reduce((sum, o) => sum + overlap(grow(r, 3), o), 0);
267
+ if (cost === 0) return c;
268
+ if (!best || cost < best.cost) best = { ...c, cost };
269
+ }
270
+ return best;
271
+ }
272
+
273
+ /** The candidates for writing placed beside the mascot or round a control: { beside: 'actor' | spec, side, dy }. */
274
+ function spotsFor(spec, size) {
275
+ const gap = 6; // past the keep-out (or the ring), whose own margin is the clear board round it
276
+ const k = spec.beside === 'actor' ? actor?.keepOut() : null;
277
+ const t = spec.beside !== 'actor' ? (ring.target && ringTarget === spec.beside ? ring.box : grow(targetRect(spec.beside), 6)) : null;
278
+ const r = k || t;
279
+ if (!r) return [];
280
+ const dy = spec.dy || 0;
281
+ const line = size.bottom - size.top;
282
+ const midY = (r.top + r.bottom) / 2 - (size.top + size.bottom) / 2;
283
+ const sides = {
284
+ right: [0, -line, line, -2 * line, 2 * line].map(o => ({ x: r.right + gap, y: midY + dy + o, anchor: 'start' })),
285
+ left: [0, -line, line, -2 * line, 2 * line].map(o => ({ x: r.left - gap, y: midY + dy + o, anchor: 'end' })),
286
+ above: [0, -40, 40, -80, 80].map(o => ({ x: (r.left + r.right) / 2 + o, y: r.top - gap - size.bottom, anchor: 'middle' })),
287
+ below: [0, -40, 40, -80, 80].map(o => ({ x: (r.left + r.right) / 2 + o, y: r.bottom + gap - size.top, anchor: 'middle' })),
288
+ };
289
+ const order = [spec.side || 'right', ...['right', 'left', 'above', 'below'].filter(x => x !== (spec.side || 'right'))];
290
+ return order.flatMap(sd => sides[sd]);
291
+ }
292
+ let lastLabel = null; // the client box of the last writing placed (a sparkle goes at its end)
293
+ let lastFit = null; // the last landing chosen: { asked, place, keepOut, board } (tests read it)
294
+
295
+ /**
296
+ * Home with the caption at the head of the board (the hand-off): the empty state makes room under the
297
+ * caption, so the mascot and the words come back clear of it; the mascot keeps its place on screen
298
+ * while the layout moves (actor.measure), then flies to the new home.
299
+ */
300
+ function makeRoom() {
301
+ const empty = transcript.querySelector(':scope > .asst-empty');
302
+ if (!empty || caption.dataset.place !== 'head') { root.classList.remove('tour-room'); return; }
303
+ const tb = transcript.getBoundingClientRect();
304
+ const cb = caption.getBoundingClientRect();
305
+ root.style.setProperty('--tour-room', `${Math.max(0, Math.round(cb.bottom - tb.top))}px`);
306
+ root.classList.add('tour-room');
307
+ actor?.measure();
308
+ }
309
+ /** The room goes when the tour does: the empty state glides back (one transform), never jumps. */
310
+ function giveRoomBack() {
311
+ const empty = transcript.querySelector(':scope > .asst-empty');
312
+ if (!root.classList.contains('tour-room')) return;
313
+ const before = empty?.getBoundingClientRect();
314
+ root.classList.remove('tour-room');
315
+ root.style.removeProperty('--tour-room');
316
+ if (!empty || !before || reduced()) return;
317
+ const after = empty.getBoundingClientRect();
318
+ const dy = before.top - after.top;
319
+ if (Math.abs(dy) > 0.5) empty.animate([{ transform: `translateY(${dy}px)` }, { transform: 'none' }], { duration: MORPH_MS, easing: EASE.emphasized });
320
+ }
321
+
322
+ /**
323
+ * A point from a beat's shorthand: [ux, uy] | { actor, dx, dy } | { keepout: 'left' | 'right' | 'top' | 'bottom', dx, dy }
324
+ * (just outside what the mascot draws) | { after: 'label', dx, dy } (the last writing's end) | { target, at, dx, dy }.
325
+ */
326
+ function point(spec) {
327
+ if (Array.isArray(spec)) return u(spec[0], spec[1]);
328
+ if (spec?.keepout && actor) {
329
+ const k = actor.keepOut();
330
+ if (!k) return null;
331
+ const cx = (k.left + k.right) / 2;
332
+ const cy = (k.top + k.bottom) / 2;
333
+ const p = spec.keepout === 'left' ? { x: k.left, y: cy } : spec.keepout === 'right' ? { x: k.right, y: cy }
334
+ : spec.keepout === 'top' ? { x: cx, y: k.top } : { x: cx, y: k.bottom };
335
+ return { x: p.x + (spec.dx || 0), y: p.y + (spec.dy || 0) };
336
+ }
337
+ if (spec?.after === 'label') return lastLabel ? { x: lastLabel.right + (spec.dx || 0), y: lastLabel.top + (spec.dy || 0) } : null;
338
+ if (spec?.actor && actor) {
339
+ const p = actor.edge(spec.actor);
340
+ return p ? { x: p.x + (spec.dx || 0), y: p.y + (spec.dy || 0) } : null;
341
+ }
342
+ if (spec?.target) {
343
+ const r = targetRect(spec.target);
344
+ if (!r) return null;
345
+ const at = spec.at || 'center';
346
+ const p = at === 'above' ? { x: r.left + r.width / 2, y: r.top } : at === 'below' ? { x: r.left + r.width / 2, y: r.bottom }
347
+ : { x: r.left + r.width / 2, y: r.top + r.height / 2 };
348
+ return { x: p.x + (spec.dx || 0), y: p.y + (spec.dy || 0) };
349
+ }
350
+ if (spec?.sel) { const r = targetRect(spec); return r ? { x: r.left + r.width / 2, y: r.top + r.height / 2 } : null; }
351
+ return null;
352
+ }
353
+
354
+ // ── The caption's motion: one FLIP per change of size or place, the surface and the words apart ──
355
+ function morph(change) {
356
+ if (reduced() || !caption.isConnected) { change(); return; }
357
+ const a = caption.getBoundingClientRect();
358
+ change();
359
+ const b = caption.getBoundingClientRect();
360
+ if (!b.width || !b.height || (Math.abs(a.left - b.left) < 0.5 && Math.abs(a.top - b.top) < 0.5 && Math.abs(a.width - b.width) < 0.5 && Math.abs(a.height - b.height) < 0.5)) return;
361
+ for (const el of [bg, body]) el.getAnimations().forEach(x => x.cancel());
362
+ bg.animate([
363
+ { transform: `translate(${a.left - b.left}px, ${a.top - b.top}px) scale(${a.width / b.width}, ${a.height / b.height})` },
364
+ { transform: 'none' },
365
+ ], { duration: MORPH_MS, easing: EASE.emphasized });
366
+ body.animate([{ transform: `translate(${a.left - b.left}px, ${a.top - b.top}px)` }, { transform: 'none' }], { duration: MORPH_MS, easing: EASE.emphasized });
367
+ }
368
+
369
+ // ── Narration: laid out whole (so the caption has its final size at once), revealed a word at a time ──
370
+ let lineAnims = [];
371
+ function say(key, my, { computerless } = {}) {
372
+ // A beat that says something else where this machine has no computer use names that line.
373
+ const textOf = (computerless && env.computer === false && text(`tour.beats.${key}.${computerless}`)) || text(`tour.beats.${key}.line`);
374
+ const chapter = Math.max(0, chapters.indexOf(beats[index]?.chapter));
375
+ if (announcer) announcer.textContent = text('tour.ui.announce', { n: chapter + 1, total: chapters.length, line: textOf }) || textOf;
376
+ lineAnims.forEach(a => a.cancel());
377
+ lineAnims = [];
378
+ const words = String(textOf).split(/\s+/).filter(Boolean);
379
+ // The line on screen fades first: a new line never cuts in over the old one.
380
+ const fading = !quick() && line.childNodes.length
381
+ ? line.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 120, easing: EASE.exit, fill: 'forwards' }).finished.catch(() => {})
382
+ : null;
383
+ if (fading) return fading.then(() => (my === gen ? lay(words, my) : undefined));
384
+ return lay(words, my);
385
+ }
386
+
387
+ function lay(words, my) {
388
+ line.getAnimations().forEach(a => a.cancel());
389
+ morph(() => {
390
+ line.replaceChildren();
391
+ words.forEach((w, i) => {
392
+ if (i) line.append(' '); // a text node between words: a space inside an inline-block collapses
393
+ line.append(node('span', 'tour-word', w));
394
+ });
395
+ });
396
+ if (quick()) return Promise.resolve();
397
+ let at = 0;
398
+ for (const span of line.querySelectorAll('.tour-word')) {
399
+ lineAnims.push(span.animate([{ opacity: 0, transform: 'translateY(3px)' }, { opacity: 1, transform: 'none' }], { duration: 200, delay: at, easing: EASE.enter, fill: 'backwards' }));
400
+ at += WORD_MS + (/[.,:;!?…]$/.test(span.textContent) ? PUNCT_MS : 0);
401
+ }
402
+ const last = lineAnims[lineAnims.length - 1];
403
+ return last ? last.finished.catch(() => {}) : Promise.resolve();
404
+ }
405
+
406
+ function setChapter(beat) {
407
+ const ci = Math.max(0, chapters.indexOf(beat.chapter));
408
+ const name = `${text('tour.ui.chapterOf', { n: ci + 1, total: chapters.length })} · ${text(`tour.chapters.${chapters[ci]}`)}`;
409
+ if (chapterName.textContent !== name) {
410
+ chapterName.textContent = name;
411
+ if (!reduced()) chapterName.animate([{ opacity: 0, transform: 'translateY(3px)' }, { opacity: 1, transform: 'none' }], { duration: 220, easing: EASE.enter });
412
+ }
413
+ [...dots.children].forEach((d, i) => { d.classList.toggle('on', i === ci); d.classList.toggle('done', i < ci); });
414
+ }
415
+
416
+ function setReady(on, beat) {
417
+ ready = on;
418
+ caption.classList.toggle('is-ready', on);
419
+ nextBtn.innerHTML = beat?.last ? `<span>${text('tour.ui.done')}</span>` : `<span>${text('tour.ui.next')}</span>${ICON_NEXT}`;
420
+ nextBtn.setAttribute('aria-label', beat?.last ? text('tour.ui.done') : text('tour.ui.next'));
421
+ // Skip and Back fade in place (their room stays), so nothing in the row jumps.
422
+ skipBtn.classList.toggle('is-off', !!beat?.last); // the hand-off has nothing left to skip
423
+ backBtn.classList.toggle('is-off', index <= 0);
424
+ hint.replaceChildren();
425
+ if (on && index === 0) {
426
+ const [before, after] = text('tour.ui.hint').split('→');
427
+ hint.append(before || '');
428
+ if (after != null) hint.append(node('kbd', '', '→'), after);
429
+ }
430
+ // The keyboard follows the way on, unless the user is typing somewhere.
431
+ const active = document.activeElement;
432
+ if (on && (!active || active === document.body || caption.contains(active) || transcript.contains(active)) && !active?.matches?.('input, textarea, [contenteditable="true"]')) {
433
+ nextBtn.focus({ preventScroll: true });
434
+ }
435
+ }
436
+
437
+ // ── Ink the board draws ──
438
+ function doodle(name, [cx, cy, size], my) {
439
+ const k = boardRect().width / 100;
440
+ const c = u(cx, cy);
441
+ const s = size * k;
442
+ const P = (x, y) => ({ x: c.x + x * s, y: c.y + y * s });
443
+ const pts = (list) => list.map(([x, y]) => P(x, y));
444
+ const at = (fn) => (my === gen ? fn() : null);
445
+ const ell = (x, y, rx, ry, opts = {}) => board.stroke(ellipsePoints(c.x + x * s, c.y + y * s, rx * s, ry * s, { seed: `${name}${x}${y}`, ...opts }).map(([px, py]) => ({ x: px, y: py })), opts);
446
+ const rbox = (x, y, w, h, opts = {}) => board.path(softBoxPath(board.localRect({ left: c.x + x * s, top: c.y + y * s, width: w * s, height: h * s }), { radius: Math.min(10, w * s / 3), seed: `${name}b${x}` }).d, opts);
447
+ switch (name) {
448
+ case 'globe': return at(async () => {
449
+ await ell(0, 0, 0.5, 0.5, { cls: 'ink', duration: 340 });
450
+ await Promise.all([ell(0, 0, 0.2, 0.5, { cls: 'ink', duration: 260, turns: 0.52, start: -Math.PI / 2 }), board.stroke(pts([[-0.48, 0.02], [0, 0.04], [0.48, 0.02]]), { duration: 200 })]);
451
+ });
452
+ case 'laptop': return at(async () => {
453
+ await rbox(-0.42, -0.36, 0.84, 0.56);
454
+ await board.stroke(pts([[-0.55, 0.3], [-0.3, 0.32], [0.3, 0.32], [0.55, 0.3]]), { duration: 200 });
455
+ });
456
+ case 'folder': return at(async () => {
457
+ await board.stroke(pts([[-0.48, -0.22], [-0.48, -0.34], [-0.42, -0.4], [-0.16, -0.4], [-0.08, -0.3], [0.44, -0.3], [0.5, -0.24], [0.5, 0.3], [0.44, 0.36], [-0.42, 0.36], [-0.48, 0.3], [-0.48, -0.22]]), { duration: 420 });
458
+ });
459
+ case 'island': return at(async () => {
460
+ const rand = seededRand('island');
461
+ const blob = [];
462
+ for (let i = 0; i <= 18; i += 1) {
463
+ const a = (i / 18) * Math.PI * 2;
464
+ const r = 0.5 + (i < 18 ? (rand() - 0.5) * 0.12 : 0) + 0.06 * Math.sin(a * 3);
465
+ blob.push([Math.cos(a) * r, Math.sin(a) * r * 0.55]);
466
+ }
467
+ await board.stroke(pts(blob), { cls: 'ink', duration: 460 });
468
+ await Promise.all([[-0.18, -0.06], [0.08, 0.08], [0.22, -0.1]].map(([x, y], i) => ell(x, y, 0.035, 0.035, { cls: 'gold', duration: 160, delay: i * 60 })));
469
+ });
470
+ case 'board': return at(async () => {
471
+ await rbox(-0.5, -0.32, 1, 0.62);
472
+ await board.stroke(pts([[-0.32, 0.06], [-0.18, -0.12], [-0.04, 0.08], [0.1, -0.1], [0.26, 0.04]]), { cls: 'gold', duration: 360 });
473
+ await board.stroke(pts([[-0.14, 0.3], [-0.2, 0.48]]), { duration: 140 });
474
+ await board.stroke(pts([[0.14, 0.3], [0.2, 0.48]]), { duration: 140 });
475
+ });
476
+ case 'clock': return at(async () => {
477
+ await ell(0, 0, 0.5, 0.5, { cls: 'ink', duration: 380 });
478
+ await board.stroke(pts([[0, -0.28], [0, 0], [0.2, 0.1]]), { cls: 'gold', duration: 260 });
479
+ await board.arrow(P(0.55, -0.34), P(0.62, 0.06), { cls: 'faint', curve: -0.4, head: 5 });
480
+ });
481
+ case 'cycle': return at(async () => {
482
+ // A loop that comes back round: what is saved returns next time.
483
+ await ell(0, 0, 0.5, 0.5, { cls: 'faint', duration: 420, turns: 0.82, start: -Math.PI * 0.35 });
484
+ const a = -Math.PI * 0.35 + Math.PI * 2 * 0.82;
485
+ const tip = P(Math.cos(a) * 0.5, Math.sin(a) * 0.5);
486
+ const back = P(Math.cos(a - 0.5) * 0.5, Math.sin(a - 0.5) * 0.5);
487
+ const ang = Math.atan2(tip.y - back.y, tip.x - back.x);
488
+ const wing = (side) => [{ x: tip.x - Math.cos(ang + side * 0.6) * 6, y: tip.y - Math.sin(ang + side * 0.6) * 6 }, tip];
489
+ await Promise.all([board.stroke(wing(1), { cls: 'faint', duration: 110 }), board.stroke(wing(-1), { cls: 'faint', duration: 110, delay: 40 })]);
490
+ });
491
+ case 'fork': return at(async () => {
492
+ await board.label('?', P(0, -0.3), { cls: 'gold big', anchor: 'middle' });
493
+ await board.stroke(pts([[0, -0.18], [0, 0.18]]), { duration: 200 });
494
+ await Promise.all([
495
+ board.arrow(P(0, 0.18), P(-0.42, 0.62), { cls: 'gold', curve: 0.12, head: 6 }),
496
+ board.arrow(P(0, 0.18), P(0.42, 0.62), { cls: 'faint', curve: -0.12, head: 6 }),
497
+ ]);
498
+ });
499
+ default: return null;
500
+ }
501
+ }
502
+
503
+ /** A memory drum: the top ellipse, the two sides, the bottom arc (units [x, y, w, h]). */
504
+ async function drum([x, y, w, h]) {
505
+ const r = uRect([x, y, w, h]);
506
+ const ry = Math.max(3, r.height * 0.22);
507
+ const cx = r.left + r.width / 2;
508
+ const rx = r.width / 2;
509
+ const toPts = (list) => list.map(([px, py]) => ({ x: px, y: py }));
510
+ await board.stroke(toPts(ellipsePoints(cx, r.top + ry, rx, ry, { seed: 'drum-top', start: Math.PI, turns: 1.02 })), { duration: 320 });
511
+ await Promise.all([
512
+ board.stroke([{ x: r.left, y: r.top + ry }, { x: r.left + 0.5, y: r.bottom - ry }], { duration: 180 }),
513
+ board.stroke([{ x: r.right, y: r.top + ry }, { x: r.right - 0.5, y: r.bottom - ry }], { duration: 180, delay: 40 }),
514
+ ]);
515
+ await board.stroke(toPts(ellipsePoints(cx, r.bottom - ry, rx, ry, { seed: 'drum-bottom', start: Math.PI, turns: -0.5, count: 12 })), { duration: 220 });
516
+ }
517
+
518
+ // ── Ops ──
519
+ async function run(op, my) {
520
+ if (my !== gen || done) return undefined;
521
+ const [kind, a, b, c] = op;
522
+ switch (kind) {
523
+ case 'pose': return actor?.pose(a, b);
524
+ case 'react': return actor?.react(a);
525
+ case 'travel': {
526
+ place = a;
527
+ if (a === 'home') makeRoom();
528
+ const asked = typeof a === 'object' && a?.beside ? besidePlace(a.beside, a.scale) : places[a]?.();
529
+ const p = fitPlace(asked);
530
+ board.measure(); // the keep-out follows the flight from this read
531
+ lastFit = { asked: asked?.point || null, place: p?.point || null, keepOut: p?.point && actor ? actor.keepOutAt(p.point, p.scale) : null, board: boardRect() };
532
+ return actor?.travel(p?.point || null, { scale: p?.scale ?? 1 });
533
+ }
534
+ case 'look': return actor?.look(point(a));
535
+ case 'point': {
536
+ if (a == null) { actor?.point(null); return undefined; }
537
+ if (Array.isArray(a)) { const p = point(a); if (p) actor?.point(p, { gap: 10 }); return undefined; }
538
+ const els = resolve(a);
539
+ if (!els) return undefined;
540
+ // A ringed control: the fingertip stops outside the ring (a poke just reaches it).
541
+ actor?.point(els.length > 1 ? rectOfEls(els) : els[0], { gap: ringTarget === a ? ringGap(a) : 8 });
542
+ return undefined;
543
+ }
544
+ case 'poke': return actor?.poke();
545
+ case 'ring': {
546
+ if (!a && !ringTarget && !ring.target) return undefined; // nothing ringed: nothing to fade
547
+ ringTarget = a || null;
548
+ const r = a ? targetRect(a) : null;
549
+ if (a && !r) return undefined; // not on screen here: the ring stays where it is
550
+ ring.to(r, a ? ringShapeFor(a) : undefined);
551
+ return sleep(quick() ? 0 : 220, my);
552
+ }
553
+ case 'say': return say(a, my, b);
554
+ case 'wait': return sleep(a, my);
555
+ case 'label': {
556
+ const words = text(`tour.ink.${a}`);
557
+ if (b?.beside) {
558
+ // Writing beside the mascot or a control picks a free side (see chooseSpot); placed before it is written.
559
+ return board.label(words, { x: 0, y: 0 }, {
560
+ ...c,
561
+ place: (size) => {
562
+ const spot = chooseSpot(spotsFor(b, size), size);
563
+ if (spot) lastLabel = { left: spot.anchor === 'middle' ? spot.x - size.w / 2 : spot.anchor === 'end' ? spot.x - size.w : spot.x, top: spot.y + size.top, right: 0, bottom: spot.y + size.bottom };
564
+ if (lastLabel && spot) lastLabel.right = lastLabel.left + size.w;
565
+ return spot;
566
+ },
567
+ });
568
+ }
569
+ const p = point(b);
570
+ return p ? board.label(words, p, c) : undefined;
571
+ }
572
+ case 'box': return board.box(uRect(a), b);
573
+ case 'line': return board.stroke(a.map(p => point(p)).filter(Boolean), b);
574
+ case 'arrow': {
575
+ const f = point(a);
576
+ const t = point(b);
577
+ // No room for a shaft (a narrow board: the "you" box meets the mascot's keep-out): no arrow at all,
578
+ // rather than a head on a stub. The figure still reads by its boxes.
579
+ if (!f || !t || Math.hypot(t.x - f.x, t.y - f.y) < ARROW_MIN_PX) return undefined;
580
+ return board.arrow(f, t, c);
581
+ }
582
+ case 'underline': return board.underline(uRect(a), b);
583
+ case 'drum': return drum(a);
584
+ case 'doodle': return doodle(a, b, my);
585
+ case 'sparkle': { const p = point(a); return p ? sparkle(board, p, b) : undefined; }
586
+ case 'erase': { if (lastPlayed) actor?.react('wiggle'); lastPlayed = null; return board.erase(); }
587
+ case 'words': root.classList.toggle('tour-board-live', !a); return undefined;
588
+ case 'context': root.classList.toggle('tour-show-context', !!a); return undefined;
589
+ case 'handoff': root.classList.toggle('tour-handoff', a !== false); return undefined;
590
+ case 'if': return env[a] === false ? (c ? run(c, my) : undefined) : run(b, my);
591
+ case 'parallel': return Promise.all(a.map(o => run(o, my)));
592
+ case 'seq': { for (const o of a) { if (my !== gen) return undefined; await run(o, my); } return undefined; }
593
+ default: return undefined;
594
+ }
595
+ }
596
+
597
+ // ── Beats ──
598
+ async function play(i, { prelude = true } = {}) {
599
+ if (done || i < 0 || i >= beats.length) return;
600
+ const my = ++gen;
601
+ clearTimers();
602
+ fast = false;
603
+ if (listOpen) setList(false, { focus: false });
604
+ const previous = lastPlayed;
605
+ index = i;
606
+ const beat = beats[i];
607
+ nextBtn.hidden = false;
608
+ backBtn.hidden = false;
609
+ skipBtn.textContent = text('tour.ui.skip');
610
+ onBeat?.(beat, i);
611
+ if (beat.last) onComplete?.();
612
+ if (!beat.last) root.classList.add('tour-board-live'); // a beat reached directly starts with the empty state's words aside
613
+ if (!beat.last && root.classList.contains('tour-room')) { root.classList.remove('tour-room'); actor?.measure(); } // back from the hand-off
614
+ // The empty state's context line (Project · Budget) is one beat's: it goes with that beat, whichever comes next.
615
+ root.classList.remove('tour-show-context');
616
+ morph(() => {
617
+ caption.dataset.place = beat.caption === 'top' ? 'head' : 'foot';
618
+ setChapter(beat);
619
+ });
620
+ setReady(false, beat);
621
+ hint.replaceChildren();
622
+ // A beat that continues another one's figure, reached some other way: that figure first, at once.
623
+ if (beat.after && previous !== beat.after && prelude) {
624
+ await board.erase();
625
+ if (my !== gen) return;
626
+ const was = fast;
627
+ fast = true;
628
+ for (const op of beat.prelude || []) { if (my !== gen) return; await run(op, my); }
629
+ fast = was;
630
+ }
631
+ for (const op of beat.script) {
632
+ if (my !== gen || done) return;
633
+ try { await run(op, my); } catch (err) { console.warn('[tour] op failed', op[0], err); }
634
+ }
635
+ if (my !== gen || done) return;
636
+ lastPlayed = beat.id;
637
+ fast = false;
638
+ setReady(true, beat);
639
+ }
640
+
641
+ /** Hurry: what is in flight runs at five times its speed, what is left is placed. */
642
+ function hurry() {
643
+ fast = true;
644
+ const anims = [...lineAnims, ...board.el.getAnimations({ subtree: true }), ...page.el.getAnimations({ subtree: true })];
645
+ for (const a of anims) { try { a.playbackRate = 5; } catch { /* finished */ } }
646
+ clearTimers();
647
+ }
648
+
649
+ function next() {
650
+ if (done) return;
651
+ if (listOpen && index < 0) return;
652
+ if (!ready) { hurry(); return; }
653
+ if (beats[index]?.last) { close('done'); return; }
654
+ play(index + 1);
655
+ }
656
+ function prev() { if (!done && index > 0) play(index - 1); }
657
+
658
+ // ── The chapter list: a returning user's way in, and the chapter name's menu mid-tour ──
659
+ function renderList() {
660
+ list.replaceChildren();
661
+ const current = index >= 0 ? chapters.indexOf(beats[index].chapter) : (resumeAt >= 0 ? chapters.indexOf(beats[resumeAt]?.chapter) : -1);
662
+ chapters.forEach((id, ci) => {
663
+ const row = node('button', 'tour-list-row');
664
+ row.type = 'button';
665
+ row.dataset.chapter = id;
666
+ row.append(node('span', 'tour-list-n', String(ci + 1)), node('span', 'tour-list-name', text(`tour.chapters.${id}`)));
667
+ if (ci === current) {
668
+ row.setAttribute('aria-current', 'step');
669
+ if (index < 0) row.append(node('span', 'tour-list-note', text('tour.ui.resume')));
670
+ }
671
+ row.addEventListener('click', (e) => {
672
+ e.stopPropagation();
673
+ const first = beats.findIndex(bt => bt.chapter === id);
674
+ // A returning user picking where they left off resumes that beat, not the chapter's first one.
675
+ play(index < 0 && ci === current && resumeAt >= 0 && beats[resumeAt]?.chapter === id ? resumeAt : first);
676
+ });
677
+ list.append(row);
678
+ });
679
+ if (onExplore) {
680
+ const explore = node('button', 'tour-list-row tour-list-explore', text('tour.ui.explore'));
681
+ explore.type = 'button';
682
+ explore.addEventListener('click', (e) => { e.stopPropagation(); close('skip', { then: onExplore }); });
683
+ list.append(explore);
684
+ }
685
+ }
686
+
687
+ function setList(open, { focus = true } = {}) {
688
+ if (done || open === listOpen) return;
689
+ listOpen = open;
690
+ if (open) renderList();
691
+ morph(() => {
692
+ caption.classList.toggle('is-list', open);
693
+ list.hidden = !open;
694
+ chapterBtn.setAttribute('aria-expanded', String(open));
695
+ });
696
+ if (!reduced()) {
697
+ const shown = open ? list : line;
698
+ shown.animate([{ opacity: 0, transform: 'translateY(4px)' }, { opacity: 1, transform: 'none' }], { duration: 200, easing: EASE.enter });
699
+ }
700
+ if (open && focus) (list.querySelector('[aria-current]') || list.firstElementChild)?.focus({ preventScroll: true });
701
+ else if (!open && focus) (ready ? nextBtn : chapterBtn).focus({ preventScroll: true });
702
+ }
703
+
704
+ /** Open on the chapter list (a returning user): the mascot wakes and waits beside it. */
705
+ async function openList() {
706
+ if (done) return;
707
+ const my = ++gen;
708
+ caption.dataset.place = 'foot';
709
+ chapterName.textContent = text('tour.ui.chapters');
710
+ root.classList.add('tour-board-live');
711
+ setReady(false, null);
712
+ nextBtn.hidden = true;
713
+ backBtn.hidden = true;
714
+ skipBtn.textContent = text('tour.ui.close');
715
+ setList(true);
716
+ if (announcer) announcer.textContent = text('tour.ui.chapters');
717
+ actor?.pose('idle');
718
+ actor?.react('hop');
719
+ await actor?.travel(places.corner()?.point || null, { scale: 0.8 });
720
+ if (my !== gen) return;
721
+ actor?.pose('present');
722
+ // The arm shows where to pick.
723
+ const rows = list.querySelector('[aria-current]') || list.firstElementChild;
724
+ if (rows) actor?.point(rows);
725
+ }
726
+
727
+ // ── Ending: the board wipes, the ring fades, the mascot flies home, the words come back ──
728
+ let closing = null;
729
+ function close(reason = 'skip', { then } = {}) {
730
+ if (closing) return closing;
731
+ done = true;
732
+ gen += 1;
733
+ clearTimers();
734
+ teardownInput();
735
+ closing = (async () => {
736
+ const calm = reason === 'sent' || reason === 'gone' || !root.isConnected;
737
+ if (!calm) {
738
+ caption.classList.add('is-leaving');
739
+ actor?.point(null);
740
+ ring.to(null);
741
+ root.classList.remove('tour-show-context');
742
+ await Promise.all([board.erase(), page.erase(), actor?.travel(null, { scale: 1 })]);
743
+ root.classList.remove('tour-board-live', 'tour-handoff');
744
+ if (!reduced()) {
745
+ await caption.animate([{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translateY(6px)' }], { duration: 200, easing: EASE.exit, fill: 'forwards' }).finished.catch(() => {});
746
+ }
747
+ }
748
+ actor?.point(null);
749
+ actor?.reset();
750
+ giveRoomBack();
751
+ ring.destroy();
752
+ board.destroy();
753
+ page.destroy();
754
+ caption.remove();
755
+ root.classList.remove('tour-on', 'tour-board-live', 'tour-handoff', 'tour-show-context');
756
+ if (hero && renderer.mascot?.() === hero) {
757
+ try { hero.rig.setPose('idle'); } catch { /* gone */ }
758
+ hero.hold?.(false);
759
+ hero.restore?.();
760
+ }
761
+ host.director?.present?.(false);
762
+ if (transcript.isConnected) transcript.scrollTop = scrollTop;
763
+ if (announcer && reason !== 'sent') announcer.textContent = '';
764
+ if (!calm && reason !== 'done' && previousFocus?.isConnected && previousFocus !== document.body) previousFocus.focus({ preventScroll: true });
765
+ onClose?.(reason);
766
+ then?.();
767
+ })();
768
+ return closing;
769
+ }
770
+
771
+ // ── Input ──
772
+ const typing = (el) => !!el?.closest?.('input, textarea, select, [contenteditable=""], [contenteditable="true"]');
773
+ function onKey(e) {
774
+ if (done || e.defaultPrevented) return;
775
+ const t = e.target;
776
+ if (e.key === 'Escape') {
777
+ if (document.querySelector('.asst-dd-menu') && !caption.contains(t)) return; // a menu the user opened closes first
778
+ e.preventDefault();
779
+ e.stopImmediatePropagation();
780
+ if (listOpen && index >= 0) { setList(false); return; }
781
+ close('skip');
782
+ return;
783
+ }
784
+ if (typing(t) || e.altKey || e.ctrlKey || e.metaKey) return;
785
+ if (listOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
786
+ const rows = [...list.querySelectorAll('button')];
787
+ const at = rows.indexOf(document.activeElement);
788
+ const to = rows[(at + (e.key === 'ArrowDown' ? 1 : -1) + rows.length) % rows.length];
789
+ e.preventDefault();
790
+ e.stopImmediatePropagation();
791
+ to?.focus({ preventScroll: true });
792
+ return;
793
+ }
794
+ // A focused button takes its own Enter and Space.
795
+ const onButton = !!t?.closest?.('button, a[href], [role="button"], [role="menuitem"]');
796
+ if (e.key === 'ArrowRight' || ((e.key === 'Enter' || e.key === ' ') && !onButton)) {
797
+ if (listOpen && index < 0) return;
798
+ e.preventDefault();
799
+ e.stopImmediatePropagation();
800
+ next();
801
+ } else if (e.key === 'ArrowLeft') {
802
+ if (listOpen) return;
803
+ e.preventDefault();
804
+ e.stopImmediatePropagation();
805
+ prev();
806
+ }
807
+ }
808
+ function onTap(e) {
809
+ if (done || e.button > 0) return;
810
+ if (caption.contains(e.target)) return;
811
+ // At the hand-off the starters are real again: a click on one is the user's, not a "continue".
812
+ if (e.target.closest?.('button, a, input, textarea, [role="button"], [role="listitem"]')) return;
813
+ if (listOpen) { if (index >= 0) setList(false); return; }
814
+ next();
815
+ }
816
+ window.addEventListener('keydown', onKey, true);
817
+ transcript.addEventListener('click', onTap);
818
+ nextBtn.addEventListener('click', (e) => { e.stopPropagation(); next(); });
819
+ backBtn.addEventListener('click', (e) => { e.stopPropagation(); prev(); });
820
+ skipBtn.addEventListener('click', (e) => { e.stopPropagation(); close('skip'); });
821
+ chapterBtn.addEventListener('click', (e) => { e.stopPropagation(); setList(!listOpen); });
822
+
823
+ // The panel changes under the tour: a resize replays the beat's places, the ring and the arms;
824
+ // the empty state going away (the user sent a message, the session closed) ends the tour at once.
825
+ let resizeTimer = 0;
826
+ const onResize = () => {
827
+ if (done) return;
828
+ const r = root.getBoundingClientRect();
829
+ if (!r.width || !r.height || !root.isConnected) { close('gone'); return; }
830
+ clearTimeout(resizeTimer);
831
+ resizeTimer = setTimeout(() => {
832
+ if (done) return;
833
+ board.measure();
834
+ actor?.measure();
835
+ const p = place ? fitPlace(typeof place === 'object' && place?.beside ? besidePlace(place.beside, place.scale) : places[place]?.()) : null;
836
+ if (place) actor?.replay(p?.point || null);
837
+ if (ringTarget) ring.refresh(targetRect(ringTarget), ringShapeFor(ringTarget));
838
+ if (ringTarget && actor?.rig.pointing) {
839
+ const els = resolve(ringTarget);
840
+ if (els) setTimeout(() => { if (!done) actor.point(els.length > 1 ? rectOfEls(els) : els[0], { gap: ringGap(ringTarget) }); }, 260);
841
+ }
842
+ }, 120);
843
+ };
844
+ const resizeObserver = typeof ResizeObserver === 'function' ? new ResizeObserver(onResize) : null;
845
+ resizeObserver?.observe(root);
846
+ window.addEventListener('resize', onResize);
847
+ // A send parks the hero (it stays the character on screen until the "Thinking" row takes it), so the
848
+ // empty state going is the signal, not the mascot changing.
849
+ const emptyWatch = new MutationObserver(() => {
850
+ if (done) return;
851
+ const now = renderer.mascot?.();
852
+ if (!transcript.querySelector(':scope > .asst-empty') || (hero && (now !== hero || hero.parked?.()))) close('sent');
853
+ });
854
+ emptyWatch.observe(transcript, { childList: true });
855
+ function teardownInput() {
856
+ window.removeEventListener('keydown', onKey, true);
857
+ window.removeEventListener('resize', onResize);
858
+ transcript.removeEventListener('click', onTap);
859
+ resizeObserver?.disconnect();
860
+ emptyWatch.disconnect();
861
+ clearTimeout(resizeTimer);
862
+ }
863
+
864
+ return {
865
+ play,
866
+ next,
867
+ prev,
868
+ close,
869
+ openList,
870
+ hurry,
871
+ get index() { return index; },
872
+ get ready() { return ready; },
873
+ get done() { return done; },
874
+ get listOpen() { return listOpen; },
875
+ get beat() { return beats[index] || null; },
876
+ /** The last landing the mascot was given, as asked and as fitted clear of the board's ink (tests read it). */
877
+ get layout() { return lastFit ? JSON.parse(JSON.stringify(lastFit)) : null; },
878
+ actor,
879
+ board,
880
+ page,
881
+ ring,
882
+ caption,
883
+ };
884
+ }