synabun 2.0.10 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (656) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +13 -13
  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/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +30 -17
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +7 -6
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +302 -0
  24. package/lib/virustotal.mjs +203 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  37. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  38. package/mcp-server/dist/services/budget-judgments.js +5 -3
  39. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  40. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  41. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  42. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  43. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +2 -2
  45. package/mcp-server/dist/services/identity.js +2 -2
  46. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  47. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  48. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  49. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  50. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  51. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  52. package/mcp-server/dist/services/memory-sessions.js +28 -2
  53. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  54. package/mcp-server/dist/services/neural-interface.d.ts +18 -11
  55. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  56. package/mcp-server/dist/services/neural-interface.js +67 -11
  57. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  58. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  59. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  60. package/mcp-server/dist/services/server-instructions.js +22 -63
  61. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  62. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  63. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  64. package/mcp-server/dist/services/tool-catalog.js +339 -0
  65. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  66. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  67. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  68. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  69. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  70. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  71. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  72. package/mcp-server/dist/services/tool-list.js +334 -0
  73. package/mcp-server/dist/services/tool-list.js.map +1 -0
  74. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  75. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  76. package/mcp-server/dist/services/tool-runtime.js +400 -0
  77. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  78. package/mcp-server/dist/services/typesafe-config.js +1 -1
  79. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  80. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  81. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  82. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  83. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  84. package/mcp-server/dist/tools/agents.d.ts +14 -14
  85. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  86. package/mcp-server/dist/tools/agents.js +29 -19
  87. package/mcp-server/dist/tools/agents.js.map +1 -1
  88. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  89. package/mcp-server/dist/tools/choice.d.ts +6 -0
  90. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  91. package/mcp-server/dist/tools/choice.js +18 -13
  92. package/mcp-server/dist/tools/choice.js.map +1 -1
  93. package/mcp-server/dist/tools/git-tools.js +2 -2
  94. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  95. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  96. package/mcp-server/dist/tools/image-tools.js +1 -1
  97. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  98. package/mcp-server/dist/tools/morelogin.js +9 -6
  99. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  100. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  101. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  102. package/mcp-server/dist/tools/skill-tools.js +642 -0
  103. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  104. package/mcp-server/dist/tools/skill.d.ts +7 -0
  105. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  106. package/mcp-server/dist/tools/skill.js +11 -0
  107. package/mcp-server/dist/tools/skill.js.map +1 -0
  108. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  109. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  110. package/mcp-server/dist/tools/tool-groups.js +147 -0
  111. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  112. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  113. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  114. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  115. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  116. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  117. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  118. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  119. package/mcp-server/package-lock.json +2 -2
  120. package/mcp-server/package.json +1 -1
  121. package/mcp-server/src/http.ts +75 -12
  122. package/mcp-server/src/index.ts +147 -154
  123. package/mcp-server/src/preload.ts +12 -12
  124. package/mcp-server/src/services/budget-judgments.ts +5 -3
  125. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  126. package/mcp-server/src/services/identity.ts +2 -2
  127. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  128. package/mcp-server/src/services/memory-sessions.ts +24 -3
  129. package/mcp-server/src/services/neural-interface.ts +81 -21
  130. package/mcp-server/src/services/server-instructions.ts +29 -64
  131. package/mcp-server/src/services/tool-catalog.ts +403 -0
  132. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  133. package/mcp-server/src/services/tool-list.ts +410 -0
  134. package/mcp-server/src/services/tool-runtime.ts +457 -0
  135. package/mcp-server/src/services/typesafe-config.ts +1 -1
  136. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  137. package/mcp-server/src/tools/agents.ts +29 -20
  138. package/mcp-server/src/tools/choice.ts +17 -12
  139. package/mcp-server/src/tools/git-tools.ts +2 -2
  140. package/mcp-server/src/tools/image-tools.ts +1 -1
  141. package/mcp-server/src/tools/morelogin.ts +6 -4
  142. package/mcp-server/src/tools/skill-tools.ts +592 -0
  143. package/mcp-server/src/tools/skill.ts +13 -0
  144. package/mcp-server/src/tools/tool-groups.ts +164 -0
  145. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  146. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  147. package/neural-interface/i18n/en.json +3953 -228
  148. package/neural-interface/i18n/pt-BR.json +3901 -179
  149. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  150. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  151. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  152. package/neural-interface/lib/antigravity/bypass.js +100 -0
  153. package/neural-interface/lib/antigravity/catalog.js +273 -0
  154. package/neural-interface/lib/antigravity/home.js +260 -0
  155. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  156. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  157. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  158. package/neural-interface/lib/antigravity/policy.js +74 -0
  159. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  160. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  161. package/neural-interface/lib/antigravity/usage.js +43 -0
  162. package/neural-interface/lib/assistant-api.js +128 -28
  163. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  164. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  165. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  166. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  167. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  168. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  169. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  170. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  171. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  172. package/neural-interface/lib/assistant-budget-split.js +42 -15
  173. package/neural-interface/lib/assistant-catalog.js +81 -15
  174. package/neural-interface/lib/assistant-config.js +6 -3
  175. package/neural-interface/lib/assistant-dispatch.js +1073 -200
  176. package/neural-interface/lib/assistant-envelope.js +156 -2
  177. package/neural-interface/lib/assistant-evidence.js +80 -0
  178. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  179. package/neural-interface/lib/assistant-persona.js +93 -19
  180. package/neural-interface/lib/assistant-plan-permissions.js +25 -9
  181. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  182. package/neural-interface/lib/assistant-route-gate.js +6 -4
  183. package/neural-interface/lib/assistant-router.js +245 -38
  184. package/neural-interface/lib/assistant-runtime.js +214 -30
  185. package/neural-interface/lib/assistant-task-prompt.js +87 -21
  186. package/neural-interface/lib/assistant-tools.js +51 -0
  187. package/neural-interface/lib/assistant-usage.js +29 -0
  188. package/neural-interface/lib/backup-service.js +626 -4
  189. package/neural-interface/lib/backup-zip-reader.js +10 -5
  190. package/neural-interface/lib/backup-zip-writer.js +257 -0
  191. package/neural-interface/lib/browser-execution.js +10 -2
  192. package/neural-interface/lib/browser-guard-cdp.js +84 -0
  193. package/neural-interface/lib/browser-network-guard.js +476 -0
  194. package/neural-interface/lib/browser-policy-roots.js +64 -0
  195. package/neural-interface/lib/browser-tool-policy.js +198 -38
  196. package/neural-interface/lib/claude-agent-bridge.js +11 -2
  197. package/neural-interface/lib/claude-history-cache.js +210 -0
  198. package/neural-interface/lib/claude-hooks-status.js +103 -0
  199. package/neural-interface/lib/claude-model-catalog.js +5 -1
  200. package/neural-interface/lib/claude-temporary.js +5 -11
  201. package/neural-interface/lib/cli-install-api.js +1 -1
  202. package/neural-interface/lib/cli-install.js +163 -154
  203. package/neural-interface/lib/cli-resolver.js +13 -3
  204. package/neural-interface/lib/cli-update-planner.js +52 -41
  205. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  206. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  207. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  208. package/neural-interface/lib/codex-worker-app-server.js +191 -0
  209. package/neural-interface/lib/confined-fs.js +30 -3
  210. package/neural-interface/lib/crash-notice-api.js +98 -0
  211. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  212. package/neural-interface/lib/editor-git-api.js +3 -20
  213. package/neural-interface/lib/exec-loop-command.js +13 -10
  214. package/neural-interface/lib/file-explorer-api.js +74 -40
  215. package/neural-interface/lib/file-explorer-watch.js +9 -3
  216. package/neural-interface/lib/git-bash/install.js +52 -6
  217. package/neural-interface/lib/git-bash/manager.js +59 -17
  218. package/neural-interface/lib/git-bash/pins.js +12 -0
  219. package/neural-interface/lib/git-read.js +2 -2
  220. package/neural-interface/lib/git-status-runner.js +390 -0
  221. package/neural-interface/lib/git-status-watchdog.js +40 -0
  222. package/neural-interface/lib/greeting/budgets.js +1 -0
  223. package/neural-interface/lib/greeting/engine.js +2 -2
  224. package/neural-interface/lib/greeting/sources.js +10 -12
  225. package/neural-interface/lib/greeting/writer.js +1 -1
  226. package/neural-interface/lib/http-guards.js +8 -2
  227. package/neural-interface/lib/install-details.js +4 -1
  228. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  229. package/neural-interface/lib/mcp-client-config.js +35 -33
  230. package/neural-interface/lib/mcp-external-servers.js +332 -0
  231. package/neural-interface/lib/mcp-installer.js +2 -1
  232. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  233. package/neural-interface/lib/memory-session-capture.js +17 -3
  234. package/neural-interface/lib/morelogin.js +177 -25
  235. package/neural-interface/lib/native-loop-providers.js +965 -66
  236. package/neural-interface/lib/native-loop-runtime.js +290 -42
  237. package/neural-interface/lib/opencode-config.js +557 -0
  238. package/neural-interface/lib/opencode-db-schema.js +17 -34
  239. package/neural-interface/lib/opencode-history.js +48 -218
  240. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  241. package/neural-interface/lib/opencode-native-boundary.js +53 -0
  242. package/neural-interface/lib/opencode-protocol.js +249 -112
  243. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  244. package/neural-interface/lib/opencode-serve-records.js +39 -21
  245. package/neural-interface/lib/opencode-v2-client.js +48 -153
  246. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  247. package/neural-interface/lib/opencode-worker-data.js +27 -0
  248. package/neural-interface/lib/opencode2-client.js +73 -37
  249. package/neural-interface/lib/opencode2-proxy.js +23 -63
  250. package/neural-interface/lib/opencode2-translate.js +51 -2
  251. package/neural-interface/lib/owner-authority.js +249 -0
  252. package/neural-interface/lib/page-session.js +275 -0
  253. package/neural-interface/lib/path-confine.js +12 -9
  254. package/neural-interface/lib/peer-identity.js +793 -0
  255. package/neural-interface/lib/pty-host/core.js +28 -1
  256. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  257. package/neural-interface/lib/pty-host/manager.js +12 -1
  258. package/neural-interface/lib/remote-policy.js +1 -1
  259. package/neural-interface/lib/request-gate.js +20 -2
  260. package/neural-interface/lib/rulesets/api.js +7 -6
  261. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  262. package/neural-interface/lib/rulesets/installer.js +180 -36
  263. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  264. package/neural-interface/lib/rulesets/render.js +1 -1
  265. package/neural-interface/lib/session-cache-builder.js +81 -31
  266. package/neural-interface/lib/session-title-generator.js +58 -36
  267. package/neural-interface/lib/skills/api-v2.js +495 -0
  268. package/neural-interface/lib/skills/bundle.js +418 -0
  269. package/neural-interface/lib/skills/catalog.js +298 -0
  270. package/neural-interface/lib/skills/creation-api.js +191 -0
  271. package/neural-interface/lib/skills/creation-brief.js +247 -0
  272. package/neural-interface/lib/skills/creation-sources.js +285 -0
  273. package/neural-interface/lib/skills/creation.js +843 -0
  274. package/neural-interface/lib/skills/deploy.js +569 -0
  275. package/neural-interface/lib/skills/digest.js +119 -0
  276. package/neural-interface/lib/skills/discover.js +111 -0
  277. package/neural-interface/lib/skills/draft-confinement.js +216 -0
  278. package/neural-interface/lib/skills/eval-confinement.js +399 -0
  279. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  280. package/neural-interface/lib/skills/eval-trace.js +135 -0
  281. package/neural-interface/lib/skills/evals-api.js +212 -0
  282. package/neural-interface/lib/skills/evals.js +1573 -0
  283. package/neural-interface/lib/skills/export.js +203 -0
  284. package/neural-interface/lib/skills/frontmatter.js +64 -0
  285. package/neural-interface/lib/skills/health.js +75 -0
  286. package/neural-interface/lib/skills/json-edit.js +165 -0
  287. package/neural-interface/lib/skills/known-bridge-digests.json +441 -0
  288. package/neural-interface/lib/skills/known-bridges.js +96 -0
  289. package/neural-interface/lib/skills/library.js +823 -0
  290. package/neural-interface/lib/skills/local-store.js +21 -0
  291. package/neural-interface/lib/skills/migration.js +433 -0
  292. package/neural-interface/lib/skills/operations.js +45 -0
  293. package/neural-interface/lib/skills/ownership.js +427 -0
  294. package/neural-interface/lib/skills/packages.js +88 -0
  295. package/neural-interface/lib/skills/reviews.js +212 -0
  296. package/neural-interface/lib/skills/revision.js +80 -0
  297. package/neural-interface/lib/skills/roots.js +269 -0
  298. package/neural-interface/lib/skills/runtime-checks.js +358 -0
  299. package/neural-interface/lib/skills/scan-assets.js +252 -0
  300. package/neural-interface/lib/skills/scan-context.js +26 -0
  301. package/neural-interface/lib/skills/scan-shell.js +324 -0
  302. package/neural-interface/lib/skills/scan.js +364 -0
  303. package/neural-interface/lib/skills/scanners.js +54 -0
  304. package/neural-interface/lib/skills/service.js +530 -0
  305. package/neural-interface/lib/skills/telemetry.js +119 -0
  306. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  307. package/neural-interface/lib/synabun-version.js +1 -1
  308. package/neural-interface/lib/system-activity.js +1 -1
  309. package/neural-interface/lib/tool-lists.js +124 -0
  310. package/neural-interface/lib/whatsapp/brain.js +7 -3
  311. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  312. package/neural-interface/lib/whatsapp/cards.js +22 -9
  313. package/neural-interface/lib/whatsapp/config.js +1 -1
  314. package/neural-interface/lib/worker-confinement.js +1268 -0
  315. package/neural-interface/lib/worker-guard-setting.js +56 -0
  316. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  317. package/neural-interface/lib/worker-question.js +74 -0
  318. package/neural-interface/lib/worker-relay-ports.js +227 -0
  319. package/neural-interface/lib/worker-resume.js +238 -0
  320. package/neural-interface/lib/worker-sandbox.js +329 -0
  321. package/neural-interface/package-lock.json +34 -12
  322. package/neural-interface/package.json +3 -2
  323. package/neural-interface/public/index.html +3 -1
  324. package/neural-interface/public/index2d.html +19 -11
  325. package/neural-interface/public/onboarding.html +3 -3
  326. package/neural-interface/public/shared/account-menu.js +8 -5
  327. package/neural-interface/public/shared/agent-runtime-options.js +125 -31
  328. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  329. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  330. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  331. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  332. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  333. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  334. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  335. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  336. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  337. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  338. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  339. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  340. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  341. package/neural-interface/public/shared/api.js +1527 -1336
  342. package/neural-interface/public/shared/app-frame.js +192 -0
  343. package/neural-interface/public/shared/assistant/asst-agents-dock.js +36 -6
  344. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  345. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  346. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  347. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  348. package/neural-interface/public/shared/assistant/asst-brain-picker.js +105 -44
  349. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  350. package/neural-interface/public/shared/assistant/asst-control.js +205 -26
  351. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  352. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  353. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  354. package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
  355. package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -0
  356. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  357. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  358. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  359. package/neural-interface/public/shared/assistant/asst-panel.js +151 -19
  360. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  361. package/neural-interface/public/shared/assistant/asst-render.js +445 -82
  362. package/neural-interface/public/shared/assistant/asst-route.js +304 -53
  363. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  364. package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
  365. package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
  366. package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
  367. package/neural-interface/public/shared/assistant/asst-state.js +87 -24
  368. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  369. package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
  370. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
  371. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  372. package/neural-interface/public/shared/automation-context.js +5 -2
  373. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  374. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  375. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  376. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  377. package/neural-interface/public/shared/cdx/cdx-output.js +34 -10
  378. package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
  379. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  380. package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
  381. package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
  382. package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
  383. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  384. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  385. package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
  386. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  387. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  388. package/neural-interface/public/shared/cli-install-view.js +11 -12
  389. package/neural-interface/public/shared/cli-status.js +11 -10
  390. package/neural-interface/public/shared/constants.js +5 -4
  391. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  392. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  393. package/neural-interface/public/shared/context-menu-model.js +367 -0
  394. package/neural-interface/public/shared/context-menu.js +372 -0
  395. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  396. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  397. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  398. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  399. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  400. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  401. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  402. package/neural-interface/public/shared/desktop-update-view.js +21 -0
  403. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  404. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  405. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  406. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  407. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  408. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  409. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  410. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  411. package/neural-interface/public/shared/html-shell.js +30 -15
  412. package/neural-interface/public/shared/i18n.js +2 -0
  413. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  414. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  415. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  416. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  417. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  418. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  419. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  420. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  421. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  422. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  423. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  424. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +74 -259
  425. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  426. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  427. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  428. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
  429. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  430. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  431. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  432. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  433. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  434. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  435. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  436. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -21
  437. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  438. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +60 -50
  439. package/neural-interface/public/shared/page-session.js +96 -0
  440. package/neural-interface/public/shared/provider-icons.js +4 -2
  441. package/neural-interface/public/shared/registry.js +5 -3
  442. package/neural-interface/public/shared/retired-2d.js +3 -0
  443. package/neural-interface/public/shared/schedules-view.js +195 -61
  444. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  445. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  446. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  447. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  448. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  449. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  450. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  451. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  452. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  453. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  454. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  455. package/neural-interface/public/shared/skills/ss-creation.js +222 -0
  456. package/neural-interface/public/shared/skills/ss-dialog.js +133 -0
  457. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  458. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  459. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  460. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  461. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  462. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  463. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  464. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  465. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  466. package/neural-interface/public/shared/skills/ss-lint.js +149 -0
  467. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  468. package/neural-interface/public/shared/skills/ss-profiles.js +513 -0
  469. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  470. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  471. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  472. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  473. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  474. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  475. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  476. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +237 -0
  477. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  478. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  479. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  480. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  481. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  482. package/neural-interface/public/shared/skills/studio/sx-dom.js +230 -0
  483. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  484. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  485. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  486. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  487. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  488. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  489. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  490. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  491. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +282 -0
  492. package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
  493. package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
  494. package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
  495. package/neural-interface/public/shared/skills/studio/sx-main.js +690 -0
  496. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  497. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  498. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  499. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  500. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  501. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  502. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  503. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  504. package/neural-interface/public/shared/skills/studio/sx-subject.js +249 -0
  505. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  506. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  507. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  508. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +426 -0
  509. package/neural-interface/public/shared/state.js +1 -1
  510. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  511. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  512. package/neural-interface/public/shared/styles.css +1822 -1756
  513. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  514. package/neural-interface/public/shared/text-cache.js +29 -0
  515. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  516. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  517. package/neural-interface/public/shared/tool-list-view.js +112 -0
  518. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  519. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  520. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  521. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  522. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  523. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  524. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  525. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  526. package/neural-interface/public/shared/tour/tour.css +144 -0
  527. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  528. package/neural-interface/public/shared/ui-assistant.js +2 -0
  529. package/neural-interface/public/shared/ui-automation-studio.js +111 -140
  530. package/neural-interface/public/shared/ui-claude-panel.js +376 -82
  531. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  532. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  533. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  534. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  535. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  536. package/neural-interface/public/shared/ui-detail.js +15 -3
  537. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  538. package/neural-interface/public/shared/ui-file-links.js +2 -2
  539. package/neural-interface/public/shared/ui-games.js +2 -2
  540. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  541. package/neural-interface/public/shared/ui-judgments.js +1 -1
  542. package/neural-interface/public/shared/ui-keybinds.js +33 -2
  543. package/neural-interface/public/shared/ui-link.js +3 -3
  544. package/neural-interface/public/shared/ui-menubar.js +10 -20
  545. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  546. package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
  547. package/neural-interface/public/shared/ui-navbar.js +49 -24
  548. package/neural-interface/public/shared/ui-notifications.js +1 -0
  549. package/neural-interface/public/shared/ui-panels.js +96 -65
  550. package/neural-interface/public/shared/ui-resume.js +9 -7
  551. package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
  552. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  553. package/neural-interface/public/shared/ui-sessions.js +27 -15
  554. package/neural-interface/public/shared/ui-settings.js +683 -526
  555. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  556. package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
  557. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  558. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  559. package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
  560. package/neural-interface/public/shared/ui-skills.js +33 -2830
  561. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  562. package/neural-interface/public/shared/ui-sync.js +15 -5
  563. package/neural-interface/public/shared/ui-terminal.js +270 -190
  564. package/neural-interface/public/shared/ui-tool-updates.js +6 -4
  565. package/neural-interface/public/shared/ui-tooltip.js +81 -51
  566. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  567. package/neural-interface/public/shared/ui-trash.js +6 -4
  568. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  569. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  570. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  571. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  572. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  573. package/neural-interface/public/shared/ui-update.js +38 -2
  574. package/neural-interface/public/shared/ui-whiteboard.js +312 -59
  575. package/neural-interface/public/shared/ui-worker-end.js +5 -1
  576. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  577. package/neural-interface/public/shared/view-variant.js +66 -0
  578. package/neural-interface/public/shared/whatsapp/wa-view.js +3 -3
  579. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  580. package/neural-interface/public/shared/worker-end.js +10 -3
  581. package/neural-interface/public/shared/worker-questions.js +34 -0
  582. package/neural-interface/public/shared/worker-restart.js +12 -0
  583. package/neural-interface/public/sidepanel-session.html +2 -2
  584. package/neural-interface/public/variant/2d/background.js +2 -1
  585. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  586. package/neural-interface/public/variant/2d/graph.js +42 -20
  587. package/neural-interface/public/variant/2d/hulls.js +14 -9
  588. package/neural-interface/public/variant/2d/lasso.js +11 -7
  589. package/neural-interface/public/variant/2d/main.js +32 -1
  590. package/neural-interface/public/variant/2d/minimap.js +11 -13
  591. package/neural-interface/public/variant/3d/main.js +69 -6
  592. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  593. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  594. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  595. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  596. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  597. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  598. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  599. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  600. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  601. package/neural-interface/scripts/build-vendor.mjs +32 -0
  602. package/neural-interface/server.js +4553 -2855
  603. package/neural-interface/templates/rulesets/README.md +1 -1
  604. package/neural-interface/templates/rulesets/core.md +1 -1
  605. package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
  606. package/neural-interface/templates/rulesets/manifest.json +8 -8
  607. package/npm-shrinkwrap.json +34 -12
  608. package/package.json +17 -3
  609. package/postinstall.js +9 -4
  610. package/preuninstall.js +1 -1
  611. package/setup.js +97 -322
  612. package/skills/skill-forge/SKILL.md +177 -0
  613. package/skills/skill-forge/evals/evals.json +54 -0
  614. package/skills/skill-forge/references/package.md +105 -0
  615. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  616. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  617. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  618. package/skills/synabun/SKILL.md +10 -7
  619. package/skills/synabun/modules/audit.md +1 -5
  620. package/skills/synabun/modules/changelog.md +1 -5
  621. package/skills/synabun/modules/idea.md +1 -1
  622. package/skills/synabun/modules/memorize.md +6 -9
  623. package/skills/synabun/modules/schedule.md +6 -6
  624. package/mcp-server/dist/services/profiles.d.ts +0 -120
  625. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  626. package/mcp-server/dist/services/profiles.js +0 -387
  627. package/mcp-server/dist/services/profiles.js.map +0 -1
  628. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  629. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  630. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  631. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  632. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  633. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  634. package/mcp-server/dist/tools/leonardo.js +0 -21
  635. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  636. package/mcp-server/dist/tools/profile.d.ts +0 -17
  637. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  638. package/mcp-server/dist/tools/profile.js +0 -114
  639. package/mcp-server/dist/tools/profile.js.map +0 -1
  640. package/mcp-server/src/services/profiles.ts +0 -428
  641. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  642. package/mcp-server/src/tools/leonardo.ts +0 -30
  643. package/mcp-server/src/tools/profile.ts +0 -122
  644. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  645. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  646. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  647. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  648. package/skills/leonardo/SKILL.md +0 -168
  649. package/skills/leonardo/icon.svg +0 -1
  650. package/skills/leonardo/modules/browser-guide.md +0 -378
  651. package/skills/leonardo/modules/image-prompter.md +0 -283
  652. package/skills/leonardo/modules/model-advisor.md +0 -100
  653. package/skills/leonardo/modules/prompt-library.md +0 -110
  654. package/skills/leonardo/modules/style-guide.md +0 -186
  655. package/skills/leonardo/modules/video-prompter.md +0 -317
  656. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -1,53 +1,33 @@
1
- // A reversible, viewport-based tour shared by both variants. It annotates the
2
- // user's current view: no config writes, forced focus mode or session creation.
3
- // What it changes to show a step (an open menu, the Assistant panel) it puts back.
1
+ // The Workspace tour's entry, and the project-exploration setup.
2
+ //
3
+ // The tour itself is public/shared/tour/: SynaBun's mascot narrates from its home in
4
+ // the Assistant panel and uses the panel as a whiteboard. This module opens it: from
5
+ // the ? button (a returning user gets the chapter list), or from the empty state's
6
+ // first-visit starter row ("Show me the workspace · 2 min", asst-panel.js). It never
7
+ // opens by itself. It presents the panel without starting a session
8
+ // (beginAssistantTour), keeps the saved cursor and the completed / skipped flags,
9
+ // and puts everything back when the tour ends.
10
+ //
11
+ // The setup (ONBOARDING_STEPS: explore → tokens → CLI → model → project → launch) is
12
+ // a separate small dialog, reached from the tour's chapter list. Only its explicit
13
+ // "Begin exploration" button starts a CLI; nothing else here starts work.
4
14
  import { storage, flushStorage } from './storage.js';
5
- import { state, emit, on } from './state.js';
15
+ import { emit, on } from './state.js';
6
16
  import { KEYS } from './constants.js';
7
17
  import { t } from './i18n.js';
8
- import { registerHelpSection, getVariant } from './registry.js';
9
- import { TUTORIAL_STEPS, FEATURE_STEPS, SKIP_HINT, CHAPTERS, buildExplorePrompt, resumeIndex } from './ui-tutorial-steps.js';
18
+ import { registerHelpSection } from './registry.js';
19
+ import { ONBOARDING_STEPS, buildExplorePrompt } from './ui-tutorial-steps.js';
10
20
  import { CLI_PROFILES, ensureModelsForProfile, modelSelectorValue, formatModelOptionLabel } from './agent-runtime-options.js';
11
- import { isAssistantPanelOpen, toggleAssistantPanel, assistantPanelPeekable } from './assistant/asst-sidepanel.js';
12
- import { createOutline, createAnimatedArrow, createHandDrawnUnderline, createDoodle } from './ui-tutorial-draw.js';
13
- import { visibleRect, unionRect, outlineShape, outlineRect, placeNote, arrowPoints } from './ui-tutorial-layout.js';
21
+ import { beginAssistantTour } from './assistant/asst-sidepanel.js';
22
+ import { dropdownHTML, setDropdownDisabled, setDropdownItems, setDropdownValue, wireDropdowns } from './cc-dropdown.js';
23
+ import { showGuestToast } from './ui-sync.js';
24
+ import { BEATS, beatsFor, resumeBeat } from './tour/tour-beats.js';
25
+ import { markTourSeen, tourReturning } from './tour/tour-state.js';
14
26
 
15
- const NS = 'http://www.w3.org/2000/svg';
16
- const INK = '#6da4ff'; // target outline, arrow, underline (Style Guide dark primary)
17
- const CHALK = 'rgba(240, 243, 247, 0.62)'; // the note's own outline
18
- const DRAW = 250; // one stroke (Style Guide motion "normal")
19
- const ERASE = 120; // the previous step's strokes fading out
20
- const SETTLE_MAX = 600; // longest wait for an opening menu or panel to stop moving
21
- const GUARDED = ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'click', 'dblclick', 'contextmenu',
22
- 'touchstart', 'dragstart', 'pointerover', 'mouseover', 'mouseenter', 'mousemove'];
23
- // Chrome the note leaves visible when there is free space.
24
- const KEEP_CLEAR = ['#title-bar', '#topright-controls', '#term-minimized-tray', '#wb-toolbar'];
25
- const PEER_PANEL_BUTTONS = ['#topright-claude-panel-btn', '#topright-codex-panel-btn', '#topright-opencode-panel-btn'];
26
- // Names the copy mentions, read off the control on screen so the text matches it
27
- // in every locale; the i18n key covers a control that is not in the DOM.
28
- const NAMES = {
29
- explorer: ['#topright-memory-explorer-btn', 'tutorial.names.explorer'],
30
- focus: ['#titlebar-viz-toggle', 'tooltip.toggleViz'],
31
- apps: ['[data-menu="apps"] > .menubar-label', 'nav.apps'],
32
- browser: ['#menu-terminal-browser', 'menu.apps.browser'],
33
- studio: ['#menu-open-automation-studio', 'menu.automations.studio'],
34
- schedules: ['#menubar-schedules-btn', 'tutorial.names.schedules'],
35
- keybinds: ['#topright-keybinds-btn', 'tutorial.names.keybinds'],
36
- settings: ['#menubar-settings-btn', 'nav.settings'],
37
- projects: [null, 'settings.redesign.nav.projects.label'],
38
- computer: [null, 'assistant.computer.toggle'],
39
- more: [null, 'assistant.toolbar.more'],
40
- budget: [null, 'assistant.budget.title'],
41
- };
42
- const WELCOME = TUTORIAL_STEPS.findIndex(s => s.id === 'welcome');
43
-
44
- let active = false, initialized = false, index = WELCOME, firstRun = false, skipHint = false, resumed = false;
45
- let svg, dom, note, ui, observer, targetObserver, previousFocus, nextButton;
46
- let inks = {}; // the strokes on screen, by part: { g, key }
47
- let menuState = [], panelPeek = null, passthrough = false, placed = false, lastBox = null;
48
- let onScreen = null; // the step whose text is in the note: the next one is drawn only once its target holds still
49
- let timers = new Set(), generation = 0, resizeFrame = 0, stepAbort = null;
50
- let offLoaded = null, autoTimer = null, lastGeometry = '', beforeAdvance = null, onPrimary = null, onLater = null, ready = null;
27
+ let initialized = false;
28
+ let ready = null; // the tour's stylesheet and its handwriting, loaded once
29
+ let tour = null; // { engine, presentation }
30
+ let opening = false;
51
31
 
52
32
  const reduced = () => matchMedia('(prefers-reduced-motion: reduce)').matches;
53
33
  const tr = (key, params) => t(`tutorial.${key}`, params);
@@ -57,552 +37,348 @@ const node = (tag, cls, text) => {
57
37
  if (text != null) el.textContent = text;
58
38
  return el;
59
39
  };
60
- const currentStep = () => (skipHint ? SKIP_HINT : TUTORIAL_STEPS[index]);
61
-
62
- function later(fn, ms) {
63
- const id = setTimeout(() => { timers.delete(id); fn(); }, ms);
64
- timers.add(id);
65
- return id;
66
- }
67
- function clearStep() {
68
- generation++;
69
- for (const id of timers) clearTimeout(id);
70
- timers.clear();
71
- stepAbort?.abort(); stepAbort = null;
72
- }
73
-
74
- // ── What the copy and the annotations read from the live UI ──
75
40
 
76
- function names() {
77
- const out = {};
78
- for (const [name, [selector, key]] of Object.entries(NAMES)) {
79
- const el = selector && document.querySelector(selector);
80
- const label = el && (el.querySelector('.menu-text')?.textContent || el.dataset.tooltip || el.textContent);
81
- // A tooltip may end in its shortcut: "Focus Mode (V)".
82
- out[name] = label?.replace(/\s*\([^)]*\)\s*$/, '').trim() || t(key);
83
- }
84
- // 2D: the Graph menu's label. 3D: the label of the Map row in Apps, which opens its submenu.
85
- out.menu = document.querySelector('[data-menu="graph"] > .menubar-label, #menu-map-label')?.textContent.trim()
86
- || t(getVariant() === '2d' ? 'nav.graph' : 'nav.map');
87
- out.view = out.menu.toLocaleLowerCase();
88
- return out;
89
- }
90
- // A dropdown the tour opened is still fading in when its item is first measured.
91
- const shown = (el) => !el.checkVisibility
92
- || el.checkVisibility({ checkOpacity: !el.closest('.menubar-item.open, #assistant-panel.open'), checkVisibilityCSS: true });
93
- function targetRect(selector, lastSelector) {
94
- for (const el of document.querySelectorAll(selector)) {
95
- if (!shown(el)) continue;
96
- const last = lastSelector && document.querySelector(lastSelector);
97
- const rect = unionRect([el.getBoundingClientRect(), last && shown(last) ? last.getBoundingClientRect() : null]);
98
- if (visibleRect(rect, innerWidth, innerHeight)) return rect;
99
- }
100
- return null;
101
- }
102
- // The first alternative on screen. A hidden toolbar (or a guest's missing control) leaves `missing`.
103
- function resolveStep(step) {
104
- const targets = step.targets || [];
105
- for (let i = 0; i < targets.length; i++) {
106
- const [selector, copy = 'body', lastSelector] = targets[i];
107
- const rect = targetRect(selector, lastSelector);
108
- if (rect) return { rect, alternative: i, copy: i === 0 && step.copy?.[getVariant()] || copy, missing: false };
109
- }
110
- return { rect: null, alternative: -1, copy: step.copy?.[getVariant()] || 'body', missing: targets.length > 0 };
111
- }
112
-
113
- // ── What the tour changes to show a step, and puts back ──
114
-
115
- // The dropdown a step opens, which can differ per variant ({ '3d': 'apps' }).
116
- const stepMenu = (step) => (step.menu && typeof step.menu === 'object' ? step.menu[getVariant()] : step.menu) || null;
117
- // The submenu of that dropdown the step opens, by its data-submenu name ({ '3d': 'map' }).
118
- const stepSubmenu = (step) => (step.submenu && typeof step.submenu === 'object' ? step.submenu[getVariant()] : step.submenu) || null;
119
-
120
- function showStage(step) {
121
- const menu = stepMenu(step);
122
- for (const [el, open] of menuState) {
123
- if (el.isConnected) el.classList.toggle('open', menu ? el.dataset.menu === menu : open);
124
- }
125
- // ui-menubar.js opens it beside its row, or closes the one a previous step opened.
126
- emit('menubar:submenu', menu ? stepSubmenu(step) : null);
127
- if (step.panel !== 'assistant') { hidePanelPeek(); return; }
128
- // With no session tab, opening the Assistant starts a session: its button is annotated instead.
129
- if (panelPeek || isAssistantPanelOpen() || !assistantPanelPeekable()) return;
130
- panelPeek = {
131
- peers: PEER_PANEL_BUTTONS.map(s => document.querySelector(s)).filter(b => b?.classList.contains('active')),
132
- lastActivePanel: state.lastActivePanel, lastActiveSidepanel: state.lastActiveSidepanel,
133
- };
134
- toggleAssistantPanel();
135
- }
136
- function hidePanelPeek() {
137
- if (!panelPeek) return;
138
- const peek = panelPeek; panelPeek = null;
139
- if (isAssistantPanelOpen()) toggleAssistantPanel();
140
- // Showing the Assistant hid the docked panel that was open: bring it back.
141
- passthrough = true;
142
- for (const button of peek.peers) if (!button.classList.contains('active')) button.click();
143
- passthrough = false;
144
- state.lastActivePanel = peek.lastActivePanel; state.lastActiveSidepanel = peek.lastActiveSidepanel;
145
- }
146
- function restoreStage() {
147
- emit('menubar:submenu', null);
148
- for (const [el, open] of menuState) if (el.isConnected) el.classList.toggle('open', open);
149
- hidePanelPeek();
41
+ function loadStyles() {
42
+ if (ready) return ready;
43
+ const sheets = ['/shared/tour/tour.css', '/shared/ui-tutorial.css', '/vendor/caveat/caveat.css'].map((href) => {
44
+ if (document.querySelector(`link[href="${href}"]`)) return Promise.resolve();
45
+ const link = node('link');
46
+ link.rel = 'stylesheet';
47
+ link.href = href;
48
+ const loaded = new Promise((done) => { link.onload = link.onerror = done; });
49
+ document.head.append(link);
50
+ return loaded;
51
+ });
52
+ // The handwriting is measured as it is written: wait for it (briefly; the fallback is cursive).
53
+ ready = Promise.race([
54
+ Promise.all([...sheets, document.fonts?.load('700 21px Caveat').catch(() => {}), document.fonts?.load('400 17px Caveat').catch(() => {})]),
55
+ new Promise(done => setTimeout(done, 1200)),
56
+ ]);
57
+ return ready;
150
58
  }
151
59
 
152
60
  export function initTutorial() {
153
61
  if (initialized) return;
154
62
  initialized = true;
155
- const css = node('link'); css.rel = 'stylesheet'; css.href = '/shared/ui-tutorial.css'; css.id = 'tutorial-styles';
156
- // The first note is measured with its stylesheet and handwriting font in place.
157
- ready = Promise.race([
158
- Promise.all([new Promise(done => { css.onload = css.onerror = done; }),
159
- document.fonts?.load("700 34px 'Caveat'").catch(() => {}), document.fonts?.load("26px 'Caveat'").catch(() => {})]),
160
- new Promise(done => setTimeout(done, 1200)),
161
- ]);
162
- document.head.append(css);
163
63
  const opener = document.querySelector('#titlebar-tutorial-btn');
164
64
  if (opener) {
165
- opener.dataset.tooltip = tr('title');
166
- opener.setAttribute('aria-label', tr('title'));
65
+ opener.dataset.tooltip = t('tour.title');
66
+ opener.setAttribute('aria-label', t('tour.title'));
167
67
  opener.setAttribute('aria-pressed', 'false');
168
68
  }
169
69
  // No HTML interpolation of translations: help registration accepts static HTML.
170
70
  const help = node('div', 'help-section');
171
- help.append(node('div', 'help-section-title', tr('title')), node('div', 'help-row', tr('helpDescription')));
71
+ help.append(node('div', 'help-section-title', t('tour.title')), node('div', 'help-row', tr('helpDescription')));
172
72
  registerHelpSection({ order: 95, html: help.outerHTML });
173
- if (![KEYS.TUTORIAL_COMPLETED, KEYS.TUTORIAL_SKIPPED, KEYS.TUTORIAL_STARTED].some(k => storage.getItem(k))) {
174
- offLoaded = on('data-loaded', () => {
175
- offLoaded?.(); offLoaded = null;
176
- autoTimer = setTimeout(() => { autoTimer = null; if (!active) startTutorial(false); }, 500);
177
- });
178
- }
73
+ // The empty state's starter row asks for the tour on the panel it sits in.
74
+ on('tour:open', (request = {}) => { startTour({ host: request.host || null, from: request.from || 'starter' }); });
75
+ window.addEventListener('pagehide', () => destroyTutorial());
179
76
  }
180
77
 
181
- export { resumeIndex } from './ui-tutorial-steps.js';
182
-
183
- // Navbar calls startTutorial(true): it is a toggle, preserving every choice.
184
- export function startTutorial(manual = false) {
185
- if (autoTimer) { clearTimeout(autoTimer); autoTimer = null; }
186
- offLoaded?.(); offLoaded = null;
187
- if (active) { destroyTutorial(); return; }
188
- firstRun = !manual && !storage.getItem(KEYS.TUTORIAL_COMPLETED) && !storage.getItem(KEYS.TUTORIAL_SKIPPED);
189
- index = resumeIndex(storage.getItem(KEYS.TUTORIAL_STEP), firstRun);
190
- resumed = index > WELCOME + 1;
191
- active = true; skipHint = false; placed = false; onScreen = null; lastBox = null; lastGeometry = ''; previousFocus = document.activeElement;
192
- menuState = [...document.querySelectorAll('.menubar-item')].map(el => [el, el.classList.contains('open')]);
193
- storage.setItem(KEYS.TUTORIAL_STARTED, 'true');
194
- svg = document.createElementNS(NS, 'svg'); svg.id = 'wb-tutorial'; svg.setAttribute('aria-hidden', 'true');
195
- dom = node('div'); dom.id = 'wb-tutorial-dom';
196
- buildNote();
197
- // Keep showcase menus and the underlying board from handling tour controls.
198
- for (const event of ['click', 'pointerdown', 'wheel']) dom.addEventListener(event, e => e.stopPropagation());
199
- document.body.append(dom, svg);
200
- document.querySelector('#titlebar-tutorial-btn')?.setAttribute('aria-pressed', 'true');
201
- window.addEventListener('keydown', keyHandler, true);
202
- for (const event of GUARDED) window.addEventListener(event, guardPointer, true);
203
- window.addEventListener('wheel', guardWheel, { capture: true, passive: false });
204
- window.addEventListener('focusin', guardFocus, true);
205
- window.addEventListener('resize', scheduleLayout);
206
- window.visualViewport?.addEventListener('resize', scheduleLayout);
207
- document.addEventListener('transitionend', onSettled, true);
208
- window.addEventListener('pagehide', destroyTutorial);
209
- observer = new ResizeObserver(scheduleLayout);
210
- observer.observe(note);
211
- targetObserver = new MutationObserver(records => {
212
- if (records.some(r => !r.target.closest?.('#wb-tutorial, #wb-tutorial-dom'))) scheduleLayout();
213
- });
214
- targetObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'hidden'], childList: true, subtree: true });
215
- emit('tutorial:start');
216
- const token = ++generation;
217
- (ready || Promise.resolve()).then(() => { if (active && token === generation) renderStep(); });
78
+ /** The ? button: a toggle. A tour on screen ends (as Esc does); else it opens. */
79
+ export function startTutorial() {
80
+ if (tour) { tour.engine.close('skip'); return; }
81
+ startTour({ from: 'help' });
218
82
  }
219
83
 
84
+ /** Ends a running tour at once (page hide, tests). */
220
85
  export function destroyTutorial() {
221
- if (!active) return;
222
- clearStep(); active = false;
223
- cancelAnimationFrame(resizeFrame); resizeFrame = 0;
224
- observer?.disconnect(); observer = null;
225
- targetObserver?.disconnect(); targetObserver = null;
226
- svg?.remove(); dom?.remove(); svg = dom = note = ui = nextButton = null; inks = {};
227
- window.removeEventListener('keydown', keyHandler, true);
228
- for (const event of GUARDED) window.removeEventListener(event, guardPointer, true);
229
- window.removeEventListener('wheel', guardWheel, true);
230
- window.removeEventListener('focusin', guardFocus, true);
231
- window.removeEventListener('resize', scheduleLayout);
232
- window.visualViewport?.removeEventListener('resize', scheduleLayout);
233
- document.removeEventListener('transitionend', onSettled, true);
234
- window.removeEventListener('pagehide', destroyTutorial);
235
- restoreStage(); menuState = [];
236
- document.querySelector('#titlebar-tutorial-btn')?.setAttribute('aria-pressed', 'false');
237
- if (previousFocus?.isConnected) previousFocus.focus({ preventScroll: true });
238
- previousFocus = null;
239
- flushStorage(); emit('tutorial:exit');
86
+ if (!tour) return;
87
+ tour.engine.close('gone');
240
88
  }
241
89
 
242
- // ── Input: the tour is modal. Nothing reaches the board, the graph or the panels behind it ──
243
-
244
- const allowed = (el) => !!el?.closest?.('#wb-tutorial-dom, #titlebar-tutorial-btn');
245
- function guardPointer(e) {
246
- if (passthrough || allowed(e.target)) return;
247
- if (e.cancelable) e.preventDefault();
248
- e.stopImmediatePropagation();
249
- }
250
- function guardWheel(e) { if (!e.target.closest?.('.tour-note')) { e.preventDefault(); e.stopImmediatePropagation(); } }
251
- // The Assistant panel focuses its composer when it opens: the keyboard stays on the tour.
252
- function guardFocus(e) { if (!passthrough && !allowed(e.target)) focusDefault(); }
253
- function focusDefault() {
254
- const launch = !skipHint && TUTORIAL_STEPS[index].kind === 'launch';
255
- (nextButton && !nextButton.disabled && !launch ? nextButton : note)?.focus({ preventScroll: true });
256
- }
257
- function keyHandler(e) {
258
- if (!active) return;
259
- if (e.key === 'Escape') { e.preventDefault(); e.stopImmediatePropagation(); destroyTutorial(); return; }
260
- if (e.key === 'Tab') {
261
- const controls = [...dom.querySelectorAll('button:not(:disabled):not([hidden]), select:not(:disabled)')];
262
- const pos = controls.indexOf(document.activeElement);
263
- if (pos < 0 || (!e.shiftKey && pos === controls.length - 1) || (e.shiftKey && pos === 0)) {
264
- e.preventDefault(); (e.shiftKey ? controls.at(-1) : controls[0])?.focus();
90
+ async function startTour({ host = null, from = 'help' } = {}) {
91
+ if (tour || opening) return;
92
+ opening = true;
93
+ try {
94
+ await loadStyles();
95
+ let sent = null; // what the user sent from a session-free preview's composer
96
+ const presentation = host ? null : beginAssistantTour({ onPreviewSend: (text) => { sent = text; tour?.engine.close('sent'); } });
97
+ const stage = host || presentation?.host;
98
+ if (!stage?.root || !stage.renderer) {
99
+ presentation?.restore({ reason: 'skip' });
100
+ if (!host) showGuestToast(t('assistant.panel.blocked'));
101
+ return;
265
102
  }
266
- e.stopImmediatePropagation(); return;
267
- }
268
- if (e.target.matches?.('select')) { e.stopImmediatePropagation(); return; }
269
- if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
270
- e.preventDefault(); e.stopImmediatePropagation();
271
- if (e.key === 'ArrowLeft') back(); else if (!nextButton.disabled) advance();
272
- return;
273
- }
274
- if (e.key === 'Enter' && !e.target.closest?.('button')) {
275
- e.preventDefault(); e.stopImmediatePropagation();
276
- if (!nextButton.disabled) advance();
277
- return;
103
+ const returning = from === 'help' && tourReturning(storage);
104
+ const saved = storage.getItem(KEYS.TUTORIAL_STEP);
105
+ const resumeAt = saved != null && !storage.getItem(KEYS.TUTORIAL_COMPLETED) ? resumeBeat(saved) : -1;
106
+ const { createTour } = await import('./tour/tour-engine.js');
107
+ storage.setItem(KEYS.TUTORIAL_STARTED, 'true');
108
+ const env = { computer: stage.computer !== false };
109
+ const engine = createTour({
110
+ host: stage,
111
+ tr: (key, params) => t(key, params),
112
+ reduced,
113
+ env,
114
+ beats: beatsFor(env),
115
+ resumeAt,
116
+ onBeat: (beat) => { storage.setItem(KEYS.TUTORIAL_STEP, beat.id); emit('tutorial:step', { id: beat.id }); },
117
+ onComplete: () => {
118
+ markTourSeen(storage, 'completed');
119
+ storage.removeItem(KEYS.TUTORIAL_STEP);
120
+ flushStorage();
121
+ try { stage.refreshStarters?.(); } catch { /* the starters come back as they were */ }
122
+ emit('tutorial:complete');
123
+ },
124
+ onExplore: () => openSetup(),
125
+ onClose: (reason) => {
126
+ if (reason === 'skip' && !storage.getItem(KEYS.TUTORIAL_COMPLETED)) {
127
+ markTourSeen(storage, 'skipped');
128
+ try { stage.refreshStarters?.(); } catch { /* as they were */ }
129
+ emit('tutorial:skip');
130
+ }
131
+ flushStorage();
132
+ presentation?.restore({ reason });
133
+ if (reason === 'sent' && sent) presentation?.deliver(sent);
134
+ if (reason === 'done' && (!presentation || presentation.live)) stage.focusComposer?.();
135
+ tour = null;
136
+ document.querySelector('#titlebar-tutorial-btn')?.setAttribute('aria-pressed', 'false');
137
+ emit('tutorial:exit', { reason });
138
+ },
139
+ });
140
+ tour = { engine, presentation };
141
+ document.querySelector('#titlebar-tutorial-btn')?.setAttribute('aria-pressed', 'true');
142
+ emit('tutorial:start', { from });
143
+ if (returning) engine.openList();
144
+ else engine.play(0);
145
+ } finally {
146
+ opening = false;
278
147
  }
279
- // Native button Enter/Space still activate; product shortcuts never leak out.
280
- e.stopImmediatePropagation();
281
148
  }
282
149
 
283
- // ── Navigation ──
150
+ export { BEATS as TOUR_BEATS };
284
151
 
285
- function go(next) {
286
- if (!active || next < (firstRun ? 0 : WELCOME) || next >= TUTORIAL_STEPS.length) return;
287
- index = next; skipHint = false; renderStep();
288
- }
289
- function back() {
290
- if (skipHint) { skipHint = false; renderStep(); return; }
291
- go(index - 1);
292
- }
293
- function advance() {
294
- if (skipHint) { destroyTutorial(); return; }
295
- const step = TUTORIAL_STEPS[index];
296
- beforeAdvance?.();
297
- if (step.kind === 'explore') {
298
- storage.setItem(KEYS.ONBOARDING_EXPLORE, 'no'); go(WELCOME); return;
299
- }
300
- if (step.kind === 'launch') return; // Only the explicit launch button starts work.
301
- if (index === TUTORIAL_STEPS.length - 1) {
302
- storage.setItem(KEYS.TUTORIAL_COMPLETED, 'true'); storage.removeItem(KEYS.TUTORIAL_STEP);
303
- destroyTutorial(); emit('tutorial:complete'); return;
304
- }
305
- go(index + 1);
306
- }
307
- function skip() {
308
- storage.setItem(KEYS.TUTORIAL_SKIPPED, 'true');
309
- skipHint = true; renderStep(); emit('tutorial:skip');
152
+ /** The tour on screen (its engine: play, next, prev, close, index…), or null. For tests and devtools. */
153
+ export function currentTour() {
154
+ return tour?.engine || null;
310
155
  }
311
156
 
312
- // ── The note: one element for the whole tour, so nothing blinks between steps ──
157
+ // ── The project-exploration setup: a small dialog of its own ──
313
158
 
314
- function button(text, fn, cls = '') {
315
- const el = node('button', cls, text); el.type = 'button'; el.addEventListener('click', fn); return el;
316
- }
317
- function buildNote() {
318
- note = node('section', 'tour-note tour-snap'); note.tabIndex = -1;
319
- note.setAttribute('role', 'dialog'); note.setAttribute('aria-modal', 'true');
320
- note.setAttribute('aria-labelledby', 'tour-heading'); note.setAttribute('aria-describedby', 'tour-copy');
321
- const progress = node('div', 'tour-progress');
322
- const dots = node('span', 'tour-dots'); dots.setAttribute('role', 'img');
323
- for (const chapter of CHAPTERS) { const dot = node('span'); dot.dataset.chapter = chapter; dots.append(dot); }
324
- ui = {
325
- label: node('span'), dots,
326
- restart: button(tr('restart'), () => { resumed = false; go(WELCOME); }, 'tour-link'),
327
- body: node('div', 'tour-body'),
328
- prev: button(tr('back'), back),
329
- skip: button('', () => (onLater || skip)(), 'tour-link tour-skip'),
330
- };
331
- nextButton = button('', () => (onPrimary || advance)(), 'primary');
332
- progress.append(ui.label, ui.restart, dots);
333
- const nav = node('nav', 'tour-nav'); nav.setAttribute('aria-label', tr('navigation'));
334
- nav.append(ui.prev, nextButton, ui.skip);
335
- note.append(progress, ui.body, nav, node('p', 'tour-key-hint', tr('keys')));
336
- dom.append(note);
159
+ let setup = null; // { dialog, index, generation, onPrimary, beforeAdvance, previousFocus, abort }
160
+
161
+ function names() {
162
+ const settings = document.querySelector('#menubar-settings-btn .menu-text, #menubar-settings-btn')?.textContent?.trim() || t('nav.settings');
163
+ return { settings, projects: t('settings.redesign.nav.projects.label') };
337
164
  }
338
- // Handwriting appears word by word; the text itself is ordinary DOM text.
339
- function write(el, text, start) {
340
- if (reduced()) { el.textContent = text; return; }
341
- const words = text.split(' ');
342
- const pace = Math.min(26, 400 / words.length);
343
- el.replaceChildren();
344
- words.forEach((word, i) => {
345
- const span = node('span', 'tour-word', word);
346
- span.style.animationDelay = `${Math.round(start + i * pace)}ms`;
347
- el.append(span);
348
- if (i < words.length - 1) el.append(' ');
349
- });
165
+
166
+ /** Open the setup at its first screen (or `index`). */
167
+ export function openSetup(index = 0) {
168
+ if (setup) { renderSetup(index); return; }
169
+ loadStyles();
170
+ const overlay = node('div', 'tour-setup-overlay');
171
+ const dialog = node('section', 'tour-setup');
172
+ dialog.setAttribute('role', 'dialog');
173
+ dialog.setAttribute('aria-modal', 'true');
174
+ dialog.setAttribute('aria-labelledby', 'tour-setup-title');
175
+ dialog.tabIndex = -1;
176
+ overlay.append(dialog);
177
+ document.body.append(overlay);
178
+ const controller = new AbortController();
179
+ wireDropdowns(dialog, { signal: controller.signal });
180
+ setup = { overlay, dialog, index: 0, generation: 0, onPrimary: null, beforeAdvance: null, previousFocus: document.activeElement, controller, abort: null };
181
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSetup(); }, { signal: controller.signal });
182
+ window.addEventListener('keydown', onSetupKey, { capture: true, signal: controller.signal });
183
+ renderSetup(index);
350
184
  }
351
- function renderStep() {
352
- clearStep();
353
- const step = currentStep();
354
- if (!skipHint) storage.setItem(KEYS.TUTORIAL_STEP, step.id);
185
+
186
+ export function closeSetup() {
187
+ if (!setup) return;
188
+ const { overlay, controller, previousFocus, abort } = setup;
189
+ abort?.abort();
190
+ setup.generation += 1;
191
+ setup = null;
192
+ controller.abort();
193
+ const leave = () => overlay.remove();
194
+ if (reduced() || typeof overlay.animate !== 'function') leave();
195
+ else overlay.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 160, easing: 'cubic-bezier(.2,0,1,.9)' }).finished.then(leave, leave);
196
+ if (previousFocus?.isConnected) previousFocus.focus({ preventScroll: true });
355
197
  flushStorage();
356
- const show = () => { showStage(step); settle(step, () => { fillNote(step); layout(true); keepFocus(step); }); };
357
- // Holding an arrow key runs through the steps: only the one it stops on moves the Assistant panel.
358
- const panelMoves = panelPeek ? step.panel !== 'assistant'
359
- : step.panel === 'assistant' && !isAssistantPanelOpen() && assistantPanelPeekable();
360
- if (panelMoves) later(show, 150); else show();
361
- emit('tutorial:step', { index, id: step.id });
362
198
  }
363
- // A menu or panel the tour just opened is still moving: draw once the target holds still.
364
- function settle(step, done) {
365
- const staged = !!stepMenu(step) || (step.panel === 'assistant' && isAssistantPanelOpen());
366
- // Staged, the target is the control inside the menu or panel, not the fallback outside it.
367
- const inside = step.targets ? step.targets.length - (step.panel ? 1 : 0) : 0;
368
- const started = performance.now();
369
- let last = null;
370
- const tick = () => {
371
- const found = resolveStep(step);
372
- const now = found.rect ? [found.alternative, found.rect.left, found.rect.top, found.rect.width, found.rect.height].map(Math.round).join() : '';
373
- const arrived = !staged || (found.alternative >= 0 && found.alternative < inside);
374
- if ((now === last && arrived) || performance.now() - started > SETTLE_MAX) { done(); return; }
375
- last = now; later(tick, 32);
376
- };
377
- tick();
378
- }
379
- function fillNote(step) {
380
- onScreen = step; beforeAdvance = onPrimary = onLater = null;
381
- const found = resolveStep(step);
382
- const featureIndex = FEATURE_STEPS.indexOf(step);
383
- const setup = !!step.kind && step.kind !== 'welcome';
384
- note.dataset.step = step.id;
385
- ui.label.textContent = setup ? tr('setupProgress', { n: index + 1, total: WELCOME })
386
- : featureIndex >= 0 ? `${tr(`chapters.${step.chapter}`)} · ${tr('progress', { n: featureIndex + 1, total: FEATURE_STEPS.length })}`
387
- : tr('title');
388
- ui.dots.hidden = !step.chapter;
389
- ui.dots.setAttribute('aria-label', step.chapter ? tr(`chapters.${step.chapter}`) : '');
390
- for (const dot of ui.dots.children) dot.classList.toggle('current', dot.dataset.chapter === step.chapter);
391
- ui.restart.hidden = !(resumed && featureIndex > 0);
392
-
393
- const heading = node('h2'); heading.id = 'tour-heading';
394
- const copy = node('p', 'tour-copy'); copy.id = 'tour-copy'; copy.dataset.copy = found.copy;
395
- // After the target's outline and the arrow; at once when there is no target.
396
- const base = found.rect ? 350 : 100;
397
- write(heading, tr(`steps.${step.id}.title`), base);
398
- write(copy, tr(`steps.${step.id}.${found.copy}`, names()), base + 150);
399
- const unavailable = node('p', 'tour-status tour-unavailable', tr('unavailable')); unavailable.hidden = !found.missing;
400
- const extra = node('div', 'tour-extra');
401
- ui.body.replaceChildren(heading, copy, unavailable, extra);
402
199
 
403
- ui.prev.disabled = !skipHint && index <= (firstRun ? 0 : WELCOME);
404
- ui.skip.hidden = skipHint || index === TUTORIAL_STEPS.length - 1;
405
- // Past the first setup screen the way out of the exploration is "later": the tour itself is skipped from its welcome.
406
- const postpone = setup && step.kind !== 'explore';
407
- ui.skip.textContent = postpone ? tr('later') : tr('skip');
408
- if (postpone) onLater = () => go(WELCOME);
409
- nextButton.disabled = false;
410
- nextButton.textContent = skipHint ? tr('close') : step.kind === 'welcome' ? tr('start') : step.kind === 'explore' ? tr('later')
411
- : index === TUTORIAL_STEPS.length - 1 ? tr('finish') : tr('next');
412
- if (setup) renderOnboarding(step, extra);
413
- }
414
- // Focus stays on the button the user is working with; it moves only when that button is gone.
415
- function keepFocus(step) {
416
- const focused = document.activeElement;
417
- if (!dom.contains(focused) || focused === note || focused.disabled || focused.hidden || (step.kind === 'launch' && focused === nextButton)) focusDefault();
418
- }
419
- function scheduleLayout() {
420
- if (!active || resizeFrame) return;
421
- resizeFrame = requestAnimationFrame(() => { resizeFrame = 0; layout(false); });
422
- }
423
- function onSettled(e) { if (!e.target.closest?.('#wb-tutorial, #wb-tutorial-dom')) scheduleLayout(); }
424
- function lastLine(el, origin) {
425
- const range = document.createRange(); range.selectNodeContents(el);
426
- const rects = [...range.getClientRects()].filter(r => r.width > 0);
427
- const bottom = Math.max(...rects.map(r => r.bottom));
428
- const line = unionRect(rects.filter(r => r.bottom > bottom - 8));
429
- return { left: line.left - origin.left, bottom: line.bottom - origin.top, width: line.width };
430
- }
431
- // One part of the drawing (the target's strokes, the note's) is redrawn only when its geometry changed.
432
- function redraw(part, key, motion, draw) {
433
- const old = inks[part];
434
- if (old?.key === key) return;
435
- if (old) {
436
- if (motion) { old.g.classList.add('tour-leaving'); setTimeout(() => old.g.remove(), ERASE); } else old.g.remove();
200
+ function onSetupKey(e) {
201
+ if (!setup) return;
202
+ if (e.key === 'Escape') {
203
+ if (setup.dialog.querySelector('.cc-dropdown.open')) return; // the open dropdown closes first
204
+ e.preventDefault(); e.stopImmediatePropagation(); closeSetup(); return;
205
+ }
206
+ if (e.key === 'Tab') {
207
+ const controls = [...setup.dialog.querySelectorAll('button:not(:disabled)')].filter(el => el.getClientRects().length);
208
+ const pos = controls.indexOf(document.activeElement);
209
+ if (pos < 0 || (!e.shiftKey && pos === controls.length - 1) || (e.shiftKey && pos === 0)) {
210
+ e.preventDefault(); (e.shiftKey ? controls.at(-1) : controls[0])?.focus();
211
+ }
212
+ e.stopImmediatePropagation();
213
+ return;
437
214
  }
438
- const g = document.createElementNS(NS, 'g'); svg.append(g);
439
- inks[part] = { g, key };
440
- draw(g);
215
+ // Product shortcuts never leak out of the dialog; its own buttons and dropdowns keep their keys.
216
+ if (setup.dialog.contains(e.target)) e.stopImmediatePropagation();
441
217
  }
442
- function layout(animate) {
443
- // A relayout follows the step on screen; the one being prepared is not drawn early.
444
- const step = onScreen;
445
- if (!active || !note || !step) return;
446
- const found = resolveStep(step);
447
- // The view changed under an open step (the whiteboard toolbar appeared): its copy follows.
448
- const copy = ui.body.querySelector('#tour-copy');
449
- if (copy.dataset.copy !== found.copy) { copy.dataset.copy = found.copy; copy.textContent = tr(`steps.${step.id}.${found.copy}`, names()); }
450
- ui.body.querySelector('.tour-unavailable').hidden = !found.missing;
451
-
452
- const rects = (selectors) => selectors.flatMap(s => [...document.querySelectorAll(s)])
453
- .filter(shown).map(el => el.getBoundingClientRect()).filter(r => r.width > 0 && r.height > 0);
454
- const avoid = rects(['.menubar-item.open .menubar-dropdown', '.menubar-item.open .menubar-submenu.open']), keep = rects(KEEP_CLEAR);
455
- const key = (...values) => values.map(v => Math.round(v)).join(',');
456
- const flat = (list) => list.flatMap(r => [r.left, r.top, r.width, r.height]);
457
- // The app redraws itself all the time: nothing the note depends on moved, nothing to do.
458
- const geometry = key(innerWidth, innerHeight, note.offsetWidth, note.scrollHeight, ...flat(found.rect ? [found.rect] : []), -1, ...flat(avoid), -1, ...flat(keep));
459
- if (geometry === lastGeometry && !animate) return;
460
- const moved = geometry !== lastGeometry;
461
- lastGeometry = geometry;
462
- note.style.maxHeight = '';
463
- const box = placeNote(found.rect, innerWidth, innerHeight, note.offsetWidth, note.offsetHeight, { avoid, keep, near: lastBox });
464
- note.style.maxHeight = `${box.height}px`;
465
-
466
- const motion = animate && !reduced();
467
- const glide = motion && placed && moved ? DRAW : 0;
468
- const stroke = (ms) => (motion ? ms : 0);
469
- note.classList.toggle('tour-snap', !glide);
470
- note.style.transform = `translate(${Math.round(box.left)}px, ${Math.round(box.top)}px)`;
471
- note.classList.add('tour-placed');
472
- placed = true; lastBox = box;
473
- svg.setAttribute('viewBox', `0 0 ${innerWidth} ${innerHeight}`);
474
218
 
475
- // Order: the target's outline, the arrow, then the note with its text.
476
- let at = 0, arrowFrom = null;
477
- if (found.rect) {
478
- const shape = outlineShape(found.rect);
479
- const ring = outlineRect(found.rect, innerWidth, innerHeight, shape);
480
- const arrow = arrowPoints(box, ring, 6);
481
- arrowFrom = arrow.from;
482
- redraw('target', key(ring.left, ring.top, ring.width, ring.height, arrow.from.x, arrow.from.y, arrow.to.x, arrow.to.y), motion, (g) => {
483
- createOutline(g, ring, { shape, color: INK, strokeWidth: 2.5, wobbleSeed: step.id, duration: stroke(DRAW) });
484
- createAnimatedArrow(g, { ...arrow, color: INK, strokeWidth: 2.5, wobbleSeed: step.id,
485
- duration: stroke(DRAW), delay: stroke(Math.max(200, glide)) });
486
- at = stroke(350);
487
- });
488
- } else redraw('target', '', motion, () => {});
489
- // The underline is SVG, the heading is not: natural wrapping, reliable font
490
- // metrics and semantic copy for screen readers.
491
- const origin = note.getBoundingClientRect();
492
- const line = lastLine(ui.body.querySelector('h2'), origin);
493
- redraw('note', key(box.left, box.top, box.width, box.height, line.left, line.bottom, line.width, arrowFrom?.x || 0), motion, (g) => {
494
- const edge = { left: box.left - 5, top: box.top - 5, right: box.right + 5, bottom: box.bottom + 5, width: box.width + 10, height: box.height + 10 };
495
- createOutline(g, edge, { shape: 'box', radius: 18, wobble: 2.4, color: CHALK, strokeWidth: 1.6,
496
- wobbleSeed: `${step.id}-note`, duration: stroke(DRAW + 100), delay: Math.max(at, glide) });
497
- createHandDrawnUnderline(g, { left: box.left + line.left, bottom: box.top + line.bottom, width: line.width },
498
- { color: INK, wobbleSeed: step.id, duration: stroke(200), delay: Math.max(at, glide) + stroke(300), offset: 3 });
499
- // The flourish sits on the note's top corner, the one the arrow does not leave from.
500
- const corner = arrowFrom && arrowFrom.x > box.left + box.width / 2 && arrowFrom.y < box.top + 60 ? box.left - 8 : box.right - 22;
501
- createDoodle(g, step.kind === 'warning' ? 'alert' : 'sparkle', { x: corner, y: Math.max(4, box.top - 20),
502
- scale: 0.7, color: INK, duration: stroke(200), delay: Math.max(at, glide) + stroke(400) });
503
- });
219
+ function button(text, fn, cls = '') {
220
+ const el = node('button', cls, text);
221
+ el.type = 'button';
222
+ el.addEventListener('click', fn);
223
+ return el;
504
224
  }
505
225
 
506
- // ── First-run setup: the optional project exploration ──
226
+ function goSetup(next) {
227
+ if (!setup) return;
228
+ if (next < 0) return;
229
+ if (next >= ONBOARDING_STEPS.length) { closeSetup(); return; }
230
+ renderSetup(next);
231
+ }
507
232
 
508
- function renderOnboarding(step, content) {
509
- const token = generation;
510
- const fresh = () => active && generation === token && content.isConnected;
233
+ function renderSetup(index) {
234
+ if (!setup) return;
235
+ setup.abort?.abort();
236
+ setup.abort = null;
237
+ const my = ++setup.generation;
238
+ setup.index = index;
239
+ setup.onPrimary = setup.beforeAdvance = null;
240
+ const step = ONBOARDING_STEPS[index];
241
+ const fresh = () => !!setup && setup.generation === my;
242
+ const { dialog } = setup;
243
+ const heading = node('h2', 'tour-setup-title', tr(`steps.${step.id}.title`));
244
+ heading.id = 'tour-setup-title';
245
+ const progress = node('div', 'tour-setup-progress', tr('setupProgress', { n: index + 1, total: ONBOARDING_STEPS.length }));
246
+ const copy = node('p', 'tour-setup-copy', tr(`steps.${step.id}.body`, names()));
247
+ const extra = node('div', 'tour-setup-extra');
248
+ const nav = node('nav', 'tour-setup-nav');
249
+ nav.setAttribute('aria-label', tr('navigation'));
250
+ const back = button(tr('back'), () => goSetup(index - 1), 'tour-setup-btn');
251
+ back.disabled = index === 0;
252
+ const later = button(tr('later'), () => closeSetup(), 'tour-setup-btn tour-setup-link');
253
+ const primary = button(step.kind === 'explore' ? tr('later') : tr('next'), () => {
254
+ if (setup?.onPrimary) { setup.onPrimary(); return; }
255
+ setup?.beforeAdvance?.();
256
+ if (step.kind === 'explore') { storage.setItem(KEYS.ONBOARDING_EXPLORE, 'no'); closeSetup(); return; }
257
+ goSetup(index + 1);
258
+ }, 'tour-setup-btn tour-setup-primary');
259
+ nav.append(back, later, primary);
260
+ if (step.kind === 'explore') later.hidden = true;
261
+ dialog.replaceChildren(progress, heading, copy, extra, nav);
262
+ if (!reduced()) dialog.animate([{ opacity: 0.4, transform: 'translateY(4px)' }, { opacity: 1, transform: 'none' }], { duration: 200, easing: 'cubic-bezier(0,0,.38,.9)' });
511
263
  const saveChoice = (key, value) => { storage.setItem(key, value); flushStorage(); };
512
- const field = (labelText) => {
513
- const label = node('label', 'tour-field', labelText);
514
- const select = node('select'); label.append(select); content.append(label); return select;
264
+ const status = (text = tr('loading')) => { const el = node('p', 'tour-setup-status', text); el.setAttribute('role', 'status'); extra.append(el); return el; };
265
+ const retry = () => extra.append(button(tr('retry'), () => renderSetup(index), 'tour-setup-btn'));
266
+ const field = (label, id) => {
267
+ const wrap = node('div', 'tour-setup-field');
268
+ const caption = node('span', 'tour-setup-label', label);
269
+ caption.id = `${id}-label`;
270
+ wrap.append(caption);
271
+ wrap.insertAdjacentHTML('beforeend', dropdownHTML(id, [{ value: '', label: tr('loading'), selected: true }], { labelledBy: caption.id, disabled: true }));
272
+ extra.append(wrap);
273
+ return wrap.querySelector(`#${id}`);
515
274
  };
516
- const option = (select, label, value) => { const o = node('option', '', label); o.value = value; select.append(o); };
517
- const status = (text = tr('loading')) => { const el = node('p', 'tour-status', text); el.setAttribute('role', 'status'); content.append(el); return el; };
518
- const retry = () => content.append(button(tr('retry'), () => renderStep()));
275
+
519
276
  if (step.kind === 'explore') {
520
- content.append(button(tr('explore'), () => { saveChoice(KEYS.ONBOARDING_EXPLORE, 'yes'); go(index + 1); }));
277
+ extra.append(button(tr('explore'), () => { saveChoice(KEYS.ONBOARDING_EXPLORE, 'yes'); goSetup(index + 1); }, 'tour-setup-btn tour-setup-primary'));
521
278
  } else if (step.kind === 'cli') {
522
- const choices = node('div', 'tour-options'); content.append(choices);
523
- nextButton.disabled = !CLI_PROFILES.some(c => c.id === storage.getItem(KEYS.ONBOARDING_CLI));
279
+ const choices = node('div', 'tour-setup-options');
280
+ extra.append(choices);
281
+ primary.disabled = !CLI_PROFILES.some(c => c.id === storage.getItem(KEYS.ONBOARDING_CLI));
524
282
  for (const cli of CLI_PROFILES) {
525
283
  const choice = button(cli.label, () => {
526
284
  if (storage.getItem(KEYS.ONBOARDING_CLI) !== cli.id) storage.removeItem(KEYS.ONBOARDING_MODEL);
527
- saveChoice(KEYS.ONBOARDING_CLI, cli.id); go(index + 1);
528
- }, 'tour-choice');
529
- choice.setAttribute('aria-pressed', String(storage.getItem(KEYS.ONBOARDING_CLI) === cli.id)); choices.append(choice);
285
+ saveChoice(KEYS.ONBOARDING_CLI, cli.id);
286
+ goSetup(index + 1);
287
+ }, 'tour-setup-choice');
288
+ choice.setAttribute('aria-pressed', String(storage.getItem(KEYS.ONBOARDING_CLI) === cli.id));
289
+ choices.append(choice);
530
290
  }
531
291
  } else if (step.kind === 'model') {
532
- const select = field(tr('model')); select.disabled = true; nextButton.disabled = true;
533
- const loading = status(); let settled = false;
292
+ const input = field(tr('model'), 'tour-setup-model');
293
+ primary.disabled = true;
294
+ const loading = status();
295
+ let settled = false;
534
296
  const finish = (models) => {
535
297
  if (!fresh() || settled) return;
536
- settled = true; clearTimeout(timeout); timers.delete(timeout);
537
- option(select, tr('cliDefault'), '');
538
- for (const m of models) option(select, formatModelOptionLabel(m, { includeService: true }), modelSelectorValue(m));
539
- select.value = storage.getItem(KEYS.ONBOARDING_MODEL) || '';
540
- if (select.selectedIndex < 0) select.value = '';
541
- select.disabled = false; nextButton.disabled = false;
298
+ settled = true;
299
+ clearTimeout(timeout);
300
+ const saved = storage.getItem(KEYS.ONBOARDING_MODEL) || '';
301
+ const entries = [{ value: '', label: tr('cliDefault') }, ...models.map(m => ({ value: modelSelectorValue(m), label: formatModelOptionLabel(m, { includeService: true }) }))];
302
+ setDropdownItems(input, entries.map(e => ({ ...e, selected: e.value === saved })));
303
+ setDropdownValue(input, entries.some(e => e.value === saved) ? saved : '');
304
+ setDropdownDisabled(input, false);
305
+ primary.disabled = false;
542
306
  loading.textContent = models.length ? tr('catalog') : tr('catalogUnavailable');
543
- select.addEventListener('change', () => saveChoice(KEYS.ONBOARDING_MODEL, select.value));
544
- beforeAdvance = () => saveChoice(KEYS.ONBOARDING_MODEL, select.value);
307
+ input.addEventListener('change', () => saveChoice(KEYS.ONBOARDING_MODEL, input.value));
308
+ setup.beforeAdvance = () => saveChoice(KEYS.ONBOARDING_MODEL, input.value);
545
309
  if (!models.length) retry();
546
310
  };
547
- const timeout = later(() => finish([]), 8000);
311
+ const timeout = setTimeout(() => finish([]), 8000);
548
312
  ensureModelsForProfile(storage.getItem(KEYS.ONBOARDING_CLI)).then(m => finish(Array.isArray(m) ? m : [])).catch(() => finish([]));
549
313
  } else if (step.kind === 'project') {
550
- const select = field(tr('project')); select.disabled = true; nextButton.disabled = true;
314
+ const input = field(tr('project'), 'tour-setup-project');
315
+ primary.disabled = true;
551
316
  const loading = status();
552
- stepAbort = new AbortController();
553
- const abort = stepAbort;
554
- const timeout = later(() => abort.abort(), 8000);
555
- fetch('/api/terminal/profiles', { signal: abort.signal }).then(r => { if (!r.ok) throw new Error('profiles'); return r.json(); }).then(data => {
317
+ const abort = new AbortController();
318
+ setup.abort = abort;
319
+ const timeout = setTimeout(() => abort.abort(), 8000);
320
+ fetch('/api/terminal/profiles', { signal: abort.signal }).then(r => { if (!r.ok) throw new Error('profiles'); return r.json(); }).then((data) => {
556
321
  if (!fresh()) return;
557
322
  const projects = (data.projects || []).filter(p => p.path);
558
323
  const name = (p) => p.label || p.path.split(/[\\/]/).filter(Boolean).pop();
559
- option(select, tr('chooseProject'), '');
560
- projects.forEach((p, i) => option(select, name(p), String(i)));
561
- let prior = null; try { prior = JSON.parse(storage.getItem(KEYS.ONBOARDING_PROJECT) || 'null'); } catch { /* choose again */ }
324
+ let prior = null;
325
+ try { prior = JSON.parse(storage.getItem(KEYS.ONBOARDING_PROJECT) || 'null'); } catch { /* choose again */ }
562
326
  const existing = projects.findIndex(p => p.path === prior?.path);
563
- select.value = existing >= 0 ? String(existing) : '';
564
- select.disabled = !projects.length; nextButton.disabled = existing < 0;
327
+ setDropdownItems(input, [{ value: '', label: tr('chooseProject') }, ...projects.map((p, i) => ({ value: String(i), label: name(p) }))]);
328
+ setDropdownValue(input, existing >= 0 ? String(existing) : '');
329
+ setDropdownDisabled(input, !projects.length);
330
+ primary.disabled = existing < 0;
565
331
  loading.textContent = projects.length ? tr('projectHint') : tr('noProjects');
566
- select.addEventListener('change', () => {
567
- const project = projects[Number(select.value)];
568
- nextButton.disabled = select.value === '';
569
- if (select.value !== '' && project) saveChoice(KEYS.ONBOARDING_PROJECT, JSON.stringify({ path: project.path, label: name(project) }));
332
+ input.addEventListener('change', () => {
333
+ const project = projects[Number(input.value)];
334
+ primary.disabled = input.value === '';
335
+ if (input.value !== '' && project) saveChoice(KEYS.ONBOARDING_PROJECT, JSON.stringify({ path: project.path, label: name(project) }));
570
336
  });
571
337
  }).catch(() => {
572
338
  if (!fresh()) return;
573
- loading.textContent = tr('projectsUnavailable'); retry();
574
- }).finally(() => { clearTimeout(timeout); timers.delete(timeout); });
575
- content.append(button(tr('addProject', names()), async () => {
576
- // Explicit user action hands off to the existing Projects settings page.
577
- destroyTutorial(); const { openSettingsModal } = await import('./ui-settings.js');
339
+ loading.textContent = tr('projectsUnavailable');
340
+ retry();
341
+ }).finally(() => clearTimeout(timeout));
342
+ extra.append(button(tr('addProject', names()), async () => {
343
+ // An explicit choice hands off to the Projects page of Settings.
344
+ closeSetup();
345
+ const { openSettingsModal } = await import('./ui-settings.js');
578
346
  await openSettingsModal({ tab: 'projects', expand: ['stg-sec-workspaces'] });
579
- }));
347
+ }, 'tour-setup-btn tour-setup-link'));
580
348
  } else if (step.kind === 'launch') {
581
- nextButton.textContent = tr('begin');
349
+ primary.textContent = tr('begin');
582
350
  // A second click meant for the previous screen must not start the CLI.
583
- nextButton.disabled = true; later(() => { nextButton.disabled = false; }, 600);
351
+ primary.disabled = true;
352
+ const unlock = setTimeout(() => { if (fresh()) primary.disabled = false; }, 600);
584
353
  const loading = status('');
585
354
  let busy = false;
586
- onPrimary = () => {
355
+ setup.onPrimary = () => {
587
356
  if (busy || !fresh()) return;
588
- let project = null; try { project = JSON.parse(storage.getItem(KEYS.ONBOARDING_PROJECT) || 'null'); } catch { /* handled below */ }
357
+ let project = null;
358
+ try { project = JSON.parse(storage.getItem(KEYS.ONBOARDING_PROJECT) || 'null'); } catch { /* handled below */ }
589
359
  const cli = storage.getItem(KEYS.ONBOARDING_CLI);
590
360
  if (!project?.path || !CLI_PROFILES.some(c => c.id === cli)) { loading.textContent = tr('selectionMissing'); return; }
591
- busy = true; nextButton.disabled = true; loading.textContent = tr('launching');
361
+ busy = true;
362
+ primary.disabled = true;
363
+ loading.textContent = tr('launching');
592
364
  // The same category slug Settings → Projects gives an exploration.
593
365
  const slug = (project.label || project.path.split(/[\\/]/).filter(Boolean).pop() || 'project').toLowerCase().replace(/[^a-z0-9]+/g, '');
594
366
  const model = storage.getItem(KEYS.ONBOARDING_MODEL);
595
367
  const finish = (err) => {
368
+ clearTimeout(timeout);
596
369
  if (!fresh()) return;
597
- clearTimeout(timeout); timers.delete(timeout);
598
- if (err) { busy = false; nextButton.disabled = false; loading.textContent = tr('launchFailed'); }
599
- else { firstRun = false; go(WELCOME); } // setup is done: Back from the welcome does not return to it
370
+ if (err) { busy = false; primary.disabled = false; loading.textContent = tr('launchFailed'); return; }
371
+ closeSetup();
600
372
  };
601
- const timeout = later(() => finish(new Error('timeout')), 15000);
373
+ const timeout = setTimeout(() => finish(new Error('timeout')), 15000);
602
374
  try {
603
375
  emit('terminal:launch-floating', { profile: cli, cwd: project.path, createOpts: model ? { model } : {},
604
376
  initialMessage: buildExplorePrompt(slug), autoSubmit: true, onDone: finish });
605
377
  } catch (err) { finish(err); }
606
378
  };
379
+ void unlock;
607
380
  }
381
+ // The keyboard lands on the way on (never on Begin: only a deliberate click launches).
382
+ const focus = step.kind === 'launch' ? dialog : (primary.disabled ? dialog : primary);
383
+ focus.focus({ preventScroll: true });
608
384
  }