synabun 2.0.9 → 2.0.10
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.
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +46 -4
- package/launcher.mjs +9 -3
- package/lib/start-launcher.js +6 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/neural-interface/i18n/en.json +218 -7
- package/neural-interface/i18n/pt-BR.json +220 -9
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +21 -7
- package/neural-interface/lib/assistant-brains/opencode.js +13 -1
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +44 -0
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +57 -0
- package/neural-interface/lib/assistant-catalog.js +17 -2
- package/neural-interface/lib/assistant-dispatch.js +3 -0
- package/neural-interface/lib/assistant-envelope.js +19 -1
- package/neural-interface/lib/assistant-runtime.js +127 -8
- package/neural-interface/lib/claude-model-catalog.js +64 -14
- package/neural-interface/lib/codex-settings-schema.js +109 -47
- package/neural-interface/lib/editor-completion-opencode.js +132 -35
- package/neural-interface/lib/exec-loop-command.js +8 -1
- package/neural-interface/lib/launch-cache.js +293 -0
- package/neural-interface/lib/login-start.js +524 -0
- package/neural-interface/lib/native-loop-providers.js +18 -3
- package/neural-interface/lib/native-loop-runtime.js +19 -0
- package/neural-interface/lib/opencode-db-schema.js +75 -0
- package/neural-interface/lib/opencode-history.js +16 -1
- package/neural-interface/lib/opencode-protocol.js +173 -0
- package/neural-interface/lib/opencode-provider-view.js +81 -0
- package/neural-interface/lib/opencode-runtime-config.js +10 -3
- package/neural-interface/lib/opencode-serve-records.js +606 -0
- package/neural-interface/lib/opencode-v2-client.js +33 -6
- package/neural-interface/lib/opencode-v2-ws-requests.js +19 -5
- package/neural-interface/lib/opencode-worker-activity.js +229 -0
- package/neural-interface/lib/opencode2-client.js +760 -0
- package/neural-interface/lib/opencode2-proxy.js +116 -0
- package/neural-interface/lib/opencode2-translate.js +1008 -0
- package/neural-interface/lib/process-identity.js +361 -0
- package/neural-interface/lib/session-cache-builder.js +26 -5
- package/neural-interface/lib/session-title-generator.js +18 -0
- package/neural-interface/lib/style-guide/api.js +4 -3
- package/neural-interface/lib/untrusted-file-headers.js +27 -0
- package/neural-interface/package-lock.json +1343 -45
- package/neural-interface/package.json +5 -1
- package/neural-interface/public/index.html +2 -2
- package/neural-interface/public/shared/agent-runtime-options.js +55 -11
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +172 -17
- package/neural-interface/public/shared/assistant/asst-api.js +11 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +195 -80
- package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
- package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
- package/neural-interface/public/shared/assistant/asst-menu.js +77 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +197 -33
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +28 -10
- package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
- package/neural-interface/public/shared/assistant/asst-render.js +382 -36
- package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
- package/neural-interface/public/shared/assistant/asst-scenes.js +528 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
- package/neural-interface/public/shared/assistant/asst-state.js +14 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +43 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +13 -1
- package/neural-interface/public/shared/cc-dropdown.js +480 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +13 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +149 -35
- package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
- package/neural-interface/public/shared/data-cache.js +223 -0
- package/neural-interface/public/shared/desktop-bridge.js +11 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
- package/neural-interface/public/shared/desktop-titlebar.js +214 -28
- package/neural-interface/public/shared/desktop-update-view.js +15 -5
- package/neural-interface/public/shared/file-links.js +73 -8
- package/neural-interface/public/shared/html-shell.js +74 -80
- package/neural-interface/public/shared/launch-prefetch.js +59 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +7 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +93 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +16 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +60 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -19
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +195 -19
- package/neural-interface/public/shared/opencode-providers.js +39 -0
- package/neural-interface/public/shared/schedules-view.js +318 -0
- package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
- package/neural-interface/public/shared/settings/settings-app.js +243 -0
- package/neural-interface/public/shared/settings/settings-data.js +88 -0
- package/neural-interface/public/shared/settings/settings-ia.js +17 -0
- package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
- package/neural-interface/public/shared/styles.css +540 -371
- package/neural-interface/public/shared/ui-assistant.js +27 -5
- package/neural-interface/public/shared/ui-automation-studio.js +67 -27
- package/neural-interface/public/shared/ui-bookmarks.js +2 -1
- package/neural-interface/public/shared/ui-claude-panel.js +76 -8
- package/neural-interface/public/shared/ui-codex-panel.js +1 -0
- package/neural-interface/public/shared/ui-detail.js +6 -6
- package/neural-interface/public/shared/ui-file-links.js +1 -0
- package/neural-interface/public/shared/ui-image-gallery.js +6 -5
- package/neural-interface/public/shared/ui-invite.js +18 -2
- package/neural-interface/public/shared/ui-link.js +9 -9
- package/neural-interface/public/shared/ui-menubar.js +232 -33
- package/neural-interface/public/shared/ui-native-loop-router.js +2 -0
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
- package/neural-interface/public/shared/ui-schedules-studio.js +2099 -869
- package/neural-interface/public/shared/ui-sessions.js +15 -4
- package/neural-interface/public/shared/ui-settings.js +87 -36
- package/neural-interface/public/shared/ui-sidebar.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +55 -4
- package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
- package/neural-interface/public/shared/ui-skills.js +70 -12
- package/neural-interface/public/shared/ui-sync.js +7 -0
- package/neural-interface/public/shared/ui-terminal.js +36 -20
- package/neural-interface/public/shared/ui-trash.js +8 -1
- package/neural-interface/public/shared/ui-tutorial-steps.js +7 -3
- package/neural-interface/public/shared/ui-tutorial.js +15 -5
- package/neural-interface/public/shared/ui-update.js +83 -14
- package/neural-interface/public/shared/ui-whiteboard.js +2 -1
- package/neural-interface/public/variant/2d/main.js +4 -1
- package/neural-interface/public/variant/3d/main.js +3 -0
- package/neural-interface/public/vendor/highlight/LICENSE +29 -0
- package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
- package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
- package/neural-interface/public/vendor/highlight/manifest.json +5 -0
- package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
- package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
- package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
- package/neural-interface/public/vendor/marked/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
- package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
- package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
- package/neural-interface/scripts/build-vendor.mjs +84 -3
- package/neural-interface/server.js +1099 -286
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/synabun/modules/schedule.md +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// SynaBun Neural Interface — what the Schedules window shows, without the page.
|
|
2
|
+
// Which schedules a view lists and in which order, how a schedule's state reads, and the cron
|
|
3
|
+
// in plain words. ui-schedules-studio.js draws it; tests import this file under Node, so it
|
|
4
|
+
// has no imports and never touches the DOM.
|
|
5
|
+
|
|
6
|
+
export const MINUTE = 60_000;
|
|
7
|
+
export const HOUR = 60 * MINUTE;
|
|
8
|
+
export const DAY = 24 * HOUR;
|
|
9
|
+
// A run this late is overdue, not merely due: the scheduler missed it.
|
|
10
|
+
export const OVERDUE_AFTER = 5 * MINUTE;
|
|
11
|
+
// "Up next" looks this far ahead.
|
|
12
|
+
export const UP_NEXT_WINDOW = DAY;
|
|
13
|
+
|
|
14
|
+
const DAY_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
15
|
+
|
|
16
|
+
export const VIEW_SCOPES = ['upcoming', 'all', 'attention', 'paused', 'timers', 'activity'];
|
|
17
|
+
export const LIST_SCOPES = ['upcoming', 'all', 'attention', 'paused'];
|
|
18
|
+
export const SORTS = ['next', 'name', 'last'];
|
|
19
|
+
|
|
20
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
21
|
+
const time = (t) => (Number.isFinite(t) ? t : NaN);
|
|
22
|
+
const parse = (iso) => (iso ? time(Date.parse(iso)) : NaN);
|
|
23
|
+
|
|
24
|
+
/** "a", "a and b", "a, b and c". */
|
|
25
|
+
export function listJoin(items) {
|
|
26
|
+
const list = items.filter(Boolean);
|
|
27
|
+
if (list.length <= 1) return list.join('');
|
|
28
|
+
return `${list.slice(0, -1).join(', ')} and ${list.at(-1)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dayList(field) {
|
|
32
|
+
if (field === '*' || field === '?') return 'Every day';
|
|
33
|
+
if (field === '1-5') return 'Weekdays';
|
|
34
|
+
if (field === '0,6' || field === '6,0' || field === '6,7') return 'Weekends';
|
|
35
|
+
const parts = field.split(',');
|
|
36
|
+
const names = [];
|
|
37
|
+
for (const part of parts) {
|
|
38
|
+
const range = part.match(/^(\d)-(\d)$/);
|
|
39
|
+
if (range) {
|
|
40
|
+
const from = DAY_SHORT[+range[1] % 7], to = DAY_SHORT[+range[2] % 7];
|
|
41
|
+
if (!from || !to) return null;
|
|
42
|
+
names.push(`${from}–${to}`);
|
|
43
|
+
} else if (/^\d$/.test(part)) {
|
|
44
|
+
names.push(DAY_SHORT[+part % 7]);
|
|
45
|
+
} else {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return listJoin(names);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function timesOf(minute, hour) {
|
|
53
|
+
const m = /^\d{1,2}$/.test(minute) ? +minute : null;
|
|
54
|
+
if (minute === '*' && hour === '*') return { every: 'every minute' };
|
|
55
|
+
const minStep = minute.match(/^\*\/(\d+)$/);
|
|
56
|
+
if (minStep && hour === '*') return { every: `every ${minStep[1]} minutes` };
|
|
57
|
+
if (m == null) {
|
|
58
|
+
// Several minutes in one hour: "at 09:00 and 09:30".
|
|
59
|
+
if (/^\d{1,2}(,\d{1,2})+$/.test(minute) && /^\d{1,2}$/.test(hour)) {
|
|
60
|
+
return { at: listJoin(minute.split(',').map(x => `${pad(+hour)}:${pad(+x)}`)) };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (hour === '*') return { every: `every hour at :${pad(m)}` };
|
|
65
|
+
const hourStep = hour.match(/^\*\/(\d+)$/);
|
|
66
|
+
if (hourStep) return { every: `every ${hourStep[1]} hours at :${pad(m)}` };
|
|
67
|
+
const hourRange = hour.match(/^(\d{1,2})-(\d{1,2})$/);
|
|
68
|
+
if (hourRange) return { every: `every hour from ${pad(+hourRange[1])}:${pad(m)} to ${pad(+hourRange[2])}:${pad(m)}` };
|
|
69
|
+
if (/^\d{1,2}(,\d{1,2})*$/.test(hour)) return { at: listJoin(hour.split(',').map(h => `${pad(+h)}:${pad(m)}`)) };
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A five-field cron in plain words: "Every day at 06:55, 14:55 and 20:55", "Weekdays at 09:00",
|
|
75
|
+
* "Every 15 minutes". Anything it cannot say plainly comes back as the expression itself.
|
|
76
|
+
*/
|
|
77
|
+
export function describeCron(cron) {
|
|
78
|
+
const expr = String(cron ?? '').trim();
|
|
79
|
+
const fields = expr.split(/\s+/);
|
|
80
|
+
if (fields.length !== 5) return expr;
|
|
81
|
+
const [minute, hour, dom, month, dow] = fields;
|
|
82
|
+
if (month !== '*') return expr;
|
|
83
|
+
let days;
|
|
84
|
+
if (dom === '*' || dom === '?') days = dayList(dow);
|
|
85
|
+
else if (dow === '*' && /^\d{1,2}(,\d{1,2})*$/.test(dom)) {
|
|
86
|
+
const dates = dom.split(',').map(Number);
|
|
87
|
+
days = `${dates.length > 1 ? 'Days' : 'Day'} ${listJoin(dates)} of every month`;
|
|
88
|
+
} else return expr;
|
|
89
|
+
const times = timesOf(minute, hour);
|
|
90
|
+
if (!days || !times) return expr;
|
|
91
|
+
if (times.at) return `${days} at ${times.at}`;
|
|
92
|
+
const every = times.every.charAt(0).toUpperCase() + times.every.slice(1);
|
|
93
|
+
return days === 'Every day' ? every : `${days}, ${times.every}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** One line of text from a run result: no newlines, no runs of spaces. */
|
|
97
|
+
export function oneLine(text) {
|
|
98
|
+
return String(text ?? '').replace(/\s+/g, ' ').trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* How the last run went, apart from whether the schedule is on: `kind` is ok, failed, started,
|
|
103
|
+
* stopped, interrupted, never or other; `label` is the word to show; `reason` the server's words.
|
|
104
|
+
*/
|
|
105
|
+
export function lastRunState(schedule) {
|
|
106
|
+
const raw = schedule?.lastRunResult;
|
|
107
|
+
if (raw == null || raw === '') return { kind: 'never', label: 'Not run yet', reason: '' };
|
|
108
|
+
const text = String(raw);
|
|
109
|
+
if (text === 'completed') return { kind: 'ok', label: 'Completed', reason: '' };
|
|
110
|
+
if (text === 'launched' || text === 'running' || text === 'started') return { kind: 'started', label: 'Started', reason: '' };
|
|
111
|
+
if (text === 'template_missing') return { kind: 'failed', label: 'Automation missing', reason: 'The automation this schedule launches no longer exists.' };
|
|
112
|
+
const colon = text.indexOf(':');
|
|
113
|
+
const head = (colon >= 0 ? text.slice(0, colon) : text).trim();
|
|
114
|
+
const reason = colon >= 0 ? text.slice(colon + 1).trim() : '';
|
|
115
|
+
if (head === 'error') return { kind: 'failed', label: 'Failed', reason: reason || 'The run failed.' };
|
|
116
|
+
if (head === 'stopped') return { kind: 'stopped', label: 'Stopped', reason: reason === 'user' ? 'Stopped by you' : reason };
|
|
117
|
+
if (head === 'interrupted') return { kind: 'interrupted', label: 'Interrupted', reason };
|
|
118
|
+
return { kind: 'other', label: head.charAt(0).toUpperCase() + head.slice(1), reason };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const isFailing = (schedule) => lastRunState(schedule).kind === 'failed';
|
|
122
|
+
|
|
123
|
+
export function isOverdue(schedule, now = Date.now()) {
|
|
124
|
+
const next = parse(schedule?.nextRun);
|
|
125
|
+
return !!schedule?.enabled && Number.isFinite(next) && next < now - OVERDUE_AFTER;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** An active schedule whose last run failed, or that missed its time. A paused one waits for you. */
|
|
129
|
+
export function needsAttention(schedule, now = Date.now()) {
|
|
130
|
+
return !!schedule?.enabled && (isFailing(schedule) || isOverdue(schedule, now));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function isUpcoming(schedule, now = Date.now(), within = UP_NEXT_WINDOW) {
|
|
134
|
+
const next = parse(schedule?.nextRun);
|
|
135
|
+
return !!schedule?.enabled && Number.isFinite(next) && next >= now - OVERDUE_AFTER && next <= now + within;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const groupIdOf = (scope) => (typeof scope === 'string' && scope.startsWith('group:') ? scope.slice(6) : null);
|
|
139
|
+
export { groupIdOf };
|
|
140
|
+
|
|
141
|
+
/** Does a schedule belong to a list scope (a view, a group, or ungrouped)? */
|
|
142
|
+
export function inScope(schedule, scope, { now = Date.now(), groupIds = null } = {}) {
|
|
143
|
+
const groupId = groupIdOf(scope);
|
|
144
|
+
if (groupId) return schedule.groupId === groupId;
|
|
145
|
+
switch (scope) {
|
|
146
|
+
case 'upcoming': return isUpcoming(schedule, now);
|
|
147
|
+
case 'attention': return needsAttention(schedule, now);
|
|
148
|
+
case 'paused': return !schedule.enabled;
|
|
149
|
+
case 'ungrouped': return !schedule.groupId || (groupIds ? !groupIds.has(schedule.groupId) : false);
|
|
150
|
+
default: return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The counts the rail shows next to each view. */
|
|
155
|
+
export function scopeCounts(schedules, now = Date.now()) {
|
|
156
|
+
const counts = { upcoming: 0, all: schedules.length, attention: 0, paused: 0, active: 0, failing: 0 };
|
|
157
|
+
for (const s of schedules) {
|
|
158
|
+
if (isUpcoming(s, now)) counts.upcoming++;
|
|
159
|
+
if (needsAttention(s, now)) counts.attention++;
|
|
160
|
+
if (s.enabled) counts.active++; else counts.paused++;
|
|
161
|
+
if (isFailing(s)) counts.failing++;
|
|
162
|
+
}
|
|
163
|
+
return counts;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** How many of a group's schedules are on, paused and need attention. */
|
|
167
|
+
export function groupSummary(schedules, now = Date.now()) {
|
|
168
|
+
const summary = { total: schedules.length, active: 0, paused: 0, attention: 0 };
|
|
169
|
+
for (const s of schedules) {
|
|
170
|
+
if (s.enabled) summary.active++; else summary.paused++;
|
|
171
|
+
if (needsAttention(s, now)) summary.attention++;
|
|
172
|
+
}
|
|
173
|
+
return summary;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Lower case, accents gone: "Ação" finds "acao". */
|
|
177
|
+
export function fold(text) {
|
|
178
|
+
return String(text ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Every word of the query must appear in the schedule's name, its automation, its group, its
|
|
183
|
+
* cadence or its model. `extra` adds the words the caller knows (automation and group names).
|
|
184
|
+
*/
|
|
185
|
+
export function matchesQuery(schedule, query, extra = []) {
|
|
186
|
+
const terms = fold(query).split(/\s+/).filter(Boolean);
|
|
187
|
+
if (!terms.length) return true;
|
|
188
|
+
const haystack = fold([
|
|
189
|
+
schedule.name, schedule.cron, describeCron(schedule.cron), schedule.model, schedule.profile, ...extra,
|
|
190
|
+
].filter(Boolean).join(' \u0000 '));
|
|
191
|
+
return terms.every(term => haystack.includes(term));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const byName = (a, b) => String(a.name || '').localeCompare(String(b.name || ''), undefined, { numeric: true, sensitivity: 'base' });
|
|
195
|
+
|
|
196
|
+
/** next: active first, soonest first. name: A to Z. last: most recent run first. */
|
|
197
|
+
export function sortSchedules(list, sort = 'next') {
|
|
198
|
+
const copy = [...list];
|
|
199
|
+
if (sort === 'name') return copy.sort(byName);
|
|
200
|
+
if (sort === 'last') {
|
|
201
|
+
return copy.sort((a, b) => {
|
|
202
|
+
const at = parse(a.lastRun), bt = parse(b.lastRun);
|
|
203
|
+
const av = Number.isFinite(at) ? at : -Infinity, bv = Number.isFinite(bt) ? bt : -Infinity;
|
|
204
|
+
return av !== bv ? bv - av : byName(a, b);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return copy.sort((a, b) => {
|
|
208
|
+
if (!!a.enabled !== !!b.enabled) return a.enabled ? -1 : 1;
|
|
209
|
+
const at = parse(a.nextRun), bt = parse(b.nextRun);
|
|
210
|
+
const av = Number.isFinite(at) ? at : Infinity, bv = Number.isFinite(bt) ? bt : Infinity;
|
|
211
|
+
return av !== bv ? av - bv : byName(a, b);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** "in 45 s", "in 26 min", "in 3 h 5 min", "in 2 d 4 h"; "Due now"; "Overdue". */
|
|
216
|
+
export function formatCountdown(iso, now = Date.now()) {
|
|
217
|
+
const next = parse(iso);
|
|
218
|
+
if (!Number.isFinite(next)) return { text: 'Not scheduled', state: 'none' };
|
|
219
|
+
const diff = next - now;
|
|
220
|
+
if (diff <= 0) return diff > -OVERDUE_AFTER ? { text: 'Due now', state: 'due' } : { text: 'Overdue', state: 'overdue' };
|
|
221
|
+
if (diff < MINUTE) return { text: `in ${Math.ceil(diff / 1000)} s`, state: 'soon' };
|
|
222
|
+
if (diff < HOUR) return { text: `in ${Math.floor(diff / MINUTE)} min`, state: diff < 15 * MINUTE ? 'soon' : 'future' };
|
|
223
|
+
if (diff < DAY) {
|
|
224
|
+
const h = Math.floor(diff / HOUR), m = Math.floor((diff % HOUR) / MINUTE);
|
|
225
|
+
return { text: m ? `in ${h} h ${m} min` : `in ${h} h`, state: 'future' };
|
|
226
|
+
}
|
|
227
|
+
const d = Math.floor(diff / DAY), h = Math.floor((diff % DAY) / HOUR);
|
|
228
|
+
return { text: h ? `in ${d} d ${h} h` : `in ${d} d`, state: 'future' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** "just now", "5 min ago", "2 h ago", "yesterday", "3 d ago". */
|
|
232
|
+
export function formatAgo(iso, now = Date.now()) {
|
|
233
|
+
const then = parse(iso);
|
|
234
|
+
if (!Number.isFinite(then)) return '';
|
|
235
|
+
const diff = Math.max(0, now - then);
|
|
236
|
+
if (diff < MINUTE) return 'just now';
|
|
237
|
+
if (diff < HOUR) return `${Math.floor(diff / MINUTE)} min ago`;
|
|
238
|
+
if (diff < DAY) return `${Math.floor(diff / HOUR)} h ago`;
|
|
239
|
+
const days = Math.floor(diff / DAY);
|
|
240
|
+
return days === 1 ? 'yesterday' : `${days} d ago`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
244
|
+
// The window's words are English, so are its dates: "Wed 22 Oct", never half in another language.
|
|
245
|
+
const LOCALE = 'en-GB';
|
|
246
|
+
|
|
247
|
+
/** "Today", "Tomorrow", "Yesterday" or the weekday and date, for a heading or a time. */
|
|
248
|
+
export function dayLabel(iso, now = Date.now(), locale = LOCALE) {
|
|
249
|
+
const then = new Date(parse(iso));
|
|
250
|
+
if (Number.isNaN(then.getTime())) return '';
|
|
251
|
+
const today = new Date(now);
|
|
252
|
+
if (sameDay(then, today)) return 'Today';
|
|
253
|
+
if (sameDay(then, new Date(now + DAY))) return 'Tomorrow';
|
|
254
|
+
if (sameDay(then, new Date(now - DAY))) return 'Yesterday';
|
|
255
|
+
return then.toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** 24-hour clock, as the cron descriptions are: "14:55". */
|
|
259
|
+
export function clockTime(iso, locale = LOCALE) {
|
|
260
|
+
const then = new Date(parse(iso));
|
|
261
|
+
if (Number.isNaN(then.getTime())) return '';
|
|
262
|
+
return then.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** "Today 14:55", "Tomorrow 09:00", "Wed 22 Oct 09:00". */
|
|
266
|
+
export function whenLabel(iso, now = Date.now(), locale = LOCALE) {
|
|
267
|
+
const day = dayLabel(iso, now, locale);
|
|
268
|
+
const clock = clockTime(iso, locale);
|
|
269
|
+
return day && clock ? `${day} ${clock}` : '';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** A run time in the fewest words: "14:55" today, "Tomorrow 09:00", "Wed 09:00" this week, then "22 Oct". */
|
|
273
|
+
export function shortWhen(iso, now = Date.now(), locale = LOCALE) {
|
|
274
|
+
const at = parse(iso);
|
|
275
|
+
if (!Number.isFinite(at)) return '';
|
|
276
|
+
const then = new Date(at);
|
|
277
|
+
const clock = clockTime(iso, locale);
|
|
278
|
+
if (sameDay(then, new Date(now))) return clock;
|
|
279
|
+
if (sameDay(then, new Date(now + DAY))) return `Tomorrow ${clock}`;
|
|
280
|
+
if (at > now && at - now < 6 * DAY) return `${then.toLocaleDateString(locale, { weekday: 'short' })} ${clock}`;
|
|
281
|
+
return then.toLocaleDateString(locale, { day: 'numeric', month: 'short' });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The launch settings a run will use and where each comes from: the schedule's own value wins,
|
|
286
|
+
* then its group's, then the automation's. `labels` names CLIs (`profiles`) and MCP presets.
|
|
287
|
+
*/
|
|
288
|
+
export function launchFacts(schedule, { group = null, template = null, profiles = [], mcpLabels = {} } = {}) {
|
|
289
|
+
const pick = (field) => {
|
|
290
|
+
for (const [source, owner] of [['schedule', schedule], ['group', group], ['automation', template]]) {
|
|
291
|
+
const value = owner?.[field];
|
|
292
|
+
if (value !== undefined && value !== null && value !== '') return { value, source };
|
|
293
|
+
}
|
|
294
|
+
return { value: null, source: 'default' };
|
|
295
|
+
};
|
|
296
|
+
const facts = [];
|
|
297
|
+
const profile = pick('profile');
|
|
298
|
+
const profileId = profile.value || 'claude-code';
|
|
299
|
+
facts.push({ key: 'profile', label: 'CLI', value: profiles.find(p => p.id === profileId)?.label || profileId, source: profile.value ? profile.source : 'default' });
|
|
300
|
+
const model = pick('model');
|
|
301
|
+
facts.push({ key: 'model', label: 'Model', value: model.value || 'Default', source: model.source, mono: !!model.value });
|
|
302
|
+
const context = pick('contextMode');
|
|
303
|
+
if (context.value && context.value !== 'default') facts.push({ key: 'contextMode', label: 'Context', value: context.value === '1m' ? '1M tokens' : context.value.charAt(0).toUpperCase() + context.value.slice(1), source: context.source });
|
|
304
|
+
const effort = pick('effort');
|
|
305
|
+
if (effort.value && effort.value !== 'off') facts.push({ key: 'effort', label: 'Thinking', value: effort.value.charAt(0).toUpperCase() + effort.value.slice(1), source: effort.source });
|
|
306
|
+
const mcp = pick('mcpProfile');
|
|
307
|
+
if (mcp.value) {
|
|
308
|
+
const value = mcpLabels[mcp.value] || String(mcp.value).split(',').map(x => x.trim()).filter(Boolean).join(', ');
|
|
309
|
+
facts.push({ key: 'mcpProfile', label: 'MCP tools', value, source: mcp.source });
|
|
310
|
+
}
|
|
311
|
+
if (profileId === 'codex') {
|
|
312
|
+
const account = pick('codexAccountId');
|
|
313
|
+
if (account.value) facts.push({ key: 'codexAccountId', label: 'OpenAI account', value: account.value, source: account.source });
|
|
314
|
+
}
|
|
315
|
+
const browser = pick('usesBrowser');
|
|
316
|
+
facts.push({ key: 'usesBrowser', label: 'Browser', value: browser.value === true ? 'On' : 'Off', source: browser.source });
|
|
317
|
+
return facts;
|
|
318
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SETTINGS — App (the pane)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The software's own controls, in four sections: Updates (the version, the last
|
|
6
|
+
// check, Check for updates, Install update, automatic updates), Startup (start at
|
|
7
|
+
// login, start in the background), Window (what the close button does, hardware
|
|
8
|
+
// acceleration) and Maintenance (the data and logs folders, Restart SynaBun).
|
|
9
|
+
//
|
|
10
|
+
// Inside the desktop app the settings are the app's (window.synabunDesktop.app,
|
|
11
|
+
// desktop-bridge.js desktopApp); in an npm or Git install they are the server's
|
|
12
|
+
// (GET /api/system/app). Updates go through ui-update.js, the same check and the
|
|
13
|
+
// same dialog as the update badge. Restart SynaBun is the Apps menu's own item
|
|
14
|
+
// (#menu-restart-server). settings-app.js decides what each row shows; this draws.
|
|
15
|
+
|
|
16
|
+
import * as kit from './settings-kit.js';
|
|
17
|
+
import { desktopApp } from '../desktop-bridge.js';
|
|
18
|
+
import { on } from '../state.js';
|
|
19
|
+
import { forceCheckSynabunUpdate, getSynabunUpdateData, noteSynabunAutoCheck, openSynabunUpdateModal, refreshSynabunUpdateData } from '../ui-update.js';
|
|
20
|
+
import {
|
|
21
|
+
APP_URL, autoUpdateRow, canInstallUpdate, checkedLine, guessPlatform, hiddenRow, loginRow, maintenanceRow, readDesktopAppState,
|
|
22
|
+
readServerAppState, serverRefusal, updateStatus, windowRows,
|
|
23
|
+
} from './settings-app.js';
|
|
24
|
+
|
|
25
|
+
const RESTART_MENU_ITEM = 'menu-restart-server';
|
|
26
|
+
const WRITE = Object.freeze({ 'Content-Type': 'application/json', 'X-SynaBun-UI': '1', Accept: 'application/json' });
|
|
27
|
+
|
|
28
|
+
const row = (id) => `<div class="stg-help stg-app-note" id="${id}" role="status" hidden></div>`;
|
|
29
|
+
|
|
30
|
+
export function buildAppTab() {
|
|
31
|
+
return `
|
|
32
|
+
<div class="stg-pane stg-app" data-stg-pane="app">
|
|
33
|
+
${kit.card({
|
|
34
|
+
id: 'stg-sec-app-updates', section: 'app.updates',
|
|
35
|
+
status: kit.pill('', { id: 'stg-app-update-pill' }),
|
|
36
|
+
body: `
|
|
37
|
+
${kit.toggleField('SYS001', `
|
|
38
|
+
<button type="button" class="stg-action-btn" id="stg-app-install" ${kit.inv('SYS003')} title="${kit.H('SYS003')}" hidden>${kit.L('SYS003')}</button>
|
|
39
|
+
<button type="button" class="stg-action-btn" id="stg-app-check" title="${kit.H('SYS001')}">${kit.L('SYS001')}</button>`, {
|
|
40
|
+
label: `<span id="stg-app-version">${kit.te('settings.redesign.about.loading')}</span>`,
|
|
41
|
+
helpHtml: '<span id="stg-app-update-line" aria-live="polite"></span>',
|
|
42
|
+
})}
|
|
43
|
+
${kit.toggleField('SYS002', kit.switchInput('stg-app-auto-update'), { forId: 'stg-app-auto-update', helpHtml: '<span id="stg-app-auto-help"></span>' })}`,
|
|
44
|
+
})}
|
|
45
|
+
${kit.card({
|
|
46
|
+
id: 'stg-sec-app-startup', section: 'app.startup',
|
|
47
|
+
body: `
|
|
48
|
+
${kit.toggleField('SYS004', `
|
|
49
|
+
<button type="button" class="stg-action-btn compact" id="stg-app-login-settings" hidden></button>
|
|
50
|
+
${kit.switchInput('stg-app-login')}`, { forId: 'stg-app-login', helpHtml: '<span id="stg-app-login-help"></span>' })}
|
|
51
|
+
${row('stg-app-login-note')}
|
|
52
|
+
${kit.toggleField('SYS005', kit.switchInput('stg-app-hidden'), { forId: 'stg-app-hidden', attrs: 'id="stg-app-hidden-row" hidden' })}
|
|
53
|
+
${row('stg-app-hidden-note')}`,
|
|
54
|
+
})}
|
|
55
|
+
${kit.card({
|
|
56
|
+
id: 'stg-sec-app-window', section: 'app.window',
|
|
57
|
+
body: `
|
|
58
|
+
${row('stg-app-window-note')}
|
|
59
|
+
${kit.toggleField('SYS006', kit.switchInput('stg-app-close-tray'), { forId: 'stg-app-close-tray' })}
|
|
60
|
+
${row('stg-app-close-note')}
|
|
61
|
+
${kit.toggleField('SYS007', `
|
|
62
|
+
<button type="button" class="stg-action-btn compact" id="stg-app-relaunch" ${kit.inv('SYS008')} title="${kit.H('SYS008')}" hidden>${kit.L('SYS008')}</button>
|
|
63
|
+
${kit.switchInput('stg-app-gpu')}`, { forId: 'stg-app-gpu' })}
|
|
64
|
+
${row('stg-app-gpu-note')}`,
|
|
65
|
+
})}
|
|
66
|
+
${kit.card({
|
|
67
|
+
id: 'stg-sec-app-maintenance', section: 'app.maintenance',
|
|
68
|
+
body: `
|
|
69
|
+
<div class="stg-action-row">
|
|
70
|
+
<button type="button" class="stg-action-btn" id="stg-app-open-data" ${kit.inv('SYS009')} title="${kit.H('SYS009')}">${kit.L('SYS009')}</button>
|
|
71
|
+
<button type="button" class="stg-action-btn" id="stg-app-open-logs" ${kit.inv('SYS010')} title="${kit.H('SYS010')}">${kit.L('SYS010')}</button>
|
|
72
|
+
<button type="button" class="stg-action-btn" id="stg-app-restart" ${kit.inv('SYS011')} title="${kit.H('SYS011')}">${kit.L('SYS011')}</button>
|
|
73
|
+
</div>
|
|
74
|
+
${row('stg-app-maint-note')}`,
|
|
75
|
+
})}
|
|
76
|
+
</div>`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Load and draw the page the first time it is shown (and on every return to it), and wire it.
|
|
81
|
+
* @param {HTMLElement} overlay the Settings panel
|
|
82
|
+
* @param {Object} [o]
|
|
83
|
+
* @param {Function} [o.fetchImpl] fetch (tests)
|
|
84
|
+
* @param {(msg: string) => void} [o.toast]
|
|
85
|
+
* @param {Window} [o.win] where the desktop app's bridge and the user agent are read
|
|
86
|
+
*/
|
|
87
|
+
export function wireAppTab(overlay, { fetchImpl = (...args) => fetch(...args), toast = () => {}, win = window } = {}) {
|
|
88
|
+
const pane = kit.paneOf(overlay, 'app');
|
|
89
|
+
if (!pane) return;
|
|
90
|
+
const $ = (id) => pane.querySelector(`#${id}`);
|
|
91
|
+
const bridge = desktopApp(win);
|
|
92
|
+
const platformGuess = guessPlatform(win.navigator?.userAgent);
|
|
93
|
+
let desktop = null; // readDesktopAppState()
|
|
94
|
+
let server = null; // readServerAppState()
|
|
95
|
+
let remote = false; // the server refused: this page is not on the computer that runs SynaBun
|
|
96
|
+
let update = getSynabunUpdateData();
|
|
97
|
+
let busy = false;
|
|
98
|
+
// Nothing is said before the first answer: a row that waits for it is off, without a note.
|
|
99
|
+
let loaded = false;
|
|
100
|
+
|
|
101
|
+
const time = (iso) => {
|
|
102
|
+
try { return new Date(iso).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); } catch { return String(iso).slice(11, 16); }
|
|
103
|
+
};
|
|
104
|
+
const say = (node, key, params) => {
|
|
105
|
+
if (!node) return;
|
|
106
|
+
const textValue = key ? kit.tx(key, params) : '';
|
|
107
|
+
node.textContent = textValue;
|
|
108
|
+
node.hidden = !textValue;
|
|
109
|
+
};
|
|
110
|
+
const setSwitch = (id, { checked, disabled }) => {
|
|
111
|
+
const input = $(id);
|
|
112
|
+
if (!input) return;
|
|
113
|
+
input.checked = !!checked;
|
|
114
|
+
input.disabled = !!disabled || busy;
|
|
115
|
+
};
|
|
116
|
+
const tone = (node, value) => { if (node) node.dataset.state = value || ''; };
|
|
117
|
+
|
|
118
|
+
// ── Drawing ──
|
|
119
|
+
|
|
120
|
+
function drawUpdates() {
|
|
121
|
+
const version = update?.current || desktop?.version || '';
|
|
122
|
+
say($('stg-app-version'), version ? 'settings.redesign.app.version' : 'settings.redesign.about.loading', { version });
|
|
123
|
+
const status = updateStatus(update);
|
|
124
|
+
const line = [kit.tx(status.key, status.params), checkedLine(update, { tx: kit.tx, time })].filter(Boolean).join(' · ');
|
|
125
|
+
const lineNode = $('stg-app-update-line');
|
|
126
|
+
if (lineNode) lineNode.textContent = line;
|
|
127
|
+
const pill = $('stg-app-update-pill');
|
|
128
|
+
if (pill) { pill.textContent = kit.tx(status.key, status.params); pill.dataset.state = status.tone; }
|
|
129
|
+
const install = $('stg-app-install');
|
|
130
|
+
if (install) install.hidden = !canInstallUpdate(update);
|
|
131
|
+
const auto = autoUpdateRow({ desktop, server, update });
|
|
132
|
+
setSwitch('stg-app-auto-update', auto);
|
|
133
|
+
say($('stg-app-auto-help'), auto.helpKey);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function drawStartup() {
|
|
137
|
+
const login = loginRow({ desktop, server, remote, platform: desktop?.platform || platformGuess });
|
|
138
|
+
setSwitch('stg-app-login', login);
|
|
139
|
+
say($('stg-app-login-help'), login.helpKey);
|
|
140
|
+
const note = $('stg-app-login-note');
|
|
141
|
+
say(note, login.note?.key, login.note?.params);
|
|
142
|
+
tone(note, login.note?.tone);
|
|
143
|
+
const open = $('stg-app-login-settings');
|
|
144
|
+
if (open) {
|
|
145
|
+
open.hidden = !(bridge && login.settingsButton);
|
|
146
|
+
if (login.settingsButton) open.textContent = kit.tx(`settings.redesign.app.startup.openSettings.${login.settingsButton}`);
|
|
147
|
+
}
|
|
148
|
+
const hidden = hiddenRow({ desktop });
|
|
149
|
+
const hiddenField = $('stg-app-hidden-row');
|
|
150
|
+
if (hiddenField) hiddenField.hidden = !hidden.shown;
|
|
151
|
+
setSwitch('stg-app-hidden', hidden);
|
|
152
|
+
say($('stg-app-hidden-note'), hidden.shown ? hidden.noteKey : '');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function drawWindow() {
|
|
156
|
+
const rows = windowRows({ desktop });
|
|
157
|
+
say($('stg-app-window-note'), rows.reasonKey);
|
|
158
|
+
setSwitch('stg-app-close-tray', rows.closeToTray);
|
|
159
|
+
say($('stg-app-close-note'), rows.closeToTray.noteKey);
|
|
160
|
+
setSwitch('stg-app-gpu', rows.gpu);
|
|
161
|
+
const relaunch = $('stg-app-relaunch');
|
|
162
|
+
if (relaunch) relaunch.hidden = !rows.gpu.restartNeeded;
|
|
163
|
+
say($('stg-app-gpu-note'), rows.gpu.restartNeeded ? 'settings.redesign.app.window.restartNeeded' : '');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function drawMaintenance() {
|
|
167
|
+
const maint = maintenanceRow({ desktop, server, remote, restartAvailable: !!document.getElementById(RESTART_MENU_ITEM) });
|
|
168
|
+
for (const id of ['stg-app-open-data', 'stg-app-open-logs']) { const btn = $(id); if (btn) btn.disabled = !maint.folders || busy; }
|
|
169
|
+
const restart = $('stg-app-restart');
|
|
170
|
+
if (restart) restart.hidden = !maint.restart;
|
|
171
|
+
const note = $('stg-app-maint-note');
|
|
172
|
+
if (note && !note.dataset.error) say(note, maint.noteKey);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function draw() {
|
|
176
|
+
if (!loaded) {
|
|
177
|
+
pane.querySelectorAll('input[type="checkbox"]').forEach((input) => { input.disabled = true; });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
drawUpdates();
|
|
181
|
+
drawStartup();
|
|
182
|
+
drawWindow();
|
|
183
|
+
drawMaintenance();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Loading ──
|
|
187
|
+
|
|
188
|
+
async function readServer() {
|
|
189
|
+
try {
|
|
190
|
+
const res = await fetchImpl(APP_URL, { headers: { Accept: 'application/json' } });
|
|
191
|
+
const body = await res.json().catch(() => null);
|
|
192
|
+
if (res.ok) { server = readServerAppState(body); remote = false; return; }
|
|
193
|
+
remote = serverRefusal(res.status, body) === 'remote';
|
|
194
|
+
server = null;
|
|
195
|
+
} catch {
|
|
196
|
+
server = null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The update state as it is, without a check of its own: the desktop app's last report, or the server's last
|
|
202
|
+
* look (GET /api/system/version without ?force: with automatic updates off, opening this page asks nothing).
|
|
203
|
+
* Through ui-update.js, so the badge and Install update see the same offer this page shows.
|
|
204
|
+
*/
|
|
205
|
+
async function readUpdate() {
|
|
206
|
+
try { return (await refreshSynabunUpdateData()) || getSynabunUpdateData(); } catch { return getSynabunUpdateData(); }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function load() {
|
|
210
|
+
if (bridge) {
|
|
211
|
+
try { desktop = readDesktopAppState(await bridge.state()); } catch { desktop = null; }
|
|
212
|
+
} else {
|
|
213
|
+
await readServer();
|
|
214
|
+
}
|
|
215
|
+
update = (await readUpdate()) || update;
|
|
216
|
+
loaded = true;
|
|
217
|
+
if (!document.body.contains(overlay)) return;
|
|
218
|
+
draw();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let loading = null;
|
|
222
|
+
const ensure = () => { if (!loading) loading = load().finally(() => { loading = null; }); return loading; };
|
|
223
|
+
draw();
|
|
224
|
+
if (kit.paneActive(overlay, 'app')) ensure();
|
|
225
|
+
overlay.addEventListener('stg-page-change', (e) => { if (e.detail?.page === 'app') ensure(); });
|
|
226
|
+
|
|
227
|
+
// The app's updater and its settings speak when they change (a check on its schedule, the login item put back).
|
|
228
|
+
const offUpdate = on('synabun-update:changed', (data) => { update = data || update; if (loaded && document.body.contains(overlay)) drawUpdates(); });
|
|
229
|
+
let offApp = null;
|
|
230
|
+
try {
|
|
231
|
+
offApp = bridge?.onChange?.((state) => {
|
|
232
|
+
const next = readDesktopAppState(state);
|
|
233
|
+
if (next && document.body.contains(overlay)) { desktop = next; draw(); }
|
|
234
|
+
});
|
|
235
|
+
} catch {}
|
|
236
|
+
const observer = new MutationObserver(() => {
|
|
237
|
+
if (document.body.contains(overlay)) return;
|
|
238
|
+
observer.disconnect();
|
|
239
|
+
try { offUpdate?.(); } catch {}
|
|
240
|
+
try { offApp?.(); } catch {}
|
|
241
|
+
});
|
|
242
|
+
observer.observe(document.body, { childList: true });
|
|
243
|
+
|
|
244
|
+
// ── Changes ──
|
|
245
|
+
|
|
246
|
+
/** One switch of the desktop app: the app answers with its whole state (and what the system said). */
|
|
247
|
+
async function setDesktop(key, value) {
|
|
248
|
+
try {
|
|
249
|
+
desktop = readDesktopAppState(await bridge.set(key, value)) || desktop;
|
|
250
|
+
} catch {
|
|
251
|
+
desktop = desktop ? { ...desktop, error: 'save-failed' } : desktop;
|
|
252
|
+
toast(kit.tx('settings.redesign.app.saveFailed'));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** One switch of an npm or Git install. */
|
|
257
|
+
async function setServer(path, payload) {
|
|
258
|
+
try {
|
|
259
|
+
const res = await fetchImpl(`${APP_URL}/${path}`, { method: 'PUT', headers: WRITE, body: JSON.stringify(payload) });
|
|
260
|
+
const body = await res.json().catch(() => null);
|
|
261
|
+
const next = readServerAppState(body);
|
|
262
|
+
if (next) server = next;
|
|
263
|
+
else {
|
|
264
|
+
if (serverRefusal(res.status, body) === 'remote') remote = true;
|
|
265
|
+
toast(kit.tx('settings.redesign.app.saveFailed'));
|
|
266
|
+
}
|
|
267
|
+
return res.ok;
|
|
268
|
+
} catch {
|
|
269
|
+
if (server) server = { ...server, error: 'network' };
|
|
270
|
+
toast(kit.tx('settings.redesign.app.saveFailed'));
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function change(work) {
|
|
276
|
+
if (busy) return;
|
|
277
|
+
busy = true;
|
|
278
|
+
try { await work(); } finally { busy = false; draw(); }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
$('stg-app-auto-update')?.addEventListener('change', (e) => change(async () => {
|
|
282
|
+
const on = e.target.checked;
|
|
283
|
+
if (desktop) {
|
|
284
|
+
await setDesktop('autoUpdate', on);
|
|
285
|
+
update = (await readUpdate()) || update;
|
|
286
|
+
} else if (await setServer('updates', { auto: on })) {
|
|
287
|
+
noteSynabunAutoCheck(on);
|
|
288
|
+
// Turned on: look now, as a start of the server would.
|
|
289
|
+
if (on) { try { update = (await forceCheckSynabunUpdate({ alert: false })) || update; } catch {} }
|
|
290
|
+
}
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
$('stg-app-login')?.addEventListener('change', (e) => change(async () => {
|
|
294
|
+
if (desktop) await setDesktop('openAtLogin', e.target.checked);
|
|
295
|
+
else await setServer('startup', { enabled: e.target.checked });
|
|
296
|
+
}));
|
|
297
|
+
$('stg-app-hidden')?.addEventListener('change', (e) => change(() => setDesktop('startHidden', e.target.checked)));
|
|
298
|
+
$('stg-app-close-tray')?.addEventListener('change', (e) => change(() => setDesktop('closeToTray', e.target.checked)));
|
|
299
|
+
$('stg-app-gpu')?.addEventListener('change', (e) => change(() => setDesktop('hardwareAcceleration', e.target.checked)));
|
|
300
|
+
$('stg-app-relaunch')?.addEventListener('click', () => { try { bridge?.relaunch?.(); } catch {} });
|
|
301
|
+
$('stg-app-login-settings')?.addEventListener('click', () => { try { bridge?.openLoginSettings?.(); } catch {} });
|
|
302
|
+
|
|
303
|
+
$('stg-app-check')?.addEventListener('click', () => change(async () => {
|
|
304
|
+
const btn = $('stg-app-check');
|
|
305
|
+
if (btn) btn.disabled = true;
|
|
306
|
+
say($('stg-app-update-line'), 'settings.redesign.app.update.checking');
|
|
307
|
+
let found = null;
|
|
308
|
+
try { found = await forceCheckSynabunUpdate({ alert: false }); } catch {}
|
|
309
|
+
// A failed check answers null: the desktop app's report says why; for the server, the check failed.
|
|
310
|
+
if (found) update = found;
|
|
311
|
+
else if (bridge) update = (await readUpdate()) || update;
|
|
312
|
+
else update = { ...(update || {}), updateAvailable: false, checkedAt: new Date().toISOString(), npmError: 'failed', gitError: 'failed' };
|
|
313
|
+
if (btn) btn.disabled = false;
|
|
314
|
+
}));
|
|
315
|
+
$('stg-app-install')?.addEventListener('click', () => openSynabunUpdateModal());
|
|
316
|
+
|
|
317
|
+
async function openFolder(which) {
|
|
318
|
+
const note = $('stg-app-maint-note');
|
|
319
|
+
let ok = false;
|
|
320
|
+
if (bridge) {
|
|
321
|
+
try { ok = !!(await bridge.openFolder(which))?.ok; } catch { ok = false; }
|
|
322
|
+
} else {
|
|
323
|
+
try {
|
|
324
|
+
const res = await fetchImpl(`${APP_URL}/open-folder`, { method: 'POST', headers: WRITE, body: JSON.stringify({ which }) });
|
|
325
|
+
ok = res.ok;
|
|
326
|
+
} catch { ok = false; }
|
|
327
|
+
}
|
|
328
|
+
if (note) {
|
|
329
|
+
if (ok) { delete note.dataset.error; drawMaintenance(); } else { note.dataset.error = '1'; say(note, 'settings.redesign.app.maintenance.openFailed'); tone(note, 'err'); }
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
$('stg-app-open-data')?.addEventListener('click', () => openFolder('data'));
|
|
333
|
+
$('stg-app-open-logs')?.addEventListener('click', () => openFolder('logs'));
|
|
334
|
+
$('stg-app-restart')?.addEventListener('click', () => document.getElementById(RESTART_MENU_ITEM)?.click());
|
|
335
|
+
}
|