synabun 2026.4.26-beta → 2026.4.26-beta.2
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 +1 -1
- package/neural-interface/public/shared/cdx/cdx-panel.js +4 -4
- package/neural-interface/public/shared/cdx/cdx-styles.js +1 -1
- package/neural-interface/public/shared/ocp/ocp-panel.js +5 -4
- package/neural-interface/public/shared/ocp/ocp-styles.js +1 -1
- package/neural-interface/public/shared/styles.css +198 -144
- package/neural-interface/public/shared/ui-claude-panel.js +5 -5
- package/neural-interface/public/shared/ui-menubar.js +64 -36
- package/neural-interface/public/shared/ui-tool-updates.js +77 -3
- package/neural-interface/public/shared/ui-update.js +53 -16
- package/neural-interface/server.js +104 -39
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Raw internal changelogs are archived in [changelog/](changelog/).
|
|
4
4
|
|
|
5
|
-
## 2026-04-
|
|
5
|
+
## 2026-04-25
|
|
6
6
|
|
|
7
7
|
### Added — Dedicated Schedules Studio
|
|
8
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.
|
|
@@ -399,8 +399,8 @@ let _cliInstallFailureForced = false;
|
|
|
399
399
|
function ensureCliBannerEl() {
|
|
400
400
|
if (!_panel) return null;
|
|
401
401
|
const container = panelEl('#cxp-messages-container');
|
|
402
|
-
if (!container) return null;
|
|
403
|
-
let banner = container.querySelector(':scope > .cxp-cli-banner');
|
|
402
|
+
if (!container || !container.parentNode) return null;
|
|
403
|
+
let banner = container.parentNode.querySelector(':scope > .cxp-cli-banner');
|
|
404
404
|
if (banner) return banner;
|
|
405
405
|
const label = getCliLabel('codex');
|
|
406
406
|
const cmd = getCliInstallCommand('codex');
|
|
@@ -418,7 +418,7 @@ function ensureCliBannerEl() {
|
|
|
418
418
|
<button class="cxp-cli-banner-recheck" type="button">Re-check</button>
|
|
419
419
|
</div>
|
|
420
420
|
`;
|
|
421
|
-
container.insertBefore(banner, container
|
|
421
|
+
container.parentNode.insertBefore(banner, container);
|
|
422
422
|
const btn = banner.querySelector('.cxp-cli-banner-recheck');
|
|
423
423
|
btn?.addEventListener('click', async () => {
|
|
424
424
|
btn.disabled = true;
|
|
@@ -437,7 +437,7 @@ function ensureCliBannerEl() {
|
|
|
437
437
|
function removeCliBannerEl() {
|
|
438
438
|
if (!_panel) return;
|
|
439
439
|
const container = panelEl('#cxp-messages-container');
|
|
440
|
-
const banner = container?.querySelector(':scope > .cxp-cli-banner');
|
|
440
|
+
const banner = container?.parentNode?.querySelector(':scope > .cxp-cli-banner');
|
|
441
441
|
banner?.remove();
|
|
442
442
|
}
|
|
443
443
|
|
|
@@ -990,7 +990,7 @@ export function injectStyles() {
|
|
|
990
990
|
grid-template-columns: auto 1fr auto;
|
|
991
991
|
gap: 10px;
|
|
992
992
|
align-items: center;
|
|
993
|
-
margin: 8px
|
|
993
|
+
margin: 6px 8px 0;
|
|
994
994
|
padding: 10px 12px;
|
|
995
995
|
border: 1px solid rgba(255,180,80,0.45);
|
|
996
996
|
border-radius: 8px;
|
|
@@ -1526,8 +1526,8 @@ let _cliInstallFailureForced = false;
|
|
|
1526
1526
|
function ensureCliBannerEl() {
|
|
1527
1527
|
if (!_panel) return null;
|
|
1528
1528
|
const container = _panel.querySelector('.ocp-messages-container');
|
|
1529
|
-
if (!container) return null;
|
|
1530
|
-
let banner = container.querySelector(':scope > .ocp-cli-banner');
|
|
1529
|
+
if (!container || !container.parentNode) return null;
|
|
1530
|
+
let banner = container.parentNode.querySelector(':scope > .ocp-cli-banner');
|
|
1531
1531
|
if (banner) return banner;
|
|
1532
1532
|
const label = getCliLabel('opencode');
|
|
1533
1533
|
const cmd = getCliInstallCommand('opencode');
|
|
@@ -1545,7 +1545,7 @@ function ensureCliBannerEl() {
|
|
|
1545
1545
|
<button class="ocp-cli-banner-recheck" type="button">Re-check</button>
|
|
1546
1546
|
</div>
|
|
1547
1547
|
`;
|
|
1548
|
-
container.insertBefore(banner, container
|
|
1548
|
+
container.parentNode.insertBefore(banner, container);
|
|
1549
1549
|
const btn = banner.querySelector('.ocp-cli-banner-recheck');
|
|
1550
1550
|
btn?.addEventListener('click', async () => {
|
|
1551
1551
|
btn.disabled = true;
|
|
@@ -1563,7 +1563,8 @@ function ensureCliBannerEl() {
|
|
|
1563
1563
|
|
|
1564
1564
|
function removeCliBannerEl() {
|
|
1565
1565
|
if (!_panel) return;
|
|
1566
|
-
const
|
|
1566
|
+
const container = _panel.querySelector('.ocp-messages-container');
|
|
1567
|
+
const banner = container?.parentNode?.querySelector(':scope > .ocp-cli-banner');
|
|
1567
1568
|
banner?.remove();
|
|
1568
1569
|
}
|
|
1569
1570
|
|
|
@@ -1527,7 +1527,7 @@ export function injectStyles() {
|
|
|
1527
1527
|
grid-template-columns: auto 1fr auto;
|
|
1528
1528
|
gap: 10px;
|
|
1529
1529
|
align-items: center;
|
|
1530
|
-
margin: 8px
|
|
1530
|
+
margin: 6px 8px 0;
|
|
1531
1531
|
padding: 10px 12px;
|
|
1532
1532
|
border: 1px solid rgba(255,180,80,0.45);
|
|
1533
1533
|
border-radius: 8px;
|
|
@@ -19449,150 +19449,150 @@
|
|
|
19449
19449
|
}
|
|
19450
19450
|
.sched-sidebar-new:hover { background: rgba(255,255,255,0.07); color: var(--t-bright); }
|
|
19451
19451
|
.sched-sidebar-empty { color: var(--t-muted); font-size: 11px; padding: 8px 6px; }
|
|
19452
|
-
.sched-sidebar-prompt {
|
|
19453
|
-
margin-top: auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
19454
|
-
border: 1px solid rgba(255,255,255,0.09); border-radius: 8px;
|
|
19455
|
-
background: rgba(255,255,255,0.04);
|
|
19456
|
-
color: var(--t-secondary); min-height: 34px; cursor: pointer; font-family: inherit;
|
|
19457
|
-
font-size: 12px; font-weight: 600; letter-spacing: 0;
|
|
19458
|
-
box-shadow: var(--shadow-highlight);
|
|
19459
|
-
transition: border-color 160ms ease, color 160ms ease, background 160ms ease, transform 120ms ease;
|
|
19460
|
-
}
|
|
19461
|
-
.sched-sidebar-prompt:hover {
|
|
19462
|
-
background: rgba(255,255,255,0.075);
|
|
19463
|
-
border-color: rgba(255,255,255,0.14);
|
|
19464
|
-
color: var(--t-bright);
|
|
19465
|
-
transform: translateY(-1px);
|
|
19466
|
-
}
|
|
19467
|
-
.sched-sidebar-prompt svg { width: 13px; height: 13px; color: var(--t-muted); }
|
|
19468
|
-
|
|
19469
|
-
.sched-prompt-backdrop {
|
|
19470
|
-
position: fixed; inset: 0; background: rgba(5,7,12,0.58);
|
|
19471
|
-
backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
|
|
19472
|
-
z-index: 300010; opacity: 0; transition: opacity 160ms ease-out;
|
|
19473
|
-
pointer-events: auto;
|
|
19474
|
-
}
|
|
19475
|
-
.sched-prompt-backdrop.open { opacity: 1; }
|
|
19476
|
-
.sched-prompt-modal {
|
|
19477
|
-
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -52%) scale(0.98);
|
|
19478
|
-
width: min(512px, calc(100vw - 32px)); padding: 0;
|
|
19479
|
-
border-radius: 12px; z-index: 300011;
|
|
19480
|
-
opacity: 0; transition: opacity 160ms ease-out, transform 200ms ease-out;
|
|
19481
|
-
background:
|
|
19482
|
-
linear-gradient(to bottom, rgba(255,255,255,0.035), rgba(255,255,255,0.01)),
|
|
19483
|
-
rgba(13,15,20,0.96);
|
|
19484
|
-
border: 1px solid rgba(255,255,255,0.085);
|
|
19485
|
-
box-shadow: 0 22px 64px rgba(0,0,0,0.58), var(--shadow-highlight);
|
|
19486
|
-
overflow: hidden;
|
|
19487
|
-
}
|
|
19488
|
-
.sched-prompt-modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
19489
|
-
.sched-prompt-head {
|
|
19490
|
-
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
19491
|
-
padding: 15px 16px 13px;
|
|
19492
|
-
border-bottom: 1px solid rgba(255,255,255,0.055);
|
|
19493
|
-
}
|
|
19494
|
-
.sched-prompt-head-text {
|
|
19495
|
-
display: flex; align-items: center; gap: 10px; min-width: 0;
|
|
19496
|
-
}
|
|
19497
|
-
.sched-prompt-icon {
|
|
19498
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
19499
|
-
width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
|
|
19500
|
-
background: rgba(255,255,255,0.055);
|
|
19501
|
-
border: 1px solid rgba(255,255,255,0.075);
|
|
19502
|
-
color: var(--t-secondary);
|
|
19503
|
-
box-shadow: var(--shadow-highlight);
|
|
19504
|
-
}
|
|
19505
|
-
.sched-prompt-icon svg { width: 14px; height: 14px; }
|
|
19506
|
-
.sched-prompt-head h3 {
|
|
19507
|
-
margin: 0; font-family: var(--ff-heading); font-size: 14px; font-weight: 650;
|
|
19508
|
-
color: var(--t-bright); letter-spacing: 0;
|
|
19509
|
-
}
|
|
19510
|
-
.sched-prompt-head p {
|
|
19511
|
-
margin: 2px 0 0; font-size: 11.5px; line-height: 1.35; color: var(--t-muted);
|
|
19512
|
-
}
|
|
19513
|
-
.sched-prompt-close {
|
|
19514
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
19515
|
-
width: 28px; height: 28px; flex: 0 0 auto;
|
|
19516
|
-
background: transparent; border: 1px solid transparent; border-radius: 7px;
|
|
19517
|
-
color: var(--t-muted); cursor: pointer; padding: 0;
|
|
19518
|
-
transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
19519
|
-
}
|
|
19520
|
-
.sched-prompt-close:hover {
|
|
19521
|
-
background: rgba(255,255,255,0.055);
|
|
19522
|
-
border-color: rgba(255,255,255,0.075);
|
|
19523
|
-
color: var(--t-bright);
|
|
19524
|
-
}
|
|
19525
|
-
.sched-prompt-close svg { width: 14px; height: 14px; }
|
|
19526
|
-
.sched-prompt-tiles {
|
|
19527
|
-
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
|
|
19528
|
-
padding: 14px 16px 16px;
|
|
19529
|
-
}
|
|
19530
|
-
@media (max-width: 560px) { .sched-prompt-tiles { grid-template-columns: 1fr; } }
|
|
19531
|
-
.sched-prompt-tile {
|
|
19532
|
-
position: relative; display: grid; grid-template-columns: 32px minmax(0, 1fr);
|
|
19533
|
-
grid-template-areas: "logo pill" "body body"; align-content: start; gap: 10px 8px;
|
|
19534
|
-
min-height: 96px; padding: 12px; border-radius: 10px; cursor: pointer; font-family: inherit;
|
|
19535
|
-
background: rgba(255,255,255,0.032);
|
|
19536
|
-
border: 1px solid rgba(255,255,255,0.065);
|
|
19537
|
-
color: var(--t-secondary); text-align: left;
|
|
19538
|
-
backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
|
|
19539
|
-
transition: background 180ms ease, border-color 180ms ease, transform 160ms ease, box-shadow 180ms ease;
|
|
19540
|
-
overflow: hidden;
|
|
19541
|
-
}
|
|
19542
|
-
.sched-prompt-tile:not(.disabled) {
|
|
19543
|
-
background: rgba(255,255,255,0.045);
|
|
19544
|
-
border-color: rgba(255,255,255,0.095);
|
|
19545
|
-
box-shadow: var(--shadow-highlight);
|
|
19546
|
-
}
|
|
19547
|
-
.sched-prompt-tile:not(.disabled):hover {
|
|
19548
|
-
background: rgba(255,255,255,0.065);
|
|
19549
|
-
border-color: rgba(255,255,255,0.13);
|
|
19550
|
-
color: var(--t-bright); transform: translateY(-1px);
|
|
19551
|
-
box-shadow: var(--shadow-xs), var(--shadow-highlight);
|
|
19552
|
-
}
|
|
19553
|
-
.sched-prompt-tile:not(.disabled):active { transform: translateY(0); }
|
|
19554
|
-
.sched-prompt-tile:focus-visible {
|
|
19555
|
-
outline: none;
|
|
19556
|
-
border-color: rgba(255,255,255,0.18);
|
|
19557
|
-
box-shadow: 0 0 0 2px rgba(255,255,255,0.14), var(--shadow-highlight);
|
|
19558
|
-
}
|
|
19559
|
-
.sched-prompt-tile.disabled { opacity: 0.58; cursor: not-allowed; pointer-events: none; }
|
|
19560
|
-
.sched-prompt-tile-logo {
|
|
19561
|
-
grid-area: logo;
|
|
19562
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
19563
|
-
width: 32px; height: 32px; border-radius: 8px;
|
|
19564
|
-
background: rgba(255,255,255,0.045);
|
|
19565
|
-
border: 1px solid rgba(255,255,255,0.075);
|
|
19566
|
-
color: color-mix(in srgb, var(--provider-color, #fff) 62%, var(--t-secondary));
|
|
19567
|
-
box-shadow: var(--shadow-highlight);
|
|
19568
|
-
}
|
|
19569
|
-
.sched-prompt-tile-logo svg { width: 16px; height: 16px; display: block; }
|
|
19570
|
-
.sched-prompt-tile-body {
|
|
19571
|
-
grid-area: body; display: flex; flex-direction: column; gap: 2px; min-width: 0;
|
|
19572
|
-
}
|
|
19573
|
-
.sched-prompt-tile-title {
|
|
19574
|
-
font-size: 12.5px; font-weight: 650; color: var(--t-secondary); letter-spacing: 0;
|
|
19575
|
-
line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19576
|
-
}
|
|
19577
|
-
.sched-prompt-tile:not(.disabled) .sched-prompt-tile-title {
|
|
19578
|
-
color: color-mix(in srgb, var(--provider-color, #fff) 56%, var(--t-bright));
|
|
19579
|
-
}
|
|
19580
|
-
.sched-prompt-tile-sub {
|
|
19581
|
-
font-size: 10.5px; color: var(--t-muted); letter-spacing: 0; line-height: 1.2;
|
|
19582
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19583
|
-
}
|
|
19584
|
-
.sched-prompt-pill {
|
|
19585
|
-
grid-area: pill; justify-self: end; align-self: start;
|
|
19586
|
-
font-size: 10px; font-weight: 600; letter-spacing: 0;
|
|
19587
|
-
padding: 2px 6px; border-radius: var(--r-pill);
|
|
19588
|
-
background: rgba(255,255,255,0.045); color: var(--t-muted);
|
|
19589
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
19590
|
-
}
|
|
19591
|
-
.sched-prompt-pill--active {
|
|
19592
|
-
background: rgba(255,255,255,0.075);
|
|
19593
|
-
border-color: rgba(255,255,255,0.12);
|
|
19594
|
-
color: var(--t-secondary);
|
|
19595
|
-
}
|
|
19452
|
+
.sched-sidebar-prompt {
|
|
19453
|
+
margin-top: auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
19454
|
+
border: 1px solid rgba(255,255,255,0.09); border-radius: 8px;
|
|
19455
|
+
background: rgba(255,255,255,0.04);
|
|
19456
|
+
color: var(--t-secondary); min-height: 34px; cursor: pointer; font-family: inherit;
|
|
19457
|
+
font-size: 12px; font-weight: 600; letter-spacing: 0;
|
|
19458
|
+
box-shadow: var(--shadow-highlight);
|
|
19459
|
+
transition: border-color 160ms ease, color 160ms ease, background 160ms ease, transform 120ms ease;
|
|
19460
|
+
}
|
|
19461
|
+
.sched-sidebar-prompt:hover {
|
|
19462
|
+
background: rgba(255,255,255,0.075);
|
|
19463
|
+
border-color: rgba(255,255,255,0.14);
|
|
19464
|
+
color: var(--t-bright);
|
|
19465
|
+
transform: translateY(-1px);
|
|
19466
|
+
}
|
|
19467
|
+
.sched-sidebar-prompt svg { width: 13px; height: 13px; color: var(--t-muted); }
|
|
19468
|
+
|
|
19469
|
+
.sched-prompt-backdrop {
|
|
19470
|
+
position: fixed; inset: 0; background: rgba(5,7,12,0.58);
|
|
19471
|
+
backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
|
|
19472
|
+
z-index: 300010; opacity: 0; transition: opacity 160ms ease-out;
|
|
19473
|
+
pointer-events: auto;
|
|
19474
|
+
}
|
|
19475
|
+
.sched-prompt-backdrop.open { opacity: 1; }
|
|
19476
|
+
.sched-prompt-modal {
|
|
19477
|
+
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -52%) scale(0.98);
|
|
19478
|
+
width: min(512px, calc(100vw - 32px)); padding: 0;
|
|
19479
|
+
border-radius: 12px; z-index: 300011;
|
|
19480
|
+
opacity: 0; transition: opacity 160ms ease-out, transform 200ms ease-out;
|
|
19481
|
+
background:
|
|
19482
|
+
linear-gradient(to bottom, rgba(255,255,255,0.035), rgba(255,255,255,0.01)),
|
|
19483
|
+
rgba(13,15,20,0.96);
|
|
19484
|
+
border: 1px solid rgba(255,255,255,0.085);
|
|
19485
|
+
box-shadow: 0 22px 64px rgba(0,0,0,0.58), var(--shadow-highlight);
|
|
19486
|
+
overflow: hidden;
|
|
19487
|
+
}
|
|
19488
|
+
.sched-prompt-modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
19489
|
+
.sched-prompt-head {
|
|
19490
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
19491
|
+
padding: 15px 16px 13px;
|
|
19492
|
+
border-bottom: 1px solid rgba(255,255,255,0.055);
|
|
19493
|
+
}
|
|
19494
|
+
.sched-prompt-head-text {
|
|
19495
|
+
display: flex; align-items: center; gap: 10px; min-width: 0;
|
|
19496
|
+
}
|
|
19497
|
+
.sched-prompt-icon {
|
|
19498
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19499
|
+
width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
|
|
19500
|
+
background: rgba(255,255,255,0.055);
|
|
19501
|
+
border: 1px solid rgba(255,255,255,0.075);
|
|
19502
|
+
color: var(--t-secondary);
|
|
19503
|
+
box-shadow: var(--shadow-highlight);
|
|
19504
|
+
}
|
|
19505
|
+
.sched-prompt-icon svg { width: 14px; height: 14px; }
|
|
19506
|
+
.sched-prompt-head h3 {
|
|
19507
|
+
margin: 0; font-family: var(--ff-heading); font-size: 14px; font-weight: 650;
|
|
19508
|
+
color: var(--t-bright); letter-spacing: 0;
|
|
19509
|
+
}
|
|
19510
|
+
.sched-prompt-head p {
|
|
19511
|
+
margin: 2px 0 0; font-size: 11.5px; line-height: 1.35; color: var(--t-muted);
|
|
19512
|
+
}
|
|
19513
|
+
.sched-prompt-close {
|
|
19514
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19515
|
+
width: 28px; height: 28px; flex: 0 0 auto;
|
|
19516
|
+
background: transparent; border: 1px solid transparent; border-radius: 7px;
|
|
19517
|
+
color: var(--t-muted); cursor: pointer; padding: 0;
|
|
19518
|
+
transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
19519
|
+
}
|
|
19520
|
+
.sched-prompt-close:hover {
|
|
19521
|
+
background: rgba(255,255,255,0.055);
|
|
19522
|
+
border-color: rgba(255,255,255,0.075);
|
|
19523
|
+
color: var(--t-bright);
|
|
19524
|
+
}
|
|
19525
|
+
.sched-prompt-close svg { width: 14px; height: 14px; }
|
|
19526
|
+
.sched-prompt-tiles {
|
|
19527
|
+
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
|
|
19528
|
+
padding: 14px 16px 16px;
|
|
19529
|
+
}
|
|
19530
|
+
@media (max-width: 560px) { .sched-prompt-tiles { grid-template-columns: 1fr; } }
|
|
19531
|
+
.sched-prompt-tile {
|
|
19532
|
+
position: relative; display: grid; grid-template-columns: 32px minmax(0, 1fr);
|
|
19533
|
+
grid-template-areas: "logo pill" "body body"; align-content: start; gap: 10px 8px;
|
|
19534
|
+
min-height: 96px; padding: 12px; border-radius: 10px; cursor: pointer; font-family: inherit;
|
|
19535
|
+
background: rgba(255,255,255,0.032);
|
|
19536
|
+
border: 1px solid rgba(255,255,255,0.065);
|
|
19537
|
+
color: var(--t-secondary); text-align: left;
|
|
19538
|
+
backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
|
|
19539
|
+
transition: background 180ms ease, border-color 180ms ease, transform 160ms ease, box-shadow 180ms ease;
|
|
19540
|
+
overflow: hidden;
|
|
19541
|
+
}
|
|
19542
|
+
.sched-prompt-tile:not(.disabled) {
|
|
19543
|
+
background: rgba(255,255,255,0.045);
|
|
19544
|
+
border-color: rgba(255,255,255,0.095);
|
|
19545
|
+
box-shadow: var(--shadow-highlight);
|
|
19546
|
+
}
|
|
19547
|
+
.sched-prompt-tile:not(.disabled):hover {
|
|
19548
|
+
background: rgba(255,255,255,0.065);
|
|
19549
|
+
border-color: rgba(255,255,255,0.13);
|
|
19550
|
+
color: var(--t-bright); transform: translateY(-1px);
|
|
19551
|
+
box-shadow: var(--shadow-xs), var(--shadow-highlight);
|
|
19552
|
+
}
|
|
19553
|
+
.sched-prompt-tile:not(.disabled):active { transform: translateY(0); }
|
|
19554
|
+
.sched-prompt-tile:focus-visible {
|
|
19555
|
+
outline: none;
|
|
19556
|
+
border-color: rgba(255,255,255,0.18);
|
|
19557
|
+
box-shadow: 0 0 0 2px rgba(255,255,255,0.14), var(--shadow-highlight);
|
|
19558
|
+
}
|
|
19559
|
+
.sched-prompt-tile.disabled { opacity: 0.58; cursor: not-allowed; pointer-events: none; }
|
|
19560
|
+
.sched-prompt-tile-logo {
|
|
19561
|
+
grid-area: logo;
|
|
19562
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19563
|
+
width: 32px; height: 32px; border-radius: 8px;
|
|
19564
|
+
background: rgba(255,255,255,0.045);
|
|
19565
|
+
border: 1px solid rgba(255,255,255,0.075);
|
|
19566
|
+
color: color-mix(in srgb, var(--provider-color, #fff) 62%, var(--t-secondary));
|
|
19567
|
+
box-shadow: var(--shadow-highlight);
|
|
19568
|
+
}
|
|
19569
|
+
.sched-prompt-tile-logo svg { width: 16px; height: 16px; display: block; }
|
|
19570
|
+
.sched-prompt-tile-body {
|
|
19571
|
+
grid-area: body; display: flex; flex-direction: column; gap: 2px; min-width: 0;
|
|
19572
|
+
}
|
|
19573
|
+
.sched-prompt-tile-title {
|
|
19574
|
+
font-size: 12.5px; font-weight: 650; color: var(--t-secondary); letter-spacing: 0;
|
|
19575
|
+
line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19576
|
+
}
|
|
19577
|
+
.sched-prompt-tile:not(.disabled) .sched-prompt-tile-title {
|
|
19578
|
+
color: color-mix(in srgb, var(--provider-color, #fff) 56%, var(--t-bright));
|
|
19579
|
+
}
|
|
19580
|
+
.sched-prompt-tile-sub {
|
|
19581
|
+
font-size: 10.5px; color: var(--t-muted); letter-spacing: 0; line-height: 1.2;
|
|
19582
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
19583
|
+
}
|
|
19584
|
+
.sched-prompt-pill {
|
|
19585
|
+
grid-area: pill; justify-self: end; align-self: start;
|
|
19586
|
+
font-size: 10px; font-weight: 600; letter-spacing: 0;
|
|
19587
|
+
padding: 2px 6px; border-radius: var(--r-pill);
|
|
19588
|
+
background: rgba(255,255,255,0.045); color: var(--t-muted);
|
|
19589
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
19590
|
+
}
|
|
19591
|
+
.sched-prompt-pill--active {
|
|
19592
|
+
background: rgba(255,255,255,0.075);
|
|
19593
|
+
border-color: rgba(255,255,255,0.12);
|
|
19594
|
+
color: var(--t-secondary);
|
|
19595
|
+
}
|
|
19596
19596
|
|
|
19597
19597
|
.sched-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
|
|
19598
19598
|
.sched-tabs {
|
|
@@ -24315,3 +24315,57 @@
|
|
|
24315
24315
|
.ig-lb-prev { left: 16px; }
|
|
24316
24316
|
.ig-lb-next { right: 16px; }
|
|
24317
24317
|
|
|
24318
|
+
/* ── Update-check toast (Apps > Check for updates) ── */
|
|
24319
|
+
.update-check-toast {
|
|
24320
|
+
position: fixed;
|
|
24321
|
+
right: 20px;
|
|
24322
|
+
bottom: 80px;
|
|
24323
|
+
z-index: 300050;
|
|
24324
|
+
display: flex;
|
|
24325
|
+
gap: 12px;
|
|
24326
|
+
align-items: flex-start;
|
|
24327
|
+
min-width: 240px;
|
|
24328
|
+
max-width: 360px;
|
|
24329
|
+
padding: 12px 14px;
|
|
24330
|
+
border-radius: 14px;
|
|
24331
|
+
background: rgba(20, 20, 24, 0.85);
|
|
24332
|
+
backdrop-filter: blur(20px) saturate(160%);
|
|
24333
|
+
-webkit-backdrop-filter: blur(20px) saturate(160%);
|
|
24334
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
24335
|
+
color: var(--t-text);
|
|
24336
|
+
font-size: 12px;
|
|
24337
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
24338
|
+
opacity: 0;
|
|
24339
|
+
transform: translateY(8px);
|
|
24340
|
+
transition: opacity 0.25s ease, transform 0.25s ease, background 0.15s ease;
|
|
24341
|
+
pointer-events: auto;
|
|
24342
|
+
}
|
|
24343
|
+
.update-check-toast.show { opacity: 1; transform: translateY(0); }
|
|
24344
|
+
.update-check-toast--clickable { cursor: pointer; }
|
|
24345
|
+
.update-check-toast--clickable:hover { background: rgba(28, 28, 32, 0.92); }
|
|
24346
|
+
.update-check-toast .utc-icon {
|
|
24347
|
+
width: 24px; height: 24px;
|
|
24348
|
+
border-radius: 50%;
|
|
24349
|
+
display: flex; align-items: center; justify-content: center;
|
|
24350
|
+
font-weight: 700; font-size: 14px;
|
|
24351
|
+
flex-shrink: 0;
|
|
24352
|
+
}
|
|
24353
|
+
.update-check-toast .utc-icon--up { background: rgba(52, 199, 89, 0.18); color: #34c759; }
|
|
24354
|
+
.update-check-toast .utc-icon--ok { background: rgba(52, 199, 89, 0.18); color: #34c759; }
|
|
24355
|
+
.update-check-toast .utc-icon--err { background: rgba(255, 69, 58, 0.18); color: #ff453a; }
|
|
24356
|
+
.update-check-toast .utc-body { flex: 1; min-width: 0; }
|
|
24357
|
+
.update-check-toast .utc-title { font-weight: 600; margin-bottom: 4px; }
|
|
24358
|
+
.update-check-toast .utc-list { list-style: none; margin: 0; padding: 0; }
|
|
24359
|
+
.update-check-toast .utc-list li { padding: 2px 0; color: var(--t-muted); }
|
|
24360
|
+
.update-check-toast .utc-list li .utc-src {
|
|
24361
|
+
display: inline-block;
|
|
24362
|
+
padding: 1px 6px;
|
|
24363
|
+
margin-left: 6px;
|
|
24364
|
+
border-radius: 6px;
|
|
24365
|
+
font-size: 10px;
|
|
24366
|
+
background: rgba(255, 255, 255, 0.08);
|
|
24367
|
+
color: var(--t-text);
|
|
24368
|
+
}
|
|
24369
|
+
.update-check-toast .utc-sub { color: var(--t-muted); }
|
|
24370
|
+
.update-check-toast .utc-hint { color: var(--t-muted); opacity: 0.7; margin-top: 6px; font-size: 11px; }
|
|
24371
|
+
|
|
@@ -439,7 +439,7 @@ function injectStyles() {
|
|
|
439
439
|
grid-template-columns: auto 1fr auto;
|
|
440
440
|
gap: 10px;
|
|
441
441
|
align-items: center;
|
|
442
|
-
margin: 8px
|
|
442
|
+
margin: 6px 8px 0;
|
|
443
443
|
padding: 10px 12px;
|
|
444
444
|
border: 1px solid rgba(255,180,80,0.45);
|
|
445
445
|
border-radius: 8px;
|
|
@@ -7331,8 +7331,8 @@ let _cliInstallFailureForced = false;
|
|
|
7331
7331
|
function _ensureCliBannerEl() {
|
|
7332
7332
|
if (!_panel) return null;
|
|
7333
7333
|
const container = _panel.querySelector('#cp-messages-container');
|
|
7334
|
-
if (!container) return null;
|
|
7335
|
-
let banner = container.querySelector(':scope > .cp-cli-banner');
|
|
7334
|
+
if (!container || !container.parentNode) return null;
|
|
7335
|
+
let banner = container.parentNode.querySelector(':scope > .cp-cli-banner');
|
|
7336
7336
|
if (banner) return banner;
|
|
7337
7337
|
const label = getCliLabel('claude-code');
|
|
7338
7338
|
const cmd = getCliInstallCommand('claude-code');
|
|
@@ -7350,7 +7350,7 @@ function _ensureCliBannerEl() {
|
|
|
7350
7350
|
<button class="cp-cli-banner-recheck" type="button">Re-check</button>
|
|
7351
7351
|
</div>
|
|
7352
7352
|
`;
|
|
7353
|
-
container.insertBefore(banner, container
|
|
7353
|
+
container.parentNode.insertBefore(banner, container);
|
|
7354
7354
|
const btn = banner.querySelector('.cp-cli-banner-recheck');
|
|
7355
7355
|
btn?.addEventListener('click', async () => {
|
|
7356
7356
|
btn.disabled = true;
|
|
@@ -7368,7 +7368,7 @@ function _ensureCliBannerEl() {
|
|
|
7368
7368
|
|
|
7369
7369
|
function _removeCliBannerEl() {
|
|
7370
7370
|
if (!_panel) return;
|
|
7371
|
-
const banner = _panel.querySelector('
|
|
7371
|
+
const banner = _panel.querySelector(':scope > .cp-cli-banner');
|
|
7372
7372
|
banner?.remove();
|
|
7373
7373
|
}
|
|
7374
7374
|
|
|
@@ -12,7 +12,8 @@ import { openHelp } from './ui-help.js';
|
|
|
12
12
|
import { registerAction } from './ui-keybinds.js';
|
|
13
13
|
import { isGuest, hasPermission } from './ui-sync.js';
|
|
14
14
|
import { sendToPanel } from './ui-claude-panel.js';
|
|
15
|
-
import { forceCheckToolUpdates } from './ui-tool-updates.js';
|
|
15
|
+
import { forceCheckToolUpdates, showUpdateToast, TOOL_LABELS } from './ui-tool-updates.js';
|
|
16
|
+
import { forceCheckSynabunUpdate, openSynabunUpdateModal } from './ui-update.js';
|
|
16
17
|
|
|
17
18
|
const $ = (id) => document.getElementById(id);
|
|
18
19
|
|
|
@@ -127,12 +128,12 @@ export function initMenubar() {
|
|
|
127
128
|
'menu-open-skills-studio': 'skills',
|
|
128
129
|
'menu-skills-new': 'skills',
|
|
129
130
|
'menu-skills-import': 'skills',
|
|
130
|
-
'menu-open-automation-studio': 'automations',
|
|
131
|
-
'menu-automations-schedule-new': 'automations',
|
|
132
|
-
'menu-automations-quick-timer': 'automations',
|
|
133
|
-
'menu-automations-schedule-groups': 'automations',
|
|
134
|
-
'menu-automations-new': 'automations',
|
|
135
|
-
'menu-automations-import': 'automations',
|
|
131
|
+
'menu-open-automation-studio': 'automations',
|
|
132
|
+
'menu-automations-schedule-new': 'automations',
|
|
133
|
+
'menu-automations-quick-timer': 'automations',
|
|
134
|
+
'menu-automations-schedule-groups': 'automations',
|
|
135
|
+
'menu-automations-new': 'automations',
|
|
136
|
+
'menu-automations-import': 'automations',
|
|
136
137
|
'menu-terminal-claude': 'terminal',
|
|
137
138
|
'menu-terminal-codex': 'terminal',
|
|
138
139
|
'menu-terminal-gemini': 'terminal',
|
|
@@ -316,34 +317,34 @@ function wireAutomationsMenu() {
|
|
|
316
317
|
emit('automations:open');
|
|
317
318
|
});
|
|
318
319
|
}
|
|
319
|
-
const schedulesItem = $('menu-automations-schedules');
|
|
320
|
-
if (schedulesItem) {
|
|
321
|
-
schedulesItem.addEventListener('click', () => {
|
|
322
|
-
closeAll();
|
|
323
|
-
emit('automations:open-schedules');
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
const newScheduleItem = $('menu-automations-schedule-new');
|
|
327
|
-
if (newScheduleItem) {
|
|
328
|
-
newScheduleItem.addEventListener('click', () => {
|
|
329
|
-
closeAll();
|
|
330
|
-
emit('automations:open-schedules', { action: 'new-schedule' });
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
const quickTimerItem = $('menu-automations-quick-timer');
|
|
334
|
-
if (quickTimerItem) {
|
|
335
|
-
quickTimerItem.addEventListener('click', () => {
|
|
336
|
-
closeAll();
|
|
337
|
-
emit('automations:open-schedules', { action: 'quick-timer' });
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
const scheduleGroupsItem = $('menu-automations-schedule-groups');
|
|
341
|
-
if (scheduleGroupsItem) {
|
|
342
|
-
scheduleGroupsItem.addEventListener('click', () => {
|
|
343
|
-
closeAll();
|
|
344
|
-
emit('automations:open-schedules', { tab: 'groups' });
|
|
345
|
-
});
|
|
346
|
-
}
|
|
320
|
+
const schedulesItem = $('menu-automations-schedules');
|
|
321
|
+
if (schedulesItem) {
|
|
322
|
+
schedulesItem.addEventListener('click', () => {
|
|
323
|
+
closeAll();
|
|
324
|
+
emit('automations:open-schedules');
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const newScheduleItem = $('menu-automations-schedule-new');
|
|
328
|
+
if (newScheduleItem) {
|
|
329
|
+
newScheduleItem.addEventListener('click', () => {
|
|
330
|
+
closeAll();
|
|
331
|
+
emit('automations:open-schedules', { action: 'new-schedule' });
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const quickTimerItem = $('menu-automations-quick-timer');
|
|
335
|
+
if (quickTimerItem) {
|
|
336
|
+
quickTimerItem.addEventListener('click', () => {
|
|
337
|
+
closeAll();
|
|
338
|
+
emit('automations:open-schedules', { action: 'quick-timer' });
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const scheduleGroupsItem = $('menu-automations-schedule-groups');
|
|
342
|
+
if (scheduleGroupsItem) {
|
|
343
|
+
scheduleGroupsItem.addEventListener('click', () => {
|
|
344
|
+
closeAll();
|
|
345
|
+
emit('automations:open-schedules', { tab: 'groups' });
|
|
346
|
+
});
|
|
347
|
+
}
|
|
347
348
|
const newItem = $('menu-automations-new');
|
|
348
349
|
if (newItem) {
|
|
349
350
|
newItem.addEventListener('click', () => {
|
|
@@ -370,7 +371,34 @@ function wireTerminalMenu() {
|
|
|
370
371
|
'menu-terminal-codex': () => emit('terminal:open', { profile: 'codex' }),
|
|
371
372
|
'menu-terminal-gemini': () => emit('terminal:open', { profile: 'gemini' }),
|
|
372
373
|
'menu-terminal-opencode': () => emit('terminal:open', { profile: 'opencode' }),
|
|
373
|
-
'menu-check-cli-updates': () =>
|
|
374
|
+
'menu-check-cli-updates': async () => {
|
|
375
|
+
const [tools, syn] = await Promise.all([
|
|
376
|
+
forceCheckToolUpdates(),
|
|
377
|
+
forceCheckSynabunUpdate(),
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
const updates = [];
|
|
381
|
+
for (const [key, info] of Object.entries(tools?.tools || {})) {
|
|
382
|
+
if (info?.updateAvailable) {
|
|
383
|
+
const label = `${TOOL_LABELS[key] || key} v${info.installed} → v${info.latest}`;
|
|
384
|
+
updates.push({ label });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (syn?.updateAvailable) {
|
|
388
|
+
const srcLabel = syn.source === 'both' ? 'npm + GitHub' : syn.source === 'github' ? 'GitHub' : 'npm';
|
|
389
|
+
updates.push({ label: `SynaBun v${syn.current} → v${syn.latest}`, source: srcLabel });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const errors = [];
|
|
393
|
+
if (!tools) errors.push('CLI versions');
|
|
394
|
+
if (!syn) errors.push('SynaBun version');
|
|
395
|
+
|
|
396
|
+
showUpdateToast({
|
|
397
|
+
updates,
|
|
398
|
+
errors,
|
|
399
|
+
onClick: syn?.updateAvailable ? () => openSynabunUpdateModal() : null,
|
|
400
|
+
});
|
|
401
|
+
},
|
|
374
402
|
'menu-terminal-shell': () => emit('terminal:open', { profile: 'shell' }),
|
|
375
403
|
'menu-terminal-browser': () => emit('browser:open'),
|
|
376
404
|
'menu-terminal-youtube': () => emit('browser:open', { url: 'https://www.youtube.com' }),
|
|
@@ -174,7 +174,81 @@ export async function initToolUpdates() {
|
|
|
174
174
|
export async function forceCheckToolUpdates() {
|
|
175
175
|
try {
|
|
176
176
|
const res = await fetch('/api/system/tool-versions?force=1');
|
|
177
|
-
if (!res.ok) return;
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
if (!res.ok) return null;
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
applyBadges(data);
|
|
180
|
+
return data;
|
|
181
|
+
} catch { return null; }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Re-export human-readable tool labels so toast/menu code can build summaries
|
|
185
|
+
// without redefining the label map.
|
|
186
|
+
export { TOOL_LABELS };
|
|
187
|
+
|
|
188
|
+
// ── Update-check toast ──
|
|
189
|
+
// Bottom-right ephemeral pill. One of three modes: updates available (with
|
|
190
|
+
// list + optional click handler), all-clear, or check-failed. Auto-dismisses.
|
|
191
|
+
|
|
192
|
+
function _toastEsc(s) {
|
|
193
|
+
const d = document.createElement('div');
|
|
194
|
+
d.textContent = String(s ?? '');
|
|
195
|
+
return d.innerHTML;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function showUpdateToast({ updates, errors, onClick } = {}) {
|
|
199
|
+
document.querySelectorAll('.update-check-toast').forEach(el => el.remove());
|
|
200
|
+
|
|
201
|
+
const toast = document.createElement('div');
|
|
202
|
+
toast.className = 'update-check-toast';
|
|
203
|
+
|
|
204
|
+
const hasUpdates = Array.isArray(updates) && updates.length > 0;
|
|
205
|
+
const hasErrors = Array.isArray(errors) && errors.length > 0;
|
|
206
|
+
const clickable = hasUpdates && typeof onClick === 'function';
|
|
207
|
+
if (clickable) toast.classList.add('update-check-toast--clickable');
|
|
208
|
+
|
|
209
|
+
if (hasUpdates) {
|
|
210
|
+
const items = updates.map(u => {
|
|
211
|
+
const label = _toastEsc(u?.label || '');
|
|
212
|
+
const src = u?.source ? `<span class="utc-src">${_toastEsc(u.source)}</span>` : '';
|
|
213
|
+
return `<li>${label}${src}</li>`;
|
|
214
|
+
}).join('');
|
|
215
|
+
toast.innerHTML = `
|
|
216
|
+
<div class="utc-icon utc-icon--up">↑</div>
|
|
217
|
+
<div class="utc-body">
|
|
218
|
+
<div class="utc-title">Updates available</div>
|
|
219
|
+
<ul class="utc-list">${items}</ul>
|
|
220
|
+
${clickable ? '<div class="utc-hint">Click to open update guide</div>' : ''}
|
|
221
|
+
</div>`;
|
|
222
|
+
} else if (hasErrors) {
|
|
223
|
+
toast.innerHTML = `
|
|
224
|
+
<div class="utc-icon utc-icon--err">!</div>
|
|
225
|
+
<div class="utc-body">
|
|
226
|
+
<div class="utc-title">Update check failed</div>
|
|
227
|
+
<div class="utc-sub">${_toastEsc(errors.join(' · '))}</div>
|
|
228
|
+
</div>`;
|
|
229
|
+
} else {
|
|
230
|
+
toast.innerHTML = `
|
|
231
|
+
<div class="utc-icon utc-icon--ok">✓</div>
|
|
232
|
+
<div class="utc-body">
|
|
233
|
+
<div class="utc-title">All up to date</div>
|
|
234
|
+
</div>`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (clickable) {
|
|
238
|
+
toast.addEventListener('click', () => {
|
|
239
|
+
try { onClick(); } catch {}
|
|
240
|
+
toast.classList.remove('show');
|
|
241
|
+
setTimeout(() => toast.remove(), 250);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
document.body.appendChild(toast);
|
|
246
|
+
requestAnimationFrame(() => toast.classList.add('show'));
|
|
247
|
+
|
|
248
|
+
const ttl = hasUpdates ? 7000 : hasErrors ? 5000 : 3500;
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
if (!toast.isConnected) return;
|
|
251
|
+
toast.classList.remove('show');
|
|
252
|
+
setTimeout(() => toast.remove(), 250);
|
|
253
|
+
}, ttl);
|
|
180
254
|
}
|
|
@@ -7,27 +7,55 @@
|
|
|
7
7
|
const $ = (id) => document.getElementById(id);
|
|
8
8
|
|
|
9
9
|
let _versionData = null;
|
|
10
|
+
let _btnWired = false;
|
|
11
|
+
|
|
12
|
+
function applyTopRightButton(data) {
|
|
13
|
+
const btn = $('topright-update-btn');
|
|
14
|
+
const badge = $('update-badge');
|
|
15
|
+
if (!btn) return;
|
|
16
|
+
const has = !!data?.updateAvailable;
|
|
17
|
+
btn.style.display = has ? '' : 'none';
|
|
18
|
+
if (badge) badge.textContent = has ? '!' : '';
|
|
19
|
+
if (has) {
|
|
20
|
+
const srcLabel = data.source === 'both' ? 'npm + GitHub' : data.source === 'github' ? 'GitHub' : 'npm';
|
|
21
|
+
btn.dataset.tooltip = `Update: v${data.current} → v${data.latest} (${srcLabel})`;
|
|
22
|
+
}
|
|
23
|
+
if (!_btnWired) {
|
|
24
|
+
_btnWired = true;
|
|
25
|
+
btn.addEventListener('click', (e) => {
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
openUpdateModal();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
10
31
|
|
|
11
32
|
export async function initUpdate() {
|
|
12
33
|
try {
|
|
13
34
|
const res = await fetch('/api/system/version');
|
|
14
35
|
if (!res.ok) return;
|
|
15
36
|
_versionData = await res.json();
|
|
37
|
+
applyTopRightButton(_versionData);
|
|
38
|
+
} catch { /* silent — no update UI if check fails */ }
|
|
39
|
+
}
|
|
16
40
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
// Force a fresh server-side check (bypasses 1h cache via ?force=1) and
|
|
42
|
+
// re-applies the top-right button + badge state. Returns the latest
|
|
43
|
+
// _synabunUpdateCache shape so callers can compose toasts.
|
|
44
|
+
export async function forceCheckSynabunUpdate() {
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch('/api/system/version?force=1');
|
|
47
|
+
if (!res.ok) return null;
|
|
48
|
+
_versionData = await res.json();
|
|
49
|
+
applyTopRightButton(_versionData);
|
|
50
|
+
return _versionData;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
25
55
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
} catch { /* silent — no update UI if check fails */ }
|
|
56
|
+
export function openSynabunUpdateModal() {
|
|
57
|
+
if (!_versionData?.updateAvailable) return;
|
|
58
|
+
openUpdateModal();
|
|
31
59
|
}
|
|
32
60
|
|
|
33
61
|
// ── Modal Wizard ──
|
|
@@ -59,6 +87,16 @@ function openUpdateModal() {
|
|
|
59
87
|
}
|
|
60
88
|
|
|
61
89
|
function renderStep1() {
|
|
90
|
+
const src = _versionData.source;
|
|
91
|
+
const srcCopy = src === 'both'
|
|
92
|
+
? 'A new version of SynaBun is available on npm and GitHub.'
|
|
93
|
+
: src === 'github'
|
|
94
|
+
? 'A new SynaBun release is tagged on GitHub. The npm publish may still be in progress.'
|
|
95
|
+
: 'A new version of SynaBun has been published to npm.';
|
|
96
|
+
|
|
97
|
+
const npmBit = _versionData.npmLatest ? `npm v${esc(_versionData.npmLatest)}` : 'npm —';
|
|
98
|
+
const gitBit = _versionData.gitLatest ? `GitHub v${esc(_versionData.gitLatest)}` : 'GitHub —';
|
|
99
|
+
|
|
62
100
|
return `
|
|
63
101
|
<h3 class="update-modal-title">Update Available</h3>
|
|
64
102
|
<div class="update-version-diff">
|
|
@@ -66,9 +104,8 @@ function openUpdateModal() {
|
|
|
66
104
|
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
|
67
105
|
<span class="update-ver update-ver--new">v${esc(_versionData.latest)}</span>
|
|
68
106
|
</div>
|
|
69
|
-
<p style="font-size:12px;color:var(--t-muted);margin:12px 0
|
|
70
|
-
|
|
71
|
-
</p>
|
|
107
|
+
<p style="font-size:12px;color:var(--t-muted);margin:12px 0 6px">${srcCopy}</p>
|
|
108
|
+
<p style="font-size:11px;color:var(--t-muted);opacity:.75;margin:0 0 18px">${npmBit} · ${gitBit}</p>
|
|
72
109
|
<div class="tag-delete-modal-actions">
|
|
73
110
|
<button class="action-btn action-btn--ghost" id="update-cancel">Cancel</button>
|
|
74
111
|
<button class="action-btn action-btn--primary" id="update-next">Next</button>
|
|
@@ -10073,58 +10073,123 @@ app.post('/api/search/memories', async (req, res) => {
|
|
|
10073
10073
|
});
|
|
10074
10074
|
|
|
10075
10075
|
// ═══════════════════════════════════════════
|
|
10076
|
-
//
|
|
10076
|
+
// SYNABUN UPDATE CHECK (NPM + GITHUB RELEASES)
|
|
10077
10077
|
// ═══════════════════════════════════════════
|
|
10078
|
+
//
|
|
10079
|
+
// Checks both the npm registry AND the project's GitHub releases. Alerts
|
|
10080
|
+
// when EITHER source is newer than the installed version (catches one-sided
|
|
10081
|
+
// publishes — e.g. release tagged on GitHub before npm publish completes).
|
|
10082
|
+
|
|
10083
|
+
let _synabunUpdateCache = {
|
|
10084
|
+
current: null,
|
|
10085
|
+
npmLatest: null,
|
|
10086
|
+
gitLatest: null,
|
|
10087
|
+
latest: null, // newer of (npmLatest, gitLatest) — legacy field
|
|
10088
|
+
npmUpdateAvailable: false,
|
|
10089
|
+
gitUpdateAvailable: false,
|
|
10090
|
+
updateAvailable: false, // npmUpdateAvailable || gitUpdateAvailable
|
|
10091
|
+
source: null, // 'npm' | 'github' | 'both' | null
|
|
10092
|
+
checkedAt: null,
|
|
10093
|
+
npmError: null,
|
|
10094
|
+
gitError: null,
|
|
10095
|
+
};
|
|
10078
10096
|
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
async function checkNpmUpdate() {
|
|
10097
|
+
async function checkSynabunUpdate() {
|
|
10098
|
+
let current = _synabunUpdateCache.current;
|
|
10082
10099
|
try {
|
|
10083
|
-
const
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
_npmUpdateCache.current = current;
|
|
10100
|
+
const pkg = JSON.parse(readFileSync(resolve(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
10101
|
+
current = pkg.version;
|
|
10102
|
+
} catch { /* keep prior current if package.json read fails */ }
|
|
10087
10103
|
|
|
10104
|
+
const fetchJsonWithTimeout = async (url, ms = 5000) => {
|
|
10088
10105
|
const controller = new AbortController();
|
|
10089
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
10090
|
-
|
|
10091
|
-
|
|
10106
|
+
const timeout = setTimeout(() => controller.abort(), ms);
|
|
10107
|
+
try {
|
|
10108
|
+
const res = await fetch(url, {
|
|
10109
|
+
signal: controller.signal,
|
|
10110
|
+
headers: { 'Accept': 'application/json', 'User-Agent': 'synabun-update-check' },
|
|
10111
|
+
});
|
|
10112
|
+
if (!res.ok) throw new Error(`${url} returned ${res.status}`);
|
|
10113
|
+
return await res.json();
|
|
10114
|
+
} finally {
|
|
10115
|
+
clearTimeout(timeout);
|
|
10116
|
+
}
|
|
10117
|
+
};
|
|
10092
10118
|
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10119
|
+
// Run both fetches in parallel — one failing must not block the other.
|
|
10120
|
+
const [npmRes, gitRes] = await Promise.allSettled([
|
|
10121
|
+
fetchJsonWithTimeout('https://registry.npmjs.org/synabun/latest'),
|
|
10122
|
+
fetchJsonWithTimeout('https://api.github.com/repos/danilokhury/Synabun/releases/latest'),
|
|
10123
|
+
]);
|
|
10096
10124
|
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
const updateAvailable = semverNewer(cleanCur, cleanLat);
|
|
10125
|
+
let npmLatest = null, gitLatest = null;
|
|
10126
|
+
let npmError = null, gitError = null;
|
|
10100
10127
|
|
|
10101
|
-
|
|
10128
|
+
if (npmRes.status === 'fulfilled') {
|
|
10129
|
+
npmLatest = npmRes.value?.version || null;
|
|
10130
|
+
} else {
|
|
10131
|
+
npmError = npmRes.reason?.message || 'npm fetch failed';
|
|
10132
|
+
}
|
|
10102
10133
|
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
}
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10134
|
+
if (gitRes.status === 'fulfilled') {
|
|
10135
|
+
const tag = gitRes.value?.tag_name || gitRes.value?.name || '';
|
|
10136
|
+
gitLatest = parseVersion(tag) || (tag ? tag.replace(/^v/i, '') : null);
|
|
10137
|
+
} else {
|
|
10138
|
+
gitError = gitRes.reason?.message || 'github fetch failed';
|
|
10139
|
+
}
|
|
10140
|
+
|
|
10141
|
+
const cleanCur = parseVersion(current) || current;
|
|
10142
|
+
const cleanNpm = npmLatest ? (parseVersion(npmLatest) || npmLatest) : null;
|
|
10143
|
+
const cleanGit = gitLatest ? (parseVersion(gitLatest) || gitLatest) : null;
|
|
10144
|
+
|
|
10145
|
+
const npmUpdateAvailable = !!(cleanNpm && semverNewer(cleanCur, cleanNpm));
|
|
10146
|
+
const gitUpdateAvailable = !!(cleanGit && semverNewer(cleanCur, cleanGit));
|
|
10147
|
+
const updateAvailable = npmUpdateAvailable || gitUpdateAvailable;
|
|
10148
|
+
|
|
10149
|
+
// Pick the newer of the two as the canonical "latest" for legacy UI.
|
|
10150
|
+
let latest = null;
|
|
10151
|
+
if (cleanNpm && cleanGit) {
|
|
10152
|
+
latest = semverNewer(cleanNpm, cleanGit) ? gitLatest : npmLatest;
|
|
10153
|
+
} else {
|
|
10154
|
+
latest = npmLatest || gitLatest;
|
|
10155
|
+
}
|
|
10156
|
+
|
|
10157
|
+
let source = null;
|
|
10158
|
+
if (npmUpdateAvailable && gitUpdateAvailable) source = 'both';
|
|
10159
|
+
else if (npmUpdateAvailable) source = 'npm';
|
|
10160
|
+
else if (gitUpdateAvailable) source = 'github';
|
|
10161
|
+
|
|
10162
|
+
_synabunUpdateCache = {
|
|
10163
|
+
current, npmLatest, gitLatest, latest,
|
|
10164
|
+
npmUpdateAvailable, gitUpdateAvailable, updateAvailable, source,
|
|
10165
|
+
checkedAt: new Date().toISOString(),
|
|
10166
|
+
npmError, gitError,
|
|
10167
|
+
};
|
|
10168
|
+
|
|
10169
|
+
if (updateAvailable) {
|
|
10170
|
+
console.log(` Updates: v${current} → v${latest} (${source})`);
|
|
10171
|
+
} else if (npmError && gitError) {
|
|
10172
|
+
console.log(` Updates: check failed (npm: ${npmError}; github: ${gitError})`);
|
|
10173
|
+
} else if (npmError) {
|
|
10174
|
+
console.log(` Updates: up to date (v${current}; npm check failed: ${npmError})`);
|
|
10175
|
+
} else if (gitError) {
|
|
10176
|
+
console.log(` Updates: up to date (v${current}; github check failed: ${gitError})`);
|
|
10177
|
+
} else {
|
|
10178
|
+
console.log(` Updates: up to date (v${current})`);
|
|
10117
10179
|
}
|
|
10118
10180
|
}
|
|
10119
10181
|
|
|
10120
10182
|
app.get('/api/system/version', async (req, res) => {
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10183
|
+
const force = req.query.force === '1';
|
|
10184
|
+
if (force || !_synabunUpdateCache.checkedAt) {
|
|
10185
|
+
try { await checkSynabunUpdate(); } catch {}
|
|
10186
|
+
} else {
|
|
10187
|
+
const age = Date.now() - new Date(_synabunUpdateCache.checkedAt).getTime();
|
|
10188
|
+
if (age > 60 * 60 * 1000) {
|
|
10189
|
+
checkSynabunUpdate().catch(() => {});
|
|
10125
10190
|
}
|
|
10126
10191
|
}
|
|
10127
|
-
res.json(
|
|
10192
|
+
res.json(_synabunUpdateCache);
|
|
10128
10193
|
});
|
|
10129
10194
|
|
|
10130
10195
|
// ═══════════════════════════════════════════
|
|
@@ -22376,8 +22441,8 @@ const httpServer = app.listen(PORT, async () => {
|
|
|
22376
22441
|
}
|
|
22377
22442
|
} catch { /* ok */ }
|
|
22378
22443
|
|
|
22379
|
-
// Check for npm
|
|
22380
|
-
try { await
|
|
22444
|
+
// Check for SynaBun updates (npm + github releases)
|
|
22445
|
+
try { await checkSynabunUpdate(); } catch {}
|
|
22381
22446
|
|
|
22382
22447
|
// Aggressively prune any stale bundled CLI tools left in our node_modules
|
|
22383
22448
|
// before the version check runs — defends against `npm install` not auto-
|