dsh-update-status 0.1.4 → 0.1.6
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 +28 -0
- package/README.md +49 -11
- package/README.zh.md +49 -11
- package/assets/sidebar-chip.png +0 -0
- package/assets/update-panel.png +0 -0
- package/cordis.patch.yml +4 -1
- package/docs/RELEASING.md +1 -1
- package/lib/client.js +139 -33
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +47 -7
- package/lib/index.js +55 -46
- package/package.json +15 -11
package/lib/client.js
CHANGED
|
@@ -42,9 +42,9 @@ window.__ModuleLoader__.load({
|
|
|
42
42
|
* JSON-only contract shared by the Host and Web halves.
|
|
43
43
|
*
|
|
44
44
|
* The static package uses the authenticated Connection RPC channel because
|
|
45
|
-
* `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs. The
|
|
46
|
+
* endpoint vocabulary remains deliberately small and private to this plugin
|
|
47
|
+
* channel.
|
|
48
48
|
*/
|
|
49
49
|
const PLUGIN_ID$1 = "dsh-update-status";
|
|
50
50
|
const PACKAGE_NAME = "@deepseek-ai/dsh";
|
|
@@ -88,6 +88,25 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
//#endregion
|
|
91
|
+
//#region src/shared/visual-state.ts
|
|
92
|
+
/**
|
|
93
|
+
* Only a read the plugin could not complete is a chip-level problem.
|
|
94
|
+
*
|
|
95
|
+
* An advisory notice — a preview channel this bundle has not been verified against,
|
|
96
|
+
* say — leaves the chip surface alone and belongs in the panel. Repainting the brand
|
|
97
|
+
* row for a plugin-side bookkeeping fact is the surprise the breathing dot replaced:
|
|
98
|
+
* an operator who upgrades DSH ahead of this bundle must not watch the chip turn red.
|
|
99
|
+
*
|
|
100
|
+
* A Host older than `warningKind` sends no field at all; `undefined` then keeps the
|
|
101
|
+
* previous behaviour (any warning is a problem) rather than silently dropping a real
|
|
102
|
+
* failure on the floor.
|
|
103
|
+
*/
|
|
104
|
+
function isChipProblem(status) {
|
|
105
|
+
if (status === null) return false;
|
|
106
|
+
if (status.warningKind === void 0) return status.warning !== null;
|
|
107
|
+
return status.warningKind === "failure";
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
91
110
|
//#region src/client/i18n.ts
|
|
92
111
|
const DICTIONARY = {
|
|
93
112
|
"brand.status": {
|
|
@@ -418,7 +437,7 @@ window.__ModuleLoader__.load({
|
|
|
418
437
|
}
|
|
419
438
|
function visualState(status, loading, error) {
|
|
420
439
|
if (loading) return "loading";
|
|
421
|
-
if (error !== null || status
|
|
440
|
+
if (error !== null || isChipProblem(status)) return status?.hasUpdate === true ? "update" : "problem";
|
|
422
441
|
return status?.hasUpdate === true ? "update" : "current";
|
|
423
442
|
}
|
|
424
443
|
function badgeLabel(status, loading, error) {
|
|
@@ -466,6 +485,7 @@ window.__ModuleLoader__.load({
|
|
|
466
485
|
children: shortVersion(version)
|
|
467
486
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
468
487
|
className: "dus-dot",
|
|
488
|
+
"data-state": state,
|
|
469
489
|
"data-update": snapshot.status?.hasUpdate === true || void 0,
|
|
470
490
|
"data-loading": snapshot.loading || void 0,
|
|
471
491
|
"aria-hidden": "true"
|
|
@@ -494,6 +514,7 @@ window.__ModuleLoader__.load({
|
|
|
494
514
|
children: "↟"
|
|
495
515
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
496
516
|
className: "dus-dot dus-footer-dot",
|
|
517
|
+
"data-state": state,
|
|
497
518
|
"data-update": snapshot.status?.hasUpdate === true || void 0,
|
|
498
519
|
"data-loading": state === "loading" || void 0
|
|
499
520
|
})]
|
|
@@ -941,6 +962,7 @@ window.__ModuleLoader__.load({
|
|
|
941
962
|
const latestVersion = record.latestVersion === null ? null : stringOrNull(record.latestVersion);
|
|
942
963
|
const checkedAt = record.checkedAt === null ? null : stringOrNull(record.checkedAt);
|
|
943
964
|
const warning = record.warning === null ? null : stringOrNull(record.warning);
|
|
965
|
+
const warningKind = record.warningKind === "failure" || record.warningKind === "notice" ? record.warningKind : void 0;
|
|
944
966
|
const publishedAt = record.publishedAt === null ? null : stringOrNull(record.publishedAt);
|
|
945
967
|
if (record.latestVersion !== null && latestVersion === null || record.checkedAt !== null && checkedAt === null || record.warning !== null && warning === null || record.publishedAt !== null && publishedAt === null) return void 0;
|
|
946
968
|
const channels = [];
|
|
@@ -964,6 +986,7 @@ window.__ModuleLoader__.load({
|
|
|
964
986
|
cached: record.cached,
|
|
965
987
|
checkedAt,
|
|
966
988
|
warning,
|
|
989
|
+
warningKind,
|
|
967
990
|
installKind: record.installKind,
|
|
968
991
|
upgradeCommand: record.upgradeCommand,
|
|
969
992
|
releaseUrl: record.releaseUrl,
|
|
@@ -1204,28 +1227,14 @@ window.__ModuleLoader__.load({
|
|
|
1204
1227
|
for (const listener of this.listeners) listener();
|
|
1205
1228
|
}
|
|
1206
1229
|
};
|
|
1207
|
-
/**
|
|
1230
|
+
/**
|
|
1231
|
+
* The one settings namespace, which on DSH 0.1.7 IS the Loader entry id in
|
|
1232
|
+
* `cordis.patch.yml`: the Host half's volatile `Config` fields are projected as
|
|
1233
|
+
* that entry's form, and the browser half addresses the same id through
|
|
1234
|
+
* `ctx.configForms.get(...)`.
|
|
1235
|
+
*/
|
|
1208
1236
|
const SETTINGS_NAMESPACE = PLUGIN_ID$1;
|
|
1209
1237
|
//#endregion
|
|
1210
|
-
//#region src/client/settings/scopeFaces.ts
|
|
1211
|
-
/** Guard: does an object look like a binder with bind()? */
|
|
1212
|
-
function binderOf(raw) {
|
|
1213
|
-
if (raw === null || typeof raw !== "object") return void 0;
|
|
1214
|
-
const binder = raw.settingsScope;
|
|
1215
|
-
if (binder === null || typeof binder !== "object") return void 0;
|
|
1216
|
-
if (typeof binder.bind !== "function") return void 0;
|
|
1217
|
-
return binder;
|
|
1218
|
-
}
|
|
1219
|
-
/** Guard: does an object look like a bound scope? */
|
|
1220
|
-
function scopeOf(value) {
|
|
1221
|
-
if (value === null || typeof value !== "object") return void 0;
|
|
1222
|
-
const scope = value;
|
|
1223
|
-
if (typeof scope.getSnapshot !== "function") return void 0;
|
|
1224
|
-
if (typeof scope.subscribe !== "function") return void 0;
|
|
1225
|
-
if (typeof scope.set !== "function") return void 0;
|
|
1226
|
-
return scope;
|
|
1227
|
-
}
|
|
1228
|
-
//#endregion
|
|
1229
1238
|
//#region src/client/settings/hostDirectScope.ts
|
|
1230
1239
|
/** A refused namespace write; `code` keeps the Host's own diagnosis. */
|
|
1231
1240
|
var SettingsWriteFailure = class extends Error {
|
|
@@ -1447,6 +1456,65 @@ window.__ModuleLoader__.load({
|
|
|
1447
1456
|
};
|
|
1448
1457
|
}
|
|
1449
1458
|
//#endregion
|
|
1459
|
+
//#region src/client/settings/configFormScope.ts
|
|
1460
|
+
/**
|
|
1461
|
+
* Guard: read the `configForms` face off an injection payload.
|
|
1462
|
+
*
|
|
1463
|
+
* The literal service key is read inside a `try`, because an injected payload
|
|
1464
|
+
* proxy can refuse a member read (the failure this plugin already hit on the
|
|
1465
|
+
* dotted `remote.settings` parent). Absence and refusal both answer
|
|
1466
|
+
* `undefined`, which leaves the channel on its direct Host path.
|
|
1467
|
+
*
|
|
1468
|
+
* @param raw - the payload a `ctx.inject(['configForms'], …)` callback received.
|
|
1469
|
+
* @returns the binder face, or undefined when this page has no settings forms.
|
|
1470
|
+
*/
|
|
1471
|
+
function configFormsOf(raw) {
|
|
1472
|
+
if (raw === null || typeof raw !== "object") return void 0;
|
|
1473
|
+
let face;
|
|
1474
|
+
try {
|
|
1475
|
+
face = raw["configForms"];
|
|
1476
|
+
} catch {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (face === null || typeof face !== "object") return void 0;
|
|
1480
|
+
if (typeof face.get !== "function") return void 0;
|
|
1481
|
+
return face;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Project one official config form onto the plugin's settings-scope contract.
|
|
1485
|
+
*
|
|
1486
|
+
* @param form - the shared form for the plugin's own Loader entry id.
|
|
1487
|
+
* @returns a scope the preferences store and the channel selection can use as-is.
|
|
1488
|
+
*/
|
|
1489
|
+
function configFormScope(form) {
|
|
1490
|
+
return {
|
|
1491
|
+
getSnapshot() {
|
|
1492
|
+
const snapshot = form.getSnapshot();
|
|
1493
|
+
return {
|
|
1494
|
+
status: snapshot.status,
|
|
1495
|
+
value: snapshot.value,
|
|
1496
|
+
base: snapshot.base,
|
|
1497
|
+
user: snapshot.user,
|
|
1498
|
+
revision: snapshot.revision,
|
|
1499
|
+
writable: snapshot.writable,
|
|
1500
|
+
mode: snapshot.mode
|
|
1501
|
+
};
|
|
1502
|
+
},
|
|
1503
|
+
subscribe(listener) {
|
|
1504
|
+
return form.subscribe(listener);
|
|
1505
|
+
},
|
|
1506
|
+
async set(field, value) {
|
|
1507
|
+
let accepted;
|
|
1508
|
+
try {
|
|
1509
|
+
accepted = await form.set(field, value);
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
throw new SettingsWriteFailure("settings/unreachable", error instanceof Error ? error.message : String(error));
|
|
1512
|
+
}
|
|
1513
|
+
if (!accepted) throw new SettingsWriteFailure("settings/conflict");
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
//#endregion
|
|
1450
1518
|
//#region src/client/settings/settingsChannel.ts
|
|
1451
1519
|
/** Snapshot used while no channel has answered yet. */
|
|
1452
1520
|
const LOADING_SNAPSHOT = Object.freeze({
|
|
@@ -1561,17 +1629,55 @@ window.__ModuleLoader__.load({
|
|
|
1561
1629
|
const UPDATE_STATUS_CSS = `
|
|
1562
1630
|
.dus-brand-name{align-items:center;gap:6px;height:24px;max-width:100%;min-width:0;display:inline-flex}
|
|
1563
1631
|
.dus-brand-deepseek{font-size:14px;font-weight:650;letter-spacing:-.015em;line-height:24px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1564
|
-
|
|
1632
|
+
/* The chip is a NEUTRAL SECOND-LEVEL SURFACE, never a high-contrast pill. It used
|
|
1633
|
+
to be label-primary + label-primary-inverted, which is a near-white box with dark
|
|
1634
|
+
text on the dark shell — a white frame that swallows the amber halo and fights the
|
|
1635
|
+
warn colour. --dsw-alias-button-floating-hover is the one palette surface that is
|
|
1636
|
+
grey in BOTH themes (#f1f3f5 light / #353638 dark), matching the reference: a light
|
|
1637
|
+
grey chip on the light shell, a dark grey chip with white text on the dark one. The
|
|
1638
|
+
sibling button-floating-fill is pure white in light mode and changes nothing there.
|
|
1639
|
+
Text stays label-primary (#0f1115 light / #f9fafb dark), so it is never low-contrast.
|
|
1640
|
+
Every colour here is a theme token, so the chip follows whatever appearance DSH
|
|
1641
|
+
resolves — light, dark, or system-follows-the-OS — with no plugin-side detection,
|
|
1642
|
+
no media query and no per-theme branch to keep in sync. */
|
|
1643
|
+
.dus-badge{--dus-badge-surface:var(--dsw-alias-button-floating-hover,#f1f3f5);align-items:center;background:var(--dus-badge-surface);border:0;border-radius:4px;color:var(--dsw-alias-label-primary,#0f1115);cursor:pointer;display:inline-flex;flex:none;font-family:var(--ds-font-family-code,var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace));font-size:10px;font-variant-numeric:tabular-nums;font-weight:600;gap:4px;height:16px;line-height:16px;max-width:140px;outline:none;padding:0 6px;touch-action:manipulation;user-select:none;white-space:nowrap}
|
|
1565
1644
|
.dus-badge:focus-visible,.dus-footer-button:focus-visible,.dus-action:focus-visible,.dus-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
1645
|
+
/* Hover has to read as "raised" in BOTH themes. brightness() only ever brightens, and on
|
|
1646
|
+
the light shell it turns the grey chip pure white — the chip all but disappears. Mixing
|
|
1647
|
+
a little of the theme's own label colour into the surface darkens it in light mode and
|
|
1648
|
+
lightens it in dark mode, so one declaration stays correct in both. The brightness
|
|
1649
|
+
filter remains the fallback: an engine without color-mix drops only the second
|
|
1650
|
+
declaration, never the whole hover rule. */
|
|
1566
1651
|
.dus-badge:hover{filter:brightness(1.08)}
|
|
1567
|
-
.dus-badge
|
|
1568
|
-
|
|
1652
|
+
@supports (background:color-mix(in srgb,red 50%,transparent)){.dus-badge:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 8%,var(--dus-badge-surface));filter:none}}
|
|
1653
|
+
/* The failure state is the one deliberate repaint, and it follows the shell's own
|
|
1654
|
+
badge pattern (brokenBadge): error fill with a bg-layer-3 label, which is white
|
|
1655
|
+
text on the dark red of the light theme and dark text on the light red of the dark
|
|
1656
|
+
theme — readable in both, unlike the fixed white text it replaces. */
|
|
1657
|
+
.dus-badge[data-error=true]{background:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-bg-layer-3,#fff)}
|
|
1569
1658
|
.dus-badge-version{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1570
1659
|
.dus-dot{background:currentColor;border-radius:50%;display:inline-block;flex:0 0 auto;height:5px;opacity:.9;width:5px}
|
|
1571
|
-
|
|
1660
|
+
/* An available update repaints NOTHING: the chip keeps its normal fill and only
|
|
1661
|
+
the amber dot breathes. The halo is animated with the dot so the glow grows
|
|
1662
|
+
and shrinks as one light, never as a separate ripple. The update state is
|
|
1663
|
+
declared AFTER the loading state, so a re-check on a known update keeps the
|
|
1664
|
+
amber glow instead of falling back to the neutral loading pulse.
|
|
1665
|
+
Each box-shadow is declared twice: a literal amber first for any engine
|
|
1666
|
+
without color-mix, then the halo mixed from the theme's own warn colour so the
|
|
1667
|
+
glow always matches the dot in both light and dark themes. */
|
|
1668
|
+
/* The dot carries the whole status. Three states, in precedence order:
|
|
1669
|
+
up to date = a plain green circle (success token, never the text colour, so a
|
|
1670
|
+
dark theme no longer paints a black dot that reads as "off"); checking = the
|
|
1671
|
+
neutral grey pulse; a pending update = the amber dot below, which is the only
|
|
1672
|
+
state allowed to breathe or glow.
|
|
1673
|
+
The up-to-date rule is declared BEFORE loading and update: equal specificity, so
|
|
1674
|
+
the later, louder states win if two ever land on the same dot. */
|
|
1675
|
+
.dus-dot[data-state=current]{background:var(--dsw-alias-state-success-primary,#22c55e);opacity:1}
|
|
1572
1676
|
.dus-dot[data-loading=true]{animation:dus-pulse .9s ease-in-out infinite}
|
|
1677
|
+
.dus-dot[data-update=true]{--dus-update-color:var(--dsw-alias-state-warn-primary,#f59e0b);animation:dus-update-pulse 1.6s ease-in-out infinite;background:var(--dus-update-color);height:6px;opacity:1;width:6px}
|
|
1573
1678
|
@keyframes dus-pulse{0%,100%{opacity:.45;transform:scale(.82)}50%{opacity:1;transform:scale(1.15)}}
|
|
1574
|
-
@
|
|
1679
|
+
@keyframes dus-update-pulse{0%,100%{box-shadow:0 0 2px 0 rgba(245,158,11,.4);box-shadow:0 0 2px 0 color-mix(in srgb,var(--dus-update-color) 45%,transparent);transform:scale(.8)}50%{box-shadow:0 0 7px 2px rgba(245,158,11,.72);box-shadow:0 0 7px 2px color-mix(in srgb,var(--dus-update-color) 72%,transparent);transform:scale(1.15)}}
|
|
1680
|
+
@media (prefers-reduced-motion:reduce){.dus-dot[data-update=true],.dus-dot[data-loading=true]{animation:none}.dus-dot[data-update=true]{box-shadow:0 0 5px 1px rgba(245,158,11,.6);box-shadow:0 0 5px 1px color-mix(in srgb,var(--dus-update-color) 62%,transparent)}}
|
|
1575
1681
|
.dus-footer-button{align-items:center;background:transparent;border:0;border-radius:10px;color:var(--dsw-alias-label-secondary);cursor:pointer;display:flex;height:44px;justify-content:center;min-height:44px;min-width:44px;padding:0;position:relative;touch-action:manipulation;width:44px}
|
|
1576
1682
|
.dus-footer-button:hover{background:var(--dsw-alias-button-floating-hover);color:var(--dsw-alias-label-primary)}
|
|
1577
1683
|
.dus-footer-icon{font-size:18px;line-height:1}
|
|
@@ -1717,11 +1823,11 @@ window.__ModuleLoader__.load({
|
|
|
1717
1823
|
};
|
|
1718
1824
|
}, "dsh-update-status: styles, settings channel and first status read");
|
|
1719
1825
|
try {
|
|
1720
|
-
ctx.inject(["
|
|
1826
|
+
ctx.inject(["configForms"], (raw) => {
|
|
1721
1827
|
try {
|
|
1722
|
-
const
|
|
1723
|
-
if (
|
|
1724
|
-
channel.setOfficial(
|
|
1828
|
+
const forms = configFormsOf(raw);
|
|
1829
|
+
if (forms === void 0) return;
|
|
1830
|
+
channel.setOfficial(configFormScope(forms.get(SETTINGS_NAMESPACE)));
|
|
1725
1831
|
} catch {}
|
|
1726
1832
|
});
|
|
1727
1833
|
} catch {}
|