synabun 2026.4.26-2 → 2026.4.26-3
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 +196 -125
- package/neural-interface/public/shared/styles.css +144 -144
- package/neural-interface/public/shared/ui-claude-panel.js +1 -2
- package/neural-interface/public/shared/ui-menubar.js +0 -40
- package/neural-interface/public/shared/ui-update.js +0 -39
- package/neural-interface/server.js +0 -142
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,128 +2,199 @@
|
|
|
2
2
|
|
|
3
3
|
Raw internal changelogs are archived in [changelog/](changelog/).
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- **
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **Banner
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- **
|
|
63
|
-
- **
|
|
64
|
-
- **
|
|
65
|
-
- **
|
|
66
|
-
-
|
|
67
|
-
- **
|
|
68
|
-
- **
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- **
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- **
|
|
76
|
-
- **
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- **
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- **
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
- **
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- **
|
|
128
|
-
- **
|
|
129
|
-
-
|
|
5
|
+
## 2026-04-25
|
|
6
|
+
|
|
7
|
+
### Added — Dedicated Schedules Studio
|
|
8
|
+
- **Standalone schedule window** — Added `neural-interface/public/shared/ui-schedules-studio.js` and initialized it from both `neural-interface/public/variant/2d/main.js` and `neural-interface/public/variant/3d/main.js`. The new window opens from `automations:open-schedules` and keeps schedule management out of the Automation Studio detail pane.
|
|
9
|
+
- **Tabbed schedule workspace** — Built Cron, Timers, Groups, and Activity tabs so recurring schedules, quick timers, group management, and run history have separate focused surfaces instead of sharing the automation editor area.
|
|
10
|
+
- **Schedule editor flow** — Added a dedicated editor with cron presets, timezone, day selection themes, enabled state, group assignment, and launch overrides for profile/model/effort/MCP/browser settings.
|
|
11
|
+
- **Timer controls** — Added quick timer creation, run-now actions, active timer countdowns, saved schedule timers, pause/resume, trigger, edit, and delete controls inside the schedules window.
|
|
12
|
+
|
|
13
|
+
### Added — Schedule Groups API and Persistence
|
|
14
|
+
- **Group-aware schedule store** — Added `loadScheduleStore()`, `saveScheduleStore()`, `loadScheduleGroups()`, `saveScheduleGroups()`, `validateScheduleGroupId()`, and `applyScheduleLaunchOverrides()` in `neural-interface/server.js`. Existing `loop-schedules.json` data remains compatible while the store can now persist `{ schedules, groups }`.
|
|
15
|
+
- **Schedule group endpoints** — Added `GET /api/schedule-groups`, `POST /api/schedule-groups`, `PUT /api/schedule-groups/:id`, `DELETE /api/schedule-groups/:id`, and `POST /api/schedule-groups/reorder` for group CRUD and ordering.
|
|
16
|
+
- **Schedule assignment metadata** — Extended schedule create/update and launch payloads to carry `groupId`, `profile`, `model`, `effort`, `mcpProfile`, and `usesBrowser`, allowing schedules in a group to keep their launch configuration with the schedule itself.
|
|
17
|
+
- **Client sync helpers** — Added schedule group API helpers in `neural-interface/public/shared/api.js` and broadcast handling for `schedule-group:created`, `schedule-group:updated`, `schedule-group:deleted`, and `schedule-group:reordered` in `neural-interface/public/shared/ui-sync.js`.
|
|
18
|
+
|
|
19
|
+
### Changed — Automation Studio Schedule Routing
|
|
20
|
+
- **Schedules removed from the Automation Studio content flow** — `initAutomationStudio()` no longer handles `automations:open-schedules` by switching `_view` to `schedules`, and Automation Studio no longer initializes schedule WebSocket rendering. The header Schedules button now emits the shared schedules event for the standalone window.
|
|
21
|
+
- **Automations submenu expanded** — Added Schedules, New Cron Schedule, Quick Timer, and Schedule Groups menu items in `neural-interface/public/shared/html-shell.js`, with handlers in `neural-interface/public/shared/ui-menubar.js` that open the dedicated schedules window directly to the requested flow.
|
|
22
|
+
- **Schedules window styling** — Added the `.schedules-studio-panel` glass shell and `.sched-*` responsive layout styles in `neural-interface/public/shared/styles.css` so the standalone window has its own grouped sidebar, tab rail, schedule lists, dialogs, and mobile layout.
|
|
23
|
+
|
|
24
|
+
### Changed — Live Countdown on Schedule Cards
|
|
25
|
+
- **Live ticking countdown** — Replaced static "Next: 4h" text on Cron-tab schedule cards with a 1-second-tick live countdown. `formatNextRun()` in `neural-interface/public/shared/ui-schedules-studio.js` rewritten as adaptive 2-unit format (`2d 3h`, `4h 23m`, `15m 42s`, `45s`, `Overdue`) that drops zero second-unit. Card markup wraps the value in `<span class="sched-countdown" data-countdown="<iso>">` so `updateTimerCountdowns()` can refresh text without re-rendering each tick. `POLL_INTERVAL` lowered from `5000` to `1000`; tick lifecycle stays bound to `startPolling()` / `stopPolling()` so it only runs while the panel is open.
|
|
26
|
+
- **Tabular-nums styling** — Added `.sched-countdown` rule in `neural-interface/public/shared/styles.css` with `font-variant-numeric: tabular-nums` to prevent layout jitter as digits change each second.
|
|
27
|
+
|
|
28
|
+
### Added — Edit Prompt Shortcut on Schedule Cards
|
|
29
|
+
- **Edit prompt icon button** — Each cron schedule card in `neural-interface/public/shared/ui-schedules-studio.js` now has an "Edit prompt" icon button (document-with-lines `script` icon) between Run Now and Edit Schedule. Click closes the Schedules panel and opens Automation Studio with the schedule's linked template selected directly in detail/editor view.
|
|
30
|
+
- New `script` icon added to the `ICONS` map
|
|
31
|
+
- New button uses `data-action="schedule-edit-prompt"` with `data-template-id="${s.templateId}"`
|
|
32
|
+
- Existing pencil tooltip clarified from "Edit" to "Edit schedule" to differentiate
|
|
33
|
+
- New `case 'schedule-edit-prompt'` handler reads `el.dataset.templateId`, calls `closePanel()`, then emits `automations:open` with `{templateId}` payload
|
|
34
|
+
- **`automations:open` event accepts `{templateId}` payload** — `initAutomationStudio()` listener in `neural-interface/public/shared/ui-automation-studio.js` now passes `opts` to `openPanel(opts = {})`. When payload contains `templateId`: if panel already open, finds template in `_templates` and calls `switchToDetail(tpl)`; on fresh open, after `loadData()` runs, looks up template and routes to detail view instead of `renderView()`. Falls back to default welcome view when no payload or template not found.
|
|
35
|
+
|
|
36
|
+
### Added — Sidepanel Tray Placeholder Pills
|
|
37
|
+
- **Persisted pills before panel bootstrap** — Added `neural-interface/public/shared/ui-sidepanel-tray.js` to render minimized placeholder pills for saved Claude, Codex, and OpenCode tabs before those provider panels have been lazy-loaded. The tray reads window-scoped tab storage, falls back to legacy single-session keys, filters out blank default sessions, and opens the matching provider via `claude-panel:show`, `codex-panel:show`, or `opencode-panel:show` with the selected `tabId`.
|
|
38
|
+
- **Provider handoff events** — Wired `initSidepanelTrayPlaceholders()` from `ui-navbar.js`, then dispatch `sidepanel-tray:provider-loaded` from `toggleClaudePanel()`, Codex `ensurePanel()`, and OpenCode `ensurePanel()` so placeholders disappear once real provider-owned pills are restored.
|
|
39
|
+
|
|
40
|
+
### Fixed — Right-Side Toolbar Reservation During Sidepanel Switches
|
|
41
|
+
- **Toolbar could overlap an open sidepanel** — `ui-sidepanel-layout.js` previously tracked one `_activeOwner`, so closing or switching one sidepanel could clear `--right-panel-width` and `--right-panel-gap` while another right panel was still active. Replaced that with a per-owner reservation map, applying the widest active reservation and returning `true` from `clearRightPanelLayout()` only when no reservations remain, preserving toolbar and adjacent editor spacing during multi-provider toggles.
|
|
42
|
+
|
|
43
|
+
### Changed — Claude Restored Tab Selection
|
|
44
|
+
- **Restored Claude tabs keep stable IDs** — Updated `createTab()` in `ui-claude-panel.js` to accept a restored `id`, and changed `restoreTabs()` to pass saved tab IDs through. Placeholder click-through can now target the restored Claude tab instead of losing selection because the panel generated a fresh `crypto.randomUUID()` during bootstrap.
|
|
45
|
+
|
|
46
|
+
### Changed — Backup System Coverage Expanded
|
|
47
|
+
- **Backup file lists extended** — Added `stored-plans.json`, `synabun-plugins.json`, `mcp-registry.json`, and `active-profile.json` to both manual `GET /api/system/backup` (`neural-interface/server.js:10457`) and auto-backup `buildBackupZipToFile()` (`neural-interface/server.js:10844`) file arrays. These cover the post-plan hook plan→memory tracker, plugin install registry, third-party MCP server registry + profile defs, and active MCP profile selection — all previously lost on restore.
|
|
48
|
+
- **Plan markdown history backed up recursively** — Added `addDirRecursive('data/plans', ...)` to both manual and auto backup paths (`neural-interface/server.js:10483`, `:10868`) so the date-sliced plan tree (`data/plans/YYYY-MM-DD/*.md`) rides along.
|
|
49
|
+
- **Restore creates `plans` subdir** — Added `'plans'` to the `mkdirSync` subdir list in `POST /api/system/restore` (`neural-interface/server.js:10617`). Existing recursive `data/` write loop already extracts plan files via `dirname(target)` mkdir.
|
|
50
|
+
- **Backward compatible with old backups** — Manifest version check still accepts `version 1 || 2`. Old backups restore exactly as before — missing new files skipped silently, no schema migration required.
|
|
51
|
+
|
|
52
|
+
### Added — CLI-Not-Installed Indicator in Sidepanels
|
|
53
|
+
- **Empty-state banner across Claude, Codex, OpenCode panels** — Detects when the corresponding CLI binary is missing and renders a warning banner inside the panel's empty state (`.cp-empty` / `.cxp-empty` / `.ocp-empty`) with the install command, an "Install guide" link to the official docs, and a "Re-check" button. Banner is removed automatically once the CLI is detected.
|
|
54
|
+
- **Shared status module `cli-status.js`** — New `neural-interface/public/shared/cli-status.js` wraps the existing `/api/system/tool-versions` endpoint with a subscriber + auto-poll layer. Exposes `subscribeCliStatus(toolKey, cb)`, `recheckCliStatus(toolKey)`, `getCliDocUrl(toolKey)`, `getCliInstallCommand(toolKey)`, `getCliLabel(toolKey)`. Polls every 30s while at least one subscribed tool reports `installed === null`; stops once detected or unsubscribed. 10s in-memory cache + single in-flight fetch dedup.
|
|
55
|
+
- **Send-button gating** — Each panel's send/input flow is now blocked while the CLI is missing. `ui-claude-panel.js` `send()` returns early with a notification. `cdx/cdx-tabs.js` `syncInputEnabled` reads the `.cxp-cli-blocked` panel class to force `send.disabled = true`, and `sendPrompt()` returns early with a system message. `ocp/ocp-panel.js` `syncSendButton` short-circuits to disabled with the "OpenCode CLI not installed" tooltip; `handleSend` returns early via `setStatus('offline', …)`.
|
|
56
|
+
- **Banner re-render hooks** — `cdx/cdx-tabs.js` `clearTranscript` now dispatches a `cdx-empty-rebuilt` CustomEvent and `ocp/ocp-render.js` `renderEmptyState` dispatches `ocp-empty-rebuilt`, so the banner re-injects itself after the empty state is rebuilt during session resets.
|
|
57
|
+
- **Official install URLs** — `claude-code` → `https://docs.claude.com/en/docs/claude-code/setup`, `codex` → `https://developers.openai.com/codex/cli/`, `opencode` → `https://opencode.ai/docs/`. (`gemini` constants defined for future use; no Gemini sidepanel currently exists.)
|
|
58
|
+
- **Banner styles** — Added `.cp-cli-missing*`, `.cxp-cli-missing*`, `.ocp-cli-missing*` rule sets to `ui-claude-panel.js` style block, `cdx/cdx-styles.js`, and `ocp/ocp-styles.js`. Includes `.cp-cli-blocked` / `.cxp-cli-blocked` / `.ocp-cli-blocked` panel classes for the dimmed input state.
|
|
59
|
+
|
|
60
|
+
### Fixed — CLI-Not-Installed Banner Hidden After Errors / Broken-Binary Cases (Sidepanels)
|
|
61
|
+
- **Banner relocated to top of messages container** — Was rendered inside `.cxp-empty` / `.cp-empty` / `.ocp-empty`. Once any system message appeared (e.g. `Codex session closed` + `Codex app-server exited before responding to request 1`), the empty-state collapsed and took the banner with it. Banner is now an independent `:first-child` of `#cxp-messages-container`, `#cp-messages-container`, and `.ocp-messages-container` via `container.insertBefore(banner, container.firstChild)`. Stays visible across transcript states. New `ensureCliBannerEl()` / `removeCliBannerEl()` / `refreshCliBanner()` helpers replace the old empty-state-scoped `renderCliMissingBanner` / `refreshCliBanners` in `neural-interface/public/shared/cdx/cdx-panel.js`, `neural-interface/public/shared/ui-claude-panel.js`, and `neural-interface/public/shared/ocp/ocp-panel.js`.
|
|
62
|
+
- **Spawn-failure pattern detection** — Banner only triggered when `/api/system/tool-versions` reported `installed: null`. A broken-but-present binary (e.g. `codex 0.125.0` spawning then exiting immediately) reports a real version, so the banner never showed. Added a per-panel `_cliInstallFailureForced` flag flipped on by WS error patterns:
|
|
63
|
+
- **Codex** — `applySocketError` in `cdx/cdx-tabs.js` matches `/Codex CLI not found|ENOENT|app-server exited before responding|app-server spawn/i` → dynamic-imports `cdx-panel.js` and calls exported `flagCdxCliInstallFailure()`.
|
|
64
|
+
- **Claude** — `case 'error'` in WS handler matches `/Claude CLI not found|ENOENT/i` → `flagClaudeCliInstallFailure()`.
|
|
65
|
+
- **OpenCode** — `onError` callback matches `/opencode.*(not.*found|ENOENT|spawn)|opencode CLI/i` → `flagOcpCliInstallFailure()`.
|
|
66
|
+
- All three flags clear automatically when the `cli-status` subscriber callback later receives an installed value.
|
|
67
|
+
- **Send-button gate now reads forced flag** — Claude `send()`, Codex `sendPrompt()` (via `.cxp-cli-blocked` panel class read in `syncInputEnabled`), and OpenCode `syncSendButton` / `handleSend` all check `_cliInstalled === null || _cliInstallFailureForced` so a broken-binary install also blocks send.
|
|
68
|
+
- **Banner restyled as sticky alert** — New `.cxp-cli-banner*`, `.cp-cli-banner*`, `.ocp-cli-banner*` rule sets in `cdx/cdx-styles.js`, `ui-claude-panel.js` style block, and `ocp/ocp-styles.js`. Three-column grid (icon, text, actions), orange/amber theme, `flex-shrink: 0`, sits at top of the scroll region with `z-index: 5` so it stays above transcript content.
|
|
69
|
+
|
|
70
|
+
### Fixed — High Severity npm audit Alerts in Install CLI (Neural Interface)
|
|
71
|
+
- **Playwright SSL cert verification CVE** — `neural-interface/package.json` pinned `playwright` at `1.54.2`, vulnerable to GHSA-7mvr-c777-76hp (browser downloader skipped SSL cert authenticity check). Bumped to `^1.59.1` (non-major) — fixed in `<1.55.1`. `setup.js` install flow no longer prints the high severity warning after `Installing MCP Server dependencies`.
|
|
72
|
+
- **lodash code injection + protobufjs RCE via transitives** — `lodash@4.17.23` (pulled by `archiver` → `archiver-utils@5.0.2`) had high severity GHSA-r5fr-rjxr-66jc (`_.template` import key code injection) plus moderate prototype pollution. `protobufjs@7.5.4` (pulled by `@huggingface/transformers` → `onnxruntime-web@1.22.0-dev.20250409-89f8206ba4`) had critical GHSA-xq3m-2v4x-88gg (arbitrary code execution). Added `overrides` block in `neural-interface/package.json` pinning `lodash: ^4.18.1` and `protobufjs: ^7.5.5` — both within the parents' implicit ranges, no breaking change.
|
|
73
|
+
|
|
74
|
+
### Fixed — Moderate/High npm audit Alerts in Install CLI (MCP Server)
|
|
75
|
+
- **uuid buffer bounds check CVE** — `mcp-server/package.json` had `uuid@^11.1.0`, vulnerable to GHSA-w5hq-g745-h8pq (missing buffer bounds check in `v3`/`v5`/`v6` when `buf` provided), fixed in `<14.0.0`. Bumped to `^14.0.0` (semver major) — `v4` named export unchanged, `src/tools/remember.ts` and `src/tui.ts` compile cleanly with no source modifications.
|
|
76
|
+
- **hono / @hono/node-server / postcss / vite / protobufjs transitives** — multiple moderate-to-high CVEs in vitest's transitive `vite@8.0.0-8.0.4` (path traversal, `server.fs.deny` bypass, websocket arbitrary file read), `hono@<4.12.14` (cookie validation, path traversal in `toSSG()`, ipRestriction IPv4-mapped IPv6), `@hono/node-server@<1.19.13` (serveStatic repeated-slash bypass), `postcss@<8.5.10` (XSS via unescaped `</style>`), and `protobufjs@<7.5.5`. Added `overrides` block in `mcp-server/package.json` pinning `protobufjs: ^7.5.5`, `hono: ^4.12.14`, `@hono/node-server: ^1.19.13`, `postcss: ^8.5.10`, `vite: ^8.0.10` — patches only, no breaking semver bumps to direct deps.
|
|
77
|
+
|
|
78
|
+
### Changed — Removed Deprecated `@types/uuid` devDependency
|
|
79
|
+
- **uuid v14 ships own type defs** — removed `@types/uuid: ^10.0.0` from `mcp-server/package.json` devDependencies. npm warned the stub package as deprecated (`This is a stub types definition. uuid provides its own type definitions, so you do not need this installed`). `tsc` build verified — no missing-types errors.
|
|
80
|
+
|
|
81
|
+
## 2026-04-24
|
|
82
|
+
|
|
83
|
+
### Added — "Continue planning" in OpenCode, Codex, and Claude plan UIs
|
|
84
|
+
- **OpenCode** — Added `data-action="continue-planning"` button to the post-plan card in `ocp-render.js` and wired `onContinuePlanning` in `showPostPlanUI()` (`ocp-tabs.js`). Handler clears `showPostPlanActions` while keeping `mode='plan'`, so the user can keep prompting to refine the plan without exiting plan mode.
|
|
85
|
+
- **Codex** — Added `Continue planning` button to the post-plan card in `cdx-render.js`. Handler removes the card, then dispatches a new prompt with `forcePlanModePrefix: true` and the current plan text as context, allowing iterative plan refinement.
|
|
86
|
+
- **Claude** — Added `Continue planning` action entry in `ui-claude-panel.js`. Handler clears `showPostPlanActions` and saves tabs, keeping `tab.planMode = true` so the next user message continues in plan mode.
|
|
87
|
+
- **OpenCode guard message** — Updated the "choose before sending" error to include "Continue planning" alongside the existing three options.
|
|
88
|
+
|
|
89
|
+
### Fixed — OpenCode Sidepanel Post-Plan Card Resurrection
|
|
90
|
+
- **`setTabMode()` leaves `showPostPlanActions` flag true when leaving plan mode** — Mode button clicks, Tab-key mode cycling, and other `setTabMode()` callers bypassed `onContinue`'s flag clear, so `tab.showPostPlanActions` could persist into build/chat. Combined with the unguarded restore in `renderTabMessages()` at `ocp-tabs.js:2258`, any subsequent tab switch / session switch / revert / WS reconnect re-rendered the "PLAN COMPLETE" card in the message area even though the user had already moved on to implementation. Fixed in `neural-interface/public/shared/ocp/ocp-tabs.js` with two defenses:
|
|
91
|
+
- `setTabMode()` now clears `tab.showPostPlanActions` and calls `removePostPlanCards(container)` on any `plan → non-plan` transition, symmetric with the existing `_exitPlanDetected` reset on entry.
|
|
92
|
+
- `renderTabMessages()` restore guard tightened to `tab.mode === 'plan' && tab.showPostPlanActions && tab.planContent`, so a leaked flag cannot resurrect the card outside plan mode.
|
|
93
|
+
|
|
94
|
+
### Fixed — Loop Launch Silent Hang (Automation Studio Browser Loops)
|
|
95
|
+
- **Orphan Chrome holding `SingletonLock` froze `launchPersistentContext`** — When a previous loop-owned Chrome in `data/browser-profiles/<hash>/` stayed alive past its session, the OS-level singleton lock remained held by the live process. Existing stale-file cleanup at `neural-interface/server.js:20080` only removed unlocked lock files — useless when a running Chrome owned the lock. Subsequent `chromium.launchPersistentContext()` calls blocked indefinitely, so `POST /api/loop/launch` never returned. UI waited with no CLI window, no toast, no error surfaced. Fix in `neural-interface/server.js:20062` (`launchPersistentProfile`) adds a pre-launch orphan-kill pass: Unix uses `pgrep -f "user-data-dir=<root>"` + `SIGKILL`, Windows uses PowerShell `Get-CimInstance Win32_Process` filtered by `CommandLine -like "*user-data-dir=$root*"` (path passed via `SYNABUN_KILL_ROOT` env var to avoid backslash quoting) + `taskkill /F /PID`.
|
|
96
|
+
- **`launchPersistentContext` had no timeout** — Added `timeout: 45000` to `launchOpts` at `neural-interface/server.js:19938` as a platform-agnostic safety net. If any edge case still leaves the singleton locked, Playwright errors after 45s instead of hanging silently, letting the UI surface `Failed to launch loop`.
|
|
97
|
+
|
|
98
|
+
### Added — Diagnostic Alert Fallbacks for Silent Launch Failures
|
|
99
|
+
- **`attachDetached` catch in `neural-interface/public/shared/ui-terminal.js:5132`** — `showTermToast` appends its toast to `#term-container`; when the terminal panel is hidden (`offsetParent === null`), the toast renders invisibly and the failure gets swallowed. Added a post-toast container-visibility check that triggers a browser `alert()` with the error message plus a DevTools pointer when the container is hidden, surfacing attach failures that otherwise disappear.
|
|
100
|
+
- **`confirmLaunch` catch in `neural-interface/public/shared/ui-automation-studio.js:1491`** — Added an `alert()` alongside the existing `showToast` so errors thrown before the `terminal:attach-floating` emit (network failures, missing state, module-load errors) reach the user even when the toast host is offscreen.
|
|
101
|
+
|
|
102
|
+
### Fixed — Codex MCP Approval Gating
|
|
103
|
+
- **MCP approval prompts bypassed sidepanel Auto mode** — `handleCodexSkinWebSocket()` in `neural-interface/server.js` previously auto-accepted `mcpServer/elicitation/request` events for any SynaBun tool listed in `~/.claude/settings.json`, so tools like `mcp__SynaBun__remember` could run without a Codex approval card even when the tab Auto toggle was off. Added per-turn `activeTurnAutoAccept` tracking and gated the silent MCP accept path on both the current tab's Auto state and the permitted-tool list.
|
|
104
|
+
- **Auto state now travels with each Codex query** — `dispatchPrompt()` in `neural-interface/public/shared/cdx/cdx-tabs.js` includes `autoAccept: !!tab?.autoAccept` in the WebSocket `query` payload, and `server.js` passes it into `startTurn()` so approval behavior follows the visible tab state.
|
|
105
|
+
- **Per-turn auto-accept state is cleared after the turn** — `server.js` resets `activeTurnAutoAccept` on `turn/completed`, `thread/closed`, Codex child close, and `turn/start` failure to prevent a prior Auto-enabled turn from leaking into later tool approvals.
|
|
106
|
+
|
|
107
|
+
### Fixed — Codex Sidepanel Edit Plan Cancel Handoff
|
|
108
|
+
- **Post-plan card stayed disabled after closing the editor** — `renderPostPlanActions()` in `neural-interface/public/shared/cdx/cdx-render.js` intentionally marks the post-plan card busy when `Edit plan` opens the file editor, but the Codex `plan-edit-cancelled` path in `neural-interface/public/shared/cdx/cdx-panel.js` tried to recover through unregistered `_host.renderPostPlanActions` callbacks. The Codex `plan-saved` and `plan-edit-cancelled` listeners now import and call `appendAssistantMarkdownMessage()` / `renderPostPlanActions()` directly from `cdx-render.js`, so closing without saving re-renders a fresh clickable card and saving reopens the `PLAN UPDATED` handoff correctly.
|
|
109
|
+
|
|
110
|
+
### Fixed — Codex Loop Silent Failure (Sentinel Ignored Exit Code)
|
|
111
|
+
- **`attachExecLoopDriver` in `neural-interface/server.js:17230` advanced 162 ghost iterations after codex broke** — Iteration sentinel was built as `<codex exec ...>; echo SYNABUN_ITER_DONE_<N>`, so the echo fired regardless of `codex` exit status. After codex started failing fast at iter 4 (~4s vs ~7m for real runs), driver kept seeing the sentinel and looped 162 fake iters in 33 minutes until the PTY died with exit code 1. Fixed by:
|
|
112
|
+
- Sentinel now `<cmd>; rc=$?; echo SYNABUN_ITER_DONE_<N> rc=$rc` — captures exit code in the echo.
|
|
113
|
+
- Detection regex `new RegExp(\`${EXEC_SENTINEL_PREFIX}${session._execCurrentIter} rc=(-?\\d+)\`)` extracts `rc`.
|
|
114
|
+
- Per-driver `consecutiveNonZero` and `consecutiveFastFails` counters with `FAST_FAIL_THRESHOLD_MS=30000` and `FAIL_LIMIT=3`. On 3-streak (non-zero exit OR sub-30s duration), driver marks loop `active:false`, sets `stoppedReason: 'fast-fail-rc'` or `'fast-fail-duration'`, records `lastExitCode`, kills PTY, and clears the interval.
|
|
115
|
+
- Iteration start timestamp tracked via `session._execIterStartedAt = Date.now()` at iter-begin.
|
|
116
|
+
|
|
117
|
+
### Fixed — PTY Exit Left Loop File Active (Zombie Loops)
|
|
118
|
+
- **`createTerminalSession` PTY exit handler in `neural-interface/server.js:16718` did not update loop state** — When a loop-owned PTY died, `terminalSessions.delete()` ran and the driver interval cleared, but the on-disk loop file still showed `active:true`. UI/scheduler saw a phantom running loop forever. Fixed by adding a post-`pty:exit` block that calls `findLoopFileForTerminal(sessionId)`, marks the file `active:false`, sets `completedAt`, `stoppedReason: 'pty-exit-<code>'`, and records `lastExitCode`. Logs `pty:exit | marked loop inactive` for forensics.
|
|
119
|
+
|
|
120
|
+
### Changed — Loop Tab Isolation Hardening
|
|
121
|
+
- **Dropped `_switchSessionTab` calls during loop launch** — `neural-interface/server.js:8351` (manual launch) and `:8751` (scheduled launch) previously called `_switchSessionTab(claimedSession, ..., browserTabId)` after `createSessionTab`, flipping the shared browser session's `activeTabId`. With concurrent loops sharing one Chromium, this caused active-tab churn and risked any code path falling back to `session.activeTabId`. Each loop already pins its own tab via the `SYNABUN_BROWSER_TAB` env var, so the switch was unnecessary. Removed both calls; replaced with a comment explaining the constraint.
|
|
122
|
+
- **`SYNABUN_BROWSER_TAB` env now wins over caller-supplied `tabId`** — `mcp-server/src/services/neural-interface.ts:88` (`resolveSession`) reordered the resolution: pinned env value takes precedence over the explicit `tabId` argument. If the caller passes a different tabId, it's logged as `[MCP] tabId override ignored` and the pin is honored. Prevents an agent or model from accidentally leaking into another loop's tab by passing the wrong tabId. Rebuilt via `npm run build` in `mcp-server/`.
|
|
123
|
+
|
|
124
|
+
## 2026-04-23
|
|
125
|
+
|
|
126
|
+
### Added — Claude Code Plugin Install from MCP UI
|
|
127
|
+
- **CLI-delegated plugin installer** — Rewrote `neural-interface/lib/plugin-installer.js` to call `claude plugin marketplace add <url>` + `claude plugin install <plugin>@<marketplace>` instead of hand-writing `~/.claude/plugins/*.json`. Claude Code now owns its own marketplace + cache state; installs survive CC restarts. Legacy `synabun-symlink` source type removed.
|
|
128
|
+
- **Auto-repair on server startup** — `server.js` `app.listen` callback invokes `repairSynabunPlugins()`. Scans `~/.claude/plugins/known_marketplaces.json` for legacy `source: synabun-symlink` entries, purges dangling symlinks and stale `installed_plugins.json` rows, reinstalls via CLI from the remembered repo path.
|
|
129
|
+
- **`POST /api/plugins/repair`** — Manual trigger for the same repair flow.
|
|
130
|
+
- **`data/synabun-plugins.json` tracker** — New JSON file SynaBun owns, recording which plugins we installed (CC remains source of truth for install state). `setTrackerPath()` export pins the path to `DATA_HOME/data/synabun-plugins.json` on boot.
|
|
131
|
+
- **GitHub URL preferred over local clone** — `/api/mcp/install/github` claude-plugin branch passes `githubUrl` into the installer; CC re-clones fresh into its own cache so the temp clone at `data/mcp/<name>/repo` is only used to read the manifest.
|
|
132
|
+
|
|
133
|
+
### Added — Plugin Slash Commands in Sidepanel Autocomplete
|
|
134
|
+
- **`GET /api/claude-code/plugin-commands`** — New endpoint in `server.js`. Walks `~/.claude/plugins/cache/*/*/*/commands/*.{toml,md}`, parses `description` from TOML (`description = "..."`) or markdown frontmatter, returns `[{ name, description, source: 'plugin', plugin, marketplace }]`. No caching — fresh scan per request, so newly installed plugins appear without restart.
|
|
135
|
+
- **Autocomplete merge in `ui-claude-panel.js`** — `loadSkills()` adds a fourth merge step after SynaBun skills + CLI built-ins + extras. Plugin commands show in the `/` hint dropdown with `(plugin: <name>)` suffix.
|
|
136
|
+
|
|
137
|
+
### Fixed — Broken Legacy Caveman Install
|
|
138
|
+
- **CC marketplace loader errored with `Marketplace configuration file is corrupted`** — Caused by the old installer writing `"source": "synabun-symlink"` to `known_marketplaces.json`; CC prunes unknown source types and wiped the `cache/caveman/` symlink so `installPath` in `installed_plugins.json` pointed at a missing directory. Hooks never fired. Cleanup + CLI reinstall restored caveman — `claude plugin list` now shows `caveman@caveman ✔ enabled` with a valid `cache/caveman/caveman/<sha>` dir.
|
|
139
|
+
|
|
140
|
+
### Fixed — Sidepanel Slash-Hints Silent Bail
|
|
141
|
+
- **`showSlashHints()` returned early when `_skillsCache` null** — First `/` keystroke could fire before the async `loadSkills()` fetch resolved, leaving no dropdown. Made `showSlashHints` async; awaits `loadSkills()` when cache empty so the dropdown is guaranteed on first keystroke.
|
|
142
|
+
|
|
143
|
+
### Fixed — Sidepanel Slash-Hints Clipped by Input Wrapper
|
|
144
|
+
- **`.cp-input-wrap` had `overflow: hidden`** — Parent wrapper clipped the upward-expanding `.cp-slash-hints` dropdown (positioned `bottom: calc(100% + 4px)`). Removed the overflow rule — the wrapper's conic-gradient border already clips itself via `-webkit-mask-composite: xor`, so the overflow was redundant. Dropdown now expands unclipped and scrolls internally.
|
|
145
|
+
|
|
146
|
+
### Changed — Sidepanel Slash-Hints Dropdown Sizing
|
|
147
|
+
- **`.cp-slash-hints` — `max-height: 200px → 320px`** — Fits more rows before scroll kicks in.
|
|
148
|
+
- **Added `overscroll-behavior: contain`** — Mouse-wheel scroll inside the dropdown no longer bubbles to the message list beneath.
|
|
149
|
+
|
|
150
|
+
### Fixed — GitHub MCP Installer Discarded Servers on Install Failure
|
|
151
|
+
- **`/api/mcp/install/github` soft-fails local dep errors** — In `neural-interface/server.js`, the install endpoint returned HTTP 500 and aborted the whole flow when `pip install -r requirements.txt` or `npm install` errored, even though the clone + detection already succeeded. Reproducible with `AminForou/mcp-gsc`: launch command was `uvx mcp-search-console` (self-fetching) but `pip` missing on PATH made the endpoint bail before registry write. Now install failures attach an `installWarning` field to the 200 response — the server still registers, the user sees a `⚠` line in the GitHub install form, and deps can be fixed manually.
|
|
152
|
+
- **Self-fetcher runtimes skip local install** — Added `commandSelfFetches(detected)` in `neural-interface/lib/mcp-installer.js` that matches `uvx` / `uv run` / `npx` / `pipx` / `bunx` / `pnpm dlx` / `yarn dlx`. When true, `installDependencies` is never called — the server is registered with `install.skipped = true, reason: "command '<cmd>' self-fetches at runtime"` and the UI shows a `⚙ Skipped local deps` hint.
|
|
153
|
+
|
|
154
|
+
### Added — Cross-OS Preflight for Self-Fetching MCP Runtimes
|
|
155
|
+
- **`checkCommandOnPath(cmd)` in `neural-interface/lib/mcp-installer.js`** — OS-agnostic PATH probe. Uses `where <cmd>` on Windows (which resolves `.exe` via PATHEXT), `sh -c 'command -v <cmd>'` on Unix. 5s timeout guard. Returns a boolean.
|
|
156
|
+
- **`checkSelfFetcherAvailable(detected)` preflight** — Called by the install endpoint when `commandSelfFetches` is true. Returns `{ available, command, installHint }`. Missing binary attaches an OS-specific install command to `installWarning` (e.g. *"'uvx' not found on PATH. Install: brew install uv. Server registered anyway — will fail at launch until installed."*).
|
|
157
|
+
- **`SELF_FETCHER_INSTALL_HINTS` map** — Per-OS install commands for `uvx`/`uv`/`pipx`/`npx`/`bunx`/`bun`/`pnpm`/`yarn`. Mac uses `brew`, Linux uses official curl installers, Windows uses `winget` or PowerShell `irm`. `installHintFor(cmd)` picks the row based on `process.platform`. Ensures fresh SynaBun installs on Windows/Mac/Linux get actionable guidance instead of a silent launch failure.
|
|
158
|
+
|
|
159
|
+
### Changed — Codex Sidepanel Reasoning and Response Controls
|
|
160
|
+
- **Expanded Codex reasoning effort support** — Updated `neural-interface/public/shared/cdx/cdx-icons.js`, `cdx-panel.js`, `cdx-styles.js`, and `cdx-tabs.js` so the Codex footer effort toggle now supports the full current set of sidepanel overrides: `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`. The toolbar indicator now renders five effort dots, includes visual states for `minimal` and `xhigh`, and `/effort` validates input while treating `default` and `none` as no per-turn override.
|
|
161
|
+
- **Exposed response-control config keys** — Added `model_reasoning_summary` and `model_verbosity` to `CODEX_CONFIG_KEYS` in `neural-interface/server.js`, then surfaced both in the Codex settings form and `/status` output in `neural-interface/public/shared/cdx/cdx-tabs.js`. Settings now support reasoning summary choices `auto`, `concise`, `detailed`, and `none`, plus response verbosity choices `low`, `medium`, and `high`.
|
|
162
|
+
|
|
163
|
+
### Fixed — Codex Sidepanel Session Cross-Talk
|
|
164
|
+
- **Per-tab WebSocket identity** — `sendSocket()` in `neural-interface/public/shared/cdx/cdx-tabs.js` now stamps every Codex sidepanel WebSocket message with the bound tab's `sessionId`, so bootstrap, query, reattach, model, interrupt, and compact traffic is routed to the owning tab instead of only the shared `windowId`.
|
|
165
|
+
- **Strict orphan reattach keys** — `_codexOrphanKey()` in `neural-interface/server.js` now requires both `windowId` and `sessionId`, and the reattach handler returns `reattach_result: false` when the tab id is missing. The close handler refuses to create window-wide orphans and kills sessionless processes, preventing a newly opened tab from replaying another tab's buffered tool-call events.
|
|
166
|
+
- **Reconnect state cleanup** — `onClose()` sets `pendingReattach` only for abnormal Codex socket closes and `onReattachResult()` clears it, preserving correct orphan recovery for the original tab without leaking state to other sessions.
|
|
167
|
+
|
|
168
|
+
### Fixed — Codex Sidepanel Active-Tab Stop Scope
|
|
169
|
+
- **Stop button aborted every running Codex tab** — The shared composer stop state in `syncInputEnabled()` was keyed off `_tabs.some((t) => t?.running)`, and `cdx-panel.js` wired click/Escape to `interruptAllTabs()`, so stopping one visible tab sent `interrupt` and delayed `force_kill` to every running tab WebSocket. The handlers now call `interruptTurn()` only when `activeTab()?.running`, the stop icon is scoped to the active tab, and `interruptTab()` sends session-scoped stop messages for the bound tab.
|
|
170
|
+
- **Stale force-kill retries could outlive successful interrupts** — `onInterruptAck()` now clears each tab's `_abortRetry`, preventing the fallback timer from killing a Codex app-server after the server already acknowledged an interrupt.
|
|
171
|
+
- **Server accepted unscoped stop messages** — `validateCodexControlMessage()` in `server.js` rejects `interrupt`/`force_kill` without a matching `sessionId` or with a mismatched `threadId`, while `forceKillCodexChild()` logs pid/session/thread/reason before terminating the app-server.
|
|
172
|
+
|
|
173
|
+
### Fixed — Codex Plan Mode Handoff
|
|
174
|
+
- **Post-plan actions no longer get skipped** — `dispatchPrompt()` in `neural-interface/public/shared/cdx/cdx-tabs.js` now marks sent plan turns with `planMode`, `planTurnActive`, and `lastPlanTurnId`, so `handleNotify('turn/completed')` captures the completed plan even if the toolbar `planMode` flag has already changed by the time the turn finishes.
|
|
175
|
+
- **Approval gate persists across tab state** — Added `planApprovalPending` / `planTurnActive` persistence through `snapshotBoundState()`, `restoreBoundState()`, `commitBoundState()`, `bindTabState()`, `createTab()`, and `saveTabs()`, and blocks `sendPrompt()` / `advanceQueue()` until the user chooses Continue, Compact, or Edit.
|
|
176
|
+
- **Continue/Edit paths clear or restore the handoff correctly** — `renderPostPlanActions()` in `cdx-render.js` clears pending plan state before dispatching the non-plan implementation prompt and restores it if send fails, while `cdx-panel.js` marks edited plan saves as pending approval again.
|
|
177
|
+
- **Plan turns run read-only server-side** — `server.js` passes the client `planMode` flag into `startTurn()`, detects explicit `[PLAN MODE]` prompts with `isCodexPlanModePrompt()`, and forces `buildCodexSandboxPolicy(..., 'read-only')` for plan turns.
|
|
178
|
+
- **Prompt text now enforces the sidepanel handoff** — `CODEX_PLAN_MODE_PREFIXES` in `cdx-icons.js` and `server.js` now tells Codex to stop after producing the plan and wait for the sidepanel’s Edit, Compact, or Continue choice.
|
|
179
|
+
|
|
180
|
+
### Fixed — CLI/TUI Rendering Corruption (Terminal)
|
|
181
|
+
- **Stable xterm renderer path** — `loadXterm()` in `neural-interface/public/shared/ui-terminal.js` now intentionally skips `@xterm/addon-webgl` and `@xterm/addon-canvas`, leaving CLI/TUI windows on xterm's DOM renderer. This avoids WebGL glyph atlas/context corruption seen after multiple long-lived floating CLI sessions.
|
|
182
|
+
- **Serialized terminal writes** — Added `_createTerminalWriter()` with 64KB chunking and `term.write()` callback backpressure, then routed fresh session, reconnect, and WebSocket reconnect output through it. This prevents large PTY bursts from piling up unbounded parser/render work during long Codex/Claude/Gemini sessions.
|
|
183
|
+
- **Snapshot-based reconnects** — `createTerminalSession()` in `neural-interface/server.js` now maintains a capped `VTermBuffer` per PTY, updates it on output and resize, and `/ws/terminal/:id` sends a synthesized `snapshot` payload instead of replaying an arbitrary raw ANSI tail. The snapshot includes a plain-text companion so `_sendOnceReady()` can still detect ready prompts without waiting for its fallback timer.
|
|
184
|
+
- **Virtual terminal snapshot support** — `neural-interface/public/shared/vterm-buffer.js` now exposes `useAltBuffer` and accepts a per-instance scrollback cap, letting live PTY sessions keep only the rows needed for reconnect recovery while preserving the larger default for existing capture use cases.
|
|
185
|
+
|
|
186
|
+
## 2026-04-22
|
|
187
|
+
|
|
188
|
+
### Added — Smart "Install from GitHub" (MCP + Claude Code Plugins Auto-Route)
|
|
189
|
+
- **Repo classifier** — New `classifyRepo(repoPath)` in `neural-interface/lib/mcp-installer.js` runs BEFORE `detectServer()` and returns `{ kind: 'mcp' | 'claude-plugin' | 'unknown' }`. Prefers MCP when both markers exist, emits `alsoClaudePlugin` hint for mixed repos, scans for codex/gemini/cursor/windsurf adjacency signals.
|
|
190
|
+
- **Claude Code plugin installer** — New `neural-interface/lib/plugin-installer.js` with `installClaudePlugin()`, `uninstallClaudePlugin()`, `listSynabunPlugins()`. Symlinks the cloned repo into `~/.claude/plugins/marketplaces/<name>` and `~/.claude/plugins/cache/<marketplace>/<plugin>/unknown`, appends SynaBun-tagged entries to `known_marketplaces.json` + `installed_plugins.json` (dedupe by `scope:'user' + installPath`), captures `gitCommitSha` via `git rev-parse HEAD`.
|
|
191
|
+
- **`POST /api/mcp/install/github` branches on classification** — After clone, `kind: 'claude-plugin'` skips MCP detection/deps install and calls `installClaudePlugin`; `kind: 'unknown'` returns 422 with hints list; MCP path unchanged. New endpoints `GET /api/plugins/list` and `DELETE /api/plugins/:marketplace?pluginName=&purgeFiles=1`.
|
|
192
|
+
- **Settings UI rename + plugin list** — "+ Add Server" button renamed to "+ Install from GitHub" in `neural-interface/public/shared/ui-settings.js`. New "Claude Code Plugins" subsection with per-row Uninstall. Install handler swaps the form into "plugin mode" (hides Type/Command/Args/env/platforms/Save) when response is `kind: 'claude-plugin'` since the plugin is already activated server-side.
|
|
193
|
+
|
|
194
|
+
### Added — Installed MCPs Sub-Tab (Cross-CLI Unified View)
|
|
195
|
+
- **`GET /api/mcp/installed-all` endpoint** — Aggregates every externally-installed MCP across SynaBun registry (non-builtin), `~/.claude.json`, `~/.gemini/settings.json`, `~/.codex/config.toml` (regex `^\[mcp_servers\.([^\].]+)\]$` excludes nested `.tools.` sections), and OpenCode's `mcp.*` config. Merges by server name; `registeredWith` is a sorted array of CLI keys.
|
|
196
|
+
- **Sub-tab navigation inside Settings → MCP** — New `.stg-mcp-page-tabs` at top of the page swaps between **Profiles** (existing matrix + always-on + per-profile External Servers toggles) and **Installed** (new cross-CLI list + Claude Code Plugins + Install form). SynaBun's built-in tool groups are excluded from the Installed view.
|
|
197
|
+
- **Per-CLI badges** — Each row in the Installed list shows compact `SB/CC/OC/CX/GE` badges indicating which CLIs have the server registered. Styles added for `.stg-mcp-page-tabs`, `.stg-mcp-page-tab.active`, `.stg-mx-ext-cli-badge`.
|
|
198
|
+
|
|
199
|
+
### Added — Cross-CLI Uninstall Button on Installed-Tab Rows
|
|
200
|
+
- **One-click uninstall across all registered CLIs** — Each row in the Installed sub-tab carries a `×` button with `data-registered` containing its precise CLI list. Click handler confirms using the badge labels (e.g., "Remove foo from CC, OC, CX?"), then: if `registeredWith` includes `synabun` → `DELETE /api/mcp/registry/servers/:name`; for every remaining CLI key → `DELETE /api/mcp/sync` with `{ name, platforms: cliList }`. Refreshes both Installed list and the Profiles-tab External Servers list afterward. Avoids pointless 404 churn by only hitting CLIs that actually have the server.
|
|
@@ -19485,150 +19485,150 @@
|
|
|
19485
19485
|
}
|
|
19486
19486
|
.sched-sidebar-new:hover { background: rgba(255,255,255,0.07); color: var(--t-bright); }
|
|
19487
19487
|
.sched-sidebar-empty { color: var(--t-muted); font-size: 11px; padding: 8px 6px; }
|
|
19488
|
-
.sched-sidebar-prompt {
|
|
19489
|
-
margin-top: auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
19490
|
-
border: 1px solid rgba(255,255,255,0.09); border-radius: 8px;
|
|
19491
|
-
background: rgba(255,255,255,0.04);
|
|
19492
|
-
color: var(--t-secondary); min-height: 34px; cursor: pointer; font-family: inherit;
|
|
19493
|
-
font-size: 12px; font-weight: 600; letter-spacing: 0;
|
|
19494
|
-
box-shadow: var(--shadow-highlight);
|
|
19495
|
-
transition: border-color 160ms ease, color 160ms ease, background 160ms ease, transform 120ms ease;
|
|
19496
|
-
}
|
|
19497
|
-
.sched-sidebar-prompt:hover {
|
|
19498
|
-
background: rgba(255,255,255,0.075);
|
|
19499
|
-
border-color: rgba(255,255,255,0.14);
|
|
19500
|
-
color: var(--t-bright);
|
|
19501
|
-
transform: translateY(-1px);
|
|
19502
|
-
}
|
|
19503
|
-
.sched-sidebar-prompt svg { width: 13px; height: 13px; color: var(--t-muted); }
|
|
19504
|
-
|
|
19505
|
-
.sched-prompt-backdrop {
|
|
19506
|
-
position: fixed; inset: 0; background: rgba(5,7,12,0.58);
|
|
19507
|
-
backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
|
|
19508
|
-
z-index: 300010; opacity: 0; transition: opacity 160ms ease-out;
|
|
19509
|
-
pointer-events: auto;
|
|
19510
|
-
}
|
|
19511
|
-
.sched-prompt-backdrop.open { opacity: 1; }
|
|
19512
|
-
.sched-prompt-modal {
|
|
19513
|
-
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -52%) scale(0.98);
|
|
19514
|
-
width: min(512px, calc(100vw - 32px)); padding: 0;
|
|
19515
|
-
border-radius: 12px; z-index: 300011;
|
|
19516
|
-
opacity: 0; transition: opacity 160ms ease-out, transform 200ms ease-out;
|
|
19517
|
-
background:
|
|
19518
|
-
linear-gradient(to bottom, rgba(255,255,255,0.035), rgba(255,255,255,0.01)),
|
|
19519
|
-
rgba(13,15,20,0.96);
|
|
19520
|
-
border: 1px solid rgba(255,255,255,0.085);
|
|
19521
|
-
box-shadow: 0 22px 64px rgba(0,0,0,0.58), var(--shadow-highlight);
|
|
19522
|
-
overflow: hidden;
|
|
19523
|
-
}
|
|
19524
|
-
.sched-prompt-modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
19525
|
-
.sched-prompt-head {
|
|
19526
|
-
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
19527
|
-
padding: 15px 16px 13px;
|
|
19528
|
-
border-bottom: 1px solid rgba(255,255,255,0.055);
|
|
19529
|
-
}
|
|
19530
|
-
.sched-prompt-head-text {
|
|
19531
|
-
display: flex; align-items: center; gap: 10px; min-width: 0;
|
|
19532
|
-
}
|
|
19533
|
-
.sched-prompt-icon {
|
|
19534
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
19535
|
-
width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
|
|
19536
|
-
background: rgba(255,255,255,0.055);
|
|
19537
|
-
border: 1px solid rgba(255,255,255,0.075);
|
|
19538
|
-
color: var(--t-secondary);
|
|
19539
|
-
box-shadow: var(--shadow-highlight);
|
|
19540
|
-
}
|
|
19541
|
-
.sched-prompt-icon svg { width: 14px; height: 14px; }
|
|
19542
|
-
.sched-prompt-head h3 {
|
|
19543
|
-
margin: 0; font-family: var(--ff-heading); font-size: 14px; font-weight: 650;
|
|
19544
|
-
color: var(--t-bright); letter-spacing: 0;
|
|
19545
|
-
}
|
|
19546
|
-
.sched-prompt-head p {
|
|
19547
|
-
margin: 2px 0 0; font-size: 11.5px; line-height: 1.35; color: var(--t-muted);
|
|
19548
|
-
}
|
|
19549
|
-
.sched-prompt-close {
|
|
19550
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
19551
|
-
width: 28px; height: 28px; flex: 0 0 auto;
|
|
19552
|
-
background: transparent; border: 1px solid transparent; border-radius: 7px;
|
|
19553
|
-
color: var(--t-muted); cursor: pointer; padding: 0;
|
|
19554
|
-
transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
19555
|
-
}
|
|
19556
|
-
.sched-prompt-close:hover {
|
|
19557
|
-
background: rgba(255,255,255,0.055);
|
|
19558
|
-
border-color: rgba(255,255,255,0.075);
|
|
19559
|
-
color: var(--t-bright);
|
|
19560
|
-
}
|
|
19561
|
-
.sched-prompt-close svg { width: 14px; height: 14px; }
|
|
19562
|
-
.sched-prompt-tiles {
|
|
19563
|
-
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
|
|
19564
|
-
padding: 14px 16px 16px;
|
|
19565
|
-
}
|
|
19566
|
-
@media (max-width: 560px) { .sched-prompt-tiles { grid-template-columns: 1fr; } }
|
|
19567
|
-
.sched-prompt-tile {
|
|
19568
|
-
position: relative; display: grid; grid-template-columns: 32px minmax(0, 1fr);
|
|
19569
|
-
grid-template-areas: "logo pill" "body body"; align-content: start; gap: 10px 8px;
|
|
19570
|
-
min-height: 96px; padding: 12px; border-radius: 10px; cursor: pointer; font-family: inherit;
|
|
19571
|
-
background: rgba(255,255,255,0.032);
|
|
19572
|
-
border: 1px solid rgba(255,255,255,0.065);
|
|
19573
|
-
color: var(--t-secondary); text-align: left;
|
|
19574
|
-
backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
|
|
19575
|
-
transition: background 180ms ease, border-color 180ms ease, transform 160ms ease, box-shadow 180ms ease;
|
|
19576
|
-
overflow: hidden;
|
|
19577
|
-
}
|
|
19578
|
-
.sched-prompt-tile:not(.disabled) {
|
|
19579
|
-
background: rgba(255,255,255,0.045);
|
|
19580
|
-
border-color: rgba(255,255,255,0.095);
|
|
19581
|
-
box-shadow: var(--shadow-highlight);
|
|
19582
|
-
}
|
|
19583
|
-
.sched-prompt-tile:not(.disabled):hover {
|
|
19584
|
-
background: rgba(255,255,255,0.065);
|
|
19585
|
-
border-color: rgba(255,255,255,0.13);
|
|
19586
|
-
color: var(--t-bright); transform: translateY(-1px);
|
|
19587
|
-
box-shadow: var(--shadow-xs), var(--shadow-highlight);
|
|
19588
|
-
}
|
|
19589
|
-
.sched-prompt-tile:not(.disabled):active { transform: translateY(0); }
|
|
19590
|
-
.sched-prompt-tile:focus-visible {
|
|
19591
|
-
outline: none;
|
|
19592
|
-
border-color: rgba(255,255,255,0.18);
|
|
19593
|
-
box-shadow: 0 0 0 2px rgba(255,255,255,0.14), var(--shadow-highlight);
|
|
19594
|
-
}
|
|
19595
|
-
.sched-prompt-tile.disabled { opacity: 0.58; cursor: not-allowed; pointer-events: none; }
|
|
19596
|
-
.sched-prompt-tile-logo {
|
|
19597
|
-
grid-area: logo;
|
|
19598
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
19599
|
-
width: 32px; height: 32px; border-radius: 8px;
|
|
19600
|
-
background: rgba(255,255,255,0.045);
|
|
19601
|
-
border: 1px solid rgba(255,255,255,0.075);
|
|
19602
|
-
color: color-mix(in srgb, var(--provider-color, #fff) 62%, var(--t-secondary));
|
|
19603
|
-
box-shadow: var(--shadow-highlight);
|
|
19604
|
-
}
|
|
19605
|
-
.sched-prompt-tile-logo svg { width: 16px; height: 16px; display: block; }
|
|
19606
|
-
.sched-prompt-tile-body {
|
|
19607
|
-
grid-area: body; display: flex; flex-direction: column; gap: 2px; min-width: 0;
|
|
19608
|
-
}
|
|
19609
|
-
.sched-prompt-tile-title {
|
|
19610
|
-
font-size: 12.5px; font-weight: 650; color: var(--t-secondary); letter-spacing: 0;
|
|
19611
|
-
line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19612
|
-
}
|
|
19613
|
-
.sched-prompt-tile:not(.disabled) .sched-prompt-tile-title {
|
|
19614
|
-
color: color-mix(in srgb, var(--provider-color, #fff) 56%, var(--t-bright));
|
|
19615
|
-
}
|
|
19616
|
-
.sched-prompt-tile-sub {
|
|
19617
|
-
font-size: 10.5px; color: var(--t-muted); letter-spacing: 0; line-height: 1.2;
|
|
19618
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19619
|
-
}
|
|
19620
|
-
.sched-prompt-pill {
|
|
19621
|
-
grid-area: pill; justify-self: end; align-self: start;
|
|
19622
|
-
font-size: 10px; font-weight: 600; letter-spacing: 0;
|
|
19623
|
-
padding: 2px 6px; border-radius: var(--r-pill);
|
|
19624
|
-
background: rgba(255,255,255,0.045); color: var(--t-muted);
|
|
19625
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
19626
|
-
}
|
|
19627
|
-
.sched-prompt-pill--active {
|
|
19628
|
-
background: rgba(255,255,255,0.075);
|
|
19629
|
-
border-color: rgba(255,255,255,0.12);
|
|
19630
|
-
color: var(--t-secondary);
|
|
19631
|
-
}
|
|
19488
|
+
.sched-sidebar-prompt {
|
|
19489
|
+
margin-top: auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
19490
|
+
border: 1px solid rgba(255,255,255,0.09); border-radius: 8px;
|
|
19491
|
+
background: rgba(255,255,255,0.04);
|
|
19492
|
+
color: var(--t-secondary); min-height: 34px; cursor: pointer; font-family: inherit;
|
|
19493
|
+
font-size: 12px; font-weight: 600; letter-spacing: 0;
|
|
19494
|
+
box-shadow: var(--shadow-highlight);
|
|
19495
|
+
transition: border-color 160ms ease, color 160ms ease, background 160ms ease, transform 120ms ease;
|
|
19496
|
+
}
|
|
19497
|
+
.sched-sidebar-prompt:hover {
|
|
19498
|
+
background: rgba(255,255,255,0.075);
|
|
19499
|
+
border-color: rgba(255,255,255,0.14);
|
|
19500
|
+
color: var(--t-bright);
|
|
19501
|
+
transform: translateY(-1px);
|
|
19502
|
+
}
|
|
19503
|
+
.sched-sidebar-prompt svg { width: 13px; height: 13px; color: var(--t-muted); }
|
|
19504
|
+
|
|
19505
|
+
.sched-prompt-backdrop {
|
|
19506
|
+
position: fixed; inset: 0; background: rgba(5,7,12,0.58);
|
|
19507
|
+
backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
|
|
19508
|
+
z-index: 300010; opacity: 0; transition: opacity 160ms ease-out;
|
|
19509
|
+
pointer-events: auto;
|
|
19510
|
+
}
|
|
19511
|
+
.sched-prompt-backdrop.open { opacity: 1; }
|
|
19512
|
+
.sched-prompt-modal {
|
|
19513
|
+
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -52%) scale(0.98);
|
|
19514
|
+
width: min(512px, calc(100vw - 32px)); padding: 0;
|
|
19515
|
+
border-radius: 12px; z-index: 300011;
|
|
19516
|
+
opacity: 0; transition: opacity 160ms ease-out, transform 200ms ease-out;
|
|
19517
|
+
background:
|
|
19518
|
+
linear-gradient(to bottom, rgba(255,255,255,0.035), rgba(255,255,255,0.01)),
|
|
19519
|
+
rgba(13,15,20,0.96);
|
|
19520
|
+
border: 1px solid rgba(255,255,255,0.085);
|
|
19521
|
+
box-shadow: 0 22px 64px rgba(0,0,0,0.58), var(--shadow-highlight);
|
|
19522
|
+
overflow: hidden;
|
|
19523
|
+
}
|
|
19524
|
+
.sched-prompt-modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
19525
|
+
.sched-prompt-head {
|
|
19526
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
19527
|
+
padding: 15px 16px 13px;
|
|
19528
|
+
border-bottom: 1px solid rgba(255,255,255,0.055);
|
|
19529
|
+
}
|
|
19530
|
+
.sched-prompt-head-text {
|
|
19531
|
+
display: flex; align-items: center; gap: 10px; min-width: 0;
|
|
19532
|
+
}
|
|
19533
|
+
.sched-prompt-icon {
|
|
19534
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19535
|
+
width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
|
|
19536
|
+
background: rgba(255,255,255,0.055);
|
|
19537
|
+
border: 1px solid rgba(255,255,255,0.075);
|
|
19538
|
+
color: var(--t-secondary);
|
|
19539
|
+
box-shadow: var(--shadow-highlight);
|
|
19540
|
+
}
|
|
19541
|
+
.sched-prompt-icon svg { width: 14px; height: 14px; }
|
|
19542
|
+
.sched-prompt-head h3 {
|
|
19543
|
+
margin: 0; font-family: var(--ff-heading); font-size: 14px; font-weight: 650;
|
|
19544
|
+
color: var(--t-bright); letter-spacing: 0;
|
|
19545
|
+
}
|
|
19546
|
+
.sched-prompt-head p {
|
|
19547
|
+
margin: 2px 0 0; font-size: 11.5px; line-height: 1.35; color: var(--t-muted);
|
|
19548
|
+
}
|
|
19549
|
+
.sched-prompt-close {
|
|
19550
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19551
|
+
width: 28px; height: 28px; flex: 0 0 auto;
|
|
19552
|
+
background: transparent; border: 1px solid transparent; border-radius: 7px;
|
|
19553
|
+
color: var(--t-muted); cursor: pointer; padding: 0;
|
|
19554
|
+
transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
19555
|
+
}
|
|
19556
|
+
.sched-prompt-close:hover {
|
|
19557
|
+
background: rgba(255,255,255,0.055);
|
|
19558
|
+
border-color: rgba(255,255,255,0.075);
|
|
19559
|
+
color: var(--t-bright);
|
|
19560
|
+
}
|
|
19561
|
+
.sched-prompt-close svg { width: 14px; height: 14px; }
|
|
19562
|
+
.sched-prompt-tiles {
|
|
19563
|
+
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
|
|
19564
|
+
padding: 14px 16px 16px;
|
|
19565
|
+
}
|
|
19566
|
+
@media (max-width: 560px) { .sched-prompt-tiles { grid-template-columns: 1fr; } }
|
|
19567
|
+
.sched-prompt-tile {
|
|
19568
|
+
position: relative; display: grid; grid-template-columns: 32px minmax(0, 1fr);
|
|
19569
|
+
grid-template-areas: "logo pill" "body body"; align-content: start; gap: 10px 8px;
|
|
19570
|
+
min-height: 96px; padding: 12px; border-radius: 10px; cursor: pointer; font-family: inherit;
|
|
19571
|
+
background: rgba(255,255,255,0.032);
|
|
19572
|
+
border: 1px solid rgba(255,255,255,0.065);
|
|
19573
|
+
color: var(--t-secondary); text-align: left;
|
|
19574
|
+
backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
|
|
19575
|
+
transition: background 180ms ease, border-color 180ms ease, transform 160ms ease, box-shadow 180ms ease;
|
|
19576
|
+
overflow: hidden;
|
|
19577
|
+
}
|
|
19578
|
+
.sched-prompt-tile:not(.disabled) {
|
|
19579
|
+
background: rgba(255,255,255,0.045);
|
|
19580
|
+
border-color: rgba(255,255,255,0.095);
|
|
19581
|
+
box-shadow: var(--shadow-highlight);
|
|
19582
|
+
}
|
|
19583
|
+
.sched-prompt-tile:not(.disabled):hover {
|
|
19584
|
+
background: rgba(255,255,255,0.065);
|
|
19585
|
+
border-color: rgba(255,255,255,0.13);
|
|
19586
|
+
color: var(--t-bright); transform: translateY(-1px);
|
|
19587
|
+
box-shadow: var(--shadow-xs), var(--shadow-highlight);
|
|
19588
|
+
}
|
|
19589
|
+
.sched-prompt-tile:not(.disabled):active { transform: translateY(0); }
|
|
19590
|
+
.sched-prompt-tile:focus-visible {
|
|
19591
|
+
outline: none;
|
|
19592
|
+
border-color: rgba(255,255,255,0.18);
|
|
19593
|
+
box-shadow: 0 0 0 2px rgba(255,255,255,0.14), var(--shadow-highlight);
|
|
19594
|
+
}
|
|
19595
|
+
.sched-prompt-tile.disabled { opacity: 0.58; cursor: not-allowed; pointer-events: none; }
|
|
19596
|
+
.sched-prompt-tile-logo {
|
|
19597
|
+
grid-area: logo;
|
|
19598
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19599
|
+
width: 32px; height: 32px; border-radius: 8px;
|
|
19600
|
+
background: rgba(255,255,255,0.045);
|
|
19601
|
+
border: 1px solid rgba(255,255,255,0.075);
|
|
19602
|
+
color: color-mix(in srgb, var(--provider-color, #fff) 62%, var(--t-secondary));
|
|
19603
|
+
box-shadow: var(--shadow-highlight);
|
|
19604
|
+
}
|
|
19605
|
+
.sched-prompt-tile-logo svg { width: 16px; height: 16px; display: block; }
|
|
19606
|
+
.sched-prompt-tile-body {
|
|
19607
|
+
grid-area: body; display: flex; flex-direction: column; gap: 2px; min-width: 0;
|
|
19608
|
+
}
|
|
19609
|
+
.sched-prompt-tile-title {
|
|
19610
|
+
font-size: 12.5px; font-weight: 650; color: var(--t-secondary); letter-spacing: 0;
|
|
19611
|
+
line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19612
|
+
}
|
|
19613
|
+
.sched-prompt-tile:not(.disabled) .sched-prompt-tile-title {
|
|
19614
|
+
color: color-mix(in srgb, var(--provider-color, #fff) 56%, var(--t-bright));
|
|
19615
|
+
}
|
|
19616
|
+
.sched-prompt-tile-sub {
|
|
19617
|
+
font-size: 10.5px; color: var(--t-muted); letter-spacing: 0; line-height: 1.2;
|
|
19618
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19619
|
+
}
|
|
19620
|
+
.sched-prompt-pill {
|
|
19621
|
+
grid-area: pill; justify-self: end; align-self: start;
|
|
19622
|
+
font-size: 10px; font-weight: 600; letter-spacing: 0;
|
|
19623
|
+
padding: 2px 6px; border-radius: var(--r-pill);
|
|
19624
|
+
background: rgba(255,255,255,0.045); color: var(--t-muted);
|
|
19625
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
19626
|
+
}
|
|
19627
|
+
.sched-prompt-pill--active {
|
|
19628
|
+
background: rgba(255,255,255,0.075);
|
|
19629
|
+
border-color: rgba(255,255,255,0.12);
|
|
19630
|
+
color: var(--t-secondary);
|
|
19631
|
+
}
|
|
19632
19632
|
|
|
19633
19633
|
.sched-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
|
|
19634
19634
|
.sched-tabs {
|
|
@@ -7368,8 +7368,7 @@ function _ensureCliBannerEl() {
|
|
|
7368
7368
|
|
|
7369
7369
|
function _removeCliBannerEl() {
|
|
7370
7370
|
if (!_panel) return;
|
|
7371
|
-
const
|
|
7372
|
-
const banner = container?.parentNode?.querySelector(':scope > .cp-cli-banner');
|
|
7371
|
+
const banner = _panel.querySelector(':scope > .cp-cli-banner');
|
|
7373
7372
|
banner?.remove();
|
|
7374
7373
|
}
|
|
7375
7374
|
|
|
@@ -128,21 +128,12 @@ export function initMenubar() {
|
|
|
128
128
|
'menu-open-skills-studio': 'skills',
|
|
129
129
|
'menu-skills-new': 'skills',
|
|
130
130
|
'menu-skills-import': 'skills',
|
|
131
|
-
<<<<<<< HEAD
|
|
132
|
-
'menu-open-automation-studio': 'automations',
|
|
133
|
-
'menu-automations-schedule-new': 'automations',
|
|
134
|
-
'menu-automations-quick-timer': 'automations',
|
|
135
|
-
'menu-automations-schedule-groups': 'automations',
|
|
136
|
-
'menu-automations-new': 'automations',
|
|
137
|
-
'menu-automations-import': 'automations',
|
|
138
|
-
=======
|
|
139
131
|
'menu-open-automation-studio': 'automations',
|
|
140
132
|
'menu-automations-schedule-new': 'automations',
|
|
141
133
|
'menu-automations-quick-timer': 'automations',
|
|
142
134
|
'menu-automations-schedule-groups': 'automations',
|
|
143
135
|
'menu-automations-new': 'automations',
|
|
144
136
|
'menu-automations-import': 'automations',
|
|
145
|
-
>>>>>>> Clean
|
|
146
137
|
'menu-terminal-claude': 'terminal',
|
|
147
138
|
'menu-terminal-codex': 'terminal',
|
|
148
139
|
'menu-terminal-gemini': 'terminal',
|
|
@@ -326,36 +317,6 @@ function wireAutomationsMenu() {
|
|
|
326
317
|
emit('automations:open');
|
|
327
318
|
});
|
|
328
319
|
}
|
|
329
|
-
<<<<<<< HEAD
|
|
330
|
-
const schedulesItem = $('menu-automations-schedules');
|
|
331
|
-
if (schedulesItem) {
|
|
332
|
-
schedulesItem.addEventListener('click', () => {
|
|
333
|
-
closeAll();
|
|
334
|
-
emit('automations:open-schedules');
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
const newScheduleItem = $('menu-automations-schedule-new');
|
|
338
|
-
if (newScheduleItem) {
|
|
339
|
-
newScheduleItem.addEventListener('click', () => {
|
|
340
|
-
closeAll();
|
|
341
|
-
emit('automations:open-schedules', { action: 'new-schedule' });
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
const quickTimerItem = $('menu-automations-quick-timer');
|
|
345
|
-
if (quickTimerItem) {
|
|
346
|
-
quickTimerItem.addEventListener('click', () => {
|
|
347
|
-
closeAll();
|
|
348
|
-
emit('automations:open-schedules', { action: 'quick-timer' });
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
const scheduleGroupsItem = $('menu-automations-schedule-groups');
|
|
352
|
-
if (scheduleGroupsItem) {
|
|
353
|
-
scheduleGroupsItem.addEventListener('click', () => {
|
|
354
|
-
closeAll();
|
|
355
|
-
emit('automations:open-schedules', { tab: 'groups' });
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
=======
|
|
359
320
|
const schedulesItem = $('menu-automations-schedules');
|
|
360
321
|
if (schedulesItem) {
|
|
361
322
|
schedulesItem.addEventListener('click', () => {
|
|
@@ -384,7 +345,6 @@ function wireAutomationsMenu() {
|
|
|
384
345
|
emit('automations:open-schedules', { tab: 'groups' });
|
|
385
346
|
});
|
|
386
347
|
}
|
|
387
|
-
>>>>>>> Clean
|
|
388
348
|
const newItem = $('menu-automations-new');
|
|
389
349
|
if (newItem) {
|
|
390
350
|
newItem.addEventListener('click', () => {
|
|
@@ -11,8 +11,6 @@ const SYNABUN_GITHUB_URL = 'https://github.com/danilokhury/Synabun';
|
|
|
11
11
|
|
|
12
12
|
let _versionData = null;
|
|
13
13
|
let _btnWired = false;
|
|
14
|
-
<<<<<<< HEAD
|
|
15
|
-
=======
|
|
16
14
|
let _pollWired = false;
|
|
17
15
|
|
|
18
16
|
function sourceLabel(data) {
|
|
@@ -22,7 +20,6 @@ function sourceLabel(data) {
|
|
|
22
20
|
if (src === 'npm') return 'npm';
|
|
23
21
|
return src || 'update';
|
|
24
22
|
}
|
|
25
|
-
>>>>>>> Clean
|
|
26
23
|
|
|
27
24
|
function applyTopRightButton(data) {
|
|
28
25
|
const btn = $('topright-update-btn');
|
|
@@ -32,11 +29,7 @@ function applyTopRightButton(data) {
|
|
|
32
29
|
btn.style.display = has ? '' : 'none';
|
|
33
30
|
if (badge) badge.textContent = has ? '!' : '';
|
|
34
31
|
if (has) {
|
|
35
|
-
<<<<<<< HEAD
|
|
36
|
-
const srcLabel = data.source === 'both' ? 'npm + GitHub' : data.source === 'github' ? 'GitHub' : 'npm';
|
|
37
|
-
=======
|
|
38
32
|
const srcLabel = sourceLabel(data);
|
|
39
|
-
>>>>>>> Clean
|
|
40
33
|
btn.dataset.tooltip = `Update: v${data.current} → v${data.latest} (${srcLabel})`;
|
|
41
34
|
}
|
|
42
35
|
if (!_btnWired) {
|
|
@@ -47,8 +40,6 @@ function applyTopRightButton(data) {
|
|
|
47
40
|
});
|
|
48
41
|
}
|
|
49
42
|
}
|
|
50
|
-
<<<<<<< HEAD
|
|
51
|
-
=======
|
|
52
43
|
|
|
53
44
|
function maybeShowUpdateAlert(data, { once = true } = {}) {
|
|
54
45
|
if (!data?.updateAvailable) return;
|
|
@@ -66,7 +57,6 @@ function maybeShowUpdateAlert(data, { once = true } = {}) {
|
|
|
66
57
|
});
|
|
67
58
|
}, 500);
|
|
68
59
|
}
|
|
69
|
-
>>>>>>> Clean
|
|
70
60
|
|
|
71
61
|
export async function initUpdate() {
|
|
72
62
|
try {
|
|
@@ -74,8 +64,6 @@ export async function initUpdate() {
|
|
|
74
64
|
if (!res.ok) return;
|
|
75
65
|
_versionData = await res.json();
|
|
76
66
|
applyTopRightButton(_versionData);
|
|
77
|
-
<<<<<<< HEAD
|
|
78
|
-
=======
|
|
79
67
|
maybeShowUpdateAlert(_versionData);
|
|
80
68
|
|
|
81
69
|
if (!_pollWired) {
|
|
@@ -84,27 +72,19 @@ export async function initUpdate() {
|
|
|
84
72
|
forceCheckSynabunUpdate({ alert: true }).catch(() => {});
|
|
85
73
|
}, 30 * 60 * 1000);
|
|
86
74
|
}
|
|
87
|
-
>>>>>>> Clean
|
|
88
75
|
} catch { /* silent — no update UI if check fails */ }
|
|
89
76
|
}
|
|
90
77
|
|
|
91
78
|
// Force a fresh server-side check (bypasses 1h cache via ?force=1) and
|
|
92
79
|
// re-applies the top-right button + badge state. Returns the latest
|
|
93
80
|
// _synabunUpdateCache shape so callers can compose toasts.
|
|
94
|
-
<<<<<<< HEAD
|
|
95
|
-
export async function forceCheckSynabunUpdate() {
|
|
96
|
-
=======
|
|
97
81
|
export async function forceCheckSynabunUpdate({ alert = false } = {}) {
|
|
98
|
-
>>>>>>> Clean
|
|
99
82
|
try {
|
|
100
83
|
const res = await fetch('/api/system/version?force=1');
|
|
101
84
|
if (!res.ok) return null;
|
|
102
85
|
_versionData = await res.json();
|
|
103
86
|
applyTopRightButton(_versionData);
|
|
104
|
-
<<<<<<< HEAD
|
|
105
|
-
=======
|
|
106
87
|
if (alert) maybeShowUpdateAlert(_versionData);
|
|
107
|
-
>>>>>>> Clean
|
|
108
88
|
return _versionData;
|
|
109
89
|
} catch {
|
|
110
90
|
return null;
|
|
@@ -207,15 +187,6 @@ function openUpdateModal() {
|
|
|
207
187
|
}
|
|
208
188
|
|
|
209
189
|
function renderStep3() {
|
|
210
|
-
<<<<<<< HEAD
|
|
211
|
-
// Channel-aware install command. Beta users staying on beta keep the
|
|
212
|
-
// @beta tag; both targets exist on stable so we surface a single line.
|
|
213
|
-
const channel = _versionData.installedChannel || 'stable';
|
|
214
|
-
const target = _versionData.latest || '';
|
|
215
|
-
const targetLooksBeta = parseSemverPre(target);
|
|
216
|
-
const installTag = (channel === 'prerelease' && targetLooksBeta) ? 'beta' : 'latest';
|
|
217
|
-
const installCmd = `npm install -g synabun@${installTag}`;
|
|
218
|
-
=======
|
|
219
190
|
const installPlan = _versionData.installPlan || {};
|
|
220
191
|
const canAutoUpdate = installPlan.canAutoUpdate === true;
|
|
221
192
|
const repoUrl = installPlan.openUrl || SYNABUN_GITHUB_URL;
|
|
@@ -244,7 +215,6 @@ function openUpdateModal() {
|
|
|
244
215
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
|
|
245
216
|
Open GitHub
|
|
246
217
|
</button>`;
|
|
247
|
-
>>>>>>> Clean
|
|
248
218
|
|
|
249
219
|
return `
|
|
250
220
|
<h3 class="update-modal-title">${title}</h3>
|
|
@@ -474,12 +444,3 @@ function esc(str) {
|
|
|
474
444
|
d.textContent = str || '';
|
|
475
445
|
return d.innerHTML;
|
|
476
446
|
}
|
|
477
|
-
|
|
478
|
-
// Returns the prerelease tag from a semver-ish string ("2026.4.26-beta.3"
|
|
479
|
-
// -> "beta.3"), or null. Used to pick install command target.
|
|
480
|
-
function parseSemverPre(raw) {
|
|
481
|
-
if (raw == null) return null;
|
|
482
|
-
const s = String(raw).trim().replace(/^v\.?/, '');
|
|
483
|
-
const m = s.match(/^\d+\.\d+\.\d+(?:-([0-9A-Za-z.-]+))?/);
|
|
484
|
-
return m && m[1] ? m[1] : null;
|
|
485
|
-
}
|
|
@@ -10495,123 +10495,20 @@ let _synabunUpdateCache = {
|
|
|
10495
10495
|
installedChannel: 'stable', // 'stable' | 'prerelease'
|
|
10496
10496
|
npmLatestStable: null, // dist-tags.latest
|
|
10497
10497
|
npmLatestBeta: null, // dist-tags.beta (or null if absent)
|
|
10498
|
-
<<<<<<< HEAD
|
|
10499
|
-
npmLatest: null, // chosen target for installed channel
|
|
10500
|
-
gitLatest: null, // tag_name from GitHub release matching channel
|
|
10501
|
-
=======
|
|
10502
10498
|
npmLatestTag: null, // dist-tag selected for install
|
|
10503
10499
|
npmLatest: null, // chosen target for installed channel
|
|
10504
10500
|
gitLatest: null, // tag_name from GitHub release matching channel
|
|
10505
10501
|
gitLatestRaw: null, // original GitHub tag, preserving leading v
|
|
10506
|
-
>>>>>>> Clean
|
|
10507
10502
|
latest: null, // newer of (npmLatest, gitLatest) — legacy field
|
|
10508
10503
|
npmUpdateAvailable: false,
|
|
10509
10504
|
gitUpdateAvailable: false,
|
|
10510
10505
|
updateAvailable: false, // npmUpdateAvailable || gitUpdateAvailable
|
|
10511
10506
|
source: null, // 'npm' | 'github' | 'both' | null
|
|
10512
|
-
<<<<<<< HEAD
|
|
10513
|
-
=======
|
|
10514
10507
|
installPlan: null, // server-owned npm/GitHub action target for click-to-update
|
|
10515
|
-
>>>>>>> Clean
|
|
10516
10508
|
checkedAt: null,
|
|
10517
10509
|
npmError: null,
|
|
10518
10510
|
gitError: null,
|
|
10519
10511
|
};
|
|
10520
|
-
<<<<<<< HEAD
|
|
10521
|
-
|
|
10522
|
-
async function checkSynabunUpdate() {
|
|
10523
|
-
let current = _synabunUpdateCache.current;
|
|
10524
|
-
try {
|
|
10525
|
-
const pkg = JSON.parse(readFileSync(resolve(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
10526
|
-
current = pkg.version;
|
|
10527
|
-
} catch { /* keep prior current if package.json read fails */ }
|
|
10528
|
-
|
|
10529
|
-
const curParsed = parseSemver(current);
|
|
10530
|
-
const installedChannel = curParsed?.pre ? 'prerelease' : 'stable';
|
|
10531
|
-
|
|
10532
|
-
const fetchJsonWithTimeout = async (url, ms = 5000) => {
|
|
10533
|
-
const controller = new AbortController();
|
|
10534
|
-
const timeout = setTimeout(() => controller.abort(), ms);
|
|
10535
|
-
try {
|
|
10536
|
-
const res = await fetch(url, {
|
|
10537
|
-
signal: controller.signal,
|
|
10538
|
-
headers: { 'Accept': 'application/json', 'User-Agent': 'synabun-update-check' },
|
|
10539
|
-
});
|
|
10540
|
-
if (!res.ok) throw new Error(`${url} returned ${res.status}`);
|
|
10541
|
-
return await res.json();
|
|
10542
|
-
} finally {
|
|
10543
|
-
clearTimeout(timeout);
|
|
10544
|
-
}
|
|
10545
|
-
};
|
|
10546
|
-
|
|
10547
|
-
// npm: fetch the FULL registry document so we can read every dist-tag
|
|
10548
|
-
// (latest + beta + any future channel). The /latest shorthand only
|
|
10549
|
-
// surfaces dist-tags.latest, which made beta installs invisible.
|
|
10550
|
-
// github: when installed is a prerelease, /releases/latest is wrong
|
|
10551
|
-
// because it skips prereleases — fetch the full list and pick newest.
|
|
10552
|
-
const npmUrl = 'https://registry.npmjs.org/synabun';
|
|
10553
|
-
const githubUrl = installedChannel === 'prerelease'
|
|
10554
|
-
? 'https://api.github.com/repos/danilokhury/Synabun/releases?per_page=20'
|
|
10555
|
-
: 'https://api.github.com/repos/danilokhury/Synabun/releases/latest';
|
|
10556
|
-
|
|
10557
|
-
const [npmRes, gitRes] = await Promise.allSettled([
|
|
10558
|
-
fetchJsonWithTimeout(npmUrl),
|
|
10559
|
-
fetchJsonWithTimeout(githubUrl),
|
|
10560
|
-
]);
|
|
10561
|
-
|
|
10562
|
-
let npmLatestStable = null, npmLatestBeta = null;
|
|
10563
|
-
let gitLatest = null;
|
|
10564
|
-
let npmError = null, gitError = null;
|
|
10565
|
-
|
|
10566
|
-
if (npmRes.status === 'fulfilled') {
|
|
10567
|
-
const tags = npmRes.value?.['dist-tags'] || {};
|
|
10568
|
-
npmLatestStable = tags.latest || null;
|
|
10569
|
-
npmLatestBeta = tags.beta || tags.next || tags.rc || null;
|
|
10570
|
-
} else {
|
|
10571
|
-
npmError = npmRes.reason?.message || 'npm fetch failed';
|
|
10572
|
-
}
|
|
10573
|
-
|
|
10574
|
-
if (gitRes.status === 'fulfilled') {
|
|
10575
|
-
if (Array.isArray(gitRes.value)) {
|
|
10576
|
-
// /releases — pick newest non-draft entry whose tag parses as semver.
|
|
10577
|
-
// For stable channel users we'd skip prereleases, but this branch only
|
|
10578
|
-
// runs for prerelease users (per githubUrl above), so include both.
|
|
10579
|
-
let best = null;
|
|
10580
|
-
for (const r of gitRes.value) {
|
|
10581
|
-
if (r?.draft) continue;
|
|
10582
|
-
const tag = r?.tag_name || r?.name || '';
|
|
10583
|
-
const parsed = parseSemver(tag);
|
|
10584
|
-
if (!parsed) continue;
|
|
10585
|
-
if (!best || compareSemver(parsed, best.parsed) > 0) {
|
|
10586
|
-
best = { tag, parsed };
|
|
10587
|
-
}
|
|
10588
|
-
}
|
|
10589
|
-
gitLatest = best?.tag.replace(/^v\.?/, '') || null;
|
|
10590
|
-
} else {
|
|
10591
|
-
// /releases/latest — single object
|
|
10592
|
-
const tag = gitRes.value?.tag_name || gitRes.value?.name || '';
|
|
10593
|
-
gitLatest = tag ? tag.replace(/^v\.?/, '') : null;
|
|
10594
|
-
}
|
|
10595
|
-
} else {
|
|
10596
|
-
gitError = gitRes.reason?.message || 'github fetch failed';
|
|
10597
|
-
}
|
|
10598
|
-
|
|
10599
|
-
// Channel-aware npm target:
|
|
10600
|
-
// - prerelease user: pick whichever of (stable, beta) is newest. Either
|
|
10601
|
-
// is genuinely "newer than current"; surfacing stable when on beta is
|
|
10602
|
-
// intentional (some users manually flip back to stable).
|
|
10603
|
-
// - stable user: only stable. Beta on a stable install would look like
|
|
10604
|
-
// a downgrade ("2026.4.26 → 2026.4.26-beta.3" via semver = older).
|
|
10605
|
-
let npmLatest = null;
|
|
10606
|
-
if (installedChannel === 'prerelease') {
|
|
10607
|
-
if (npmLatestStable && npmLatestBeta) {
|
|
10608
|
-
npmLatest = compareSemver(npmLatestBeta, npmLatestStable) > 0 ? npmLatestBeta : npmLatestStable;
|
|
10609
|
-
} else {
|
|
10610
|
-
npmLatest = npmLatestStable || npmLatestBeta;
|
|
10611
|
-
}
|
|
10612
|
-
} else {
|
|
10613
|
-
npmLatest = npmLatestStable;
|
|
10614
|
-
=======
|
|
10615
10512
|
|
|
10616
10513
|
const SYNABUN_GITHUB_REPO_URL = 'https://github.com/danilokhury/Synabun';
|
|
10617
10514
|
|
|
@@ -10810,7 +10707,6 @@ async function checkSynabunUpdate() {
|
|
|
10810
10707
|
} else {
|
|
10811
10708
|
npmLatest = npmLatestStable;
|
|
10812
10709
|
npmLatestTag = npmLatest ? 'latest' : null;
|
|
10813
|
-
>>>>>>> Clean
|
|
10814
10710
|
}
|
|
10815
10711
|
|
|
10816
10712
|
const npmUpdateAvailable = !!(npmLatest && semverNewer(current, npmLatest));
|
|
@@ -10830,8 +10726,6 @@ async function checkSynabunUpdate() {
|
|
|
10830
10726
|
else if (npmUpdateAvailable) source = 'npm';
|
|
10831
10727
|
else if (gitUpdateAvailable) source = 'github';
|
|
10832
10728
|
|
|
10833
|
-
<<<<<<< HEAD
|
|
10834
|
-
=======
|
|
10835
10729
|
const installPlan = buildSynabunInstallPlan({
|
|
10836
10730
|
current,
|
|
10837
10731
|
installedChannel,
|
|
@@ -10839,30 +10733,21 @@ async function checkSynabunUpdate() {
|
|
|
10839
10733
|
updateAvailable,
|
|
10840
10734
|
});
|
|
10841
10735
|
|
|
10842
|
-
>>>>>>> Clean
|
|
10843
10736
|
_synabunUpdateCache = {
|
|
10844
10737
|
current,
|
|
10845
10738
|
installedChannel,
|
|
10846
10739
|
npmLatestStable,
|
|
10847
10740
|
npmLatestBeta,
|
|
10848
|
-
<<<<<<< HEAD
|
|
10849
|
-
npmLatest,
|
|
10850
|
-
gitLatest,
|
|
10851
|
-
=======
|
|
10852
10741
|
npmLatestTag,
|
|
10853
10742
|
npmLatest,
|
|
10854
10743
|
gitLatest,
|
|
10855
10744
|
gitLatestRaw,
|
|
10856
|
-
>>>>>>> Clean
|
|
10857
10745
|
latest,
|
|
10858
10746
|
npmUpdateAvailable,
|
|
10859
10747
|
gitUpdateAvailable,
|
|
10860
10748
|
updateAvailable,
|
|
10861
10749
|
source,
|
|
10862
|
-
<<<<<<< HEAD
|
|
10863
|
-
=======
|
|
10864
10750
|
installPlan,
|
|
10865
|
-
>>>>>>> Clean
|
|
10866
10751
|
checkedAt: new Date().toISOString(),
|
|
10867
10752
|
npmError,
|
|
10868
10753
|
gitError,
|
|
@@ -10917,12 +10802,6 @@ function parseVersion(raw) {
|
|
|
10917
10802
|
}
|
|
10918
10803
|
|
|
10919
10804
|
// Structured semver parse — handles calver bumps and prerelease tags:
|
|
10920
|
-
<<<<<<< HEAD
|
|
10921
|
-
// "2026.4.26" -> { major:2026, minor:4, patch:26, pre:null }
|
|
10922
|
-
// "2026.4.20-2" -> { major:2026, minor:4, patch:20, pre:"2" }
|
|
10923
|
-
// "2026.4.26-beta.3" -> { major:2026, minor:4, patch:26, pre:"beta.3" }
|
|
10924
|
-
// "v1.2.3-rc.1" -> { major:1, minor:2, patch:3, pre:"rc.1" }
|
|
10925
|
-
=======
|
|
10926
10805
|
// "2026.4.26" -> { major:2026, minor:4, patch:26, pre:null, iter:null }
|
|
10927
10806
|
// "2026.4.20-2" -> { major:2026, minor:4, patch:20, pre:"2", iter:2 }
|
|
10928
10807
|
// "2026.4.26-beta.3" -> { major:2026, minor:4, patch:26, pre:"beta.3", iter:null }
|
|
@@ -10933,29 +10812,17 @@ function parseVersion(raw) {
|
|
|
10933
10812
|
// Strict semver §9 treats any prerelease suffix as PRE-release (lower than no
|
|
10934
10813
|
// suffix). SynaBun publishes calver iterations as `latest` on npm, so we need
|
|
10935
10814
|
// to treat numeric-only suffixes as POST-release iterations instead.
|
|
10936
|
-
>>>>>>> Clean
|
|
10937
10815
|
function parseSemver(raw) {
|
|
10938
10816
|
if (raw == null) return null;
|
|
10939
10817
|
const s = String(raw).trim().replace(/^v\.?/, '');
|
|
10940
10818
|
const m = s.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
|
|
10941
10819
|
if (!m) return null;
|
|
10942
|
-
<<<<<<< HEAD
|
|
10943
|
-
=======
|
|
10944
10820
|
const pre = m[4] || null;
|
|
10945
10821
|
const iter = pre && /^\d+$/.test(pre) ? Number(pre) : null;
|
|
10946
|
-
>>>>>>> Clean
|
|
10947
10822
|
return {
|
|
10948
10823
|
major: +m[1],
|
|
10949
10824
|
minor: +m[2],
|
|
10950
10825
|
patch: +m[3],
|
|
10951
|
-
<<<<<<< HEAD
|
|
10952
|
-
pre: m[4] || null,
|
|
10953
|
-
};
|
|
10954
|
-
}
|
|
10955
|
-
|
|
10956
|
-
// Semver-spec precedence including prerelease handling:
|
|
10957
|
-
// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0
|
|
10958
|
-
=======
|
|
10959
10826
|
pre,
|
|
10960
10827
|
iter,
|
|
10961
10828
|
};
|
|
@@ -10965,7 +10832,6 @@ function parseSemver(raw) {
|
|
|
10965
10832
|
// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0 < 1.0.0-1 < 1.0.0-2
|
|
10966
10833
|
// Numeric-only suffix = post-release iteration (NEWER than no-suffix release).
|
|
10967
10834
|
// Mixed/alpha suffix = prerelease (OLDER than no-suffix release, per semver).
|
|
10968
|
-
>>>>>>> Clean
|
|
10969
10835
|
// Returns negative if a<b, positive if a>b, 0 if equal. Accepts strings or
|
|
10970
10836
|
// parsed objects.
|
|
10971
10837
|
function compareSemver(a, b) {
|
|
@@ -10977,13 +10843,6 @@ function compareSemver(a, b) {
|
|
|
10977
10843
|
if (pa.major !== pb.major) return pa.major - pb.major;
|
|
10978
10844
|
if (pa.minor !== pb.minor) return pa.minor - pb.minor;
|
|
10979
10845
|
if (pa.patch !== pb.patch) return pa.patch - pb.patch;
|
|
10980
|
-
<<<<<<< HEAD
|
|
10981
|
-
// Equal x.y.z. Prerelease vs none: a release without prerelease is GREATER.
|
|
10982
|
-
if (!pa.pre && !pb.pre) return 0;
|
|
10983
|
-
if (!pa.pre && pb.pre) return 1;
|
|
10984
|
-
if ( pa.pre && !pb.pre) return -1;
|
|
10985
|
-
// Both have prerelease — compare identifiers per semver §11.4.
|
|
10986
|
-
=======
|
|
10987
10846
|
// Equal x.y.z. Order from oldest → newest:
|
|
10988
10847
|
// alpha-prerelease < no-suffix release < numeric iteration
|
|
10989
10848
|
if (!pa.pre && !pb.pre) return 0;
|
|
@@ -10997,7 +10856,6 @@ function compareSemver(a, b) {
|
|
|
10997
10856
|
if (!pa.pre && pb.pre) return 1;
|
|
10998
10857
|
if ( pa.pre && !pb.pre) return -1;
|
|
10999
10858
|
// Both have alpha-flavoured prerelease — compare identifiers per semver §11.4.
|
|
11000
|
-
>>>>>>> Clean
|
|
11001
10859
|
const ia = pa.pre.split('.');
|
|
11002
10860
|
const ib = pb.pre.split('.');
|
|
11003
10861
|
const len = Math.max(ia.length, ib.length);
|