synabun 2.0.0-beta → 2.0.5

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 (374) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +51 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/launcher.mjs +40 -0
  14. package/lib/claude-hooks.js +61 -8
  15. package/lib/client-config-repair.js +4 -3
  16. package/lib/data-home-migration.js +4 -0
  17. package/lib/desktop-pwa.js +206 -0
  18. package/lib/external-tools.js +221 -0
  19. package/lib/first-launch-protection.js +5 -0
  20. package/lib/packaged-runtime.js +110 -0
  21. package/lib/start-launcher.js +1396 -0
  22. package/mcp-server/package-lock.json +2 -2
  23. package/mcp-server/package.json +1 -3
  24. package/neural-interface/i18n/en.json +1540 -154
  25. package/neural-interface/i18n/pt-BR.json +1538 -153
  26. package/neural-interface/lib/assistant-api.js +62 -6
  27. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  28. package/neural-interface/lib/assistant-catalog.js +179 -27
  29. package/neural-interface/lib/assistant-config.js +31 -2
  30. package/neural-interface/lib/assistant-dispatch.js +62 -13
  31. package/neural-interface/lib/assistant-envelope.js +9 -6
  32. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  33. package/neural-interface/lib/assistant-memory.js +24 -10
  34. package/neural-interface/lib/assistant-persona.js +6 -2
  35. package/neural-interface/lib/assistant-pricing.js +21 -6
  36. package/neural-interface/lib/assistant-runtime.js +181 -12
  37. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  38. package/neural-interface/lib/assistant-test-density.js +142 -0
  39. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  40. package/neural-interface/lib/assistant-user-context.js +908 -0
  41. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  42. package/neural-interface/lib/assistant-user-learn.js +404 -0
  43. package/neural-interface/lib/assistant-user-threads.js +226 -0
  44. package/neural-interface/lib/assistant-user-writer.js +181 -0
  45. package/neural-interface/lib/augmented-path.js +23 -3
  46. package/neural-interface/lib/browser-background-tab.js +134 -0
  47. package/neural-interface/lib/browser-language.js +83 -0
  48. package/neural-interface/lib/browser-screenshot.js +678 -61
  49. package/neural-interface/lib/claude-agent-bridge.js +87 -153
  50. package/neural-interface/lib/claude-executable.js +241 -0
  51. package/neural-interface/lib/claude-model-catalog.js +82 -93
  52. package/neural-interface/lib/claude-temporary.js +46 -7
  53. package/neural-interface/lib/cloudflared/install.js +533 -0
  54. package/neural-interface/lib/cloudflared/manager.js +545 -0
  55. package/neural-interface/lib/cloudflared/pins.js +115 -0
  56. package/neural-interface/lib/cloudflared/pins.json +63 -0
  57. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  58. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  59. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  60. package/neural-interface/lib/editor-completion.js +714 -0
  61. package/neural-interface/lib/editor-config.js +149 -0
  62. package/neural-interface/lib/editor-files-api.js +268 -0
  63. package/neural-interface/lib/editor-git-api.js +134 -0
  64. package/neural-interface/lib/file-explorer-api.js +1039 -0
  65. package/neural-interface/lib/file-explorer-system.js +355 -0
  66. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  67. package/neural-interface/lib/file-explorer-watch.js +239 -0
  68. package/neural-interface/lib/git-diff-api.js +103 -0
  69. package/neural-interface/lib/git-read.js +202 -0
  70. package/neural-interface/lib/greeting/api.js +89 -0
  71. package/neural-interface/lib/greeting/budgets.js +90 -0
  72. package/neural-interface/lib/greeting/engine.js +461 -0
  73. package/neural-interface/lib/greeting/facts.js +320 -0
  74. package/neural-interface/lib/greeting/sources.js +252 -0
  75. package/neural-interface/lib/greeting/writer.js +139 -0
  76. package/neural-interface/lib/http-guards.js +189 -13
  77. package/neural-interface/lib/machine-names.js +88 -0
  78. package/neural-interface/lib/memory-api.js +31 -7
  79. package/neural-interface/lib/native-binary-runtime.js +69 -337
  80. package/neural-interface/lib/native-loop-providers.js +40 -49
  81. package/neural-interface/lib/offline-page.js +54 -0
  82. package/neural-interface/lib/plugin-installer.js +4 -2
  83. package/neural-interface/lib/project-storage.js +5 -7
  84. package/neural-interface/lib/request-gate.js +589 -0
  85. package/neural-interface/lib/session-title-generator.js +5 -0
  86. package/neural-interface/lib/synabun-update-plan.js +19 -0
  87. package/neural-interface/lib/ui-blob-api.js +108 -0
  88. package/neural-interface/lib/ui-blob-store.js +68 -0
  89. package/neural-interface/package-lock.json +701 -253
  90. package/neural-interface/package.json +13 -2
  91. package/neural-interface/public/install-app.html +34 -0
  92. package/neural-interface/public/manifest.json +2 -0
  93. package/neural-interface/public/offline.html +402 -66
  94. package/neural-interface/public/onboarding.html +264 -147
  95. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  96. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  97. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  98. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  99. package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
  100. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  101. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  102. package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
  103. package/neural-interface/public/shared/assistant/asst-render.js +219 -67
  104. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  105. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  106. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  107. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  108. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  109. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  110. package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
  111. package/neural-interface/public/shared/blob-store.js +111 -22
  112. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  113. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  114. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  115. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  116. package/neural-interface/public/shared/command-runner-model.js +30 -0
  117. package/neural-interface/public/shared/constants.js +6 -1
  118. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  119. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  120. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  121. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  122. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  123. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  124. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  125. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  126. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  127. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  128. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  129. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  130. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  131. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  132. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  133. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  134. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  135. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  136. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  137. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  138. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  139. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  140. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  141. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  142. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  143. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  144. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  145. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  146. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  147. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  148. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  149. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  150. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  151. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  152. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  153. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  154. package/neural-interface/public/shared/file-links.js +339 -0
  155. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  156. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  157. package/neural-interface/public/shared/html-shell.js +169 -170
  158. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  159. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  160. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  161. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  162. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  163. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  164. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  165. package/neural-interface/public/shared/pwa-install-page.js +62 -0
  166. package/neural-interface/public/shared/pwa-install.css +12 -0
  167. package/neural-interface/public/shared/pwa-install.js +127 -0
  168. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  169. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  170. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  171. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  172. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  173. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  174. package/neural-interface/public/shared/start-bridge.js +802 -0
  175. package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
  176. package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
  177. package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
  178. package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
  179. package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
  180. package/neural-interface/public/shared/styles.css +2219 -1117
  181. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  182. package/neural-interface/public/shared/term-ready.js +307 -0
  183. package/neural-interface/public/shared/tunnel-view.js +167 -0
  184. package/neural-interface/public/shared/ui-claude-panel.js +160 -19
  185. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  186. package/neural-interface/public/shared/ui-detail.js +15 -0
  187. package/neural-interface/public/shared/ui-explorer.js +22 -0
  188. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  189. package/neural-interface/public/shared/ui-file-links.js +103 -0
  190. package/neural-interface/public/shared/ui-help.js +26 -0
  191. package/neural-interface/public/shared/ui-invite.js +126 -116
  192. package/neural-interface/public/shared/ui-judgments.js +1 -1
  193. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  194. package/neural-interface/public/shared/ui-loading.js +367 -12
  195. package/neural-interface/public/shared/ui-menubar.js +21 -55
  196. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  197. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  198. package/neural-interface/public/shared/ui-navbar.js +45 -21
  199. package/neural-interface/public/shared/ui-settings.js +491 -547
  200. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  201. package/neural-interface/public/shared/ui-sync.js +16 -0
  202. package/neural-interface/public/shared/ui-terminal.js +153 -149
  203. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  204. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  205. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  206. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  207. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  208. package/neural-interface/public/shared/worker-end.js +251 -0
  209. package/neural-interface/public/sw.js +3 -1
  210. package/neural-interface/public/variant/2d/main.js +2 -1
  211. package/neural-interface/public/variant/3d/main.js +2 -1
  212. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  309. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  310. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  311. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  312. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  313. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  314. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  315. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  316. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  317. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  318. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  319. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  320. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  321. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  322. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  323. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  324. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  325. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  326. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  327. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  328. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  329. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  330. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  331. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  332. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  333. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  334. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  335. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  336. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  337. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  338. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  339. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  340. package/neural-interface/scripts/build-vendor.mjs +78 -2
  341. package/neural-interface/scripts/rebuild-pty.js +15 -12
  342. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  343. package/neural-interface/server.js +1092 -1212
  344. package/neural-interface/templates/rulesets/README.md +1 -1
  345. package/package.json +13 -7
  346. package/postinstall.js +11 -81
  347. package/setup.js +111 -6
  348. package/benchmarks/browser-live-results.json +0 -79
  349. package/benchmarks/browser-v2-results.json +0 -258
  350. package/docs/browser-v2.md +0 -109
  351. package/docs/hook-integrity.md +0 -89
  352. package/docs/judgments.md +0 -182
  353. package/docs/style-guide.md +0 -246
  354. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  355. package/neural-interface/public/category-logos/claude.svg +0 -1
  356. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  357. package/neural-interface/public/category-logos/openai.svg +0 -1
  358. package/neural-interface/public/category-logos/synabun.png +0 -0
  359. package/neural-interface/public/claude.svg +0 -1
  360. package/neural-interface/public/gallery.svg +0 -6
  361. package/neural-interface/public/leonardoai.svg +0 -1
  362. package/neural-interface/public/mcp.svg +0 -4
  363. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  364. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  365. package/neural-interface/public/sessionicon.svg +0 -8
  366. package/neural-interface/public/toolbar-album.svg +0 -8
  367. package/neural-interface/public/toolbar-question.svg +0 -6
  368. package/neural-interface/public/toolbar-sessions.svg +0 -6
  369. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  370. package/scripts/audit-claude-hook-usage.mjs +0 -12
  371. package/scripts/audit-memory-duplicates.mjs +0 -44
  372. package/scripts/measure-claude-hook-overhead.mjs +0 -77
  373. package/scripts/recover-user-config.mjs +0 -254
  374. package/skills/synabun/synabunicon.png +0 -0
@@ -1,534 +1,317 @@
1
- // ═══════════════════════════════════════════
2
- // SynaBun Neural Interface — Command Runner
3
- // Save and one-click execute terminal commands,
4
- // organized by category groups.
5
- // ═══════════════════════════════════════════
6
-
1
+ // SynaBun — a searchable library of saved terminal commands.
7
2
  import { emit, on } from './state.js';
8
3
  import { KEYS, COLOR_PALETTE } from './constants.js';
9
4
  import { storage } from './storage.js';
5
+ import { commandLaunchPayload, selectCommandGroups, isCommandLaunchAvailable } from './command-runner-model.js';
10
6
 
11
- const $ = (id) => document.getElementById(id);
12
- const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
13
- const genId = (prefix) => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
14
-
15
- // ── Module-local state ──
16
- let _panel = null;
17
- let _data = { categories: [], commands: [] };
18
- let _editTarget = null; // { type: 'command'|'category', id: string|null } — null = creating new
19
-
7
+ const $ = id => document.getElementById(id);
8
+ const esc = value => String(value ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
9
+ const genId = prefix => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
10
+ const color = value => CSS.supports('color', String(value)) ? value : COLOR_PALETTE[0];
20
11
  const PANEL_KEY = KEYS.PANEL_PREFIX + 'command-runner';
21
12
  const DATA_KEY = KEYS.COMMAND_RUNNER;
13
+ const canRun = () => isCommandLaunchAvailable(navigator.onLine, location.hostname);
14
+ const icon = path => `<svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
15
+ const SVG = {
16
+ terminal: icon('<path d="m4 6 6 6-6 6M13 18h7"/>'),
17
+ play: icon('<path d="m8 5 11 7-11 7Z"/>'),
18
+ plus: icon('<path d="M12 5v14M5 12h14"/>'),
19
+ search: icon('<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 4 4"/>'),
20
+ folder: icon('<path d="M3 7V5h6l2 2h10v12H3Z"/>'),
21
+ edit: icon('<path d="m16 3 5 5-12 12-6 1 1-6Z"/><path d="m13 6 5 5"/>'),
22
+ trash: icon('<path d="M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7"/>'),
23
+ chevron: icon('<path d="m9 5 7 7-7 7"/>'),
24
+ close: icon('<path d="m6 6 12 12M6 18 18 6"/>'),
25
+ check: icon('<path d="m5 12 4 4L19 6"/>'),
26
+ };
27
+ let _panel = null, _editTarget = null, _modalOpener = null, _panelOpener = null;
28
+ let _data = { categories: [], commands: [] };
29
+ let _view = { query: '', categoryId: '', sort: 'saved' };
30
+ let _dataError = false, _launchTimer = null, _launchedId = null, _abort = null;
31
+ const _searchCollapsed = new Set();
22
32
 
23
- // ── SVGs ──
24
- const SVG_PLAY = `<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><polygon points="4,2 14,8 4,14"/></svg>`;
25
- const SVG_EDIT = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 2.5l2 2L5 13l-3 1 1-3z"/></svg>`;
26
- const SVG_DELETE = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="4" x2="13" y2="4"/><path d="M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1"/><path d="M4.5 4l.5 9a1 1 0 001 1h4a1 1 0 001-1l.5-9"/></svg>`;
27
- const SVG_CHEVRON = `<svg viewBox="0 0 16 16" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 4 10 8 6 12"/></svg>`;
28
- const SVG_CLOSE = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>`;
29
- const SVG_TERMINAL = `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`;
30
-
31
- // ═══════════════════════════════════════════
32
- // INIT
33
- // ═══════════════════════════════════════════
33
+ export function initCommandRunner() { return on('command-runner:open', openPanel); }
34
34
 
35
- export function initCommandRunner() {
36
- on('command-runner:open', () => openPanel());
35
+ function clampPanel() {
36
+ if (!_panel) return;
37
+ const width = _panel.offsetWidth, height = _panel.offsetHeight;
38
+ const top = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--navbar-height')) || 48;
39
+ _panel.style.left = Math.max(8, Math.min(parseFloat(_panel.style.left) || 8, window.innerWidth - width - 8)) + 'px';
40
+ _panel.style.top = Math.max(top + 8, Math.min(parseFloat(_panel.style.top) || top + 8, window.innerHeight - height - 8)) + 'px';
37
41
  }
38
-
39
- // ═══════════════════════════════════════════
40
- // PANEL LIFECYCLE
41
- // ═══════════════════════════════════════════
42
-
43
42
  function openPanel() {
44
- if (_panel) { _panel.focus(); return; }
45
-
46
- _panel = document.createElement('div');
43
+ if (_panel) { $('cr-search')?.focus(); return; }
44
+ _panelOpener = document.activeElement;
45
+ _view = { query: '', categoryId: '', sort: 'saved' };
46
+ _searchCollapsed.clear();
47
+ _abort = new AbortController();
48
+ _panel = document.createElement('section');
47
49
  _panel.className = 'command-runner-panel glass resizable';
48
50
  _panel.id = 'command-runner-panel';
51
+ _panel.setAttribute('role', 'region');
52
+ _panel.setAttribute('aria-labelledby', 'cr-title');
49
53
  _panel.innerHTML = buildPanelHTML();
50
54
  document.body.appendChild(_panel);
51
-
52
- // Restore saved position
53
55
  try {
54
56
  const saved = JSON.parse(storage.getItem(PANEL_KEY));
55
57
  if (saved) {
56
- if (saved.x != null) _panel.style.left = saved.x + 'px';
57
- if (saved.y != null) _panel.style.top = Math.max(48, saved.y) + 'px';
58
- if (saved.w) _panel.style.width = saved.w + 'px';
59
- if (saved.h) _panel.style.height = saved.h + 'px';
58
+ if (Number.isFinite(saved.x)) _panel.style.left = saved.x + 'px';
59
+ if (Number.isFinite(saved.y)) _panel.style.top = saved.y + 'px';
60
+ if (saved.w > 0) _panel.style.width = saved.w + 'px';
61
+ if (saved.h > 0) _panel.style.height = saved.h + 'px';
60
62
  }
61
- } catch {}
62
-
63
- // Center if no saved position
64
- if (!_panel.style.left) {
65
- const vw = window.innerWidth, vh = window.innerHeight;
66
- _panel.style.left = Math.max(20, (vw - 420) / 2) + 'px';
67
- _panel.style.top = Math.max(48, (vh - 500) / 2) + 'px';
68
- }
69
-
70
- loadData();
71
- wirePanel();
72
- renderBody();
73
-
74
- requestAnimationFrame(() => _panel.classList.add('open'));
63
+ } catch { /* Use the default layout if old geometry cannot be read. */ }
64
+ if (!_panel.style.left) _panel.style.left = (window.innerWidth - _panel.offsetWidth) / 2 + 'px';
65
+ if (!_panel.style.top) _panel.style.top = (window.innerHeight - _panel.offsetHeight) / 2 + 'px';
66
+ clampPanel();
67
+ loadData(); wirePanel(); renderBody();
68
+ window.addEventListener('resize', clampPanel, { signal: _abort.signal });
69
+ window.addEventListener('online', renderBody, { signal: _abort.signal });
70
+ window.addEventListener('offline', renderBody, { signal: _abort.signal });
71
+ const panel = _panel;
72
+ requestAnimationFrame(() => {
73
+ if (_panel !== panel) return;
74
+ panel.classList.add('open');
75
+ $('cr-search')?.focus();
76
+ });
75
77
  }
76
-
77
78
  function closePanel() {
78
79
  if (!_panel) return;
79
- // Save position
80
- try {
81
- const rect = _panel.getBoundingClientRect();
82
- storage.setItem(PANEL_KEY, JSON.stringify({
83
- x: Math.round(rect.left), y: Math.round(rect.top),
84
- w: Math.round(rect.width), h: Math.round(rect.height),
85
- }));
86
- } catch {}
87
-
88
- _panel.remove();
89
- _panel = null;
90
- _editTarget = null;
80
+ const rect = _panel.getBoundingClientRect();
81
+ try { storage.setItem(PANEL_KEY, JSON.stringify({ x: Math.round(rect.left), y: Math.round(rect.top), w: Math.round(rect.width), h: Math.round(rect.height) })); } catch {}
82
+ _abort?.abort(); clearTimeout(_launchTimer);
83
+ _panel.remove(); _panel = null; _editTarget = null; _launchedId = null;
84
+ if (_panelOpener?.isConnected) _panelOpener.focus();
91
85
  }
92
-
93
- // ═══════════════════════════════════════════
94
- // DATA
95
- // ═══════════════════════════════════════════
96
-
97
86
  function loadData() {
87
+ _dataError = false;
98
88
  try {
99
89
  const raw = storage.getItem(DATA_KEY);
100
- if (raw) {
101
- const parsed = JSON.parse(raw);
102
- _data = {
103
- categories: Array.isArray(parsed.categories) ? parsed.categories : [],
104
- commands: Array.isArray(parsed.commands) ? parsed.commands : [],
105
- };
106
- } else {
107
- _data = { categories: [], commands: [] };
108
- }
90
+ const parsed = raw ? JSON.parse(raw) : { categories: [], commands: [] };
91
+ if (!Array.isArray(parsed.categories) || !Array.isArray(parsed.commands)
92
+ || !parsed.categories.every(c => c && typeof c.id === 'string' && typeof c.name === 'string')
93
+ || !parsed.commands.every(c => c && typeof c.id === 'string' && typeof c.name === 'string' && typeof c.command === 'string')) throw new Error('Invalid library');
94
+ _data = { categories: parsed.categories, commands: parsed.commands };
109
95
  } catch {
110
96
  _data = { categories: [], commands: [] };
97
+ _dataError = true; // Never overwrite an unreadable library with an empty one.
111
98
  }
112
99
  }
113
-
114
- function saveData() {
115
- storage.setItem(DATA_KEY, JSON.stringify(_data));
116
- }
117
-
118
- function getCategoryCommands(catId) {
119
- return _data.commands
120
- .filter(c => c.categoryId === catId)
121
- .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
122
- }
123
-
124
- // ═══════════════════════════════════════════
125
- // HTML BUILDERS
126
- // ═══════════════════════════════════════════
100
+ function saveData() { storage.setItem(DATA_KEY, JSON.stringify(_data)); }
127
101
 
128
102
  function buildPanelHTML() {
129
103
  return `
130
- <div class="resize-handle resize-handle-t" data-resize="t"></div>
131
- <div class="resize-handle resize-handle-r" data-resize="r"></div>
132
- <div class="resize-handle resize-handle-b" data-resize="b"></div>
133
- <div class="resize-handle resize-handle-l" data-resize="l"></div>
134
- <div class="resize-handle resize-handle-tl" data-resize="tl"></div>
135
- <div class="resize-handle resize-handle-tr" data-resize="tr"></div>
136
- <div class="resize-handle resize-handle-bl" data-resize="bl"></div>
137
- <div class="resize-handle resize-handle-br" data-resize="br"></div>
138
-
139
- <div class="cr-header drag-handle" data-drag="command-runner-panel">
140
- <div class="cr-header-left">
141
- <span class="cr-header-icon">${SVG_TERMINAL}</span>
142
- <h3>Command Runner</h3>
143
- <span class="cr-count" id="cr-total-count"></span>
144
- </div>
145
- <div class="cr-header-actions">
146
- <button class="cr-header-btn" id="cr-add-cat-btn">+ Group</button>
147
- <button class="cr-header-btn cr-primary" id="cr-add-cmd-btn">+ Command</button>
148
- </div>
149
- <button class="cr-close" id="cr-close-btn">${SVG_CLOSE}</button>
104
+ ${['t', 'r', 'b', 'l', 'tl', 'tr', 'bl', 'br'].map(edge => `<div aria-hidden="true" class="resize-handle resize-handle-${edge}" data-resize="${edge}"></div>`).join('')}
105
+ <header class="cr-header drag-handle" data-drag="command-runner-panel">
106
+ <div class="cr-header-left"><h3 id="cr-title">Command Runner</h3><span class="cr-count" id="cr-total-count">0</span></div>
107
+ <button type="button" class="cr-close" id="cr-close-btn" aria-label="Close Command Runner" title="Close (Escape)">${SVG.close}</button>
108
+ </header>
109
+ <div class="cr-main" id="cr-main">
110
+ <div class="cr-library-head"><div><h2>Saved commands</h2><p>Organize commands and run them in a new terminal.</p></div><button type="button" class="cr-button cr-primary" id="cr-add-cmd-btn">${SVG.plus}<span>New command</span></button></div>
111
+ <div class="cr-toolbar"><div class="cr-search-wrap">${SVG.search}<label class="cr-sr" for="cr-search">Search commands, groups or directories</label><input id="cr-search" type="search" placeholder="Search commands, groups or directories…" autocomplete="off" spellcheck="false"><button type="button" class="cr-icon-btn" id="cr-clear-search" aria-label="Clear search" hidden>${SVG.close}</button><kbd aria-hidden="true">/</kbd></div><label class="cr-sr" for="cr-sort">Command order</label><select id="cr-sort" class="cr-sort"><option value="saved">Saved order</option><option value="recent">Recently run</option></select></div>
112
+ <div class="cr-filter-bar"><nav id="cr-filters" class="cr-filters" aria-label="Filter by group"></nav><button type="button" class="cr-button cr-new-group" id="cr-add-cat-btn" title="Create a group">${SVG.plus}<span>Group</span></button></div>
113
+ <div class="cr-notice" id="cr-notice" role="status" hidden></div>
114
+ <div class="cr-body" id="cr-body" role="region" aria-label="Saved commands"></div>
115
+ <footer class="cr-footer"><span id="cr-results" role="status" aria-live="polite" aria-atomic="true"></span><span class="cr-footer-hint">${SVG.terminal}Runs in a new terminal</span></footer>
150
116
  </div>
151
-
152
- <div class="cr-body" id="cr-body"></div>
153
-
154
- <div class="cr-modal-overlay hidden" id="cr-modal-overlay">
155
- <div class="cr-modal glass" id="cr-modal"></div>
117
+ <div class="cr-modal-overlay hidden" id="cr-modal-overlay"><div class="cr-modal" id="cr-modal" role="dialog" aria-modal="true" aria-labelledby="cr-modal-title" tabindex="-1"></div></div>`;
118
+ }
119
+ function emptyState(title, description, svg, action = '') {
120
+ return `<div class="cr-empty"><div class="cr-empty-icon">${svg}</div><h3>${esc(title)}</h3><p>${esc(description)}</p>${action}</div>`;
121
+ }
122
+ function renderCommand(cmd) {
123
+ const launched = cmd.id === _launchedId;
124
+ return `<article class="cr-cmd${launched ? ' cr-cmd--launched' : ''}" data-cmd-id="${esc(cmd.id)}">
125
+ <div class="cr-cmd-content"><h4 class="cr-cmd-name" dir="auto">${esc(cmd.name)}</h4>
126
+ <div class="cr-code"><span class="cr-prompt" aria-hidden="true">$</span><code class="cr-cmd-text" dir="ltr">${esc(cmd.command)}</code></div>
127
+ <span class="cr-cmd-cwd" title="${esc(cmd.cwd || 'Default terminal directory')}">${SVG.folder}<span dir="ltr">${esc(cmd.cwd || 'Default terminal directory')}</span></span>
156
128
  </div>
157
- `;
129
+ <div class="cr-cmd-actions"><button type="button" class="cr-icon-btn" data-action="edit-cmd" aria-label="Edit command ${esc(cmd.name)}" title="Edit command">${SVG.edit}</button><button type="button" class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cmd" aria-label="Delete command ${esc(cmd.name)}" title="Delete command">${SVG.trash}</button><button type="button" class="cr-run-btn${launched ? ' sent' : ''}" data-action="run" aria-label="Run ${esc(cmd.name)} in a new terminal"${!canRun() ? ' disabled title="Reconnect to run this command"' : ''}>${launched ? SVG.check : SVG.play}<span>${launched ? 'Sent' : 'Run'}</span></button></div>
130
+ </article>`;
158
131
  }
159
-
160
132
  function renderBody() {
161
133
  const body = $('cr-body');
162
134
  if (!body) return;
163
-
164
- const countEl = $('cr-total-count');
165
- if (countEl) countEl.textContent = _data.commands.length || '';
166
-
135
+ const focus = document.activeElement;
136
+ const focusedCommand = focus?.closest('.cr-cmd')?.dataset.cmdId;
137
+ const focusedGroup = focus?.closest('.cr-category')?.dataset.catId;
138
+ const focusedAction = focus?.dataset.action;
139
+ const focusedFilter = focus?.dataset.filter;
140
+ if (_view.categoryId && !_data.categories.some(c => c.id === _view.categoryId)) _view.categoryId = '';
167
141
  const sorted = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
168
-
169
- if (sorted.length === 0) {
170
- body.innerHTML = `
171
- <div class="cr-empty">
172
- <div class="cr-empty-icon">${SVG_TERMINAL}</div>
173
- <p>No commands yet</p>
174
- <p class="cr-empty-hint">Click <strong>+ Group</strong> to create a category,<br>then <strong>+ Command</strong> to add commands.</p>
175
- </div>
176
- `;
177
- return;
178
- }
179
-
180
- let html = '';
181
- for (const cat of sorted) {
182
- const cmds = getCategoryCommands(cat.id);
183
- const collapsed = cat.collapsed ? ' collapsed' : '';
184
- const dotColor = cat.color || COLOR_PALETTE[0];
185
-
186
- html += `<div class="cr-category${collapsed}" data-cat-id="${esc(cat.id)}">`;
187
- html += ` <div class="cr-cat-header">`;
188
- html += ` <span class="cr-cat-chevron">${SVG_CHEVRON}</span>`;
189
- html += ` <span class="cr-cat-dot" style="background:${esc(dotColor)}"></span>`;
190
- html += ` <span class="cr-cat-name">${esc(cat.name)}</span>`;
191
- html += ` <span class="cr-cat-count">${cmds.length}</span>`;
192
- html += ` <span class="cr-cat-actions">`;
193
- html += ` <button class="cr-icon-btn" data-action="edit-cat" title="Edit group">${SVG_EDIT}</button>`;
194
- html += ` <button class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cat" title="Delete group">${SVG_DELETE}</button>`;
195
- html += ` </span>`;
196
- html += ` </div>`;
197
- html += ` <div class="cr-cat-body">`;
198
-
199
- if (cmds.length === 0) {
200
- html += `<div class="cr-cat-empty">No commands in this group</div>`;
201
- } else {
202
- for (const cmd of cmds) {
203
- html += `<div class="cr-cmd" data-cmd-id="${esc(cmd.id)}">`;
204
- html += ` <button class="cr-run-btn" title="Run command">${SVG_PLAY}</button>`;
205
- html += ` <div class="cr-cmd-info">`;
206
- html += ` <div class="cr-cmd-name">${esc(cmd.name)}</div>`;
207
- html += ` <div class="cr-cmd-text">${esc(cmd.command)}</div>`;
208
- if (cmd.cwd) {
209
- html += ` <div class="cr-cmd-cwd">${esc(cmd.cwd)}</div>`;
210
- }
211
- html += ` </div>`;
212
- html += ` <span class="cr-cmd-actions">`;
213
- html += ` <button class="cr-icon-btn" data-action="edit-cmd" title="Edit command">${SVG_EDIT}</button>`;
214
- html += ` <button class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cmd" title="Delete command">${SVG_DELETE}</button>`;
215
- html += ` </span>`;
216
- html += `</div>`;
217
- }
218
- }
219
-
220
- html += ` </div>`;
221
- html += `</div>`;
222
- }
223
-
224
- body.innerHTML = html;
142
+ $('cr-total-count').textContent = String(_data.commands.length);
143
+ $('cr-clear-search').hidden = !_view.query;
144
+ $('cr-add-cat-btn').disabled = _dataError; $('cr-add-cmd-btn').disabled = _dataError;
145
+ $('cr-filters').innerHTML = [{ id: '', name: 'All commands', count: _data.commands.length }, ...sorted.map(c => ({ ...c, count: _data.commands.filter(cmd => cmd.categoryId === c.id).length }))].map(c => `
146
+ <button type="button" class="cr-filter${_view.categoryId === c.id ? ' active' : ''}" data-filter="${esc(c.id)}" aria-pressed="${_view.categoryId === c.id}" title="${esc(c.name)}">${c.id ? `<span class="cr-cat-dot" style="background:${esc(color(c.color))}" aria-hidden="true"></span>` : ''}<span>${esc(c.name)}</span><span class="cr-filter-count">${c.count}</span></button>`).join('');
147
+ const notice = $('cr-notice');
148
+ notice.hidden = !_dataError && navigator.onLine !== false;
149
+ notice.innerHTML = _dataError ? `Your command library could not be loaded. <button type="button" class="cr-button" data-action="retry">Try again</button>` : canRun() ? 'You’re offline. Local terminal commands are still available.' : 'You’re offline. Your saved commands are available; reconnect to run them.';
150
+ const groups = selectCommandGroups(_data, _view);
151
+ const count = groups.reduce((n, group) => n + group.commands.length, 0);
152
+ $('cr-results').textContent = _dataError ? 'Library unavailable' : `${count} of ${_data.commands.length} command${_data.commands.length === 1 ? '' : 's'}`;
153
+ if (_dataError) body.innerHTML = emptyState('Your library is safe', 'Try loading it again before making changes.', SVG.folder);
154
+ else if (!_data.categories.length) body.innerHTML = emptyState('No saved commands', 'Create a group, then save the commands you use regularly.', SVG.terminal, `<button type="button" class="cr-button cr-primary" data-action="first-group">${SVG.plus}Create a group</button>`);
155
+ else if (!groups.length || (!count && _view.query.trim())) body.innerHTML = emptyState('No commands found', `Try a different search${_view.categoryId ? ' or another group' : ''}.`, SVG.search, '<button type="button" class="cr-button" data-action="reset-filters">Clear filters</button>');
156
+ else body.innerHTML = groups.map(({ category: cat, commands }) => {
157
+ const collapsed = _view.query.trim() ? _searchCollapsed.has(cat.id) : cat.collapsed;
158
+ const listId = 'cr-list-' + cat.id;
159
+ return `<section class="cr-category${collapsed ? ' collapsed' : ''}" data-cat-id="${esc(cat.id)}">
160
+ <div class="cr-cat-header"><button type="button" class="cr-cat-toggle" data-action="collapse" aria-expanded="${!collapsed}" aria-controls="${esc(listId)}" title="${esc(cat.name)}"><span class="cr-cat-chevron">${SVG.chevron}</span><span class="cr-cat-dot" style="background:${esc(color(cat.color))}" aria-hidden="true"></span><span class="cr-cat-name">${esc(cat.name)}</span><span class="cr-cat-count">${commands.length}</span></button>
161
+ <span class="cr-cat-actions"><button type="button" class="cr-icon-btn" data-action="add-in-group" aria-label="Add command to ${esc(cat.name)}" title="Add command">${SVG.plus}</button><button type="button" class="cr-icon-btn" data-action="edit-cat" aria-label="Edit group ${esc(cat.name)}" title="Edit group">${SVG.edit}</button><button type="button" class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cat" aria-label="Delete group ${esc(cat.name)}" title="Delete group">${SVG.trash}</button></span></div>
162
+ <div class="cr-cat-body" id="${esc(listId)}"${collapsed ? ' hidden' : ''}>${commands.length ? commands.map(renderCommand).join('') : `<div class="cr-cat-empty"><span>No commands in this group yet.</span><button type="button" class="cr-button" data-action="add-in-group">${SVG.plus}Add a command</button></div>`}</div></section>`;
163
+ }).join('');
164
+ if (focusedFilter != null) [...$('cr-filters').querySelectorAll('button')].find(b => b.dataset.filter === focusedFilter)?.focus();
165
+ else if (focusedCommand) [...body.querySelectorAll('.cr-cmd')].find(e => e.dataset.cmdId === focusedCommand)?.querySelector(`[data-action="${focusedAction}"]`)?.focus();
166
+ else if (focusedGroup) [...body.querySelectorAll('.cr-category')].find(e => e.dataset.catId === focusedGroup)?.querySelector(`[data-action="${focusedAction}"]`)?.focus();
225
167
  }
226
168
 
227
- // ═══════════════════════════════════════════
228
- // MODAL (Add/Edit forms)
229
- // ═══════════════════════════════════════════
230
-
231
- function openModal(type, id) {
232
- _editTarget = { type, id: id || null };
233
- const overlay = $('cr-modal-overlay');
169
+ function openModal(type, id = null, categoryId = '', opener = document.activeElement) {
170
+ if (_dataError) return;
171
+ _modalOpener = opener;
172
+ _editTarget = { type, id, categoryId };
234
173
  const modal = $('cr-modal');
235
- if (!overlay || !modal) return;
236
-
237
- if (type === 'category') {
238
- const existing = id ? _data.categories.find(c => c.id === id) : null;
239
- modal.innerHTML = buildCategoryForm(existing);
240
- } else {
241
- const existing = id ? _data.commands.find(c => c.id === id) : null;
242
- modal.innerHTML = buildCommandForm(existing);
243
- }
244
-
245
- overlay.classList.remove('hidden');
174
+ if (type === 'category') modal.innerHTML = buildCategoryForm(id ? _data.categories.find(c => c.id === id) : null);
175
+ else if (type === 'command') modal.innerHTML = buildCommandForm(id ? _data.commands.find(c => c.id === id) : null, categoryId);
176
+ else modal.innerHTML = buildDeleteDialog(type, id);
177
+ $('cr-modal-overlay').classList.remove('hidden');
178
+ $('cr-main').inert = true; _panel.querySelector('.cr-header').inert = true;
179
+ modal.querySelector('input:not([type="hidden"]), #cr-modal-cancel')?.focus();
246
180
  }
247
-
248
181
  function closeModal() {
249
- const overlay = $('cr-modal-overlay');
250
- if (overlay) overlay.classList.add('hidden');
251
- _editTarget = null;
182
+ $('cr-modal-overlay')?.classList.add('hidden');
183
+ $('cr-main').inert = false; _panel.querySelector('.cr-header').inert = false; _editTarget = null;
184
+ if (_modalOpener?.isConnected) _modalOpener.focus(); else $('cr-search')?.focus();
185
+ }
186
+ function modalHeader(title, description = '') {
187
+ return `<div class="cr-modal-header"><div><h4 id="cr-modal-title">${esc(title)}</h4>${description ? `<p>${esc(description)}</p>` : ''}</div><button type="button" class="cr-icon-btn" id="cr-modal-close" aria-label="Close dialog">${SVG.close}</button></div>`;
188
+ }
189
+ function modalFooter(label) {
190
+ return `<p class="cr-form-error" id="cr-form-error" role="alert" hidden></p><div class="cr-modal-footer"><button type="button" class="cr-button" id="cr-modal-cancel">Cancel</button><button type="submit" class="cr-button cr-primary" id="cr-modal-save">${label}</button></div>`;
252
191
  }
253
-
254
192
  function buildCategoryForm(existing) {
255
- const name = existing ? esc(existing.name) : '';
256
193
  const activeColor = existing?.color || COLOR_PALETTE[0];
257
-
258
- let swatches = '';
259
- for (const c of COLOR_PALETTE.slice(0, 16)) {
260
- const sel = c === activeColor ? ' selected' : '';
261
- swatches += `<button type="button" class="cr-color-swatch${sel}" data-color="${c}" style="background:${c}"></button>`;
262
- }
263
-
264
- return `
265
- <div class="cr-modal-header">
266
- <h4>${existing ? 'Edit' : 'New'} Group</h4>
267
- <button class="cr-icon-btn" id="cr-modal-close">${SVG_CLOSE}</button>
268
- </div>
269
- <div class="cr-modal-body">
270
- <label class="cr-form-label">Name</label>
271
- <input class="cr-form-input" id="cr-cat-name" type="text" value="${name}" placeholder="e.g. Dev Servers" autocomplete="off" />
272
- <label class="cr-form-label" style="margin-top:12px">Color</label>
273
- <div class="cr-color-picker" id="cr-cat-color">${swatches}</div>
274
- <input type="hidden" id="cr-cat-color-val" value="${esc(activeColor)}" />
275
- </div>
276
- <div class="cr-modal-footer">
277
- <button class="cr-modal-btn" id="cr-modal-cancel">Cancel</button>
278
- <button class="cr-modal-btn cr-modal-btn--primary" id="cr-modal-save">${existing ? 'Save' : 'Create'}</button>
279
- </div>
280
- `;
194
+ const swatches = COLOR_PALETTE.slice(0, 16).map(c => `<button type="button" class="cr-color-swatch${c === activeColor ? ' selected' : ''}" data-color="${c}" aria-label="Group color ${c}" aria-pressed="${c === activeColor}" style="--swatch:${c}">${SVG.check}</button>`).join('');
195
+ return `<form id="cr-form" novalidate>${modalHeader(existing ? 'Edit group' : 'Create a group', 'Keep related commands together.')}<div class="cr-modal-body"><div class="cr-form-fields"><div class="cr-form-row"><label class="cr-form-label" for="cr-cat-name">Group name</label><input class="cr-form-input" id="cr-cat-name" required type="text" value="${esc(existing?.name)}" placeholder="Development, checks, deployment…" autocomplete="off"></div></div><fieldset class="cr-color-field"><legend class="cr-form-label">Group color</legend><div class="cr-color-picker" id="cr-cat-color">${swatches}</div></fieldset><input type="hidden" id="cr-cat-color-val" value="${esc(activeColor)}"></div>${modalFooter(existing ? 'Save changes' : 'Create group')}</form>`;
281
196
  }
282
-
283
- function buildCommandForm(existing) {
284
- const name = existing ? esc(existing.name) : '';
285
- const command = existing ? esc(existing.command) : '';
286
- const cwd = existing?.cwd ? esc(existing.cwd) : '';
287
- const catId = existing?.categoryId || '';
288
-
289
- let catOptions = '';
290
- const sorted = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
291
- for (const cat of sorted) {
292
- const sel = cat.id === catId ? ' selected' : '';
293
- catOptions += `<option value="${esc(cat.id)}"${sel}>${esc(cat.name)}</option>`;
294
- }
295
-
296
- return `
297
- <div class="cr-modal-header">
298
- <h4>${existing ? 'Edit' : 'New'} Command</h4>
299
- <button class="cr-icon-btn" id="cr-modal-close">${SVG_CLOSE}</button>
300
- </div>
301
- <div class="cr-modal-body">
302
- <label class="cr-form-label">Group</label>
303
- <select class="cr-form-input cr-form-select" id="cr-cmd-cat">${catOptions}</select>
304
-
305
- <label class="cr-form-label" style="margin-top:12px">Name</label>
306
- <input class="cr-form-input" id="cr-cmd-name" type="text" value="${name}" placeholder="e.g. Start Dev Server" autocomplete="off" />
307
-
308
- <label class="cr-form-label" style="margin-top:12px">Command</label>
309
- <input class="cr-form-input cr-form-mono" id="cr-cmd-command" type="text" value="${command}" placeholder="e.g. npm run dev" autocomplete="off" />
310
-
311
- <label class="cr-form-label" style="margin-top:12px">Working Directory <span class="cr-form-hint">(optional)</span></label>
312
- <input class="cr-form-input cr-form-mono" id="cr-cmd-cwd" type="text" value="${cwd}" placeholder="e.g. /home/user/myproject" autocomplete="off" />
313
- </div>
314
- <div class="cr-modal-footer">
315
- <button class="cr-modal-btn" id="cr-modal-cancel">Cancel</button>
316
- <button class="cr-modal-btn cr-modal-btn--primary" id="cr-modal-save">${existing ? 'Save' : 'Create'}</button>
317
- </div>
318
- `;
197
+ function buildCommandForm(existing, categoryId) {
198
+ const catId = existing?.categoryId || categoryId || _view.categoryId;
199
+ const options = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).map(c => `<option value="${esc(c.id)}"${c.id === catId ? ' selected' : ''}>${esc(c.name)}</option>`).join('');
200
+ return `<form id="cr-form" novalidate>${modalHeader(existing ? 'Edit command' : 'Save a command', 'Choose a group and enter the command to save.')}<div class="cr-modal-body"><div class="cr-form-fields">
201
+ <div class="cr-form-row"><label class="cr-form-label" for="cr-cmd-name">Command name</label><input class="cr-form-input" id="cr-cmd-name" required type="text" value="${esc(existing?.name)}" placeholder="Start dev server" autocomplete="off"></div>
202
+ <div class="cr-form-row"><label class="cr-form-label" for="cr-cmd-cat">Group</label><select class="cr-form-input" id="cr-cmd-cat">${options}</select></div>
203
+ <div class="cr-form-row cr-form-row--command"><label class="cr-form-label" for="cr-cmd-command">Command</label><textarea class="cr-form-input cr-form-mono" id="cr-cmd-command" required rows="3" placeholder="npm run dev" spellcheck="false" autocomplete="off">${esc(existing?.command)}</textarea></div>
204
+ <div class="cr-form-row"><label class="cr-form-label" for="cr-cmd-cwd">Working directory <span class="cr-form-hint">Optional</span></label><div><input class="cr-form-input cr-form-mono" id="cr-cmd-cwd" type="text" value="${esc(existing?.cwd)}" placeholder="/path/to/your/project" autocomplete="off" aria-describedby="cr-cwd-help"><p id="cr-cwd-help" class="cr-form-help">Leave blank to use the default terminal directory.</p></div></div>
205
+ </div></div>${modalFooter(existing ? 'Save changes' : 'Save command')}</form>`;
206
+ }
207
+ function buildDeleteDialog(type, id) {
208
+ const group = type === 'delete-category';
209
+ const entry = (group ? _data.categories : _data.commands).find(e => e.id === id);
210
+ const count = _data.commands.filter(c => c.categoryId === id).length;
211
+ const description = group ? `This removes the group${count ? ` and its ${count} saved command${count === 1 ? '' : 's'}` : ''}.` : 'This removes the saved command from your library.';
212
+ return `${modalHeader(group ? 'Delete this group?' : 'Delete this command?')}<div class="cr-modal-body"><p class="cr-delete-name" dir="auto">${esc(entry?.name)}</p><p class="cr-form-help">${description} Existing terminals stay open.</p></div><div class="cr-modal-footer"><button type="button" class="cr-button" id="cr-modal-cancel">Cancel</button><button type="button" class="cr-button cr-danger" data-action="confirm-delete">Delete ${group ? 'group' : 'command'}</button></div>`;
213
+ }
214
+ function invalid(field, message) {
215
+ field?.setAttribute('aria-invalid', 'true'); field?.setAttribute('aria-describedby', 'cr-form-error');
216
+ $('cr-form-error').textContent = message; $('cr-form-error').hidden = false; field?.focus();
319
217
  }
320
-
321
- // ═══════════════════════════════════════════
322
- // CRUD
323
- // ═══════════════════════════════════════════
324
-
325
218
  function saveFromModal() {
326
- if (!_editTarget) return;
327
-
219
+ if (!_editTarget || _dataError) return;
220
+ const nameEl = $(_editTarget.type === 'category' ? 'cr-cat-name' : 'cr-cmd-name'), name = nameEl?.value.trim();
221
+ if (!name) return invalid(nameEl, 'Give this ' + (_editTarget.type === 'category' ? 'group' : 'command') + ' a name.');
328
222
  if (_editTarget.type === 'category') {
329
- const nameEl = $('cr-cat-name');
330
- const colorEl = $('cr-cat-color-val');
331
- const name = nameEl?.value.trim();
332
- if (!name) { nameEl?.focus(); return; }
333
- const color = colorEl?.value || COLOR_PALETTE[0];
334
-
335
- if (_editTarget.id) {
336
- // Update existing
337
- const cat = _data.categories.find(c => c.id === _editTarget.id);
338
- if (cat) { cat.name = name; cat.color = color; }
339
- } else {
340
- // Create new
341
- _data.categories.push({
342
- id: genId('cat'),
343
- name,
344
- color,
345
- collapsed: false,
346
- order: _data.categories.length,
347
- });
348
- }
223
+ const selectedColor = $('cr-cat-color-val').value || COLOR_PALETTE[0];
224
+ if (_editTarget.id) { const cat = _data.categories.find(c => c.id === _editTarget.id); if (cat) Object.assign(cat, { name, color: selectedColor }); }
225
+ else _data.categories.push({ id: genId('cat'), name, color: selectedColor, collapsed: false, order: _data.categories.length });
349
226
  } else {
350
- const catEl = $('cr-cmd-cat');
351
- const nameEl = $('cr-cmd-name');
352
- const cmdEl = $('cr-cmd-command');
353
- const cwdEl = $('cr-cmd-cwd');
354
-
355
- const categoryId = catEl?.value;
356
- const name = nameEl?.value.trim();
357
- const command = cmdEl?.value.trim();
358
- const cwd = cwdEl?.value.trim() || null;
359
-
360
- if (!categoryId) { catEl?.focus(); return; }
361
- if (!name) { nameEl?.focus(); return; }
362
- if (!command) { cmdEl?.focus(); return; }
363
-
364
- if (_editTarget.id) {
365
- // Update existing
366
- const cmd = _data.commands.find(c => c.id === _editTarget.id);
367
- if (cmd) {
368
- cmd.categoryId = categoryId;
369
- cmd.name = name;
370
- cmd.command = command;
371
- cmd.cwd = cwd;
372
- }
373
- } else {
374
- // Create new
375
- const sibling = getCategoryCommands(categoryId);
376
- _data.commands.push({
377
- id: genId('cmd'),
378
- categoryId,
379
- name,
380
- command,
381
- cwd,
382
- order: sibling.length,
383
- });
384
- }
227
+ const categoryId = $('cr-cmd-cat').value, command = $('cr-cmd-command').value.trim(), cwd = $('cr-cmd-cwd').value.trim() || null;
228
+ if (!categoryId) return invalid($('cr-cmd-cat'), 'Choose a group for this command.');
229
+ if (!command) return invalid($('cr-cmd-command'), 'Enter the command you want to save.');
230
+ if (_editTarget.id) { const cmd = _data.commands.find(c => c.id === _editTarget.id); if (cmd) Object.assign(cmd, { categoryId, name, command, cwd }); }
231
+ else _data.commands.push({ id: genId('cmd'), categoryId, name, command, cwd, order: _data.commands.filter(c => c.categoryId === categoryId).length });
232
+ _view.categoryId = categoryId;
233
+ const category = _data.categories.find(c => c.id === categoryId);
234
+ if (category) category.collapsed = false;
385
235
  }
386
-
387
- saveData();
388
- closeModal();
389
- renderBody();
236
+ _view.query = ''; $('cr-search').value = ''; // Reveal the saved entry after editing.
237
+ saveData(); closeModal(); renderBody(); setStatus('Saved to your command library.');
390
238
  }
391
-
392
- function deleteCommand(id) {
393
- _data.commands = _data.commands.filter(c => c.id !== id);
394
- saveData();
395
- renderBody();
239
+ function confirmDelete() {
240
+ const { type, id } = _editTarget;
241
+ _data.commands = _data.commands.filter(c => type === 'delete-category' ? c.categoryId !== id : c.id !== id);
242
+ if (type === 'delete-category') _data.categories = _data.categories.filter(c => c.id !== id);
243
+ saveData(); closeModal(); renderBody(); $('cr-search')?.focus();
244
+ setStatus(type === 'delete-category' ? 'Group deleted.' : 'Command deleted.');
396
245
  }
397
-
398
- function deleteCategory(id) {
399
- const cmds = getCategoryCommands(id);
400
- if (cmds.length > 0) {
401
- if (!confirm(`Delete group and its ${cmds.length} command${cmds.length > 1 ? 's' : ''}?`)) return;
402
- }
403
- _data.commands = _data.commands.filter(c => c.categoryId !== id);
404
- _data.categories = _data.categories.filter(c => c.id !== id);
405
- saveData();
406
- renderBody();
246
+ function setStatus(text) { $('cr-results').textContent = text; }
247
+ function runCommand(id) {
248
+ const cmd = _data.commands.find(c => c.id === id);
249
+ if (!cmd || !canRun()) return;
250
+ emit('terminal:run-command', commandLaunchPayload(cmd));
251
+ cmd.lastRunAt = Date.now(); saveData(); _launchedId = id;
252
+ renderBody(); setStatus(`Sent “${cmd.name}” to a new terminal.`);
253
+ clearTimeout(_launchTimer);
254
+ _launchTimer = setTimeout(() => { if (!_panel) return; _launchedId = null; renderBody(); }, 2500);
407
255
  }
408
-
409
- function toggleCategoryCollapse(catId) {
410
- const cat = _data.categories.find(c => c.id === catId);
411
- if (cat) {
412
- cat.collapsed = !cat.collapsed;
413
- saveData();
414
- renderBody();
415
- }
416
- }
417
-
418
- // ═══════════════════════════════════════════
419
- // RUN COMMAND
420
- // ═══════════════════════════════════════════
421
-
422
- function runCommand(cmdId) {
423
- const cmd = _data.commands.find(c => c.id === cmdId);
424
- if (!cmd) return;
425
- emit('terminal:run-command', {
426
- command: cmd.command,
427
- cwd: cmd.cwd || null,
428
- label: cmd.name,
429
- });
430
- }
431
-
432
- // ═══════════════════════════════════════════
433
- // EVENT WIRING
434
- // ═══════════════════════════════════════════
256
+ function resetFilters() { _view.query = ''; _view.categoryId = ''; $('cr-search').value = ''; renderBody(); $('cr-search').focus(); }
435
257
 
436
258
  function wirePanel() {
437
- if (!_panel) return;
438
-
439
- // ── Main click delegation ──
440
- _panel.addEventListener('click', (e) => {
441
- // Close button
442
- if (e.target.closest('#cr-close-btn')) return closePanel();
443
-
444
- // Header add buttons
445
- if (e.target.closest('#cr-add-cat-btn')) return openModal('category');
446
- if (e.target.closest('#cr-add-cmd-btn')) {
447
- if (_data.categories.length === 0) {
448
- // Need at least one category first
449
- openModal('category');
450
- } else {
451
- openModal('command');
259
+ _panel.addEventListener('input', event => {
260
+ if (event.target.id === 'cr-search') { _view.query = event.target.value; _searchCollapsed.clear(); renderBody(); }
261
+ else if (event.target.closest('#cr-form')) { event.target.removeAttribute('aria-invalid'); if ($('cr-form-error')) $('cr-form-error').hidden = true; }
262
+ });
263
+ _panel.addEventListener('change', event => { if (event.target.id === 'cr-sort') { _view.sort = event.target.value; renderBody(); } });
264
+ _panel.addEventListener('submit', event => { if (event.target.id !== 'cr-form') return; event.preventDefault(); saveFromModal(); });
265
+ _panel.addEventListener('click', event => {
266
+ const button = event.target.closest('button');
267
+ if (!button || button.disabled) return;
268
+ if (button.id === 'cr-close-btn') return closePanel();
269
+ if (button.id === 'cr-clear-search') { _view.query = ''; $('cr-search').value = ''; renderBody(); $('cr-search').focus(); return; }
270
+ if (button.id === 'cr-add-cat-btn' || button.dataset.action === 'first-group') return openModal('category', null, '', button);
271
+ if (button.id === 'cr-add-cmd-btn') return openModal(_data.categories.length ? 'command' : 'category', null, '', button);
272
+ if (button.id === 'cr-modal-close' || button.id === 'cr-modal-cancel') return closeModal();
273
+ if (button.hasAttribute('data-filter')) { _view.categoryId = button.dataset.filter; renderBody(); return; }
274
+ const action = button.dataset.action, catId = button.closest('.cr-category')?.dataset.catId, cmdId = button.closest('.cr-cmd')?.dataset.cmdId;
275
+ if (action === 'run') return runCommand(cmdId);
276
+ if (action === 'add-in-group') return openModal('command', null, catId, button);
277
+ if (action === 'edit-cat') return openModal('category', catId, '', button);
278
+ if (action === 'delete-cat') return openModal('delete-category', catId, '', button);
279
+ if (action === 'edit-cmd') return openModal('command', cmdId, '', button);
280
+ if (action === 'delete-cmd') return openModal('delete-command', cmdId, '', button);
281
+ if (action === 'confirm-delete') return confirmDelete();
282
+ if (action === 'collapse') {
283
+ const cat = _data.categories.find(c => c.id === catId);
284
+ if (cat) {
285
+ if (_view.query.trim()) {
286
+ if (_searchCollapsed.has(catId)) _searchCollapsed.delete(catId); else _searchCollapsed.add(catId);
287
+ } else { cat.collapsed = !cat.collapsed; saveData(); }
288
+ renderBody();
452
289
  }
453
- return;
454
290
  }
455
-
456
- // Run button
457
- const runBtn = e.target.closest('.cr-run-btn');
458
- if (runBtn) {
459
- const cmdEl = runBtn.closest('.cr-cmd');
460
- if (cmdEl) return runCommand(cmdEl.dataset.cmdId);
461
- }
462
-
463
- // Category actions
464
- const editCat = e.target.closest('[data-action="edit-cat"]');
465
- if (editCat) {
466
- const catEl = editCat.closest('.cr-category');
467
- if (catEl) return openModal('category', catEl.dataset.catId);
468
- }
469
- const delCat = e.target.closest('[data-action="delete-cat"]');
470
- if (delCat) {
471
- const catEl = delCat.closest('.cr-category');
472
- if (catEl) return deleteCategory(catEl.dataset.catId);
473
- }
474
-
475
- // Command actions
476
- const editCmd = e.target.closest('[data-action="edit-cmd"]');
477
- if (editCmd) {
478
- const cmdEl = editCmd.closest('.cr-cmd');
479
- if (cmdEl) return openModal('command', cmdEl.dataset.cmdId);
480
- }
481
- const delCmd = e.target.closest('[data-action="delete-cmd"]');
482
- if (delCmd) {
483
- const cmdEl = delCmd.closest('.cr-cmd');
484
- if (cmdEl) return deleteCommand(cmdEl.dataset.cmdId);
485
- }
486
-
487
- // Category header click → toggle collapse (but not if clicking actions)
488
- const catHeader = e.target.closest('.cr-cat-header');
489
- if (catHeader && !e.target.closest('.cr-cat-actions')) {
490
- const catEl = catHeader.closest('.cr-category');
491
- if (catEl) return toggleCategoryCollapse(catEl.dataset.catId);
492
- }
493
-
494
- // Modal buttons
495
- if (e.target.closest('#cr-modal-close') || e.target.closest('#cr-modal-cancel')) return closeModal();
496
- if (e.target.closest('#cr-modal-save')) return saveFromModal();
497
-
498
- // Color swatch selection
499
- const swatch = e.target.closest('.cr-color-swatch');
500
- if (swatch) {
501
- const picker = swatch.closest('.cr-color-picker');
502
- if (picker) {
503
- picker.querySelectorAll('.cr-color-swatch').forEach(s => s.classList.remove('selected'));
504
- swatch.classList.add('selected');
505
- const hidden = $('cr-cat-color-val');
506
- if (hidden) hidden.value = swatch.dataset.color;
507
- }
508
- return;
291
+ if (action === 'reset-filters') return resetFilters();
292
+ if (action === 'retry') { loadData(); renderBody(); return; }
293
+ if (button.dataset.color) {
294
+ $('cr-cat-color').querySelectorAll('button').forEach(swatch => { const selected = swatch === button; swatch.classList.toggle('selected', selected); swatch.setAttribute('aria-pressed', String(selected)); });
295
+ $('cr-cat-color-val').value = button.dataset.color;
509
296
  }
510
297
  });
511
-
512
- // ── Modal overlay background click → close ──
513
- const overlay = $('cr-modal-overlay');
514
- if (overlay) {
515
- overlay.addEventListener('click', (e) => {
516
- if (e.target === overlay) closeModal();
517
- });
518
- }
519
-
520
- // ── Enter key submits modal form ──
521
- _panel.addEventListener('keydown', (e) => {
522
- if (e.key === 'Enter' && _editTarget) {
523
- e.preventDefault();
524
- saveFromModal();
298
+ $('cr-modal-overlay').addEventListener('click', event => { if (event.target.id === 'cr-modal-overlay') closeModal(); });
299
+ _panel.addEventListener('keydown', event => {
300
+ const editing = event.target.closest('input, textarea, select, [contenteditable]');
301
+ if (_editTarget && event.key === 'Tab') {
302
+ const focusable = [...$('cr-modal').querySelectorAll('button:not(:disabled), input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled)')];
303
+ // Explicit traversal includes buttons even when Safari skips them by default.
304
+ const index = focusable.indexOf(document.activeElement);
305
+ const next = index < 0 ? (event.shiftKey ? focusable.length - 1 : 0) : (index + (event.shiftKey ? -1 : 1) + focusable.length) % focusable.length;
306
+ event.preventDefault(); focusable[next]?.focus();
525
307
  }
526
- if (e.key === 'Escape') {
527
- if (_editTarget) {
528
- closeModal();
529
- } else {
530
- closePanel();
531
- }
308
+ if (event.key === 'Escape') {
309
+ event.preventDefault(); event.stopPropagation();
310
+ if (_editTarget) closeModal();
311
+ else if (_view.query) { _view.query = ''; $('cr-search').value = ''; renderBody(); $('cr-search').focus(); }
312
+ else closePanel();
532
313
  }
314
+ if (!_editTarget && ((!editing && event.key === '/') || ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'f'))) { event.preventDefault(); event.stopPropagation(); $('cr-search').focus(); }
315
+ if (_editTarget && (event.ctrlKey || event.metaKey) && event.key === 'Enter' && $('cr-form')) { event.preventDefault(); saveFromModal(); }
533
316
  });
534
317
  }