dsh-disk-cleaner 1.6.3 → 1.6.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.
- package/README.md +21 -19
- package/lib/client.js +117 -64
- package/package.json +1 -1
- package/smoke.mjs +25 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<!-- badges -->
|
|
6
6
|
| | |
|
|
7
7
|
| --- | --- |
|
|
8
|
-
| **version** | `1.6.
|
|
8
|
+
| **version** | `1.6.4` |
|
|
9
9
|
| **DSH** | `0.2.1-rc.5`(开发与实测版本;用到 `plugins.bundle.config`、`settings.section`) |
|
|
10
10
|
| **license** | [MIT](LICENSE) |
|
|
11
11
|
| **Node** | `>= 22`(仅运行冒烟测试需要) |
|
|
@@ -166,16 +166,16 @@ These are enforced in `lib/catalog.js` and must not be relaxed:
|
|
|
166
166
|
|
|
167
167
|
A catalog entry has been submitted to the curated [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) list, which powers **dshmarket → Discover** ([PR #12970](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/12970), pending review) — once it merges, search **dsh-disk-cleaner** there and install it in one click. Until then, use a direct install below.
|
|
168
168
|
|
|
169
|
-
Published on npm as [`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner) — latest `1.6.
|
|
169
|
+
Published on npm as [`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner) — latest `1.6.4`:
|
|
170
170
|
|
|
171
171
|
```bash
|
|
172
172
|
npm i dsh-disk-cleaner
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
A prebuilt tarball is attached to the [v1.6.3 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.
|
|
175
|
+
A prebuilt tarball is attached to the [v1.6.3 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.4); installing from it skips DSH's build-approval step:
|
|
176
176
|
|
|
177
177
|
```bash
|
|
178
|
-
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.
|
|
178
|
+
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.4/dsh-disk-cleaner-1.6.4.tgz
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
Then enable it in the profile — in `$DSH_PROFILE_DIR/package.json`:
|
|
@@ -183,7 +183,7 @@ Then enable it in the profile — in `$DSH_PROFILE_DIR/package.json`:
|
|
|
183
183
|
```json
|
|
184
184
|
{
|
|
185
185
|
"dependencies": {
|
|
186
|
-
"dsh-disk-cleaner": "^1.6.
|
|
186
|
+
"dsh-disk-cleaner": "^1.6.4"
|
|
187
187
|
},
|
|
188
188
|
"dsh": {
|
|
189
189
|
"profile": {
|
|
@@ -204,7 +204,7 @@ The bundle's own `cordis.patch.yml` inserts the plugin row; no profile patch edi
|
|
|
204
204
|
### Development
|
|
205
205
|
|
|
206
206
|
```bash
|
|
207
|
-
node smoke.mjs #
|
|
207
|
+
node smoke.mjs # 98 assertions, zero dependencies
|
|
208
208
|
node --check lib/client.js # and lib/index.js, lib/catalog.js, lib/audit.js, lib/plan.js
|
|
209
209
|
```
|
|
210
210
|
|
|
@@ -258,22 +258,23 @@ What the smoke test deliberately does **not** cover: anything that walks a real
|
|
|
258
258
|
```
|
|
259
259
|
├─ package.json dsh.bundle.patch + dsh.client { platform: "web" }
|
|
260
260
|
├─ cordis.patch.yml - insert: id: dsh-disk-cleaner
|
|
261
|
-
├─ lib/index.js (
|
|
262
|
-
├─ lib/catalog.js (
|
|
261
|
+
├─ lib/index.js (434) Host: HTTP route + disk_cleaner tool, same run() for both; reveal
|
|
262
|
+
├─ lib/catalog.js (1222) The 32 targets, measurePath / purgePath / resolveTargets / drives
|
|
263
263
|
├─ lib/audit.js (365) Read-only assessment, 10-minute cache, areaPaths, exposes no deletion
|
|
264
264
|
├─ lib/plan.js (150) Pure: measurements → ordered steps
|
|
265
|
-
├─ lib/client.js (
|
|
265
|
+
├─ lib/client.js (1597) Client half: slots, panel, preferences, zh/en dictionaries, CSS
|
|
266
266
|
├─ locale/{en,zh}.json Display metadata (meta.title / meta.description)
|
|
267
267
|
├─ icon.svg Plugin icon
|
|
268
268
|
├─ README.md This file
|
|
269
269
|
├─ LICENSE MIT
|
|
270
|
-
└─ smoke.mjs
|
|
270
|
+
└─ smoke.mjs 98 assertions, zero dependencies
|
|
271
271
|
```
|
|
272
272
|
|
|
273
273
|
Client-half slots: `sidebar.panellist` (order 50, disposed in settings-only mode), `main` (keyed by the package name), `settings.section` (order 30, disposed in sidebar-only mode), `plugins.bundle.config` (keyed by the package name).
|
|
274
274
|
|
|
275
275
|
### What's new
|
|
276
276
|
|
|
277
|
+
- **1.6.4** — The panel now paints itself with [dsh-design-baseline](https://github.com/stallenlu/dsh-design-baseline): the stylesheet is inlined into the client bundle, the panel root carries `dsb-root`, and every colour resolves through a `--dsb-*` token instead of a host `--dsw-alias-*` name. That means it follows the host's light and dark themes (including `color-scheme`, so native `<select>` menus and scrollbars match), and colour is spent only on data that has state — the primary button and the active chip are now inverted neutrals rather than brand blue. No behaviour changes.
|
|
277
278
|
- **1.6.3** — Safety and honesty rather than new features. The Recycle Bin is no longer filed under `safe`: emptying it cannot be undone, so it is never preselected, never picked by "select all", and carries its own `irreversible` note instead of the generic "rebuilt; next start is slower". The confirm dialog now names each target and the total instead of asking a generic "delete these?". A target that names no environment root is bounded by the volume it sits on rather than skipping the boundary check. `measure` and `purge` now count entries in the same unit, and a walk that hits the 2,000,000-entry cap says so (`truncated`) instead of claiming exact totals. A directory removal refused for a reason other than "not empty" is counted as `blocked` instead of being swallowed, and a clean that left files behind reports a warning rather than a green success. The orphan target is named for what it actually measures (the QQGuild folder).
|
|
278
279
|
- **1.6.2** — Documentation only: the README, the in-source comment, the panel's zh/en explanations and the tool's note legend now describe `orphan-app-data` as it actually behaves (the QQGuild folder is offered for full deletion once no file in it has been written for 30 days), instead of claiming four independent signals and a two-year idle period. No behaviour change; the refreshed README ships in the npm package and the [v1.6.2 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.2) tarball.
|
|
279
280
|
- **1.6.1** — Fixed the `disk_cleaner` agent tool: `output.schema` now declares `type: 'string'`, matching the text `execute()` returns (previously every call was rejected by the host's output validator with `tool "disk_cleaner" returned invalid output: "value" must be an object`). A real delete through the agent tool now invalidates the assessment cache, the same way the panel's HTTP route does. Two regression assertions added; a prebuilt tarball ships on the [v1.6.1 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.1).
|
|
@@ -435,16 +436,16 @@ Client-half slots: `sidebar.panellist` (order 50, disposed in settings-only mode
|
|
|
435
436
|
|
|
436
437
|
已向精选目录 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 提交收录,它是 **dshmarket → 发现** 的数据源([PR #12970](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/12970),审核中)——合并后即可在 dshmarket 里搜索 **dsh-disk-cleaner** 一键安装。在那之前,用下面任一种直接安装方式。
|
|
437
438
|
|
|
438
|
-
已发布到 npm:[`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner),最新版 `1.6.
|
|
439
|
+
已发布到 npm:[`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner),最新版 `1.6.4`。
|
|
439
440
|
|
|
440
441
|
```bash
|
|
441
442
|
npm i dsh-disk-cleaner
|
|
442
443
|
```
|
|
443
444
|
|
|
444
|
-
[v1.6.3 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.
|
|
445
|
+
[v1.6.3 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.4) 还附带一个预构建 tarball,从它安装可跳过 DSH 的构建授权步骤:
|
|
445
446
|
|
|
446
447
|
```bash
|
|
447
|
-
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.
|
|
448
|
+
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.4/dsh-disk-cleaner-1.6.4.tgz
|
|
448
449
|
```
|
|
449
450
|
|
|
450
451
|
然后在 profile 里启用 —— 在 `$DSH_PROFILE_DIR/package.json`:
|
|
@@ -452,7 +453,7 @@ dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner
|
|
|
452
453
|
```json
|
|
453
454
|
{
|
|
454
455
|
"dependencies": {
|
|
455
|
-
"dsh-disk-cleaner": "^1.6.
|
|
456
|
+
"dsh-disk-cleaner": "^1.6.4"
|
|
456
457
|
},
|
|
457
458
|
"dsh": {
|
|
458
459
|
"profile": {
|
|
@@ -473,7 +474,7 @@ bundle 自带的 `cordis.patch.yml` 会插入插件行,无需改 profile 的 p
|
|
|
473
474
|
### 开发
|
|
474
475
|
|
|
475
476
|
```bash
|
|
476
|
-
node smoke.mjs #
|
|
477
|
+
node smoke.mjs # 98 条断言,零依赖
|
|
477
478
|
node --check lib/client.js # 以及 lib/index.js、lib/catalog.js、lib/audit.js、lib/plan.js
|
|
478
479
|
```
|
|
479
480
|
|
|
@@ -527,22 +528,23 @@ node --check lib/client.js # 以及 lib/index.js、lib/catalog.js、li
|
|
|
527
528
|
```
|
|
528
529
|
├─ package.json dsh.bundle.patch + dsh.client { platform: "web" }
|
|
529
530
|
├─ cordis.patch.yml - insert: id: dsh-disk-cleaner
|
|
530
|
-
├─ lib/index.js (
|
|
531
|
-
├─ lib/catalog.js (
|
|
531
|
+
├─ lib/index.js (434) Host:HTTP 路由 + disk_cleaner 工具,共用同一个 run(),含 reveal
|
|
532
|
+
├─ lib/catalog.js (1222) 32 个目标,measurePath / purgePath / resolveTargets / drives
|
|
532
533
|
├─ lib/audit.js (365) 只读评估,10 分钟缓存,带 areaPaths,不暴露任何删除
|
|
533
534
|
├─ lib/plan.js (150) 纯函数:测量结果 → 有序步骤
|
|
534
|
-
├─ lib/client.js (
|
|
535
|
+
├─ lib/client.js (1597) Client 半面:slot、面板、偏好、zh/en 字典、CSS
|
|
535
536
|
├─ locale/{en,zh}.json 展示元数据(meta.title / meta.description)
|
|
536
537
|
├─ icon.svg 插件图标
|
|
537
538
|
├─ README.md 本文件
|
|
538
539
|
├─ LICENSE MIT
|
|
539
|
-
└─ smoke.mjs
|
|
540
|
+
└─ smoke.mjs 98 条断言,零依赖
|
|
540
541
|
```
|
|
541
542
|
|
|
542
543
|
Client 半面注册的 slot:`sidebar.panellist`(order 50,仅设置页模式下拆除)、`main`(以包名为 key)、`settings.section`(order 30,仅侧边栏模式下拆除)、`plugins.bundle.config`(以包名为 key)。
|
|
543
544
|
|
|
544
545
|
### 更新记录
|
|
545
546
|
|
|
547
|
+
- **1.6.4** — 面板改用 [dsh-design-baseline](https://github.com/stallenlu/dsh-design-baseline) 上色:样式表内联进 client bundle,根节点带 `dsb-root`,所有颜色都走 `--dsb-*` 令牌而不是宿主的 `--dsw-alias-*` 名字。因此它跟随宿主的明暗主题(含 `color-scheme`,原生 `<select>` 弹层与滚动条也跟着变),并且颜色只花在有状态的数据上——主按钮与选中项改成中性反色,不再是品牌蓝。行为没有变化。
|
|
546
548
|
- **1.6.3** — 改的是安全与诚实,不是新功能。回收站不再归入「安全」:清空不可恢复,所以它永不被预选、不被「全选」勾中,并单独带 `irreversible` 注意码,而不是笼统的「重建,下次启动慢一些」。确认框现在逐条列出目标名与合计字节,不再只问一句「删除选中缓存目录里的文件?」。没有声明根目录的目标,改为按所在卷做边界校验,而不是直接跳过检查。`measure` 与 `purge` 现在用同一单位计数,走查触到 200 万条上限时会如实标 `truncated`,不再声称精确总量。目录删除失败若原因不是「非空」会计入 `blocked`(此前被静默吞掉),清理后仍有残留的目标会报警告而不是绿色成功。orphan 目标按它实际测量的东西重新命名(QQ 频道目录)。
|
|
547
549
|
- **1.6.2** — 仅文档修正:README、源码注释、面板中英文说明和工具里的注意码释义,现在都按真实行为描述 `orphan-app-data`(QQGuild 目录内 30 天没有文件写入时会被整目录列为可全清),不再声称「四条独立证据 / 两年未写」。行为不变;更新后的 README 也随 npm 包与 [v1.6.2 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.2) 的 tarball 一起发布。
|
|
548
550
|
- **1.6.1** — 修复了 `disk_cleaner` agent 工具:`output.schema` 现在声明 `type: 'string'`,与 `execute()` 返回的文本一致(此前每次调用都被宿主的输出校验器拒绝,报 `tool "disk_cleaner" returned invalid output: "value" must be an object`)。经由 agent 工具真删后也会让评估缓存失效,与面板的 HTTP 路径一致。新增两条回归断言;[v1.6.1 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.1) 附带预构建 tarball。
|
package/lib/client.js
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({
|
|
2
2
|
id: 'dsh-disk-cleaner',
|
|
3
3
|
factory(require) {
|
|
4
|
+
// >>> dsh-design-baseline v1.1.0 sha256 76858a2ccf04445daf6a05a19bd8903ff09a8b34789dde945e4ac0cbd47a0a7a
|
|
5
|
+
// Generated by dsh-design-baseline/sync.mjs — edit baseline.css, run build.mjs,
|
|
6
|
+
// then sync.mjs. Everything between these two markers is overwritten.
|
|
7
|
+
const BASELINE_CSS = "/* ============================================================================\n DSH plugin design baseline · dsh-design-baseline v1\n ----------------------------------------------------------------------------\n Derived from the FreeLLMAPI router dashboard (:3001) theme, which is the\n look approved as the house style for every DSH plugin I write.\n\n The whole point of this file is one rule:\n\n Neutral surfaces come from the DSH host. Colour comes from nowhere else\n except the six accents below.\n\n The dashboard's own palette has chroma 0 for every neutral — pure grey\n (oklch(...% 0 0)). That is why it looks calm: no tint is competing. The only\n saturated pixels on the entire page are the three score bars and the state\n dots. Reproduce that and you get the same calm for free.\n\n Host tokens are referenced by name (--dsw-alias-*) so light/dark switching,\n including DSH's own theme overrides, is automatic. Every reference carries a\n literal fallback so a panel still renders if a token is ever renamed.\n ========================================================================= */\n\n/* IMPORTANT — why this is body/.dsb-root and not :root.\n The host declares its dark overrides on `body[data-ds-dark-theme]`, not on\n :root. A var(--dsw-alias-x, fallback) resolved at :root therefore never sees\n the dark values and silently sticks to the light fallback (this bit me: the\n whole panel rendered in light colours while the app was dark). Resolving the\n mapping on body — or on your plugin's own root element, which is the tidier\n option — puts it on the same element the host's dark block targets, so the\n cascade picks the right set in both themes. */\nbody,\n.dsb-root {\n /* --- accents: the ONLY saturated colours in the system ------------------ */\n --dsb-accent-stability: #22c55e; /* emerald-500 — reliability */\n --dsb-accent-speed: #3b82f6; /* blue-500 — throughput */\n --dsb-accent-intelligence: #a855f7; /* violet-500 — capability */\n --dsb-accent-warn: #f59e0b; /* amber-500 — rate limited */\n --dsb-accent-error: #f43f5e; /* rose-500 — invalid/error*/\n --dsb-accent-info: #06b6d4; /* cyan-500 — vision/info */\n\n /* Soft washes for badges and chips: 15% of the accent, per the dashboard. */\n --dsb-wash-stability: color-mix(in srgb, var(--dsb-accent-stability) 15%, transparent);\n --dsb-wash-warn: color-mix(in srgb, var(--dsb-accent-warn) 15%, transparent);\n --dsb-wash-error: color-mix(in srgb, var(--dsb-accent-error) 15%, transparent);\n --dsb-wash-info: color-mix(in srgb, var(--dsb-accent-info) 15%, transparent);\n --dsb-wash-brand: color-mix(in srgb, var(--dsb-accent-intelligence) 15%, transparent);\n\n /* --- neutrals: mapped from the host, never hardcoded ------------------- */\n --dsb-surface: var(--dsw-alias-bg-layer-1, #ffffff);\n --dsb-surface-2: var(--dsw-alias-bg-layer-2, #fafafa);\n --dsb-surface-3: var(--dsw-alias-bg-layer-3, #f5f5f5);\n --dsb-card: var(--dsw-alias-bg-module-platform, #f9fafb);\n --dsb-track: var(--dsw-alias-bg-layer-2, #f5f5f5);\n\n --dsb-line: var(--dsw-alias-border-l1, #0000000a);\n --dsb-line-strong: var(--dsw-alias-border-l2, #0000001a);\n\n --dsb-text: var(--dsw-alias-label-primary, #0f1115);\n --dsb-text-secondary: var(--dsw-alias-label-secondary, #61666b);\n --dsb-text-tertiary: var(--dsw-alias-label-tertiary, #81858c);\n --dsb-text-caption: var(--dsw-alias-label-caption, #adb2b8);\n\n --dsb-inverted-bg: var(--dsw-alias-label-primary, #0f1115);\n --dsb-inverted-fg: var(--dsw-alias-label-primary-foreground, #ffffff);\n --dsb-hover: var(--dsw-alias-interactive-bg-hover, #2631480f);\n --dsb-hover-strong: var(--dsw-alias-interactive-bg-hover-accent, #26314824);\n\n /* --- geometry ---------------------------------------------------------- */\n --dsb-radius: 12px; /* cards, grouped chips, tables */\n --dsb-radius-sm: 8px; /* buttons inside those, tabs */\n --dsb-radius-pill: 9999px; /* bars, chips, badges, dots */\n --dsb-gap: 12px;\n\n --dsb-font: var(--dsw-font-family, \"Geist Variable\", -apple-system,\n \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif);\n --dsb-font-mono: var(--ds-font-family-code, \"Geist Mono Variable\",\n ui-monospace, SFMono-Regular, Menlo, monospace);\n}\n\n/* ---------------------------------------------------------------------------\n 1. Surfaces\n ------------------------------------------------------------------------ */\n\n.dsb-card {\n border: 1px solid var(--dsb-line-strong);\n border-radius: var(--dsb-radius);\n background: var(--dsb-surface);\n padding: 16px;\n color: var(--dsb-text);\n font-family: var(--dsb-font);\n}\n\n.dsb-card--flush { padding: 0; }\n\n.dsb-card-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--dsb-gap);\n margin-bottom: 12px;\n}\n\n.dsb-title {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--dsb-text);\n}\n\n.dsb-subtitle {\n margin: 2px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsb-text-secondary);\n}\n\n.dsb-muted { color: var(--dsb-text-secondary); }\n.dsb-caption {\n font-size: 11px;\n color: var(--dsb-text-caption);\n}\n\n/* Section divider: a hairline, never a filled band. */\n.dsb-divider {\n height: 1px;\n border: 0;\n margin: 16px 0;\n background: var(--dsb-line-strong);\n}\n\n/* ---------------------------------------------------------------------------\n 2. The score bar — the signature element\n Track 48×6px, pill, --dsb-track. Fill: display:block, width %, pill.\n NOTE: the fill MUST be display:block. As an inline <span> its width and\n height are ignored and the bar silently disappears, leaving only the grey\n track. That exact bug shipped once; do not let it back in.\n ------------------------------------------------------------------------ */\n\n.dsb-bar {\n display: flex;\n align-items: center;\n gap: 6px;\n white-space: nowrap;\n}\n\n.dsb-bar-track {\n position: relative;\n width: 48px;\n height: 6px;\n flex: 0 0 48px;\n border-radius: var(--dsb-radius-pill);\n background: var(--dsb-track);\n overflow: hidden;\n}\n\n.dsb-bar-fill {\n display: block;\n height: 100%;\n border-radius: var(--dsb-radius-pill);\n background: var(--dsb-accent-speed);\n transition: width 150ms ease-out;\n}\n\n.dsb-bar-fill--stability { background: var(--dsb-accent-stability); }\n.dsb-bar-fill--speed { background: var(--dsb-accent-speed); }\n.dsb-bar-fill--intelligence { background: var(--dsb-accent-intelligence); }\n\n/* No data: a dashed outline and an en dash. Never draw a 0-width bar and let\n it read as \"scores zero\". */\n.dsb-bar--empty .dsb-bar-track {\n background: transparent;\n border: 1px dashed color-mix(in srgb, var(--dsb-text-tertiary) 40%, transparent);\n}\n\n.dsb-bar-num {\n width: 28px;\n text-align: right;\n font-family: var(--dsb-font-mono);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsb-text-secondary);\n}\n\n/* ---------------------------------------------------------------------------\n 3. State dot\n ------------------------------------------------------------------------ */\n\n.dsb-dot {\n display: inline-block;\n width: 6px;\n height: 6px;\n flex: 0 0 6px;\n border-radius: var(--dsb-radius-pill);\n background: var(--dsb-text-tertiary);\n}\n\n.dsb-dot--healthy { background: var(--dsb-accent-stability); }\n.dsb-dot--warn { background: var(--dsb-accent-warn); }\n.dsb-dot--error { background: var(--dsb-accent-error); }\n.dsb-dot--idle { background: var(--dsb-text-tertiary); }\n\n/* ---------------------------------------------------------------------------\n 4. Segmented control — grouped options inside one pill-ish frame\n ------------------------------------------------------------------------ */\n\n.dsb-segmented {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px;\n border: 1px solid var(--dsb-line-strong);\n border-radius: var(--dsb-radius);\n}\n\n.dsb-seg {\n padding: 4px 10px;\n border: 0;\n border-radius: var(--dsb-radius-sm);\n background: transparent;\n color: var(--dsb-text-secondary);\n font-family: inherit;\n font-size: 12px;\n line-height: 1.4;\n cursor: pointer;\n transition: background-color 150ms, color 150ms;\n}\n\n.dsb-seg:hover { background: var(--dsb-hover); color: var(--dsb-text); }\n\n/* Selected = inverted, not coloured. Colour is reserved for data. */\n.dsb-seg[aria-pressed=\"true\"],\n.dsb-seg[data-selected=\"true\"] {\n background: var(--dsb-inverted-bg);\n color: var(--dsb-inverted-fg);\n font-weight: 500;\n}\n\n/* ---------------------------------------------------------------------------\n 5. Chips and badges\n ------------------------------------------------------------------------ */\n\n.dsb-chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n border-radius: var(--dsb-radius-pill);\n background: var(--dsb-surface-3);\n color: var(--dsb-text-secondary);\n font-size: 10px;\n line-height: 1.6;\n white-space: nowrap;\n}\n\n.dsb-chip--active {\n background: var(--dsb-inverted-bg);\n color: var(--dsb-inverted-fg);\n font-weight: 500;\n}\n\n.dsb-chip--stability { background: var(--dsb-wash-stability); color: var(--dsb-accent-stability); }\n.dsb-chip--warn { background: var(--dsb-wash-warn); color: var(--dsb-accent-warn); }\n.dsb-chip--error { background: var(--dsb-wash-error); color: var(--dsb-accent-error); }\n.dsb-chip--info { background: var(--dsb-wash-info); color: var(--dsb-accent-info); }\n.dsb-chip--brand { background: var(--dsb-wash-brand); color: var(--dsb-accent-intelligence); }\n\n/* In dark mode the 15% wash over a dark surface needs a lighter source colour\n to stay legible; the host's dark flag drives the swap. */\n[data-ds-dark-theme] .dsb-chip--stability,\n.dark .dsb-chip--stability { color: color-mix(in srgb, var(--dsb-accent-stability) 70%, white); }\n[data-ds-dark-theme] .dsb-chip--warn,\n.dark .dsb-chip--warn { color: color-mix(in srgb, var(--dsb-accent-warn) 70%, white); }\n[data-ds-dark-theme] .dsb-chip--error,\n.dark .dsb-chip--error { color: color-mix(in srgb, var(--dsb-accent-error) 70%, white); }\n\n/* ---------------------------------------------------------------------------\n 6. Tabs — underline on the active one, no pill fill\n ------------------------------------------------------------------------ */\n\n.dsb-tabs {\n display: flex;\n align-items: center;\n gap: 20px;\n border-bottom: 1px solid var(--dsb-line-strong);\n}\n\n.dsb-tab {\n position: relative;\n padding: 8px 0 10px;\n border: 0;\n background: transparent;\n color: var(--dsb-text-secondary);\n font-family: inherit;\n font-size: 13px;\n cursor: pointer;\n}\n\n.dsb-tab:hover { color: var(--dsb-text); }\n\n.dsb-tab[aria-selected=\"true\"],\n.dsb-tab[data-selected=\"true\"] {\n color: var(--dsb-text);\n font-weight: 500;\n}\n\n.dsb-tab[aria-selected=\"true\"]::after,\n.dsb-tab[data-selected=\"true\"]::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: -1px;\n height: 2px;\n border-radius: 2px 2px 0 0;\n background: var(--dsb-text);\n}\n\n/* ---------------------------------------------------------------------------\n 7. Table row — hairline separators, transparent background\n ------------------------------------------------------------------------ */\n\n.dsb-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n color: var(--dsb-text);\n}\n\n.dsb-table th {\n padding: 8px 12px 8px 0;\n text-align: left;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsb-text-secondary);\n border-bottom: 1px solid var(--dsb-line-strong);\n white-space: nowrap;\n}\n\n.dsb-table td {\n padding: 8px 12px 8px 0;\n vertical-align: middle;\n border-bottom: 1px solid var(--dsb-line);\n}\n\n.dsb-table tr:last-child td { border-bottom: 0; }\n.dsb-table tbody tr:hover { background: var(--dsb-hover); }\n\n.dsb-num {\n font-family: var(--dsb-font-mono);\n font-variant-numeric: tabular-nums;\n}\n\n.dsb-row-index {\n width: 28px;\n color: var(--dsb-text-caption);\n font-family: var(--dsb-font-mono);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.dsb-row-id {\n font-family: var(--dsb-font-mono);\n font-size: 12px;\n color: var(--dsb-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dsb-row-dim { opacity: 0.55; }\n\n/* ---------------------------------------------------------------------------\n 8. Toggle\n ------------------------------------------------------------------------ */\n\n.dsb-switch {\n position: relative;\n width: 34px;\n height: 20px;\n flex: 0 0 34px;\n padding: 0;\n border: 0;\n border-radius: var(--dsb-radius-pill);\n background: color-mix(in srgb, var(--dsb-text-tertiary) 28%, transparent);\n cursor: pointer;\n transition: background-color 150ms;\n}\n\n.dsb-switch::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsb-inverted-fg);\n transition: transform 150ms ease-out;\n}\n\n.dsb-switch[aria-checked=\"true\"] { background: var(--dsb-accent-stability); }\n.dsb-switch[aria-checked=\"true\"]::after { transform: translateX(14px); }\n\n/* Disabled stays readable: dim the track, not the whole row. */\n.dsb-switch:disabled { opacity: 0.4; cursor: default; }\n\n/* ---------------------------------------------------------------------------\n 9. Buttons\n ------------------------------------------------------------------------ */\n\n.dsb-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--dsb-line-strong);\n border-radius: var(--dsb-radius-sm);\n background: transparent;\n color: var(--dsb-text);\n font-family: inherit;\n font-size: 12px;\n line-height: 1.5;\n text-decoration: none;\n cursor: pointer;\n transition: background-color 150ms;\n}\n\n.dsb-btn:hover { background: var(--dsb-hover); }\n.dsb-btn:disabled { opacity: 0.45; cursor: default; background: transparent; }\n\n.dsb-btn--primary {\n border-color: transparent;\n background: var(--dsb-inverted-bg);\n color: var(--dsb-inverted-fg);\n font-weight: 500;\n}\n\n.dsb-btn--primary:hover { background: var(--dsb-inverted-bg); opacity: 0.9; }\n\n.dsb-btn--danger { color: var(--dsb-accent-error); }\n\n/* ---------------------------------------------------------------------------\n 10. Empty state\n ------------------------------------------------------------------------ */\n\n.dsb-empty {\n padding: 32px 16px;\n text-align: center;\n font-size: 12px;\n color: var(--dsb-text-secondary);\n}\n\n.dsb-empty--error { color: var(--dsb-accent-error); }\n"
|
|
8
|
+
const injectBaseline = (doc, plugin) => {
|
|
9
|
+
const target = doc || (typeof document === "undefined" ? null : document)
|
|
10
|
+
if (!target || !target.head) return null
|
|
11
|
+
const key = plugin || "dsh-design-baseline"
|
|
12
|
+
const existing = target.querySelector(`style[data-dsb="${key}"]`)
|
|
13
|
+
if (existing) return existing
|
|
14
|
+
const style = target.createElement("style")
|
|
15
|
+
style.dataset.dsb = key
|
|
16
|
+
style.textContent = BASELINE_CSS
|
|
17
|
+
target.head.appendChild(style)
|
|
18
|
+
return style
|
|
19
|
+
}
|
|
20
|
+
const THEME = {
|
|
21
|
+
"text": "var(--dsw-alias-label-primary, #0f1115)",
|
|
22
|
+
"textSecondary": "var(--dsw-alias-label-secondary, #61666b)",
|
|
23
|
+
"textTertiary": "var(--dsw-alias-label-tertiary, #81858c)",
|
|
24
|
+
"textCaption": "var(--dsw-alias-label-caption, #adb2b8)",
|
|
25
|
+
"surface": "var(--dsw-alias-bg-layer-1, #ffffff)",
|
|
26
|
+
"surface2": "var(--dsw-alias-bg-layer-2, #fafafa)",
|
|
27
|
+
"surface3": "var(--dsw-alias-bg-layer-3, #f5f5f5)",
|
|
28
|
+
"card": "var(--dsw-alias-bg-module-platform, #f9fafb)",
|
|
29
|
+
"track": "var(--dsw-alias-bg-layer-2, #f5f5f5)",
|
|
30
|
+
"line": "var(--dsw-alias-border-l1, rgba(0,0,0,0.04))",
|
|
31
|
+
"lineStrong": "var(--dsw-alias-border-l2, rgba(0,0,0,0.10))",
|
|
32
|
+
"hover": "var(--dsw-alias-interactive-bg-hover, rgba(38,49,72,0.06))",
|
|
33
|
+
"invertedBg": "var(--dsw-alias-label-primary, #0f1115)",
|
|
34
|
+
"invertedFg": "var(--dsw-alias-label-primary-foreground, #ffffff)",
|
|
35
|
+
"primaryFill": "var(--dsw-alias-button-primary-fill, #3b82f6)",
|
|
36
|
+
"primaryFg": "var(--dsw-alias-label-primary-foreground, #ffffff)",
|
|
37
|
+
"accentStability": "#22c55e",
|
|
38
|
+
"accentSpeed": "#3b82f6",
|
|
39
|
+
"accentIntelligence": "#a855f7",
|
|
40
|
+
"accentWarn": "#f59e0b",
|
|
41
|
+
"accentError": "#f43f5e",
|
|
42
|
+
"accentInfo": "#06b6d4",
|
|
43
|
+
"radius": "12px",
|
|
44
|
+
"radiusSm": "8px",
|
|
45
|
+
"radiusPill": "9999px",
|
|
46
|
+
"gap": "12px"
|
|
47
|
+
}
|
|
48
|
+
const wash = (accent) => `color-mix(in srgb, ${accent} 15%, transparent)`
|
|
49
|
+
// <<< dsh-design-baseline
|
|
4
50
|
const React = require('react');
|
|
5
51
|
const h = React.createElement;
|
|
6
52
|
const { useCallback, useEffect, useMemo, useRef, useState } = React;
|
|
@@ -362,102 +408,106 @@ window.__ModuleLoader__.load({
|
|
|
362
408
|
};
|
|
363
409
|
|
|
364
410
|
const CSS = `
|
|
365
|
-
.dsc-root { box-sizing: border-box; height: 100%; overflow-y: auto; padding: 16px 20px 32px; font-size: 13px; color: var(--
|
|
411
|
+
.dsc-root { box-sizing: border-box; height: 100%; overflow-y: auto; padding: 16px 20px 32px; font-family: var(--dsb-font); font-size: 13px; color: var(--dsb-text); background: transparent; color-scheme: light; }
|
|
412
|
+
/* The host flags dark mode on body, not through prefers-color-scheme; matching that
|
|
413
|
+
flag is what makes native <select> menus and scrollbars render dark too. */
|
|
414
|
+
body[data-ds-dark-theme] .dsc-root, .dark .dsc-root { color-scheme: dark; }
|
|
366
415
|
.dsc-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
|
|
367
416
|
.dsc-head-spacer { margin-left: auto; }
|
|
368
|
-
.dsc-head-btn { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: 12px; line-height: 1; padding: 6px 11px; border-radius:
|
|
369
|
-
.dsc-head-btn:hover { color: var(--
|
|
417
|
+
.dsc-head-btn { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: 12px; line-height: 1; padding: 6px 11px; border-radius: var(--dsb-radius-pill); cursor: pointer; border: 1px solid var(--dsb-line-strong); background: var(--dsb-surface-2); color: var(--dsb-text-secondary); white-space: nowrap; }
|
|
418
|
+
.dsc-head-btn:hover { color: var(--dsb-text); background: var(--dsb-hover); border-color: var(--dsb-line-strong); }
|
|
370
419
|
.dsc-head h1 { margin: 0; font-size: 16px; font-weight: 600; line-height: 24px; }
|
|
371
|
-
.dsc-head-sub { color: var(--
|
|
372
|
-
.dsc-card { box-sizing: border-box; background: var(--
|
|
420
|
+
.dsc-head-sub { color: var(--dsb-text-secondary); font-size: 12px; }
|
|
421
|
+
.dsc-card { box-sizing: border-box; background: var(--dsb-surface); border: 1px solid var(--dsb-line-strong); border-radius: var(--dsb-radius); padding: 12px 14px; margin-bottom: 12px; }
|
|
373
422
|
.dsc-card-title { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-weight: 600; }
|
|
374
423
|
.dsc-drives { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); gap: 10px 18px; }
|
|
375
424
|
.dsc-drive-label { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; font-size: 12px; margin-bottom: 4px; }
|
|
376
425
|
.dsc-drive-label b { font-size: 13px; }
|
|
377
|
-
.dsc-drive-label span { color: var(--
|
|
378
|
-
.dsc-bar { height: 6px; border-radius:
|
|
379
|
-
.dsc-bar-fill { height: 100%; border-radius:
|
|
380
|
-
.dsc-bar-fill.dsc-warn { background: var(--
|
|
381
|
-
.dsc-bar-fill.dsc-crit { background: var(--
|
|
426
|
+
.dsc-drive-label span { color: var(--dsb-text-secondary); font-variant-numeric: tabular-nums; }
|
|
427
|
+
.dsc-bar { height: 6px; border-radius: var(--dsb-radius-pill); background: var(--dsb-surface-2); overflow: hidden; }
|
|
428
|
+
.dsc-bar-fill { height: 100%; border-radius: var(--dsb-radius-pill); background: var(--dsb-accent-intelligence); transition: width .25s ease; }
|
|
429
|
+
.dsc-bar-fill.dsc-warn { background: var(--dsb-accent-warn); }
|
|
430
|
+
.dsc-bar-fill.dsc-crit { background: var(--dsb-accent-error); }
|
|
382
431
|
.dsc-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; }
|
|
383
|
-
.dsc-chips-label { color: var(--
|
|
384
|
-
.dsc-chip { font: inherit; font-size: 12px; line-height: 1; padding: 5px 10px; border-radius:
|
|
385
|
-
.dsc-chip:hover:not(.dsc-chip-active) { color: var(--
|
|
386
|
-
.dsc-chip-active { border-color: var(--
|
|
432
|
+
.dsc-chips-label { color: var(--dsb-text-secondary); font-size: 12px; margin-right: 2px; }
|
|
433
|
+
.dsc-chip { font: inherit; font-size: 12px; line-height: 1; padding: 5px 10px; border-radius: var(--dsb-radius-pill); cursor: pointer; border: 1px solid var(--dsb-line-strong); background: transparent; color: var(--dsb-text-secondary); }
|
|
434
|
+
.dsc-chip:hover:not(.dsc-chip-active) { color: var(--dsb-text); background: var(--dsb-hover); }
|
|
435
|
+
.dsc-chip-active { border-color: transparent; background: var(--dsb-inverted-bg); color: var(--dsb-inverted-fg); font-weight: 600; }
|
|
387
436
|
.dsc-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
|
|
388
|
-
.dsc-btn { font: inherit; font-size: 12px; line-height: 1; padding: 6px 12px; border-radius:
|
|
389
|
-
.dsc-btn:hover:not(:disabled) { background: var(--
|
|
437
|
+
.dsc-btn { font: inherit; font-size: 12px; line-height: 1; padding: 6px 12px; border-radius: var(--dsb-radius-sm); cursor: pointer; border: 1px solid var(--dsb-line-strong); background: var(--dsb-surface); color: var(--dsb-text); }
|
|
438
|
+
.dsc-btn:hover:not(:disabled) { background: var(--dsb-surface-2); }
|
|
390
439
|
.dsc-btn:disabled { opacity: .5; cursor: default; }
|
|
391
|
-
.dsc-btn-primary { background: var(--
|
|
392
|
-
.dsc-btn-primary:hover:not(:disabled) {
|
|
393
|
-
.dsc-btn-danger { border-color: var(--
|
|
394
|
-
.dsc-btn-danger:hover:not(:disabled) { background: var(--
|
|
440
|
+
.dsc-btn-primary { background: var(--dsb-inverted-bg); border-color: transparent; color: var(--dsb-inverted-fg); font-weight: 600; }
|
|
441
|
+
.dsc-btn-primary:hover:not(:disabled) { opacity: .9; }
|
|
442
|
+
.dsc-btn-danger { border-color: var(--dsb-accent-error); color: var(--dsb-accent-error); }
|
|
443
|
+
.dsc-btn-danger:hover:not(:disabled) { background: var(--dsb-surface-2); }
|
|
395
444
|
.dsc-spacer { flex: 1; }
|
|
396
|
-
.dsc-total { color: var(--
|
|
397
|
-
.dsc-total b { color: var(--
|
|
445
|
+
.dsc-total { color: var(--dsb-text-secondary); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
446
|
+
.dsc-total b { color: var(--dsb-text); font-size: 13px; }
|
|
398
447
|
.dsc-list { display: flex; flex-direction: column; gap: 2px; }
|
|
399
|
-
.dsc-row { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius:
|
|
448
|
+
.dsc-row { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius: var(--dsb-radius-sm); cursor: pointer; }
|
|
400
449
|
.dsc-group { display: flex; flex-direction: column; gap: 2px; }
|
|
401
450
|
.dsc-group + .dsc-group { margin-top: 6px; }
|
|
402
|
-
.dsc-group-head { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; padding: 6px 8px; border: 0; border-radius:
|
|
403
|
-
.dsc-group-head:hover { background: var(--
|
|
404
|
-
.dsc-group-head:focus-visible { outline: 2px solid var(--
|
|
405
|
-
.dsc-caret { width: 10px; color: var(--
|
|
451
|
+
.dsc-group-head { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; padding: 6px 8px; border: 0; border-radius: var(--dsb-radius-sm); background: transparent; font: inherit; color: var(--dsb-text); cursor: pointer; text-align: left; }
|
|
452
|
+
.dsc-group-head:hover { background: var(--dsb-hover); }
|
|
453
|
+
.dsc-group-head:focus-visible { outline: 2px solid var(--dsb-text); outline-offset: 1px; }
|
|
454
|
+
.dsc-caret { width: 10px; color: var(--dsb-text-secondary); font-size: 10px; line-height: 1; }
|
|
406
455
|
.dsc-group-title { flex: 1; min-width: 0; font-size: 12px; font-weight: 600; letter-spacing: .01em; }
|
|
407
|
-
.dsc-group-meta { color: var(--
|
|
408
|
-
.dsc-row:hover { background: var(--
|
|
409
|
-
.dsc-row input { margin: 0; accent-color: var(--
|
|
456
|
+
.dsc-group-meta { color: var(--dsb-text-secondary); font-size: 11px; }
|
|
457
|
+
.dsc-row:hover { background: var(--dsb-hover); }
|
|
458
|
+
.dsc-row input { margin: 0; accent-color: var(--dsb-inverted-bg); }
|
|
410
459
|
.dsc-row-name { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
|
411
460
|
.dsc-row-label { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
412
|
-
.dsc-row-hint { color: var(--
|
|
413
|
-
.dsc-legend { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--
|
|
414
|
-
.dsc-legend-row { display: flex; align-items: flex-start; gap: 8px; color: var(--
|
|
461
|
+
.dsc-row-hint { color: var(--dsb-text-secondary); font-size: 11px; }
|
|
462
|
+
.dsc-legend { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--dsb-line-strong); margin-top: 10px; padding-top: 10px; }
|
|
463
|
+
.dsc-legend-row { display: flex; align-items: flex-start; gap: 8px; color: var(--dsb-text-secondary); font-size: 11px; line-height: 16px; }
|
|
415
464
|
.dsc-legend-row > span:last-child { flex: 1; min-width: 0; }
|
|
416
|
-
.dsc-badge { flex: none; font-size: 10px; line-height: 16px; padding: 0 6px; border-radius:
|
|
417
|
-
.dsc-badge.dsc-badge-caution { border-color: var(--
|
|
465
|
+
.dsc-badge { flex: none; font-size: 10px; line-height: 16px; padding: 0 6px; border-radius: var(--dsb-radius-sm); border: 1px solid var(--dsb-line-strong); color: var(--dsb-text-secondary); }
|
|
466
|
+
.dsc-badge.dsc-badge-caution { border-color: var(--dsb-accent-warn); color: var(--dsb-accent-warn); }
|
|
418
467
|
.dsc-size { flex: none; min-width: 72px; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
419
|
-
.dsc-size.dsc-idle { color: var(--
|
|
420
|
-
.dsc-empty { color: var(--
|
|
421
|
-
.dsc-notice { margin-top: 10px; padding: 8px 10px; border-radius:
|
|
422
|
-
.dsc-notice.dsc-ok { border-left: 3px solid var(--
|
|
423
|
-
.dsc-notice.dsc-err { border-left: 3px solid var(--
|
|
424
|
-
.dsc-notice.dsc-warn { border-left: 3px solid var(--
|
|
425
|
-
.dsc-audit-hint { color: var(--
|
|
426
|
-
.dsc-plan { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--
|
|
427
|
-
.dsc-plan-step { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; border-bottom: 1px solid var(--
|
|
468
|
+
.dsc-size.dsc-idle { color: var(--dsb-text-secondary); font-weight: 400; }
|
|
469
|
+
.dsc-empty { color: var(--dsb-text-secondary); padding: 18px 4px; text-align: center; line-height: 1.7; }
|
|
470
|
+
.dsc-notice { margin-top: 10px; padding: 8px 10px; border-radius: var(--dsb-radius-sm); font-size: 12px; line-height: 1.6; background: var(--dsb-surface-2); }
|
|
471
|
+
.dsc-notice.dsc-ok { border-left: 3px solid var(--dsb-accent-stability); }
|
|
472
|
+
.dsc-notice.dsc-err { border-left: 3px solid var(--dsb-accent-error); color: var(--dsb-accent-error); }
|
|
473
|
+
.dsc-notice.dsc-warn { border-left: 3px solid var(--dsb-accent-warn); }
|
|
474
|
+
.dsc-audit-hint { color: var(--dsb-text-secondary); font-size: 12px; line-height: 1.7; margin: -4px 0 10px; }
|
|
475
|
+
.dsc-plan { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--dsb-line-strong); }
|
|
476
|
+
.dsc-plan-step { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; border-bottom: 1px solid var(--dsb-line-strong); }
|
|
428
477
|
.dsc-plan-step:last-child { border-bottom: 0; }
|
|
429
478
|
.dsc-plan-head { display: flex; align-items: baseline; gap: 8px; }
|
|
430
|
-
.dsc-plan-index { flex: none; width: 18px; height: 18px; border-radius:
|
|
479
|
+
.dsc-plan-index { flex: none; width: 18px; height: 18px; border-radius: var(--dsb-radius-pill); background: var(--dsb-surface-2); color: var(--dsb-text-secondary); font-size: 11px; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums; }
|
|
431
480
|
.dsc-plan-title { flex: 1; min-width: 0; font-weight: 600; }
|
|
432
|
-
.dsc-plan-size { flex: none; font-variant-numeric: tabular-nums; color: var(--
|
|
433
|
-
.dsc-plan-detail { color: var(--
|
|
481
|
+
.dsc-plan-size { flex: none; font-variant-numeric: tabular-nums; color: var(--dsb-text-secondary); }
|
|
482
|
+
.dsc-plan-detail { color: var(--dsb-text-secondary); font-size: 12px; line-height: 1.7; padding-left: 26px; }
|
|
434
483
|
.dsc-plan-cmd { display: flex; align-items: flex-start; gap: 8px; padding-left: 26px; }
|
|
435
|
-
.dsc-plan-cmd pre { flex: 1; min-width: 0; margin: 0; padding: 8px 10px; border-radius:
|
|
484
|
+
.dsc-plan-cmd pre { flex: 1; min-width: 0; margin: 0; padding: 8px 10px; border-radius: var(--dsb-radius-sm); background: var(--dsb-surface); border: 1px solid var(--dsb-line-strong); font-family: var(--dsb-font-mono); font-size: 11px; line-height: 1.7; overflow-x: auto; white-space: pre; }
|
|
436
485
|
.dsc-plan-step .dsc-btn { align-self: flex-start; margin-left: 26px; }
|
|
437
486
|
.dsc-audit-group { margin-top: 12px; }
|
|
438
487
|
.dsc-audit-group:first-of-type { margin-top: 4px; }
|
|
439
|
-
.dsc-audit-group-title { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--
|
|
440
|
-
.dsc-audit-row { display: flex; align-items: baseline; gap: 10px; padding: 6px 8px; border-radius:
|
|
441
|
-
.dsc-audit-row:hover { background: var(--
|
|
488
|
+
.dsc-audit-group-title { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--dsb-text-secondary); margin-bottom: 4px; }
|
|
489
|
+
.dsc-audit-row { display: flex; align-items: baseline; gap: 10px; padding: 6px 8px; border-radius: var(--dsb-radius-sm); }
|
|
490
|
+
.dsc-audit-row:hover { background: var(--dsb-hover); }
|
|
442
491
|
.dsc-openable { cursor: pointer; }
|
|
443
|
-
.dsc-openable:hover .dsc-audit-label { text-decoration: underline; text-decoration-color: var(--
|
|
444
|
-
.dsc-openable:focus-visible { outline: 2px solid var(--
|
|
492
|
+
.dsc-openable:hover .dsc-audit-label { text-decoration: underline; text-decoration-color: var(--dsb-line); text-underline-offset: 3px; }
|
|
493
|
+
.dsc-openable:focus-visible { outline: 2px solid var(--dsb-text); outline-offset: 1px; }
|
|
445
494
|
.dsc-audit-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
446
495
|
.dsc-audit-label { font-weight: 600; }
|
|
447
|
-
.dsc-audit-why { color: var(--
|
|
496
|
+
.dsc-audit-why { color: var(--dsb-text-secondary); font-size: 11px; line-height: 16px; }
|
|
448
497
|
.dsc-audit-size { flex: none; min-width: 72px; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
449
498
|
.dsc-settings { display: flex; flex-direction: column; gap: 12px; }
|
|
450
499
|
.dsc-settings .dsc-card { margin: 0; }
|
|
451
|
-
.dsc-embedded { border-top: 1px solid var(--
|
|
500
|
+
.dsc-embedded { border-top: 1px solid var(--dsb-line-strong); padding-top: 12px; }
|
|
452
501
|
.dsc-embedded > .dsc-root { height: auto; padding: 0; background: transparent; }
|
|
453
|
-
.dsc-cfg-group + .dsc-cfg-group { border-top: .5px solid var(--
|
|
454
|
-
.dsc-cfg-group-title { color: var(--
|
|
502
|
+
.dsc-cfg-group + .dsc-cfg-group { border-top: .5px solid var(--dsb-line-strong); margin-top: 18px; padding-top: 18px; }
|
|
503
|
+
.dsc-cfg-group-title { color: var(--dsb-text); font-size: 16px; font-weight: 600; line-height: 24px; letter-spacing: .01em; }
|
|
455
504
|
.dsc-cfg-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; }
|
|
456
|
-
.dsc-cfg-label { flex: 1; min-width: 0; color: var(--
|
|
505
|
+
.dsc-cfg-label { flex: 1; min-width: 0; color: var(--dsb-text); font-size: 14px; font-weight: 400; }
|
|
457
506
|
.dsc-cfg-pick { position: relative; display: inline-flex; flex: none; }
|
|
458
|
-
.dsc-cfg-select { appearance: none; -webkit-appearance: none; box-sizing: border-box; height: 36px; max-width: 220px; padding: 0 34px 0 14px; border: none; border-radius:
|
|
459
|
-
.dsc-cfg-select:focus-visible { outline: 2px solid var(--
|
|
460
|
-
.dsc-cfg-
|
|
507
|
+
.dsc-cfg-select { appearance: none; -webkit-appearance: none; box-sizing: border-box; height: 36px; max-width: 220px; padding: 0 34px 0 14px; border: none; border-radius: var(--dsb-radius-pill); background: var(--dsb-card); color: var(--dsb-text); font: inherit; font-size: 14px; line-height: 22px; cursor: pointer; }
|
|
508
|
+
.dsc-cfg-select:focus-visible { outline: 2px solid var(--dsb-text); outline-offset: 1px; }
|
|
509
|
+
.dsc-cfg-select option { background: var(--dsb-surface); color: var(--dsb-text); }
|
|
510
|
+
.dsc-cfg-chevron { position: absolute; top: 50%; right: 13px; transform: translateY(-50%); pointer-events: none; color: var(--dsb-text-secondary); display: inline-flex; }
|
|
461
511
|
`;
|
|
462
512
|
|
|
463
513
|
function insertStyles(css) {
|
|
@@ -689,7 +739,7 @@ window.__ModuleLoader__.load({
|
|
|
689
739
|
drive.root,
|
|
690
740
|
h(
|
|
691
741
|
'span',
|
|
692
|
-
{ style: { color: 'var(--
|
|
742
|
+
{ style: { color: 'var(--dsb-text-secondary)', fontWeight: 400, marginLeft: 6 } },
|
|
693
743
|
`${Math.round(ratio * 100)}%`,
|
|
694
744
|
),
|
|
695
745
|
),
|
|
@@ -839,7 +889,7 @@ window.__ModuleLoader__.load({
|
|
|
839
889
|
|
|
840
890
|
return h(
|
|
841
891
|
'div',
|
|
842
|
-
{ className: 'dsc-root' },
|
|
892
|
+
{ className: 'dsc-root dsb-root' },
|
|
843
893
|
h(
|
|
844
894
|
'div',
|
|
845
895
|
{ className: 'dsc-head' },
|
|
@@ -1475,7 +1525,10 @@ window.__ModuleLoader__.load({
|
|
|
1475
1525
|
ctx.effect(() => locale.register(NS, 'zh', zh), 'dsh-disk-cleaner: locale zh');
|
|
1476
1526
|
ctx.effect(() => locale.register(NS, 'en', en), 'dsh-disk-cleaner: locale en');
|
|
1477
1527
|
}
|
|
1478
|
-
ctx.effect(() =>
|
|
1528
|
+
ctx.effect(() => {
|
|
1529
|
+
injectBaseline(document, PANEL_ID);
|
|
1530
|
+
return insertStyles(CSS);
|
|
1531
|
+
}, 'dsh-disk-cleaner: styles');
|
|
1479
1532
|
|
|
1480
1533
|
const label = () => {
|
|
1481
1534
|
if (!locale) return 'Disk Cleaner';
|
package/package.json
CHANGED
package/smoke.mjs
CHANGED
|
@@ -51,6 +51,9 @@ const window = {
|
|
|
51
51
|
|
|
52
52
|
const document = {
|
|
53
53
|
head,
|
|
54
|
+
// injectBaseline() de-duplicates through querySelector; the throwaway DOM has no
|
|
55
|
+
// stylesheet registry, so it simply reports "not injected yet".
|
|
56
|
+
querySelector: () => null,
|
|
54
57
|
createElement: () => ({ dataset: {}, setAttribute() {}, set textContent(v) {} }),
|
|
55
58
|
};
|
|
56
59
|
|
|
@@ -409,7 +412,28 @@ check(
|
|
|
409
412
|
source.includes('QQ') ,
|
|
410
413
|
'it only ever measures the QQGuild folder',
|
|
411
414
|
);
|
|
412
|
-
check('the version badge and package agree', pkg.version === '1.6.
|
|
415
|
+
check('the version badge and package agree', pkg.version === '1.6.4');
|
|
416
|
+
|
|
417
|
+
check(
|
|
418
|
+
'the panel carries dsh-design-baseline (full mode: css inlined, root tagged, tokens only)',
|
|
419
|
+
/>>> dsh-design-baseline v/.test(source) &&
|
|
420
|
+
/const BASELINE_CSS = "/.test(source) &&
|
|
421
|
+
/const injectBaseline = /.test(source) &&
|
|
422
|
+
/className: 'dsc-root dsb-root'/.test(source) &&
|
|
423
|
+
/injectBaseline\(document, PANEL_ID\)/.test(source),
|
|
424
|
+
'baseline stylesheet, root class and injector must all be present',
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
check(
|
|
428
|
+
'no colour reaches for a raw host token outside the baseline block',
|
|
429
|
+
(() => {
|
|
430
|
+
const from = source.indexOf('// >>> dsh-design-baseline');
|
|
431
|
+
const to = source.indexOf('// <<< dsh-design-baseline');
|
|
432
|
+
if (from < 0 || to < from) return false;
|
|
433
|
+
return !/--dsw-alias-[a-z0-9-]+/.test(source.slice(0, from) + source.slice(to));
|
|
434
|
+
})(),
|
|
435
|
+
'panel css must go through --dsb-* so the host theme is followed',
|
|
436
|
+
);
|
|
413
437
|
|
|
414
438
|
console.log(`\n${pass} passed, ${fail} failed`);
|
|
415
439
|
if (fail > 0) process.exit(1);
|