dsh-split-view 0.2.3 → 0.2.4

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.
Files changed (2) hide show
  1. package/lib/client.js +150 -0
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -434,6 +434,9 @@ function collectSyncedStorage() {
434
434
  const PREFS_KEY = "dsh-split:prefs";
435
435
  /** Locale namespace of the settings section and its rows. */
436
436
  const SETTINGS_NS = "settings.splitView";
437
+ // Keep in sync with the "version" field in package.json — the client bundle
438
+ // has no runtime version source (boot manifest entries carry no version).
439
+ const PLUGIN_VERSION = "0.2.4";
437
440
  const ACTION_IDS = ["split-right", "split-down", "close", "maximize"];
438
441
  const DEFAULT_SHORTCUTS = Object.freeze({
439
442
  "split-right": "Mod+Shift+D",
@@ -626,6 +629,12 @@ function collectSyncedStorage() {
626
629
  "reset.title": "恢复默认",
627
630
  "reset.button": "恢复默认设置",
628
631
  "reset.hint": "把快捷键、选中高亮色、标题栏开关全部还原为默认值。",
632
+ "version.current": "当前版本",
633
+ "version.latest": "最新",
634
+ "version.upToDate": "已是最新",
635
+ "version.upgrade": "升级",
636
+ "version.copied": "已复制",
637
+ "version.upgradeHint": "在终端执行上面的命令后,重启 dsh web 生效。",
629
638
  "reset.confirm": "确定把分屏设置全部恢复为默认值吗?"
630
639
  };
631
640
 
@@ -654,6 +663,12 @@ function collectSyncedStorage() {
654
663
  "reset.title": "Restore Defaults",
655
664
  "reset.button": "Restore default settings",
656
665
  "reset.hint": "Reset shortcuts, focus highlight color and the title-bar switch back to their defaults.",
666
+ "version.current": "Current version",
667
+ "version.latest": "Latest",
668
+ "version.upToDate": "Up to date",
669
+ "version.upgrade": "Upgrade",
670
+ "version.copied": "Copied",
671
+ "version.upgradeHint": "Run the command above in a terminal, then restart dsh web to apply.",
657
672
  "reset.confirm": "Restore all split-view settings to their defaults?"
658
673
  };
659
674
  //#endregion
@@ -1012,6 +1027,43 @@ function collectSyncedStorage() {
1012
1027
  boxSizing: "border-box"
1013
1028
  },
1014
1029
  buttonDanger: { color: "var(--dsw-alias-state-error-primary,#e5484d)" },
1030
+ versionRow: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" },
1031
+ versionText: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px", lineHeight: "18px" },
1032
+ versionBadge: {
1033
+ display: "inline-flex",
1034
+ alignItems: "center",
1035
+ height: "18px",
1036
+ padding: "0 8px",
1037
+ borderRadius: "999px",
1038
+ fontSize: "12px",
1039
+ lineHeight: "18px"
1040
+ },
1041
+ // Green intentionally uses a raw hex: no theme token is known for success.
1042
+ versionBadgeOk: { color: "#1a7f37", background: "rgba(26,127,55,.12)" },
1043
+ versionBadgeBehind: { color: "var(--dsw-alias-state-warning-primary,#b8860b)", background: "rgba(184,134,11,.14)" },
1044
+ versionButton: {
1045
+ height: "24px",
1046
+ padding: "0 10px",
1047
+ borderRadius: "7px",
1048
+ border: "1px solid var(--dsw-alias-border-l2)",
1049
+ background: "var(--dsw-alias-button-elevated-fill)",
1050
+ color: "var(--dsw-alias-label-primary)",
1051
+ cursor: "pointer",
1052
+ fontSize: "12px",
1053
+ font: "inherit",
1054
+ boxSizing: "border-box"
1055
+ },
1056
+ versionCommand: {
1057
+ marginTop: "8px",
1058
+ padding: "6px 10px",
1059
+ borderRadius: "6px",
1060
+ border: "1px solid var(--dsw-alias-border-l2)",
1061
+ background: "var(--dsw-alias-bg-layer-1)",
1062
+ color: "var(--dsw-alias-label-secondary)",
1063
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
1064
+ fontSize: "12px",
1065
+ userSelect: "text"
1066
+ },
1015
1067
  comboChip: {
1016
1068
  height: "28px",
1017
1069
  minWidth: "110px",
@@ -1266,6 +1318,95 @@ function collectSyncedStorage() {
1266
1318
  );
1267
1319
  }
1268
1320
 
1321
+ /** Compare two dotted numeric versions; returns -1 / 0 / 1. */
1322
+ function compareVersions(a, b) {
1323
+ const pa = String(a).split(".").map((n) => parseInt(n, 10) || 0);
1324
+ const pb = String(b).split(".").map((n) => parseInt(n, 10) || 0);
1325
+ const len = Math.max(pa.length, pb.length);
1326
+ for (let i = 0; i < len; i += 1) {
1327
+ const da = pa[i] || 0;
1328
+ const db = pb[i] || 0;
1329
+ if (da < db) return -1;
1330
+ if (da > db) return 1;
1331
+ }
1332
+ return 0;
1333
+ }
1334
+
1335
+ /**
1336
+ * Version footer at the bottom of the settings section: current version +
1337
+ * latest-version badge (queried straight from npmmirror/npmjs in the browser,
1338
+ * both registries are CORS-open) + an upgrade entry when behind. Upgrade =
1339
+ * copy the install command for the terminal; the client bundle cannot run
1340
+ * shell itself, and a one-click upgrade would need server-side orchestration
1341
+ * (the market plugin's approach), which this plugin deliberately avoids.
1342
+ */
1343
+ function VersionRow({ t }) {
1344
+ const [latest, setLatest] = React.useState(null);
1345
+ const [copied, setCopied] = React.useState(false);
1346
+ const [showCommand, setShowCommand] = React.useState(false);
1347
+ React.useEffect(() => {
1348
+ let alive = true;
1349
+ (async () => {
1350
+ // Debug hook: localStorage dsh-split:debugLatest forces the
1351
+ // "latest" value (exercises the upgrade path without a release).
1352
+ try {
1353
+ const forced = window.localStorage.getItem("dsh-split:debugLatest");
1354
+ if (forced && forced.trim()) {
1355
+ if (alive) setLatest(forced.trim());
1356
+ return;
1357
+ }
1358
+ } catch {
1359
+ // storage unavailable — fall through to the network
1360
+ }
1361
+ const sources = [
1362
+ "https://registry.npmmirror.com/dsh-split-view/latest",
1363
+ "https://registry.npmjs.org/dsh-split-view/latest"
1364
+ ];
1365
+ for (const url of sources) {
1366
+ try {
1367
+ const res = await fetch(url, { cache: "no-store" });
1368
+ if (!res.ok) continue;
1369
+ const data = await res.json();
1370
+ if (data && typeof data.version === "string") {
1371
+ if (alive) setLatest(data.version.trim());
1372
+ return;
1373
+ }
1374
+ } catch {
1375
+ // try the next source
1376
+ }
1377
+ }
1378
+ // Every source failed: stay on the current version alone —
1379
+ // the badge and the upgrade button simply do not render.
1380
+ })();
1381
+ return () => { alive = false; };
1382
+ }, []);
1383
+ const behind = latest !== null && compareVersions(PLUGIN_VERSION, latest) < 0;
1384
+ const command = "dsh plugin --profile web add dsh-split-view@latest";
1385
+ const upgrade = async () => {
1386
+ setShowCommand(true);
1387
+ try {
1388
+ await navigator.clipboard.writeText(command);
1389
+ setCopied(true);
1390
+ setTimeout(() => setCopied(false), 2500);
1391
+ } catch {
1392
+ // clipboard refused — the command box is already on the page
1393
+ }
1394
+ };
1395
+ return React.createElement("div", { style: { padding: "14px 0 2px" } },
1396
+ React.createElement("div", { style: settingsStyles.versionRow },
1397
+ React.createElement("span", { style: settingsStyles.versionText }, t("version.current") + " v" + PLUGIN_VERSION),
1398
+ latest !== null
1399
+ ? behind
1400
+ ? React.createElement("span", { style: Object.assign({}, settingsStyles.versionBadge, settingsStyles.versionBadgeBehind) }, t("version.latest") + " v" + latest)
1401
+ : React.createElement("span", { style: Object.assign({}, settingsStyles.versionBadge, settingsStyles.versionBadgeOk) }, t("version.upToDate"))
1402
+ : null,
1403
+ behind ? React.createElement("button", { type: "button", style: settingsStyles.versionButton, onClick: upgrade }, copied ? t("version.copied") : t("version.upgrade")) : null
1404
+ ),
1405
+ behind && showCommand ? React.createElement("div", { style: settingsStyles.versionCommand }, command) : null,
1406
+ behind && showCommand ? React.createElement("div", { style: settingsStyles.hint }, t("version.upgradeHint")) : null
1407
+ );
1408
+ }
1409
+
1269
1410
  /**
1270
1411
  * Register the settings section + rows.
1271
1412
  * @param ctx - client cordis context.
@@ -1350,6 +1491,15 @@ function collectSyncedStorage() {
1350
1491
  inject: bindRow
1351
1492
  }, ResetRow));
1352
1493
 
1494
+ ctx.slots.inject("settings.splitView.item", () => ctx.slots.register({
1495
+ name: "settings.splitView.item",
1496
+ id: "split-view-version",
1497
+ order: 95,
1498
+ store: rowStore,
1499
+ locale: SETTINGS_NS,
1500
+ inject: bindRow
1501
+ }, VersionRow));
1502
+
1353
1503
  // The shell's settings nav hard-codes the per-section icon by entry id
1354
1504
  // (models / agent-presets / plugins get distinct icons, everything else
1355
1505
  // falls back to the generic gear) and the settings.section contract
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-split-view",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Multi-session split view for the DeepSeek Harness web GUI: the top window shadows the root slot with a resizable split container; each pane is an iframe running a full DSH client pinned to its own session, with per-pane title bars, LRU hibernation and a dual-loopback origin pool.",
5
5
  "license": "MIT",
6
6
  "type": "module",