@mars-sea/dsh-commandcode-provider 0.6.0 → 0.6.1
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 +14 -0
- package/lib/client.js +135 -19
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +19 -10
- package/lib/index.js +278 -202
- package/lib/index.js.map +1 -1
- package/package.json +19 -16
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.6.1] - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **New model: Ox Alpha (`stealth/ox-alpha`, command-code@1.31.0).** A free 1M-context reasoning model with vision input, included on every plan: it appears in the picker under the Go tier with `FREE` · `Image` · `1M` markers, is whitelisted as vision-capable and auto-thinking (it reasons automatically with no selectable effort levels), and carries a `FREE` deal while the stealth preview lasts.
|
|
12
|
+
- **The settings page now shows the plugin version** as a muted footer line ("Command Code Provider v0.6.1"), read from package.json at build time so it always matches the published release.
|
|
13
|
+
- **Settings-page input polish:** a brief "Saved ✓" confirmation flashes after each accepted save (failures already showed an error); the two millisecond timeout fields now validate their range while typing with specific messages ("must be at least 1" / "above the allowed maximum") instead of a generic save-time failure; and the API-key fields (default + every account) gained a Show/Hide toggle so a pasted key can be spot-checked.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Retries are now persistent (opencode-style) instead of two attempts.** Transient failures — rate limits, server errors, timeouts, transport drops, empty responses — retry up to 1000 times at the agent-step boundary, with waits doubling from 500 ms and capping at 15 minutes (±10% jitter). Permanent failures (an invalid key, unsupported content, plan rejections) still fail fast on the first attempt. Waits are smart: a 429's `Retry-After` header and the rotation pool's earliest known window-reset time are honored verbatim up to the 15-minute cap, so when every account's usage window is exhausted the session sleeps through the window and recovers in place instead of dying after two quick tries.
|
|
18
|
+
- **Synced with the official command-code@1.31.0 CLI** (upstream moved 1.28.4 → 1.29.0 → 1.30.0 → 1.30.1 → 1.31.0; the intermediate releases shipped BYOK provider support, model-traffic routing, and CLI UI fixes — none touch the Provider API). `COMMAND_CODE_CLI_VERSION` is now `1.31.0`. The only snapshot change across those releases is the new Ox Alpha model above; wire protocol, endpoints, effort map, plan maps, deals, and peak pricing are all unchanged.
|
|
19
|
+
- **Verified compatibility with DeepSeek Harness 0.1.0-rc.7 / 0.1.0-rc.8 / 0.1.1-rc.1**, including rc.8's reworked client boot protocol: the client bundle now declares its module-graph dependency (`dsh.client.external`), so client-module load order no longer relies on the platform seed table. The supported peer range widened to `^0.1.0-rc.6 || ^0.1.1-rc.1`, so 0.1.1 hosts install without unmet-peer-dependency warnings.
|
|
20
|
+
|
|
7
21
|
## [0.6.0] - 2026-08-19
|
|
8
22
|
|
|
9
23
|
### Changed
|
package/lib/client.js
CHANGED
|
@@ -59,8 +59,15 @@ window.__ModuleLoader__.load({
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
62
|
+
/**
|
|
63
|
+
* A numeric field; an empty draft clears it, anything non-numeric blocks
|
|
64
|
+
* save, and an optional inclusive `bounds` range rejects out-of-range values
|
|
65
|
+
* with a specific reason (the Host schema would reject them at save time with
|
|
66
|
+
* only a generic failure — catching it here names the problem while typing).
|
|
67
|
+
* Decimals pass: the Host schema is `z.number()` too, and a fractional
|
|
68
|
+
* millisecond value is harmless even if pointless.
|
|
69
|
+
*/
|
|
70
|
+
function numberField$1(field, bounds) {
|
|
64
71
|
return {
|
|
65
72
|
field,
|
|
66
73
|
format: (value) => typeof value === "number" ? String(value) : "",
|
|
@@ -68,10 +75,22 @@ window.__ModuleLoader__.load({
|
|
|
68
75
|
const trimmed = text.trim();
|
|
69
76
|
if (trimmed === "") return { kind: "clear" };
|
|
70
77
|
const parsed = Number(trimmed);
|
|
71
|
-
|
|
78
|
+
if (!Number.isFinite(parsed)) return {
|
|
79
|
+
kind: "invalid",
|
|
80
|
+
reason: "format"
|
|
81
|
+
};
|
|
82
|
+
if (bounds?.min !== void 0 && parsed < bounds.min) return {
|
|
83
|
+
kind: "invalid",
|
|
84
|
+
reason: "tooSmall"
|
|
85
|
+
};
|
|
86
|
+
if (bounds?.max !== void 0 && parsed > bounds.max) return {
|
|
87
|
+
kind: "invalid",
|
|
88
|
+
reason: "tooLarge"
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
72
91
|
kind: "set",
|
|
73
92
|
value: parsed
|
|
74
|
-
}
|
|
93
|
+
};
|
|
75
94
|
}
|
|
76
95
|
};
|
|
77
96
|
}
|
|
@@ -95,16 +114,26 @@ window.__ModuleLoader__.load({
|
|
|
95
114
|
kind: "set",
|
|
96
115
|
value: false
|
|
97
116
|
};
|
|
98
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
kind: "invalid",
|
|
119
|
+
reason: "format"
|
|
120
|
+
};
|
|
99
121
|
}
|
|
100
122
|
};
|
|
101
123
|
}
|
|
124
|
+
const MAX_TIMEOUT_MS = 2147483647;
|
|
102
125
|
/** The fields this page edits inside the `llm-commandcode` namespace. */
|
|
103
126
|
const SECTION_FIELDS = [
|
|
104
127
|
textField("apiBase"),
|
|
105
128
|
textField("workingDir"),
|
|
106
|
-
numberField$1("requestTimeoutMs"
|
|
107
|
-
|
|
129
|
+
numberField$1("requestTimeoutMs", {
|
|
130
|
+
min: 1,
|
|
131
|
+
max: MAX_TIMEOUT_MS
|
|
132
|
+
}),
|
|
133
|
+
numberField$1("streamIdleTimeoutMs", {
|
|
134
|
+
min: 1,
|
|
135
|
+
max: MAX_TIMEOUT_MS
|
|
136
|
+
}),
|
|
108
137
|
booleanField$1("filterModelsByPlan"),
|
|
109
138
|
textField("activeAccount")
|
|
110
139
|
];
|
|
@@ -136,6 +165,7 @@ window.__ModuleLoader__.load({
|
|
|
136
165
|
keyDrafts = /* @__PURE__ */ new Map();
|
|
137
166
|
saving = false;
|
|
138
167
|
failed = false;
|
|
168
|
+
savedCount = 0;
|
|
139
169
|
/**
|
|
140
170
|
* @param scope - bound scope for the `llm-commandcode` namespace.
|
|
141
171
|
* @param api - credentials wire face.
|
|
@@ -206,7 +236,8 @@ window.__ModuleLoader__.load({
|
|
|
206
236
|
text: this.staged.get("apiKey")?.text ?? "",
|
|
207
237
|
clear: false,
|
|
208
238
|
overridden: false,
|
|
209
|
-
invalid: false
|
|
239
|
+
invalid: false,
|
|
240
|
+
invalidReason: void 0
|
|
210
241
|
},
|
|
211
242
|
apiBase: this.field("apiBase"),
|
|
212
243
|
workingDir: this.field("workingDir"),
|
|
@@ -220,7 +251,8 @@ window.__ModuleLoader__.load({
|
|
|
220
251
|
dirty: plan.length > 0 || this.accountsDirty(),
|
|
221
252
|
invalid: plan.some((item) => item.run === void 0),
|
|
222
253
|
saving: this.saving,
|
|
223
|
-
failed: this.failed
|
|
254
|
+
failed: this.failed,
|
|
255
|
+
savedCount: this.savedCount
|
|
224
256
|
};
|
|
225
257
|
}
|
|
226
258
|
/** Stage a new extra account (saved on the next `save()`). */
|
|
@@ -322,6 +354,7 @@ window.__ModuleLoader__.load({
|
|
|
322
354
|
this.saving = false;
|
|
323
355
|
this.failed = !landed;
|
|
324
356
|
if (landed) {
|
|
357
|
+
this.savedCount += 1;
|
|
325
358
|
this.staged.clear();
|
|
326
359
|
this.clearAccountStaging();
|
|
327
360
|
} else this.reconcileAccountStaging();
|
|
@@ -356,14 +389,16 @@ window.__ModuleLoader__.load({
|
|
|
356
389
|
text: spec.format(this.sectionValue(field)),
|
|
357
390
|
clear: false,
|
|
358
391
|
overridden: this.stored(field),
|
|
359
|
-
invalid: false
|
|
392
|
+
invalid: false,
|
|
393
|
+
invalidReason: void 0
|
|
360
394
|
};
|
|
361
395
|
const parsed = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
|
|
362
396
|
return {
|
|
363
397
|
text: staged.text,
|
|
364
398
|
clear: staged.clear,
|
|
365
399
|
overridden: parsed.kind === "set",
|
|
366
|
-
invalid: parsed.kind === "invalid"
|
|
400
|
+
invalid: parsed.kind === "invalid",
|
|
401
|
+
invalidReason: parsed.kind === "invalid" ? parsed.reason : void 0
|
|
367
402
|
};
|
|
368
403
|
}
|
|
369
404
|
sectionValue(field) {
|
|
@@ -809,6 +844,21 @@ window.__ModuleLoader__.load({
|
|
|
809
844
|
}]
|
|
810
845
|
};
|
|
811
846
|
//#endregion
|
|
847
|
+
//#region src/client/version.ts
|
|
848
|
+
/**
|
|
849
|
+
* The plugin's own version, read from package.json at build time.
|
|
850
|
+
*
|
|
851
|
+
* The client bundle inlines the JSON import (rolldown resolves it during the
|
|
852
|
+
* tsdown build; node tests read it through tsx), so the rendered value always
|
|
853
|
+
* matches the published package version with no second constant to keep in
|
|
854
|
+
* sync. Rendered as a muted footer line on the settings page so a user can
|
|
855
|
+
* report the exact build they run.
|
|
856
|
+
*
|
|
857
|
+
* @module dsh-commandcode-provider/client/version
|
|
858
|
+
*/
|
|
859
|
+
/** The published package version (e.g. `'0.6.0'`). */
|
|
860
|
+
const PLUGIN_VERSION = "0.6.1";
|
|
861
|
+
//#endregion
|
|
812
862
|
//#region src/client/section.tsx
|
|
813
863
|
/**
|
|
814
864
|
* React component for the "Command Code" settings page (browser half).
|
|
@@ -861,11 +911,17 @@ window.__ModuleLoader__.load({
|
|
|
861
911
|
}),
|
|
862
912
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
863
913
|
className: state.invalid ? "cc-invalid" : "cc-hint",
|
|
864
|
-
children: state.invalid ? t
|
|
914
|
+
children: state.invalid ? invalidCopy(state.invalidReason, t) : hint
|
|
865
915
|
})
|
|
866
916
|
]
|
|
867
917
|
});
|
|
868
918
|
}
|
|
919
|
+
/** The per-field error copy for a staged draft's failure reason. */
|
|
920
|
+
function invalidCopy(reason, t) {
|
|
921
|
+
if (reason === "tooSmall") return t("numberTooSmall");
|
|
922
|
+
if (reason === "tooLarge") return t("numberTooLarge");
|
|
923
|
+
return t("invalidNumber");
|
|
924
|
+
}
|
|
869
925
|
/**
|
|
870
926
|
* One boolean field row rendered as a toggle. The staged text is `'true'` /
|
|
871
927
|
* `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
|
|
@@ -911,8 +967,13 @@ window.__ModuleLoader__.load({
|
|
|
911
967
|
})]
|
|
912
968
|
});
|
|
913
969
|
}
|
|
914
|
-
/**
|
|
915
|
-
|
|
970
|
+
/**
|
|
971
|
+
* The API-key control: write-only, reports configured state, never echoes the
|
|
972
|
+
* key. The input is masked by default with a Show/Hide toggle so a pasted key
|
|
973
|
+
* can be spot-checked without leaving the field.
|
|
974
|
+
*/
|
|
975
|
+
function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, showLabel, hideLabel, onEdit }) {
|
|
976
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
916
977
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
917
978
|
className: "cc-field",
|
|
918
979
|
children: [
|
|
@@ -922,19 +983,26 @@ window.__ModuleLoader__.load({
|
|
|
922
983
|
className: "cc-label",
|
|
923
984
|
htmlFor: "cc-api-key",
|
|
924
985
|
children: label
|
|
925
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
986
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
926
987
|
className: "cc-badges",
|
|
927
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
988
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
928
989
|
className: configured ? "cc-badge" : "cc-badgeMuted",
|
|
929
990
|
children: configured ? configuredLabel : unconfiguredLabel
|
|
930
|
-
})
|
|
991
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
992
|
+
type: "button",
|
|
993
|
+
className: "cc-reset",
|
|
994
|
+
disabled,
|
|
995
|
+
onClick: () => setVisible((value) => !value),
|
|
996
|
+
children: visible ? hideLabel : showLabel
|
|
997
|
+
})]
|
|
931
998
|
})]
|
|
932
999
|
}),
|
|
933
1000
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
934
1001
|
id: "cc-api-key",
|
|
935
1002
|
className: "cc-input",
|
|
936
|
-
type: "password",
|
|
1003
|
+
type: visible ? "text" : "password",
|
|
937
1004
|
autoComplete: "off",
|
|
1005
|
+
spellCheck: false,
|
|
938
1006
|
value: state.text,
|
|
939
1007
|
disabled,
|
|
940
1008
|
onChange: (event) => onEdit(event.target.value)
|
|
@@ -1287,6 +1355,7 @@ window.__ModuleLoader__.load({
|
|
|
1287
1355
|
*/
|
|
1288
1356
|
function AccountRow({ account, disabled, t, onLabel, onKey, onRemove }) {
|
|
1289
1357
|
const locked = !account.writable;
|
|
1358
|
+
const [keyVisible, setKeyVisible] = (0, react.useState)(false);
|
|
1290
1359
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1291
1360
|
className: "cc-field",
|
|
1292
1361
|
children: [
|
|
@@ -1307,6 +1376,13 @@ window.__ModuleLoader__.load({
|
|
|
1307
1376
|
className: account.configured ? "cc-badge" : "cc-badgeMuted",
|
|
1308
1377
|
children: account.configured ? t("apiKeySet") : t("apiKeyUnset")
|
|
1309
1378
|
}),
|
|
1379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1380
|
+
type: "button",
|
|
1381
|
+
className: "cc-reset",
|
|
1382
|
+
disabled,
|
|
1383
|
+
onClick: () => setKeyVisible((value) => !value),
|
|
1384
|
+
children: keyVisible ? t("hide") : t("show")
|
|
1385
|
+
}),
|
|
1310
1386
|
account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1311
1387
|
type: "button",
|
|
1312
1388
|
className: "cc-reset",
|
|
@@ -1328,8 +1404,9 @@ window.__ModuleLoader__.load({
|
|
|
1328
1404
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1329
1405
|
id: `cc-account-key-${account.id}`,
|
|
1330
1406
|
className: "cc-input",
|
|
1331
|
-
type: "password",
|
|
1407
|
+
type: keyVisible ? "text" : "password",
|
|
1332
1408
|
autoComplete: "off",
|
|
1409
|
+
spellCheck: false,
|
|
1333
1410
|
placeholder: t("accountKey"),
|
|
1334
1411
|
value: account.keyText,
|
|
1335
1412
|
disabled: disabled || locked,
|
|
@@ -1432,6 +1509,21 @@ window.__ModuleLoader__.load({
|
|
|
1432
1509
|
]
|
|
1433
1510
|
});
|
|
1434
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Show the "Saved ✓" affordance for a short window after each accepted save.
|
|
1514
|
+
* The controller only counts saves (`savedCount`); the flash timing lives
|
|
1515
|
+
* here so the state machine stays timer-free.
|
|
1516
|
+
*/
|
|
1517
|
+
function useSavedFlash(tick) {
|
|
1518
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
1519
|
+
(0, react.useEffect)(() => {
|
|
1520
|
+
if (tick === 0) return;
|
|
1521
|
+
setVisible(true);
|
|
1522
|
+
const timer = setTimeout(() => setVisible(false), 2500);
|
|
1523
|
+
return () => clearTimeout(timer);
|
|
1524
|
+
}, [tick]);
|
|
1525
|
+
return visible;
|
|
1526
|
+
}
|
|
1435
1527
|
/** The settings page body: connection facts for the Command Code provider. */
|
|
1436
1528
|
function CommandCodeSettingsPage(props) {
|
|
1437
1529
|
const { t } = props;
|
|
@@ -1439,6 +1531,7 @@ window.__ModuleLoader__.load({
|
|
|
1439
1531
|
const usage = props.useCommandCodeUsage((snapshot) => snapshot);
|
|
1440
1532
|
const disabled = !state.writable;
|
|
1441
1533
|
const keyLocked = !state.apiKeyWritable;
|
|
1534
|
+
const savedVisible = useSavedFlash(state.savedCount);
|
|
1442
1535
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1443
1536
|
className: "cc-section",
|
|
1444
1537
|
"aria-label": t("title"),
|
|
@@ -1488,6 +1581,8 @@ window.__ModuleLoader__.load({
|
|
|
1488
1581
|
configured: state.apiKeyConfigured,
|
|
1489
1582
|
configuredLabel: t("apiKeySet"),
|
|
1490
1583
|
unconfiguredLabel: t("apiKeyUnset"),
|
|
1584
|
+
showLabel: t("show"),
|
|
1585
|
+
hideLabel: t("hide"),
|
|
1491
1586
|
onEdit: (text) => props.edit("apiKey", text)
|
|
1492
1587
|
}),
|
|
1493
1588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
@@ -1554,6 +1649,11 @@ window.__ModuleLoader__.load({
|
|
|
1554
1649
|
role: "status",
|
|
1555
1650
|
children: t("saveFailed")
|
|
1556
1651
|
}) : null,
|
|
1652
|
+
savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1653
|
+
className: "cc-saved",
|
|
1654
|
+
role: "status",
|
|
1655
|
+
children: t("saved")
|
|
1656
|
+
}) : null,
|
|
1557
1657
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1558
1658
|
variant: "ghost",
|
|
1559
1659
|
size: "sm",
|
|
@@ -1569,6 +1669,10 @@ window.__ModuleLoader__.load({
|
|
|
1569
1669
|
children: t(state.saving ? "saving" : "save")
|
|
1570
1670
|
})
|
|
1571
1671
|
]
|
|
1672
|
+
}),
|
|
1673
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1674
|
+
className: "cc-version",
|
|
1675
|
+
children: ["Command Code Provider v", PLUGIN_VERSION]
|
|
1572
1676
|
})
|
|
1573
1677
|
]
|
|
1574
1678
|
});
|
|
@@ -1608,13 +1712,18 @@ window.__ModuleLoader__.load({
|
|
|
1608
1712
|
overridden: "已覆盖",
|
|
1609
1713
|
reset: "重置",
|
|
1610
1714
|
invalidNumber: "无效数字",
|
|
1715
|
+
numberTooSmall: "不能小于 1(毫秒)",
|
|
1716
|
+
numberTooLarge: "超出允许上限(2147483647 毫秒)",
|
|
1611
1717
|
readOnly: "当前配置为只读。",
|
|
1612
1718
|
unsaved: "未保存",
|
|
1613
1719
|
save: "保存",
|
|
1614
1720
|
saving: "保存中",
|
|
1721
|
+
saved: "已保存 ✓",
|
|
1615
1722
|
saveFailed: "保存失败,请重试。",
|
|
1616
1723
|
discard: "放弃",
|
|
1617
1724
|
cancel: "取消",
|
|
1725
|
+
show: "显示",
|
|
1726
|
+
hide: "隐藏",
|
|
1618
1727
|
usageTitle: "账户用量",
|
|
1619
1728
|
usageRefresh: "刷新",
|
|
1620
1729
|
usageRefreshing: "刷新中…",
|
|
@@ -1676,13 +1785,18 @@ window.__ModuleLoader__.load({
|
|
|
1676
1785
|
overridden: "Overridden",
|
|
1677
1786
|
reset: "Reset",
|
|
1678
1787
|
invalidNumber: "Invalid number",
|
|
1788
|
+
numberTooSmall: "Must be at least 1 (ms)",
|
|
1789
|
+
numberTooLarge: "Above the allowed maximum (2147483647 ms)",
|
|
1679
1790
|
readOnly: "Settings are read-only.",
|
|
1680
1791
|
unsaved: "Unsaved",
|
|
1681
1792
|
save: "Save",
|
|
1682
1793
|
saving: "Saving",
|
|
1794
|
+
saved: "Saved ✓",
|
|
1683
1795
|
saveFailed: "Save failed, please retry.",
|
|
1684
1796
|
discard: "Discard",
|
|
1685
1797
|
cancel: "Cancel",
|
|
1798
|
+
show: "Show",
|
|
1799
|
+
hide: "Hide",
|
|
1686
1800
|
usageTitle: "Account usage",
|
|
1687
1801
|
usageRefresh: "Refresh",
|
|
1688
1802
|
usageRefreshing: "Refreshing…",
|
|
@@ -1783,6 +1897,8 @@ window.__ModuleLoader__.load({
|
|
|
1783
1897
|
.cc-usageMetaSpacer{flex:1}
|
|
1784
1898
|
.cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1785
1899
|
.cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
|
|
1900
|
+
.cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
1901
|
+
.cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
|
|
1786
1902
|
`;
|
|
1787
1903
|
/** Inject the page stylesheet once (idempotent per tag). */
|
|
1788
1904
|
function injectPageCss() {
|