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
|
@@ -14,8 +14,8 @@ import { t } from './i18n.js';
|
|
|
14
14
|
import { registerAction } from './ui-keybinds.js';
|
|
15
15
|
import { isGuest, hasPermission, showGuestToast } from './ui-sync.js';
|
|
16
16
|
import { createAssistantHosts, describeBrainFallback } from './assistant/asst-hosts.js';
|
|
17
|
-
import { createAssistantSession, getAssistantSession, listAssistantSessions } from './assistant/asst-api.js';
|
|
18
|
-
import { providerShortLabel, readStoredBrain } from './assistant/asst-state.js';
|
|
17
|
+
import { createAssistantSession, getAssistantSession, listAssistantSessions, removeAssistantSession } from './assistant/asst-api.js';
|
|
18
|
+
import { providerShortLabel, readStoredBrain, tabStorageKey } from './assistant/asst-state.js';
|
|
19
19
|
|
|
20
20
|
/** Guests need the terminal permission for the Assistant, in either host. */
|
|
21
21
|
export function assistantBlocked() {
|
|
@@ -30,7 +30,7 @@ function brainFallbackText(fallback) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const hosts = createAssistantHosts({
|
|
33
|
-
api: { list: listAssistantSessions, get: getAssistantSession, create: createAssistantSession },
|
|
33
|
+
api: { list: listAssistantSessions, get: getAssistantSession, create: createAssistantSession, remove: removeAssistantSession },
|
|
34
34
|
isBlocked: assistantBlocked,
|
|
35
35
|
toast: (text) => showGuestToast(text),
|
|
36
36
|
storedBrain: () => readStoredBrain(storage),
|
|
@@ -48,6 +48,11 @@ export const noteAssistantFocus = (sessionId, hostId) => hosts.noteFocus(session
|
|
|
48
48
|
|
|
49
49
|
let _initialized = false;
|
|
50
50
|
|
|
51
|
+
/** A removed session's per-tab UI state (composer draft, dock, route mode): written by its tab as it closes, then dropped. */
|
|
52
|
+
function forgetTabState(sessionId) {
|
|
53
|
+
try { storage.removeItem(tabStorageKey(sessionId)); } catch { /* storage unavailable */ }
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
/** Bus events + keybinds for both hosts. Idempotent: initNavbar and initTerminal both call it. */
|
|
52
57
|
export function initAssistant() {
|
|
53
58
|
if (_initialized) return;
|
|
@@ -60,8 +65,24 @@ export function initAssistant() {
|
|
|
60
65
|
floating: data?.floating,
|
|
61
66
|
prompt: data?.prompt,
|
|
62
67
|
}));
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
// `replaces`: the session the user left by picking this one, when nobody chatted in it: it goes.
|
|
69
|
+
on('assistant:resume', async (data) => {
|
|
70
|
+
if (!data?.sessionId) return;
|
|
71
|
+
const res = await hosts.open({ resume: data.sessionId, host: data?.host || null, floating: data?.floating });
|
|
72
|
+
if (res && data.replaces && data.replaces !== data.sessionId) hosts.discard(data.replaces);
|
|
73
|
+
});
|
|
74
|
+
// Sessions menu → Remove. `done(result)` tells the menu that asked how it went.
|
|
75
|
+
on('assistant:remove', (data) => {
|
|
76
|
+
if (!data?.sessionId) return;
|
|
77
|
+
hosts.remove(data.sessionId, { host: data.host || null, fresh: data.fresh === true, brain: data.brain || null })
|
|
78
|
+
.then((result) => {
|
|
79
|
+
if (result?.ok) forgetTabState(data.sessionId);
|
|
80
|
+
try { data.done?.(result); } catch { /* the menu is gone */ }
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// A session the server no longer has: its tab closes (a fresh one in its place when it was showing unused).
|
|
84
|
+
on('assistant:gone', (data) => {
|
|
85
|
+
if (data?.sessionId) hosts.gone(data.sessionId, { host: data.host || null, fresh: data.fresh === true, brain: data.brain || null }).finally(() => forgetTabState(data.sessionId));
|
|
65
86
|
});
|
|
66
87
|
on('assistant:toggle', () => hosts.toggle());
|
|
67
88
|
// Notification clicks (toasts and OS banners) — see ui-notifications _getNotifMeta.
|
|
@@ -71,6 +92,7 @@ export function initAssistant() {
|
|
|
71
92
|
on('sync:assistant:session-ended', (msg) => {
|
|
72
93
|
const id = msg?.sessionId || msg?.session?.id || msg?.id;
|
|
73
94
|
if (id) hosts.ended(id);
|
|
95
|
+
if (id && msg?.destroyed) forgetTabState(id); // removed for good: its tab's UI state goes too
|
|
74
96
|
});
|
|
75
97
|
registerAction('launch-assistant', () => hosts.openOrFocus());
|
|
76
98
|
registerAction('toggle-assistant', () => hosts.toggle());
|
|
@@ -9,6 +9,7 @@ import { storage } from './storage.js';
|
|
|
9
9
|
import { initI18n, isReady as i18nReady, t } from './i18n.js';
|
|
10
10
|
import { fetchStyleGuideProjects, fetchStyleGuideSummary } from './api.js';
|
|
11
11
|
import { isGuest, hasPermission, showGuestToast } from './ui-sync.js';
|
|
12
|
+
import { registerLaunchPrefetch } from './launch-prefetch.js';
|
|
12
13
|
import {
|
|
13
14
|
getNativeLoopRouterClaimToken,
|
|
14
15
|
getNativeLoopRouterWindowId,
|
|
@@ -90,6 +91,9 @@ let _prevLoopActive = false;
|
|
|
90
91
|
|
|
91
92
|
// Cleanup: stored references for document-level listeners so closePanel() can remove them
|
|
92
93
|
let _docListeners = [];
|
|
94
|
+
// What the last read found (templates, folders, the active loop, agents): a later open paints from it at once
|
|
95
|
+
// and reads it again behind it.
|
|
96
|
+
let _keptData = null;
|
|
93
97
|
|
|
94
98
|
// Detail/editor state
|
|
95
99
|
let _metaDirty = false;
|
|
@@ -283,9 +287,9 @@ function renderMcpProfileChips(presets, selected) {
|
|
|
283
287
|
return renderLaunchSelect('mcp', options, selected);
|
|
284
288
|
}
|
|
285
289
|
return entries.map(([id, p]) => `
|
|
286
|
-
<button class="as-launch-mcp-profile${id === selected ? ' active' : ''}" data-mcp-profile="${id}">
|
|
287
|
-
<span class="as-launch-mcp-profile-name">${p.label || id}</span>
|
|
288
|
-
<span class="as-launch-mcp-profile-tools">${p.tools} tools</span>
|
|
290
|
+
<button class="as-launch-mcp-profile${id === selected ? ' active' : ''}" data-mcp-profile="${_escLaunch(id)}">
|
|
291
|
+
<span class="as-launch-mcp-profile-name">${_escLaunch(p.label || id)}</span>
|
|
292
|
+
<span class="as-launch-mcp-profile-tools">${_escLaunch(p.tools)} tools</span>
|
|
289
293
|
</button>
|
|
290
294
|
`).join('');
|
|
291
295
|
}
|
|
@@ -1187,9 +1191,9 @@ function renderModelChips(profileId, selectedModel) {
|
|
|
1187
1191
|
return renderLaunchSelect('model', models, selectedModel, { groupByDesc: profileId === 'opencode' });
|
|
1188
1192
|
}
|
|
1189
1193
|
return models.map(m => `
|
|
1190
|
-
<button class="as-launch-model${modelMatchesSelector(m, selectedModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-model="${modelSelectorValue(m)}">
|
|
1191
|
-
<span class="as-launch-model-name">${m.label}</span>
|
|
1192
|
-
<span class="as-launch-model-desc">${m.desc}</span>
|
|
1194
|
+
<button class="as-launch-model${modelMatchesSelector(m, selectedModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-model="${_escLaunch(modelSelectorValue(m))}">
|
|
1195
|
+
<span class="as-launch-model-name">${_escLaunch(m.label)}</span>
|
|
1196
|
+
<span class="as-launch-model-desc">${_escLaunch(m.desc)}</span>
|
|
1193
1197
|
</button>
|
|
1194
1198
|
`).join('');
|
|
1195
1199
|
}
|
|
@@ -1299,7 +1303,10 @@ function wireLaunchSelect(root, type, onChange) {
|
|
|
1299
1303
|
});
|
|
1300
1304
|
});
|
|
1301
1305
|
|
|
1302
|
-
|
|
1306
|
+
// Outside click closes it; removed with the panel (closePanel) like the studio's other document listeners.
|
|
1307
|
+
const onOutside = e => { if (!sel.contains(e.target)) close(); };
|
|
1308
|
+
document.addEventListener('click', onOutside);
|
|
1309
|
+
_docListeners.push(['click', onOutside]);
|
|
1303
1310
|
});
|
|
1304
1311
|
}
|
|
1305
1312
|
|
|
@@ -1721,6 +1728,8 @@ function buildTemplateCommand(template) {
|
|
|
1721
1728
|
// ═══════════════════════════════════════════
|
|
1722
1729
|
|
|
1723
1730
|
export function initAutomationStudio() {
|
|
1731
|
+
// Launch: the studio's lists are read once the page has painted (launch-prefetch.js), so the first open paints at once.
|
|
1732
|
+
registerLaunchPrefetch('automation-studio', prefetchAutomationStudio, { order: 72 });
|
|
1724
1733
|
on('automations:open', (opts) => openPanel(opts || {}));
|
|
1725
1734
|
on('automations:open-wizard', () => { openPanel(); });
|
|
1726
1735
|
on('automations:import', () => { openPanel().then(() => triggerImport()); });
|
|
@@ -1759,15 +1768,44 @@ async function openPanel(opts = {}) {
|
|
|
1759
1768
|
_panel.style.top = Math.max(48, (window.innerHeight - 520) / 2) + 'px';
|
|
1760
1769
|
|
|
1761
1770
|
wirePanel();
|
|
1762
|
-
|
|
1771
|
+
const kept = _keptData;
|
|
1772
|
+
if (kept) {
|
|
1773
|
+
// The studio as last read paints at once; it is read again behind it and the sidebar follows in place.
|
|
1774
|
+
_templates = kept.templates; _folders = kept.folders; _activeLoop = kept.activeLoop; _agents = kept.agents;
|
|
1775
|
+
updateSidebarFooter();
|
|
1776
|
+
} else {
|
|
1777
|
+
await loadData();
|
|
1778
|
+
}
|
|
1763
1779
|
if (opts.templateId) {
|
|
1764
1780
|
const tpl = _templates.find(t => t.id === opts.templateId);
|
|
1765
1781
|
if (tpl) switchToDetail(tpl); else renderView();
|
|
1766
1782
|
} else {
|
|
1767
1783
|
renderView();
|
|
1768
1784
|
}
|
|
1769
|
-
requestAnimationFrame(() => { _backdrop
|
|
1785
|
+
requestAnimationFrame(() => { _backdrop?.classList.add('open'); _panel?.classList.add('open'); });
|
|
1770
1786
|
startPolling();
|
|
1787
|
+
if (kept) refreshDataInPlace();
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// The data read again behind a studio painted from the kept one: the sidebar list is redrawn when the templates
|
|
1791
|
+
// or folders changed (an open template, the wizard and the editors keep what the user is doing).
|
|
1792
|
+
async function refreshDataInPlace() {
|
|
1793
|
+
const before = JSON.stringify([_templates, _folders]);
|
|
1794
|
+
await loadData();
|
|
1795
|
+
if (!_panel) return;
|
|
1796
|
+
if (JSON.stringify([_templates, _folders]) === before) return;
|
|
1797
|
+
const list = $('as-template-list');
|
|
1798
|
+
const scroll = list ? list.scrollTop : 0;
|
|
1799
|
+
renderSidebar();
|
|
1800
|
+
const count = $('as-total-count');
|
|
1801
|
+
if (count) count.textContent = `${PRESET_TEMPLATES.length + _templates.length}`;
|
|
1802
|
+
if (list && list.isConnected) list.scrollTop = scroll;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
/** Launch prefetch: the studio's data read once, so the first open paints at once (nothing is drawn here). */
|
|
1806
|
+
export function prefetchAutomationStudio() {
|
|
1807
|
+
if (_keptData || _panel || (isGuest() && !hasPermission('automations'))) return Promise.resolve();
|
|
1808
|
+
return loadData().catch(() => {});
|
|
1771
1809
|
}
|
|
1772
1810
|
|
|
1773
1811
|
function closePanel() {
|
|
@@ -2326,11 +2364,15 @@ async function promptCreateFolder() {
|
|
|
2326
2364
|
// ═══════════════════════════════════════════
|
|
2327
2365
|
|
|
2328
2366
|
async function loadData() {
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2367
|
+
// One round trip for the three lists (they were asked one after another), then the agents.
|
|
2368
|
+
const [templates, folders, active] = await Promise.allSettled([fetchLoopTemplates(), fetchLoopFolders(), fetchActiveLoop()]);
|
|
2369
|
+
_templates = templates.status === 'fulfilled' ? templates.value : [];
|
|
2370
|
+
_folders = folders.status === 'fulfilled' ? folders.value : [];
|
|
2371
|
+
_activeLoop = active.status === 'fulfilled' ? active.value : null;
|
|
2332
2372
|
await refreshAgents();
|
|
2333
|
-
updateSidebarFooter();
|
|
2373
|
+
if (_panel) updateSidebarFooter();
|
|
2374
|
+
// Kept only when the templates answered (a failed read must not paint an empty studio next time).
|
|
2375
|
+
if (templates.status === 'fulfilled') _keptData = { templates: _templates, folders: _folders, activeLoop: _activeLoop, agents: _agents };
|
|
2334
2376
|
}
|
|
2335
2377
|
|
|
2336
2378
|
function startPolling() {
|
|
@@ -3754,16 +3796,14 @@ async function refreshAgents() {
|
|
|
3754
3796
|
try {
|
|
3755
3797
|
const data = await fetchAgents();
|
|
3756
3798
|
_agents = data?.agents || [];
|
|
3757
|
-
// Update local output buffers with any text we haven't captured via WebSocket
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
}
|
|
3766
|
-
}
|
|
3799
|
+
// Update local output buffers with any text we haven't captured via WebSocket (together, not one by one)
|
|
3800
|
+
await Promise.all(_agents.filter(a => !_agentOutputs[a.id] && a.textLength > 0).map(async (a) => {
|
|
3801
|
+
// Fetch full details to get textOutput
|
|
3802
|
+
try {
|
|
3803
|
+
const full = await fetchAgent(a.id);
|
|
3804
|
+
if (full?.textOutput) _agentOutputs[a.id] = full.textOutput;
|
|
3805
|
+
} catch {}
|
|
3806
|
+
}));
|
|
3767
3807
|
} catch {}
|
|
3768
3808
|
}
|
|
3769
3809
|
|
|
@@ -4742,9 +4782,9 @@ function renderSchedulesMain() {
|
|
|
4742
4782
|
? (qtModels.length > LAUNCH_CHIP_MAX
|
|
4743
4783
|
? renderLaunchSelect('qt-model', qtModels, qtCurrentModel, { groupByDesc: qtProfile === 'opencode' })
|
|
4744
4784
|
: qtModels.map(m => `
|
|
4745
|
-
<button class="as-launch-model${modelMatchesSelector(m, qtCurrentModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-action="qt-model" data-model="${modelSelectorValue(m)}">
|
|
4746
|
-
<span class="as-launch-model-name">${m.label}</span>
|
|
4747
|
-
<span class="as-launch-model-desc">${m.desc}</span>
|
|
4785
|
+
<button class="as-launch-model${modelMatchesSelector(m, qtCurrentModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-action="qt-model" data-model="${_escLaunch(modelSelectorValue(m))}">
|
|
4786
|
+
<span class="as-launch-model-name">${_escLaunch(m.label)}</span>
|
|
4787
|
+
<span class="as-launch-model-desc">${_escLaunch(m.desc)}</span>
|
|
4748
4788
|
</button>
|
|
4749
4789
|
`).join(''))
|
|
4750
4790
|
: `<span class="as-launch-no-models">${esc(dynamicModelEmptyText(qtProfile))}</span>`;
|
|
@@ -4942,7 +4982,7 @@ function renderScheduleEditorMain() {
|
|
|
4942
4982
|
</div>`).join('');
|
|
4943
4983
|
|
|
4944
4984
|
const templateOptions = allTemplates.map(t =>
|
|
4945
|
-
`<option value="${t.id}"${(s?.templateId === t.id) ? ' selected' : ''}>${esc(t.name)} (${t.category})</option>`
|
|
4985
|
+
`<option value="${esc(t.id)}"${(s?.templateId === t.id) ? ' selected' : ''}>${esc(t.name)} (${esc(t.category)})</option>`
|
|
4946
4986
|
).join('');
|
|
4947
4987
|
|
|
4948
4988
|
// Reorder: Mon(1)–Sat(6) then Sun(0) so 2-column grid pairs weekdays nicely
|
|
@@ -8,6 +8,7 @@ import { state, emit, on } from './state.js';
|
|
|
8
8
|
import { KEYS } from './constants.js';
|
|
9
9
|
import { storage } from './storage.js';
|
|
10
10
|
import { catColor } from './colors.js';
|
|
11
|
+
import { escapeHtml } from './utils.js';
|
|
11
12
|
|
|
12
13
|
const $ = (id) => document.getElementById(id);
|
|
13
14
|
|
|
@@ -120,7 +121,7 @@ export function renderBookmarks() {
|
|
|
120
121
|
item.innerHTML = `
|
|
121
122
|
<div class="dropdown-item-dot" style="background:${color}"></div>
|
|
122
123
|
<div class="dropdown-item-text">
|
|
123
|
-
<div class="dropdown-item-title">${b.title}</div>
|
|
124
|
+
<div class="dropdown-item-title">${escapeHtml(String(b.title ?? ''))}</div>
|
|
124
125
|
<div class="dropdown-item-sub">${b.category}</div>
|
|
125
126
|
</div>
|
|
126
127
|
<button class="icon-btn icon-btn--danger" style="width:22px;height:22px;font-size:13px;border:none" data-tooltip="Remove">×</button>
|
|
@@ -24,6 +24,7 @@ import { requestGeneratedSessionTitle } from './session-title.js';
|
|
|
24
24
|
import { wireFileLinks } from './ui-file-links.js';
|
|
25
25
|
import { WORKER_END_CLASS, backToAssistant, buildWorkerEndCard, endWorkerRunOnClose, forgetWorkerEnd, trackWorkerEnd, updateWorkerEndBusy } from './ui-worker-end.js';
|
|
26
26
|
import { isDispatchedRun, isEndOfRunAbort } from './worker-end.js';
|
|
27
|
+
import { runAwaitsAnswer } from './sidepanel-session-status.js';
|
|
27
28
|
// ── cp/ feature modules (SDK-engine CLI parity: diffs, agents, bash, perms, statusline) ──
|
|
28
29
|
import { setCpCtx } from './cp/cp-ctx.js';
|
|
29
30
|
import { buildDiffCard, buildDiffPreviewEl, finalizeDiffCard } from './cp/cp-diff.js';
|
|
@@ -46,7 +47,8 @@ import { historyHasResultSupport, historyResultsNotice, snapshotWantsResults, fu
|
|
|
46
47
|
import { CP_STYLES } from './cp/cp-styles.js';
|
|
47
48
|
import { TEMPORARY_CAPABILITY, keepsNothing, chooseTemporary, applyTemporary, temporaryRefusal, endTemporary, leaveTemporary, tabAfterTemporary, persistableTabs, temporaryBlocks, reattaches } from './cp/cp-temporary.js';
|
|
48
49
|
import { paintTemporary } from './cp/cp-temporary-view.js';
|
|
49
|
-
import { renderMarkdown
|
|
50
|
+
import { renderMarkdown } from './cp/cp-markdown.js';
|
|
51
|
+
import { sanitizeStoredHtml } from './assistant/asst-sanitize.js';
|
|
50
52
|
import { escapeHtml, classToken } from './cp/cp-markdown.js';
|
|
51
53
|
import { rehydrateStoredTranscript } from './cp/cp-rehydrate.js';
|
|
52
54
|
import { compactHolds, compactQueue, isCompactCommand, newConversation, nextQueued, ownsQueued, queuedRefusal, restoredQueue, QUEUED_REFUSAL_LINES } from './cp/cp-compose.js';
|
|
@@ -178,7 +180,7 @@ let _cliBanner = null;
|
|
|
178
180
|
let _marked = null;
|
|
179
181
|
(async () => {
|
|
180
182
|
try {
|
|
181
|
-
const m = await import('
|
|
183
|
+
const m = await import('/vendor/marked/marked-14.1.4.esm.js').catch(() => null);
|
|
182
184
|
if (m) {
|
|
183
185
|
_marked = m.marked || m.default;
|
|
184
186
|
if (_marked?.setOptions) _marked.setOptions({ breaks: true, gfm: true });
|
|
@@ -2338,6 +2340,11 @@ function injectStyles() {
|
|
|
2338
2340
|
.cp-session-pill.cp-pill-running .term-minimized-pill-icon { color: rgba(212, 162, 127, 1); }
|
|
2339
2341
|
.cp-session-pill:hover .term-minimized-pill-icon { color: rgba(212, 162, 127, 0.85); }
|
|
2340
2342
|
.cp-session-pill .term-minimized-pill-icon svg { width: 12px; height: 12px; }
|
|
2343
|
+
/* A session that waits on you (a permission, a plan, an MCP server's request, a question): the
|
|
2344
|
+
warn hairline and mark of the Assistant's and a detached window's pills, in place of the
|
|
2345
|
+
working look (paintPillState). */
|
|
2346
|
+
.cp-session-pill.cp-pill-attention { border-color: color-mix(in srgb, var(--accent-orange, #FFB74D) 42%, transparent); }
|
|
2347
|
+
.cp-session-pill.cp-pill-attention .term-minimized-pill-icon { color: var(--accent-orange, #FFB74D); }
|
|
2341
2348
|
.cp-session-pill.cp-pill-enter {
|
|
2342
2349
|
animation: cp-pill-pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
|
2343
2350
|
}
|
|
@@ -2909,6 +2916,7 @@ function newTabState(sessionId = null, label = 'New chat', id = null) {
|
|
|
2909
2916
|
mcpServers: [], // from system/init + mcp_status events
|
|
2910
2917
|
automationRunId: null,
|
|
2911
2918
|
automationActive: false,
|
|
2919
|
+
automationAwaits: false, // the run's turn ended asking the user (runAwaitsAnswer): its pill needs you
|
|
2912
2920
|
automationOwnerId: null,
|
|
2913
2921
|
releasedRunId: null, // the ended run this tab showed before the user continued it by hand
|
|
2914
2922
|
};
|
|
@@ -3201,6 +3209,9 @@ function _createTrayPill(tab) {
|
|
|
3201
3209
|
if (idx >= 0) closeTab(idx);
|
|
3202
3210
|
});
|
|
3203
3211
|
pill.style.display = 'none'; // Hidden by default; renderPills() controls visibility
|
|
3212
|
+
// A pill made after the tab started working (a tray that was not there yet) starts out working too,
|
|
3213
|
+
// or needing you.
|
|
3214
|
+
paintPillState(pill, tab);
|
|
3204
3215
|
tray.appendChild(pill);
|
|
3205
3216
|
return pill;
|
|
3206
3217
|
}
|
|
@@ -3220,8 +3231,10 @@ function _renderPillsNow() {
|
|
|
3220
3231
|
const panelOpen = _visible;
|
|
3221
3232
|
for (let i = 0; i < _tabs.length; i++) {
|
|
3222
3233
|
const tab = _tabs[i];
|
|
3223
|
-
if (!tab.pillEl) tab.pillEl = _createTrayPill(tab);
|
|
3234
|
+
if (!tab.pillEl?.isConnected) tab.pillEl = _createTrayPill(tab);
|
|
3224
3235
|
if (!tab.pillEl) continue;
|
|
3236
|
+
// Every pass says it again: a re-render never leaves a working tab's pill idle.
|
|
3237
|
+
paintPillState(tab.pillEl, tab);
|
|
3225
3238
|
const isActive = i === activeIdx;
|
|
3226
3239
|
const shouldShow = !isActive || !panelOpen;
|
|
3227
3240
|
const wasHidden = tab.pillEl.style.display === 'none';
|
|
@@ -3247,15 +3260,52 @@ function updatePillLabel(tab) {
|
|
|
3247
3260
|
if (lbl) lbl.textContent = tab.label;
|
|
3248
3261
|
}
|
|
3249
3262
|
|
|
3263
|
+
/**
|
|
3264
|
+
* A tab works while its turn runs, and also once the turn ended while background work it started
|
|
3265
|
+
* still runs (agents, shells, workflows: the CLI's own live list, non-ambient, the statusline's count):
|
|
3266
|
+
* the session reports back when they end, so its pill keeps moving, like their cards in the transcript.
|
|
3267
|
+
*/
|
|
3268
|
+
function tabWorking(tab) {
|
|
3269
|
+
return !!tab?.running || (Array.isArray(tab?.backgroundWork) && tab.backgroundWork.length > 0);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* A tab needs you while its session waits on your answer: a permission, a plan approval or an MCP
|
|
3274
|
+
* server's request (the card on screen, and the ones queued behind it), or a question; and, for a
|
|
3275
|
+
* dispatched worker, while its turn ended asking you something (the run's outcome: automationAwaits).
|
|
3276
|
+
*/
|
|
3277
|
+
function tabNeedsYou(tab) {
|
|
3278
|
+
return !!(tab?._activePerm || tab?._permQueue?.length || tab?.pendingAskRequestId || tab?.automationAwaits);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
/** What a tab's pill says: it needs you > it works > it rests (read for a detached window's pill too). */
|
|
3282
|
+
function paintPillState(pill, tab) {
|
|
3283
|
+
const needsYou = tabNeedsYou(tab);
|
|
3284
|
+
pill.classList.toggle('cp-pill-attention', needsYou);
|
|
3285
|
+
pill.classList.toggle('cp-pill-running', !needsYou && tabWorking(tab));
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
// Whether each tab was saved needing you: a change is written at once, for the placeholder a reload
|
|
3289
|
+
// shows and the pill of a detached window that has not loaded yet (ui-sidepanel-tray.js).
|
|
3290
|
+
const _needsYouSaved = new WeakMap();
|
|
3250
3291
|
function updatePillRunning(tab) {
|
|
3251
|
-
if (tab.pillEl) tab.pillEl
|
|
3292
|
+
if (tab.pillEl) paintPillState(tab.pillEl, tab);
|
|
3293
|
+
const needsYou = tabNeedsYou(tab);
|
|
3294
|
+
if ((_needsYouSaved.get(tab) || false) === needsYou) return;
|
|
3295
|
+
_needsYouSaved.set(tab, needsYou);
|
|
3296
|
+
saveTabs();
|
|
3252
3297
|
}
|
|
3253
3298
|
|
|
3254
3299
|
function saveTabs() {
|
|
3255
3300
|
try {
|
|
3256
3301
|
// A temporary tab is never written: not the tab, its label, its queue or its session id.
|
|
3302
|
+
// `backgroundWork`: how many background agents and shells it still ran (tabWorking). A tab whose turn
|
|
3303
|
+
// ended while they run is working too: after a reload its placeholder says so and the panel loads to ask
|
|
3304
|
+
// (ui-sidepanel-tray.js). Never restored: the reattach's live list says what still runs. `needsYou`:
|
|
3305
|
+
// a permission or a question waited on the user (tabNeedsYou): its placeholder says so and the panel
|
|
3306
|
+
// loads to ask, and the reattach brings the card back.
|
|
3257
3307
|
const kept = persistableTabs(_tabs, _activeTabIdx);
|
|
3258
|
-
const localTabs = kept.tabs.map(t => ({ id: t.id, sessionId: t.sessionId, label: t.label, titleState: t.titleState === 'generating' ? 'default' : (t.titleState || 'default'), sessionCost: t.sessionCost || 0, running: t.running || false, project: t.project || '', model: t.model || '', effort: t.effort || 'off', planMode: t.planMode || false, ...savedMode(t), toolPolicy: t.toolPolicy || 'full', accountId: t.accountId || '', session: normalizeSession(t.session), transcriptAt: t.transcriptAt || null, planFilePath: t.planFilePath || '', conversation: t.conversation || '', queue: t.queue || [], queuePaused: t.queuePaused || false, automationRunId: t.automationRunId || null, automationActive: !!t.automationActive, automationOwnerId: t.automationOwnerId || null, releasedRunId: t.releasedRunId || null }));
|
|
3308
|
+
const localTabs = kept.tabs.map(t => ({ id: t.id, sessionId: t.sessionId, label: t.label, titleState: t.titleState === 'generating' ? 'default' : (t.titleState || 'default'), sessionCost: t.sessionCost || 0, running: t.running || false, backgroundWork: Array.isArray(t.backgroundWork) ? t.backgroundWork.length : 0, needsYou: tabNeedsYou(t), project: t.project || '', model: t.model || '', effort: t.effort || 'off', planMode: t.planMode || false, ...savedMode(t), toolPolicy: t.toolPolicy || 'full', accountId: t.accountId || '', session: normalizeSession(t.session), transcriptAt: t.transcriptAt || null, planFilePath: t.planFilePath || '', conversation: t.conversation || '', queue: t.queue || [], queuePaused: t.queuePaused || false, automationRunId: t.automationRunId || null, automationActive: !!t.automationActive, automationOwnerId: t.automationOwnerId || null, releasedRunId: t.releasedRunId || null }));
|
|
3259
3309
|
const localRunIds = new Set(localTabs.map((tab) => tab.automationRunId).filter(Boolean));
|
|
3260
3310
|
let foreignAutomationTabs = [];
|
|
3261
3311
|
try {
|
|
@@ -4550,9 +4600,9 @@ function flushAllSessionSnapshots() {
|
|
|
4550
4600
|
function renderStoredSession(snapshot, $msgs, tab) {
|
|
4551
4601
|
const norm = _normalizeSnapshotEntry(snapshot);
|
|
4552
4602
|
if (!norm || !$msgs) return false;
|
|
4553
|
-
// A snapshot comes back from storage and may predate the sanitiser:
|
|
4554
|
-
//
|
|
4555
|
-
$msgs.replaceChildren(
|
|
4603
|
+
// A snapshot comes back from storage and may predate the sanitiser: it is rebuilt
|
|
4604
|
+
// from the stored-transcript allowlist every panel uses (asst-sanitize.js).
|
|
4605
|
+
$msgs.replaceChildren(sanitizeStoredHtml(norm.html));
|
|
4556
4606
|
// Restored DOM has no event handlers. Pending prompts (permission, plan, ask
|
|
4557
4607
|
// cards) are stale and go, answered ones stay inert; the read-only actions
|
|
4558
4608
|
// are wired again from what the markup saved: rewind (prompt uuid), the
|
|
@@ -5282,10 +5332,14 @@ function _setBackgroundWork(tab, tasks) {
|
|
|
5282
5332
|
// reloaded while it ran) gets one, so /tasks can show and stop it.
|
|
5283
5333
|
try { adoptLiveTasks(tab.tasks, tasks); } catch {}
|
|
5284
5334
|
try { reconcileTasks(tab.tasks, tasks); } catch {}
|
|
5335
|
+
const before = Array.isArray(tab.backgroundWork) ? tab.backgroundWork.length : 0;
|
|
5285
5336
|
tab.backgroundWork = (Array.isArray(tasks) ? tasks : [])
|
|
5286
5337
|
.filter(t => t && !t.ambient)
|
|
5287
5338
|
.map(t => ({ id: t.task_id, type: t.task_type || '', description: t.description || '' }));
|
|
5288
5339
|
if (tab === activeTab()) { try { renderStatusline(tab); } catch {} }
|
|
5340
|
+
updatePillRunning(tab);
|
|
5341
|
+
// Saved as it changes: a reload with only this left running loads the panel to ask (saveTabs).
|
|
5342
|
+
if (tab.backgroundWork.length !== before) saveTabs();
|
|
5289
5343
|
}
|
|
5290
5344
|
|
|
5291
5345
|
function _processTabMsg(tab, msg) {
|
|
@@ -5343,6 +5397,7 @@ function _processTabMsg(tab, msg) {
|
|
|
5343
5397
|
if (tab._pendingPlan?.requestId === msg.request_id) tab._pendingPlan = null;
|
|
5344
5398
|
if (tab._activePerm) { tab._activePerm = false; _showNextPerm(tab); }
|
|
5345
5399
|
else _flushMsgBuffer(tab);
|
|
5400
|
+
updatePillRunning(tab);
|
|
5346
5401
|
break;
|
|
5347
5402
|
}
|
|
5348
5403
|
case 'reattach_result': {
|
|
@@ -6885,6 +6940,7 @@ function sendAskAnswer(tab, questions, answers, annotations = null) {
|
|
|
6885
6940
|
tab.pendingAskRequestId = null;
|
|
6886
6941
|
tab.pendingAskToolUseId = null;
|
|
6887
6942
|
tab.pendingAskBufferedAnswer = null;
|
|
6943
|
+
updatePillRunning(tab); // answered: the pill no longer needs you
|
|
6888
6944
|
// Flush BEFORE clearing askRenderedViaControl — buffered assistant events
|
|
6889
6945
|
// must see the flag so buildAskFromToolUse returns a hidden placeholder
|
|
6890
6946
|
// instead of rendering a duplicate card.
|
|
@@ -7400,6 +7456,7 @@ function handleControlRequest(tab, msg) {
|
|
|
7400
7456
|
if (toolName === 'AskUserQuestion') {
|
|
7401
7457
|
// AskUserQuestion: save the request_id — answer is sent via control_response when user picks an option
|
|
7402
7458
|
tab.pendingAskRequestId = requestId;
|
|
7459
|
+
updatePillRunning(tab); // the pill needs you
|
|
7403
7460
|
// If tool_use block hasn't rendered the ask card yet, render from control_request input
|
|
7404
7461
|
// Also check askRenderedViaControl to prevent duplicate cards from denial control_requests
|
|
7405
7462
|
if (!tab.pendingAskToolUseId && !tab.askRenderedViaControl) {
|
|
@@ -8501,6 +8558,7 @@ function finishTab(tab, skipNotif) {
|
|
|
8501
8558
|
if (tab.sessionId && tab._snapshotDirty) flushSessionSnapshotSave(tab);
|
|
8502
8559
|
hideThinking(tab); setRunning(tab, false); tab._wasRunning = false; tab.currentMsgEl = null; tab.currentMsgId = null; tab.pendingAskToolUseId = null; tab.pendingAskRequestId = null; tab.pendingAskBufferedAnswer = null; tab.askRenderedViaControl = false; tab.sendStartedAt = null; tab._planContentCaptured = false; if (tab._stream?.mdTimer) clearTimeout(tab._stream.mdTimer); tab._stream = null;
|
|
8503
8560
|
tab.currentActivity = null;
|
|
8561
|
+
updatePillRunning(tab); // (its question went with the turn)
|
|
8504
8562
|
if (tab === activeTab()) { try { renderStatusline(tab); } catch {} }
|
|
8505
8563
|
if (tab.compacting) { tab.compacting = false; if (tab === activeTab()) _setCompactingUI(false); }
|
|
8506
8564
|
tab._compactSentAt = 0; // a Compact the user started ends here too (cp-compose.js)
|
|
@@ -10347,6 +10405,14 @@ function applyClaudeVisibility(visible) {
|
|
|
10347
10405
|
|
|
10348
10406
|
export function isClaudePanelOpen() { return _visible; }
|
|
10349
10407
|
|
|
10408
|
+
/**
|
|
10409
|
+
* Boot the panel without showing it (ui-sidepanel-tray.js, when a tab saved while it worked comes back
|
|
10410
|
+
* after a reload): its tabs reconnect and reattach, so their pills say what the sessions do now.
|
|
10411
|
+
*/
|
|
10412
|
+
export async function bootClaudePanel() {
|
|
10413
|
+
await _ensureClaudePanelReady();
|
|
10414
|
+
}
|
|
10415
|
+
|
|
10350
10416
|
export function endHostedClaudeSessions() {
|
|
10351
10417
|
while (_tabs.length) closeTab(_tabs.length - 1);
|
|
10352
10418
|
}
|
|
@@ -10396,6 +10462,8 @@ export async function attachClaudeAutomation(run, { focus = !!run?.focus, isCanc
|
|
|
10396
10462
|
tab.project = run.cwd || tab.project;
|
|
10397
10463
|
tab.model = run.model || tab.model;
|
|
10398
10464
|
tab.effort = run.effort || tab.effort;
|
|
10465
|
+
// A warm worker whose turn ended on a question needs you, like one with a card up (setRunning repaints).
|
|
10466
|
+
tab.automationAwaits = runAwaitsAnswer(run);
|
|
10399
10467
|
setRunning(tab, running);
|
|
10400
10468
|
updatePillLabel(tab);
|
|
10401
10469
|
tab._automationIteration = run.currentIteration;
|
|
@@ -12,7 +12,7 @@ import { updateMemory, deleteMemory, fetchMemory } from './api.js';
|
|
|
12
12
|
import { KEYS } from './constants.js';
|
|
13
13
|
import { storage } from './storage.js';
|
|
14
14
|
import { catColor } from './colors.js';
|
|
15
|
-
import { truncate, formatMemoryContent, exportMemoryAsMarkdown, rafThrottle } from './utils.js';
|
|
15
|
+
import { truncate, formatMemoryContent, exportMemoryAsMarkdown, rafThrottle, escapeHtml } from './utils.js';
|
|
16
16
|
import { t, getLocale } from './i18n.js';
|
|
17
17
|
import { savePanelLayout, restorePanelLayout } from './ui-panels.js';
|
|
18
18
|
import { sendSync as _sendSync, hasPermission, isGuest } from './ui-sync.js';
|
|
@@ -111,7 +111,7 @@ function showTagDeleteModal(tagName, onConfirm) {
|
|
|
111
111
|
overlay.innerHTML = `
|
|
112
112
|
<div class="tag-delete-modal">
|
|
113
113
|
<div class="tag-delete-modal-title">Remove tag</div>
|
|
114
|
-
<div class="tag-delete-modal-tag">${tagName}</div>
|
|
114
|
+
<div class="tag-delete-modal-tag">${escapeHtml(tagName)}</div>
|
|
115
115
|
<div class="tag-delete-modal-actions">
|
|
116
116
|
<button class="action-btn action-btn--ghost tag-modal-cancel">Cancel</button>
|
|
117
117
|
<button class="action-btn action-btn--danger tag-modal-confirm">Delete</button>
|
|
@@ -363,7 +363,7 @@ function openCategoryChangeModal(card, node) {
|
|
|
363
363
|
<div class="cat-opt-dot" style="color:${catColor(cat)}; background:${catColor(cat)}"></div>
|
|
364
364
|
<div class="cat-opt-info">
|
|
365
365
|
<span class="cat-opt-name">${cat}</span>
|
|
366
|
-
${desc ? `<span class="cat-opt-desc">${desc}</span>` : ''}
|
|
366
|
+
${desc ? `<span class="cat-opt-desc">${escapeHtml(desc)}</span>` : ''}
|
|
367
367
|
</div>
|
|
368
368
|
</div>`;
|
|
369
369
|
}
|
|
@@ -375,7 +375,7 @@ function openCategoryChangeModal(card, node) {
|
|
|
375
375
|
<div class="cat-opt-dot" style="color:${catColor(cat)}; background:${catColor(cat)}"></div>
|
|
376
376
|
<div class="cat-opt-info">
|
|
377
377
|
<span class="cat-opt-name">${cat}</span>
|
|
378
|
-
${desc ? `<span class="cat-opt-desc">${desc}</span>` : ''}
|
|
378
|
+
${desc ? `<span class="cat-opt-desc">${escapeHtml(desc)}</span>` : ''}
|
|
379
379
|
</div>
|
|
380
380
|
${isActive ? '<svg class="cat-opt-check" viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg>' : ''}
|
|
381
381
|
</div>`;
|
|
@@ -863,7 +863,7 @@ export function openMemoryCard(node, savedPosition) {
|
|
|
863
863
|
if (!value) return;
|
|
864
864
|
const row = document.createElement('div');
|
|
865
865
|
row.className = 'detail-meta-row';
|
|
866
|
-
row.innerHTML = `<span style="color:rgba(255,255,255,0.3)">${label}</span><span>${value}</span>`;
|
|
866
|
+
row.innerHTML = `<span style="color:rgba(255,255,255,0.3)">${label}</span><span>${escapeHtml(String(value))}</span>`;
|
|
867
867
|
meta.appendChild(row);
|
|
868
868
|
};
|
|
869
869
|
addMeta('Project', p.project);
|
|
@@ -893,7 +893,7 @@ export function openMemoryCard(node, savedPosition) {
|
|
|
893
893
|
const filesContent = q(card, 'files-content');
|
|
894
894
|
if (p.related_files && p.related_files.length) {
|
|
895
895
|
filesContent.innerHTML = p.related_files.map(f =>
|
|
896
|
-
`<div style="font-family:'JetBrains Mono','SF Mono',Consolas,monospace;font-size:12px;color:rgba(255,255,255,0.45);padding:2px 0">${f}</div>`
|
|
896
|
+
`<div style="font-family:'JetBrains Mono','SF Mono',Consolas,monospace;font-size:12px;color:rgba(255,255,255,0.45);padding:2px 0">${escapeHtml(String(f))}</div>`
|
|
897
897
|
).join('');
|
|
898
898
|
filesDiv.style.display = '';
|
|
899
899
|
} else {
|
|
@@ -65,6 +65,7 @@ const handlers = createFileLinkHandlers({
|
|
|
65
65
|
say: fileLinkText,
|
|
66
66
|
notify: showFileLinkNotice,
|
|
67
67
|
follow: (anchor) => {
|
|
68
|
+
if (anchor.protocol !== 'http:' && anchor.protocol !== 'https:') return; // an address of this app, never a script link
|
|
68
69
|
if (anchor.target === '_blank') window.open(anchor.href, '_blank', 'noopener,noreferrer');
|
|
69
70
|
else window.location.assign(anchor.href);
|
|
70
71
|
},
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { emit, on } from './state.js';
|
|
8
8
|
import { fetchImages, toggleImageFavorite, deleteImage } from './api.js';
|
|
9
9
|
import { addImageToWhiteboard } from './ui-whiteboard.js';
|
|
10
|
+
import { escapeHtml } from './utils.js';
|
|
10
11
|
|
|
11
12
|
const $ = (id) => document.getElementById(id);
|
|
12
13
|
|
|
@@ -153,9 +154,9 @@ function buildPanelHTML() {
|
|
|
153
154
|
|
|
154
155
|
function buildCard(img) {
|
|
155
156
|
return `
|
|
156
|
-
<div class="ig-card" data-filename="${img.filename}">
|
|
157
|
+
<div class="ig-card" data-filename="${escapeHtml(img.filename).replace(/"/g, '"')}">
|
|
157
158
|
<div class="ig-thumb-wrap">
|
|
158
|
-
<img class="ig-thumb" src="${imageUrl(img.filename)}" loading="lazy" alt="${img.filename}">
|
|
159
|
+
<img class="ig-thumb" src="${imageUrl(img.filename)}" loading="lazy" alt="${escapeHtml(img.filename).replace(/"/g, '"')}">
|
|
159
160
|
<div class="ig-card-actions">
|
|
160
161
|
<button class="ig-card-btn ig-card-fav${img.favorite ? ' active' : ''}" data-action="fav" data-tooltip="${img.favorite ? 'Unfavorite' : 'Favorite'}">${img.favorite ? ICON_HEART_FILL : ICON_HEART}</button>
|
|
161
162
|
<button class="ig-card-btn ig-card-wb" data-action="whiteboard" data-tooltip="Add to Whiteboard">${ICON_WHITEBOARD}</button>
|
|
@@ -164,7 +165,7 @@ function buildCard(img) {
|
|
|
164
165
|
</div>
|
|
165
166
|
</div>
|
|
166
167
|
<div class="ig-card-info">
|
|
167
|
-
<span class="ig-card-name">${img.filename.length > 30 ? img.filename.slice(0, 27) + '...' : img.filename}</span>
|
|
168
|
+
<span class="ig-card-name">${escapeHtml(img.filename.length > 30 ? img.filename.slice(0, 27) + '...' : img.filename)}</span>
|
|
168
169
|
<span class="ig-card-meta">${formatSize(img.size)} · ${formatDate(img.modifiedAt)}</span>
|
|
169
170
|
</div>
|
|
170
171
|
</div>`;
|
|
@@ -185,9 +186,9 @@ function openLightbox(filename) {
|
|
|
185
186
|
lb.innerHTML = `
|
|
186
187
|
<div class="ig-lb-overlay" id="ig-lb-overlay"></div>
|
|
187
188
|
<div class="ig-lb-content">
|
|
188
|
-
<img class="ig-lb-img" src="${imageUrl(filename)}" alt="${filename}">
|
|
189
|
+
<img class="ig-lb-img" src="${imageUrl(filename)}" alt="${escapeHtml(filename).replace(/"/g, '"')}">
|
|
189
190
|
<div class="ig-lb-bar">
|
|
190
|
-
<span class="ig-lb-name">${filename}</span>
|
|
191
|
+
<span class="ig-lb-name">${escapeHtml(filename)}</span>
|
|
191
192
|
${img ? `<span class="ig-lb-size">${formatSize(img.size)}</span>` : ''}
|
|
192
193
|
<div class="ig-lb-actions">
|
|
193
194
|
<button class="ig-btn" id="ig-lb-fav" data-tooltip="Toggle favorite">${img?.favorite ? ICON_HEART_FILL : ICON_HEART}</button>
|
|
@@ -37,6 +37,7 @@ const _tunnel = createTunnelCard({
|
|
|
37
37
|
let _keyMode = 'auto'; // 'auto' | 'custom'
|
|
38
38
|
let _revealedKey = null; // shown only right after generation
|
|
39
39
|
let _permissions = { terminal: false, whiteboard: false, memories: true, skills: false, cards: true, browser: false };
|
|
40
|
+
let _dataLoaded = false; // the status was read once (the badge's read at start-up counts)
|
|
40
41
|
|
|
41
42
|
// ─── Dropdown open/close ────────────────
|
|
42
43
|
|
|
@@ -47,7 +48,12 @@ function openDropdown() {
|
|
|
47
48
|
dd.style.display = '';
|
|
48
49
|
$('invite-btn')?.classList.add('active');
|
|
49
50
|
emit('panel:close-all-dropdowns-except', 'invite');
|
|
50
|
-
|
|
51
|
+
// The status as last read (the badge's read at start-up, an earlier open) paints at once once the tunnel's
|
|
52
|
+
// state is known too (an earlier open asked it); the read below redraws only what changed, and never under a
|
|
53
|
+
// field being typed in.
|
|
54
|
+
const early = _dataLoaded && _tunnelKind !== null;
|
|
55
|
+
if (early) render();
|
|
56
|
+
loadAndRender({ inPlace: early });
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
function closeDropdown() {
|
|
@@ -65,7 +71,10 @@ function toggleDropdown() {
|
|
|
65
71
|
|
|
66
72
|
// ─── Data fetching ──────────────────────
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
const inviteSnapshot = () => { try { return JSON.stringify([_data, _permissions, _tunnel.view()]); } catch { return ''; } };
|
|
75
|
+
|
|
76
|
+
async function loadAndRender({ inPlace = false } = {}) {
|
|
77
|
+
const before = inPlace ? inviteSnapshot() : null;
|
|
69
78
|
try {
|
|
70
79
|
const [inviteRes, , permRes] = await Promise.all([
|
|
71
80
|
fetchInviteStatus(),
|
|
@@ -73,10 +82,16 @@ async function loadAndRender() {
|
|
|
73
82
|
fetchInvitePermissions(),
|
|
74
83
|
]);
|
|
75
84
|
_data = inviteRes;
|
|
85
|
+
_dataLoaded = true;
|
|
76
86
|
if (permRes?.permissions) _permissions = permRes.permissions;
|
|
77
87
|
} catch (err) {
|
|
78
88
|
console.warn('[invite] Failed to load status:', err);
|
|
79
89
|
}
|
|
90
|
+
if (inPlace) {
|
|
91
|
+
if (inviteSnapshot() === before) return;
|
|
92
|
+
const typing = document.activeElement?.closest?.('#invite-dropdown') && /^(INPUT|TEXTAREA)$/.test(document.activeElement.tagName);
|
|
93
|
+
if (typing) return; // the next open shows it
|
|
94
|
+
}
|
|
80
95
|
render();
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -524,6 +539,7 @@ export function initInvite() {
|
|
|
524
539
|
// Load initial badge count
|
|
525
540
|
fetchInviteStatus().then((res) => {
|
|
526
541
|
_data = res;
|
|
542
|
+
_dataLoaded = true;
|
|
527
543
|
updateBadge();
|
|
528
544
|
}).catch(() => {});
|
|
529
545
|
}
|