dsh-config-manager 0.2.3 → 0.3.0
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/lib/adapters/self.d.ts +44 -4
- package/lib/adapters/self.js +63 -3
- package/lib/client.d.ts +11 -6
- package/lib/client.js +1256 -1185
- package/lib/core/analyzer.d.ts +3 -0
- package/lib/core/analyzer.js +32 -56
- package/lib/core/backup.d.ts +36 -0
- package/lib/core/backup.js +125 -3
- package/lib/core/import-defaults.d.ts +13 -0
- package/lib/core/import-defaults.js +14 -0
- package/lib/core/importer.d.ts +10 -1
- package/lib/core/importer.js +7 -1
- package/lib/core/messages.d.ts +4 -0
- package/lib/core/messages.js +8 -0
- package/lib/core/model-tools.js +19 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +12 -22
- package/lib/routes/import.js +3 -1
- package/lib/routes/recovery.js +226 -2
- package/lib/routes/sync.js +26 -7
- package/lib/security/integrity.d.ts +24 -0
- package/lib/security/integrity.js +30 -0
- package/lib/security/unverified-file-blocks.d.ts +73 -0
- package/lib/security/unverified-file-blocks.js +151 -0
- package/lib/sync/autosync-config.d.ts +8 -4
- package/lib/sync/autosync-config.js +2 -2
- package/lib/sync/autosync-scheduler.d.ts +61 -3
- package/lib/sync/autosync-scheduler.js +87 -10
- package/lib/sync/config-issue-reporting.d.ts +32 -0
- package/lib/sync/config-issue-reporting.js +63 -0
- package/lib/sync/gc-skip.d.ts +42 -0
- package/lib/sync/gc-skip.js +57 -0
- package/lib/sync/git/git-transport.d.ts +56 -2
- package/lib/sync/git/git-transport.js +344 -73
- package/lib/sync/merge-preview-text.d.ts +11 -0
- package/lib/sync/merge-preview-text.js +38 -0
- package/lib/sync/s3/s3-transport.d.ts +8 -1
- package/lib/sync/s3/s3-transport.js +19 -5
- package/lib/sync/session-tombstones.d.ts +53 -0
- package/lib/sync/session-tombstones.js +52 -0
- package/lib/sync/sync-config.d.ts +55 -5
- package/lib/sync/sync-config.js +188 -15
- package/lib/sync/sync-engine.d.ts +21 -7
- package/lib/sync/sync-engine.js +63 -85
- package/lib/sync/webdav/webdav-transport.d.ts +11 -1
- package/lib/sync/webdav/webdav-transport.js +22 -6
- package/lib/ui/i18n.js +56 -36
- package/lib/ui/import-wizard.d.ts +1 -1
- package/lib/ui/import-wizard.js +2 -1
- package/lib/ui/overview-view.d.ts +6 -2
- package/lib/ui/overview-view.js +8 -1
- package/lib/ui/report.js +2 -1
- package/lib/utils/git-fast-forward.d.ts +7 -2
- package/lib/utils/git-fast-forward.js +8 -3
- package/package.json +2 -2
- package/src/adapters/self.test.ts +126 -85
- package/src/adapters/self.ts +73 -6
- package/src/client/ConfigManagerSection.tsx +0 -3
- package/src/client/common/Icon.tsx +3 -0
- package/src/client/common/SectionComposition.tsx +6 -1
- package/src/client/common/info-hint-guard.test.ts +7 -5
- package/src/client/config-manager.module.css +254 -13
- package/src/client/environment/EnvironmentPanel.tsx +57 -47
- package/src/client/environment/maintenance/DiskUsageCard.tsx +3 -1
- package/src/client/export/ExportView.tsx +3 -1
- package/src/client/home/HomePanel.tsx +25 -5
- package/src/client/import/ImportWizardView.tsx +1 -3
- package/src/client/import/import-wizard-steps.tsx +1 -17
- package/src/client/library/ArtifactRow.tsx +13 -2
- package/src/client/library/LibraryPanel.tsx +12 -2
- package/src/client/locales.ts +53 -34
- package/src/client/lucide-icons.d.ts +2 -0
- package/src/client/market/MarketPanel.tsx +12 -11
- package/src/client/market/market-locales.ts +16 -16
- package/src/client/recovery/RollbackCard.tsx +4 -2
- package/src/client/recovery/recovery-locales.test.ts +4 -2
- package/src/client/recovery/recovery-locales.ts +23 -23
- package/src/client/sync/AutosyncCard.tsx +36 -10
- package/src/client/sync/SyncChannelEntryCard.tsx +18 -19
- package/src/client/sync/SyncPanel.tsx +32 -33
- package/src/client/sync/SyncProbeRow.tsx +23 -6
- package/src/client/sync/SyncSectionPickerDialog.tsx +2 -2
- package/src/client/sync/sync-locales.ts +24 -24
- package/src/core/analyzer.ts +37 -56
- package/src/core/backup.test.ts +139 -1
- package/src/core/backup.ts +145 -4
- package/src/core/import-defaults.ts +13 -0
- package/src/core/importer.ts +12 -2
- package/src/core/integrity-extra-entries.test.ts +205 -0
- package/src/core/messages.test.ts +3 -1
- package/src/core/messages.ts +8 -0
- package/src/core/model-tools.test.ts +33 -0
- package/src/core/model-tools.ts +20 -4
- package/src/index.ts +12 -21
- package/src/routes/import.ts +4 -1
- package/src/routes/recovery.test.ts +198 -1
- package/src/routes/recovery.ts +194 -1
- package/src/routes/sync.ts +27 -8
- package/src/security/integrity.ts +37 -0
- package/src/security/unverified-file-blocks.ts +192 -0
- package/src/sync/autosync-config.ts +9 -5
- package/src/sync/autosync-scheduler.test.ts +133 -6
- package/src/sync/autosync-scheduler.ts +118 -14
- package/src/sync/config-issue-reporting.test.ts +148 -0
- package/src/sync/config-issue-reporting.ts +75 -0
- package/src/sync/gc-skip.test.ts +66 -0
- package/src/sync/gc-skip.ts +64 -0
- package/src/sync/git/git-probe.test.ts +6 -1
- package/src/sync/git/git-transport.test.ts +351 -14
- package/src/sync/git/git-transport.ts +343 -79
- package/src/sync/merge-preview-text.ts +37 -0
- package/src/sync/s3/s3-delete-index-404.test.ts +26 -1
- package/src/sync/s3/s3-transport.ts +23 -4
- package/src/sync/session-tombstones.ts +90 -0
- package/src/sync/sync-config.test.ts +170 -1
- package/src/sync/sync-config.ts +184 -14
- package/src/sync/sync-engine.test.ts +238 -0
- package/src/sync/sync-engine.ts +79 -83
- package/src/sync/webdav/webdav-delete-index-404.test.ts +29 -1
- package/src/sync/webdav/webdav-transport.ts +26 -5
- package/src/ui/disk-usage-view.test.ts +2 -1
- package/src/ui/i18n.test.ts +47 -0
- package/src/ui/i18n.ts +58 -38
- package/src/ui/import-wizard.test.ts +18 -1
- package/src/ui/import-wizard.ts +3 -2
- package/src/ui/overview-view.test.ts +9 -0
- package/src/ui/overview-view.ts +9 -2
- package/src/ui/report.test.ts +4 -3
- package/src/ui/report.ts +2 -1
- package/src/utils/git-fast-forward.ts +8 -3
package/src/adapters/self.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* self 分区 adapter(插件自身配置,设计「self 分区」):
|
|
3
3
|
* 数据源 = $DSH_HOME/dsh-config-manager/ 下的插件自身配置文件(白名单收集,非递归):
|
|
4
|
-
* - sync/sync-config.json 同步通道配置(git repoUrl / webdav url+username;凭据值走 credentials 槽位,不含 secret)
|
|
5
|
-
* - sync/sync-autosync.json 自动同步调度配置
|
|
6
4
|
* - sync/sync-selection.json 同步分区选择(默认/高级模式 + 勾选分区)
|
|
7
5
|
* - sync/ui-prefs.json 插件 UI 偏好(如上次选择的同步通道;从 localStorage 迁入)
|
|
6
|
+
* - sync/backup-schedule.json 备份调度配置
|
|
8
7
|
* - market/market-config.json 配置市场配置
|
|
8
|
+
* - exports/.backup-notes.json 导出产物备注清单
|
|
9
|
+
*
|
|
10
|
+
* **可信边界(SEC-01)**:`sync/sync-config.json` 与 `sync/sync-autosync.json` 是「同步目标」本身,
|
|
11
|
+
* 永不可由**包**决定 —— 一个把 repoUrl 指向攻击者仓库的包,导入完用户下一次同步就把家目录配置
|
|
12
|
+
* 推过去。它们不在白名单里(不导出),导入侧另由 `SELF_IMPORT_BLOCKED_RELS` 显式挡下并留 warning。
|
|
9
13
|
*
|
|
10
14
|
* 排除项:sync/sync-history.json(执行记录,属数据非配置)、market/cache/(缓存)、
|
|
11
15
|
* snapshots/(快照)、tmp/ exports/(临时/导出产物)、rollback-points/(回滚点库——磁盘体检
|
|
@@ -44,17 +48,22 @@
|
|
|
44
48
|
* 安全不变量:配置文件本身不含凭据值(同步凭据走 DSH credentials 槽位引用),
|
|
45
49
|
* 且文件类分区不进 SecretScanner(与 pluginFiles/skills 同语义)。
|
|
46
50
|
*/
|
|
47
|
-
import type { HostContext } from '../core/types.ts';
|
|
51
|
+
import type { ApplyResult, HostContext, ImportContext, PlanItem } from '../core/types.ts';
|
|
52
|
+
import type { FilesSection } from '../schema/types.ts';
|
|
48
53
|
import type { AutosyncConfig } from '../sync/autosync-config.ts';
|
|
49
54
|
import type { BackupScheduleConfig } from '../sync/backup-schedule-config.ts';
|
|
50
55
|
import type { RecursiveListing } from '../utils/recursive-walk.ts';
|
|
51
56
|
import { sectionMeta } from '../schema/section-registry.ts';
|
|
52
57
|
import { FileCollectionAdapter } from './file-collection.ts';
|
|
53
58
|
|
|
54
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* self 分区白名单文件(相对 baseDir,即 $DSH_HOME/dsh-config-manager/)。
|
|
61
|
+
*
|
|
62
|
+
* `sync/sync-config.json` 与 `sync/sync-autosync.json` **不在其中**(SEC-01):同步目标与自动同步开关
|
|
63
|
+
* 属于「目标机自己的决定」,跨机搬运等于让包决定把本机配置推到哪里。白名单只管导出侧;
|
|
64
|
+
* 导入侧的包内容不受白名单约束,所以另有 `SELF_IMPORT_BLOCKED_RELS` 显式挡下。
|
|
65
|
+
*/
|
|
55
66
|
export const SELF_CONFIG_FILES: readonly string[] = [
|
|
56
|
-
'sync/sync-config.json',
|
|
57
|
-
'sync/sync-autosync.json',
|
|
58
67
|
'sync/sync-selection.json',
|
|
59
68
|
'sync/ui-prefs.json',
|
|
60
69
|
'sync/backup-schedule.json',
|
|
@@ -64,6 +73,25 @@ export const SELF_CONFIG_FILES: readonly string[] = [
|
|
|
64
73
|
'exports/.backup-notes.json',
|
|
65
74
|
];
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* 导入侧**一律不写盘**的 rel(SEC-01)。
|
|
78
|
+
*
|
|
79
|
+
* 与白名单的关系:白名单管「导出带什么」,本清单管「包里有也绝不落地」。两者故意不共用一份
|
|
80
|
+
* 数组 —— 白名单将来若因别的理由重新收进某个文件,也不该连带解除导入侧的保护。
|
|
81
|
+
*/
|
|
82
|
+
export const SELF_IMPORT_BLOCKED_RELS: readonly string[] = [
|
|
83
|
+
'sync/sync-config.json',
|
|
84
|
+
'sync/sync-autosync.json',
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 拦截报告项里的**稳定机器码**前缀(`self-import-blocked:<rel>`)。
|
|
89
|
+
*
|
|
90
|
+
* 为什么要有码而不是只给文案:文案随语言/措辞变,调用方(报告、日志、将来的自动判定)需要
|
|
91
|
+
* 一个能逐字比对的判据。
|
|
92
|
+
*/
|
|
93
|
+
export const SELF_IMPORT_BLOCKED_CODE = "self-import-blocked";
|
|
94
|
+
|
|
67
95
|
/**
|
|
68
96
|
* 调度配置里「运行态字段」的**命名口径**:调度器每轮收尾都会改写的字段名形状
|
|
69
97
|
* (`lastRun*` 一族 + `consecutiveFailures`)。剔除表必须覆盖两个调度配置的并集,
|
|
@@ -223,4 +251,43 @@ export class SelfAdapter extends FileCollectionAdapter {
|
|
|
223
251
|
// 回落原引用(解析失败 / 非对象):走整文件哈希,且不额外重算包内哈希
|
|
224
252
|
return canonicalizeWithoutRunState(data) ?? data;
|
|
225
253
|
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 导入侧拦截(SEC-01):包里带 `sync/sync-config.json` / `sync/sync-autosync.json` → 改判 Warning。
|
|
257
|
+
*
|
|
258
|
+
* 为什么必须在**计划阶段**改判而不是只在 applyItem 里拒绝:这两份文件是「同步目标」本身,
|
|
259
|
+
* 一个把 repoUrl 指向攻击者仓库的包,导入完用户下一次同步就会把本机配置推过去 —— 而用户
|
|
260
|
+
* 从来没被问过「要不要换同步目标」。去掉 `target` 后引擎不会为它登记快照、也不会调 applyItem;
|
|
261
|
+
* 同时留下一项 report 级 warning(带稳定码 `self-import-blocked:<rel>`),让用户看得见
|
|
262
|
+
* 「包里有这个文件、我没写」,绝不静默。
|
|
263
|
+
*/
|
|
264
|
+
override async analyzeImport(data: FilesSection, ctx: ImportContext): Promise<PlanItem[]> {
|
|
265
|
+
const items = await super.analyzeImport(data, ctx);
|
|
266
|
+
return items.map((item) => {
|
|
267
|
+
const rel = item.target?.ref;
|
|
268
|
+
if (rel === undefined || !SELF_IMPORT_BLOCKED_RELS.includes(rel)) return item;
|
|
269
|
+
const blocked: PlanItem = {
|
|
270
|
+
id: item.id,
|
|
271
|
+
kind: 'Warning',
|
|
272
|
+
adapter: this.id,
|
|
273
|
+
description: ctx.msg('adapter.selfImportBlocked', { path: rel }),
|
|
274
|
+
detail: `${SELF_IMPORT_BLOCKED_CODE}:${rel}`,
|
|
275
|
+
severity: 'warning',
|
|
276
|
+
};
|
|
277
|
+
if (item.unitId !== undefined) blocked.unitId = item.unitId;
|
|
278
|
+
return blocked;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 纵深防御(SEC-01):即使有人绕过引擎直接把被拦项喂进 applyItem,也一字节都不写。
|
|
284
|
+
* 报 warning=true(非致命、不触发整体回滚),与计划阶段的判定同源。
|
|
285
|
+
*/
|
|
286
|
+
override async applyItem(item: PlanItem, ctx: ImportContext): Promise<ApplyResult> {
|
|
287
|
+
const ref = item.target?.ref;
|
|
288
|
+
if (ref !== undefined && SELF_IMPORT_BLOCKED_RELS.includes(ref)) {
|
|
289
|
+
return { ok: false, message: ctx.msg('adapter.selfImportBlocked', { path: ref }), warning: true };
|
|
290
|
+
}
|
|
291
|
+
return super.applyItem(item, ctx);
|
|
292
|
+
}
|
|
226
293
|
}
|
|
@@ -649,9 +649,6 @@ const navLayoutResult = navWidths === null
|
|
|
649
649
|
<ImportWizardView
|
|
650
650
|
api={api}
|
|
651
651
|
t={t}
|
|
652
|
-
uiT={uiT}
|
|
653
|
-
// 向导 → 来源选择:同样是改 Task 入参(面板状态只有一个事实源)
|
|
654
|
-
onOpenForeignImport={() => { runStore.patch({ task: { ...activeTask, payload: { foreign: true } } }) }}
|
|
655
652
|
/>
|
|
656
653
|
))}
|
|
657
654
|
{activeTask.kind === 'market' && (
|
|
@@ -42,6 +42,7 @@ import PackageCheck from 'lucide-react/dist/esm/icons/package-check.mjs'
|
|
|
42
42
|
import Pencil from 'lucide-react/dist/esm/icons/pencil.mjs'
|
|
43
43
|
import RefreshCw from 'lucide-react/dist/esm/icons/refresh-cw.mjs'
|
|
44
44
|
import RotateCw from 'lucide-react/dist/esm/icons/rotate-cw.mjs'
|
|
45
|
+
import Plug from 'lucide-react/dist/esm/icons/plug.mjs'
|
|
45
46
|
import Search from 'lucide-react/dist/esm/icons/search.mjs'
|
|
46
47
|
import Trash2 from 'lucide-react/dist/esm/icons/trash-2.mjs'
|
|
47
48
|
import TriangleAlert from 'lucide-react/dist/esm/icons/triangle-alert.mjs'
|
|
@@ -63,6 +64,7 @@ const ICONS = {
|
|
|
63
64
|
clock: Clock,
|
|
64
65
|
history: History,
|
|
65
66
|
about: Info,
|
|
67
|
+
probe: Plug,
|
|
66
68
|
info: Info,
|
|
67
69
|
lock: Lock,
|
|
68
70
|
message: MessageSquare,
|
|
@@ -236,6 +238,7 @@ export const ActivityIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon n
|
|
|
236
238
|
export const ClockIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="clock" {...p} />
|
|
237
239
|
export const HistoryIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="history" {...p} />
|
|
238
240
|
export const AboutIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="about" {...p} />
|
|
241
|
+
export const ProbeIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="probe" {...p} />
|
|
239
242
|
export const MessageIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="message" {...p} />
|
|
240
243
|
export const PencilIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="pencil" {...p} />
|
|
241
244
|
export const CloseIcon = (p: Omit<IconProps, 'name'>): JSX.Element => <Icon name="close" {...p} />
|
|
@@ -48,8 +48,13 @@ export function SectionComposition({ sections, t, sectionLabel }: SectionComposi
|
|
|
48
48
|
<div className={css.sectionGrid}>
|
|
49
49
|
{sections.map((s) => {
|
|
50
50
|
const state = s.state ?? 'ready'
|
|
51
|
+
/* 真·空行降权(2026-11-01):条目与体积都为 0 的分区(如未启用的 Agent Presets)
|
|
52
|
+
与有内容的行同权重,会让「这一项什么都没有」读成和「941 条」一样重要。
|
|
53
|
+
app-screens §13:零值不给状态色,用灰 —— 有内容但 0 条目的行(如 Prompts 0 条 / 26 B)
|
|
54
|
+
保持正常色,因为它确实带着配置。 */
|
|
55
|
+
const isEmptyRow = state === 'ready' && s.count === 0 && s.sizeBytes === 0
|
|
51
56
|
return (
|
|
52
|
-
<div key={s.section} className={css.sectionRow}>
|
|
57
|
+
<div key={s.section} className={isEmptyRow ? `${css.sectionRow} ${css.sectionRowEmpty}` : css.sectionRow}>
|
|
53
58
|
<span className={css.sectionName}>{sectionLabel?.(s.section) ?? s.section}</span>
|
|
54
59
|
{state === 'ready'
|
|
55
60
|
? (
|
|
@@ -103,11 +103,11 @@ const MOVE_PINS: readonly MovePin[] = [
|
|
|
103
103
|
{ key: 'webdav.usernameHint', file: 'src/client/sync/ChannelConfigDialog.tsx', kind: '输入规则' },
|
|
104
104
|
{ key: 'config.saveHint', file: 'src/client/sync/ChannelConfigDialog.tsx', kind: '机制' },
|
|
105
105
|
{ key: 'mode.sessionsPickHint', file: 'src/client/sync/SyncSectionPickerDialog.tsx', kind: '机制' },
|
|
106
|
-
{ key: 'mode.pickerHint', file: 'src/client/sync/SyncSectionPickerDialog.tsx', kind: '机制' },
|
|
107
106
|
{ key: 'mode.sessionsLimitHint', file: 'src/client/sync/SyncSectionPickerDialog.tsx', kind: '输入规则' },
|
|
108
107
|
{ key: 'mode.sectionsHint', file: 'src/client/sync/SyncSectionPickerDialog.tsx', kind: '机制' },
|
|
109
108
|
{ key: 'mode.hint', file: 'src/client/sync/SyncPanel.tsx', kind: '机制' },
|
|
110
|
-
|
|
109
|
+
// 2026-11-01 用户要求:删掉两处「保存/即时生效」ⓘ(弹窗底部与同步页分区行)——
|
|
110
|
+
// 它们在解释用户无从选择的事,MOVE 台账随之退出 2 条。
|
|
111
111
|
{ key: 'channel.openHint', file: 'src/client/sync/SyncChannelEntryCard.tsx', kind: '机制' },
|
|
112
112
|
{ key: 'autosync.description', file: 'src/client/sync/AutosyncCard.tsx', kind: '机制' },
|
|
113
113
|
{ key: 'autosync.intervalHint', file: 'src/client/sync/AutosyncCard.tsx', kind: '机制' },
|
|
@@ -155,7 +155,6 @@ const MOVE_PINS: readonly MovePin[] = [
|
|
|
155
155
|
{ key: 'import.decryptArchive.hint', file: 'src/client/import/import-wizard-steps.tsx', kind: '机制' },
|
|
156
156
|
{ key: 'import.conflicts.hint', file: 'src/client/import/import-wizard-steps.tsx', kind: '机制' },
|
|
157
157
|
{ key: 'import.paths.hint', file: 'src/client/import/import-wizard-steps.tsx', kind: '机制' },
|
|
158
|
-
{ key: 'foreign.source.hint', file: 'src/client/import/import-wizard-steps.tsx', kind: '机制' },
|
|
159
158
|
{ key: 'import.secrets.hint', file: 'src/client/import/import-wizard-steps.tsx', kind: '机制' },
|
|
160
159
|
{ key: 'import.picker.hint', file: 'src/client/import/ImportWizardView.tsx', kind: '机制' },
|
|
161
160
|
{ key: 'foreign.source.hint', file: 'src/client/import/ForeignImportView.tsx', kind: '机制' },
|
|
@@ -385,14 +384,17 @@ function parseInfoHintEntry(rel: string): { zh: string | undefined; en: string |
|
|
|
385
384
|
return { zh: values[0], en: values[1], count: values.length };
|
|
386
385
|
}
|
|
387
386
|
|
|
388
|
-
test('t6-1 MOVE:
|
|
387
|
+
test('t6-1 MOVE:79 个说明键都已收进 ⓘ(逐字复用原键)且不再有可见说明行', () => {
|
|
389
388
|
const files = readClientSources();
|
|
390
389
|
// 扫描范围自检:客户端源码至少覆盖 t6 涉及的面板目录
|
|
391
390
|
const dirs = new Set(MOVE_PINS.map((p) => p.file.split('/').slice(0, 3).join('/')));
|
|
392
391
|
assert.ok(dirs.size >= 8, 'MOVE 清单必须覆盖全部面板目录(实际 ' + String(dirs.size) + ' 个)');
|
|
393
392
|
// 2026-10-27 文案审计:about.subtitle 是「页面目录式副标题」(插件信息、作者与反馈入口),
|
|
394
393
|
// 页面本身已由卡片表达,随页级标题一并删除 ⇒ MOVE 清单从 82 降到 81(不是删检查项)。
|
|
395
|
-
|
|
394
|
+
// 2026-11-01:删掉「改动即时生效并保存」「分区选择已保存到本机」两处 ⓘ(用户要求,
|
|
395
|
+
// 属解释「用户无从选择的事」的废话)⇒ 81 降到 79(不是删检查项)。
|
|
396
|
+
// 2026-11-01:导入选择页的「从其它 agent 导入」按钮与其 ⓘ 一并移除(首页已有直达入口)⇒ 79 降到 78。
|
|
397
|
+
assert.ok(MOVE_PINS.length === 78, 'MOVE 清单长度应为 78(实际 ' + String(MOVE_PINS.length) + ')');
|
|
396
398
|
assert.ok(
|
|
397
399
|
MOVE_PINS.filter((p) => p.kind === '输入规则').length >= 5,
|
|
398
400
|
'输入规则类 MOVE 至少 5 处(config.repoUrlHint / webdav.urlHint / mode.sessionsLimitHint / retention.keep*Hint / export.fileNameHint)',
|
|
@@ -1086,6 +1086,11 @@ body {
|
|
|
1086
1086
|
overflow-y: auto;
|
|
1087
1087
|
overscroll-behavior: contain;
|
|
1088
1088
|
min-height: 0;
|
|
1089
|
+
/* 2026-11-01 视觉 pass:滚动条槽固定 + 末行留 4px 呼吸。
|
|
1090
|
+
此前滚动条出现/消失会让整行宽度跳 5px,末行又紧贴底栏的 1px 横线,
|
|
1091
|
+
读起来像「这一行被界面切断了」。 */
|
|
1092
|
+
scrollbar-gutter: stable;
|
|
1093
|
+
padding-bottom: 4px;
|
|
1089
1094
|
}
|
|
1090
1095
|
|
|
1091
1096
|
/* 空态 / 加载态:与 .artifactList 同一条契约 —— 同样吃掉剩余高度,
|
|
@@ -1183,6 +1188,23 @@ body {
|
|
|
1183
1188
|
white-space: nowrap;
|
|
1184
1189
|
}
|
|
1185
1190
|
|
|
1191
|
+
/* 行内主操作常态是 ghost(2026-11-01 视觉 pass):八行八颗实心按钮让 accent 变成壁纸,
|
|
1192
|
+
整屏最亮的元素成了 chrome,主色不再表示「哪一行值得动」。悬停该行或展开该行时才升到
|
|
1193
|
+
primary —— 此刻它确实是这一行要做的事。过渡走 --cm-motion-fast,与全站一致。 */
|
|
1194
|
+
.artifactActions .rowPrimary {
|
|
1195
|
+
transition:
|
|
1196
|
+
background var(--cm-motion-fast) ease,
|
|
1197
|
+
border-color var(--cm-motion-fast) ease,
|
|
1198
|
+
color var(--cm-motion-fast) ease;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.artifactRowHead:hover .artifactActions .rowPrimary,
|
|
1202
|
+
.artifactRow[data-expanded] .artifactActions .rowPrimary {
|
|
1203
|
+
background: var(--cm-primary-fill);
|
|
1204
|
+
border-color: transparent;
|
|
1205
|
+
color: var(--cm-text-on-accent);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1186
1208
|
/* 徽章与动作 flex:none:标题先被压缩(ellipsis),不允许把按钮挤成两行 */
|
|
1187
1209
|
.artifactBadges,
|
|
1188
1210
|
.artifactActions {
|
|
@@ -1360,6 +1382,47 @@ body {
|
|
|
1360
1382
|
outline-offset: 2px;
|
|
1361
1383
|
}
|
|
1362
1384
|
|
|
1385
|
+
/* 「左标题 · 右开关」行(2026-11-01)。设置类开关的标题就是这一行的名字,
|
|
1386
|
+
开关是这一行的值 —— 控件跟在标题后面读起来是「启用 ☑」,用户得先读半句再找方框,
|
|
1387
|
+
而 564px 画布里每行右侧本来就空着。开关统一贴右:勾选框仍在标签内(点标题也能切),
|
|
1388
|
+
但 InfoHint 不放进 label,否则点 ⓘ 会顺手改掉设置。 */
|
|
1389
|
+
.toggleRow {
|
|
1390
|
+
display: flex;
|
|
1391
|
+
align-items: center;
|
|
1392
|
+
gap: 6px;
|
|
1393
|
+
font-size: var(--cm-fs-base);
|
|
1394
|
+
color: var(--dsw-alias-label-primary);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
.toggleLabel {
|
|
1398
|
+
cursor: pointer;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.toggleInput {
|
|
1402
|
+
width: 16px;
|
|
1403
|
+
height: 16px;
|
|
1404
|
+
margin: 0;
|
|
1405
|
+
flex: none;
|
|
1406
|
+
accent-color: var(--cm-brand);
|
|
1407
|
+
cursor: pointer;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
.toggleInput:focus-visible {
|
|
1411
|
+
outline: var(--cm-focus-width) solid var(--cm-focus-color);
|
|
1412
|
+
outline-offset: 2px;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/* 小节标题行:标题在左、该节的开关贴右(与 .toggleRow 同一口径)。 */
|
|
1416
|
+
.subBlockHead {
|
|
1417
|
+
display: flex;
|
|
1418
|
+
align-items: center;
|
|
1419
|
+
gap: 8px;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
.subBlockHead > .groupLabel {
|
|
1423
|
+
margin-bottom: 0;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1363
1426
|
/* —— 徽章 —— */
|
|
1364
1427
|
.badge {
|
|
1365
1428
|
display: inline-flex;
|
|
@@ -2832,6 +2895,19 @@ body {
|
|
|
2832
2895
|
margin: 0 0 10px;
|
|
2833
2896
|
}
|
|
2834
2897
|
|
|
2898
|
+
/* 平分整行的动作行(2026-11-01 同步页):两个方向按钮各占一半,右边缘与卡内其余行齐平 ——
|
|
2899
|
+
宽度各随文案时,三个按钮排一行会形成左对齐锯齿,且窄的那个看起来更次要(显得随意)。
|
|
2900
|
+
主次靠 primary 底色表达,不靠宽度。 */
|
|
2901
|
+
.actionRow[data-split] {
|
|
2902
|
+
flex-wrap: nowrap;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
.actionRow[data-split] > .splitBtn {
|
|
2906
|
+
flex: 1 1 0;
|
|
2907
|
+
min-width: 0;
|
|
2908
|
+
justify-content: center;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2835
2911
|
|
|
2836
2912
|
|
|
2837
2913
|
/* 上方有说明文案的操作行(如「通道配置」按钮紧跟在 hint 之后):
|
|
@@ -2892,6 +2968,19 @@ body {
|
|
|
2892
2968
|
grid-column: 1 / -1;
|
|
2893
2969
|
}
|
|
2894
2970
|
|
|
2971
|
+
/* 市场操作行:标题在左、动作在右,同一行(原为一张只有标题 + 两按钮的卡,
|
|
2972
|
+
右侧永远空着 —— 这张卡没有任何独立内容,改成一行即省掉整块死空间)。 */
|
|
2973
|
+
.marketRepoRow {
|
|
2974
|
+
display: flex;
|
|
2975
|
+
align-items: center;
|
|
2976
|
+
gap: 8px;
|
|
2977
|
+
margin-bottom: 10px;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
.marketRepoRow > .groupLabel {
|
|
2981
|
+
margin-bottom: 0;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2895
2984
|
/* 筛选区下方的元信息行(分区未知提示 + 条目计数徽章) */
|
|
2896
2985
|
.marketFilterMeta {
|
|
2897
2986
|
display: flex;
|
|
@@ -3120,6 +3209,56 @@ body {
|
|
|
3120
3209
|
flex: 1 1 auto;
|
|
3121
3210
|
min-height: 0;
|
|
3122
3211
|
max-height: none;
|
|
3212
|
+
/* 2026-11-01:与首页活动列表同一个毛病 —— 卡片已有外框,滚动区再套一层
|
|
3213
|
+
border + radius 就是双圈;它的 bg-base 又比卡面深一档,整块读作「卡片里挖了个洞」
|
|
3214
|
+
(附带的边框线还让表头看起来像一条深色填充带,app-screens §7.2 明令无填充表头带)。
|
|
3215
|
+
卡内滚动区应当是透明的:卡片面连续,只有滚动条提示「内容还没完」。 */
|
|
3216
|
+
border: none;
|
|
3217
|
+
border-radius: 0;
|
|
3218
|
+
background: transparent;
|
|
3219
|
+
padding-right: 2px;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
/* 卡片固定高度(420px)下,除明细表外的子项一律不参与压缩(2026-11-01 实测):
|
|
3223
|
+
内容一多(比如出现「有目录读不到」的提示行)时,flex 会按比例压扁每个子项,
|
|
3224
|
+
.cellMeta 被压到 4px、文字被 overflow:hidden 裁成半个字 —— 读起来像「这行坏了」。
|
|
3225
|
+
设计意图写在上面的注释②:只有明细表滚动,其余恒在视野内。 */
|
|
3226
|
+
.diskUsageCard > * {
|
|
3227
|
+
flex-shrink: 0;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
.diskUsageCard > .diskUsageScroll {
|
|
3231
|
+
flex-shrink: 1;
|
|
3232
|
+
min-height: 0;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
/* 列宽显式给定(2026-11-01):table-layout: fixed 下不给宽度时浏览器按首行内容分配,
|
|
3236
|
+
Cleanup policy 只剩 ~118px —— "Auto-removed after 7 days"、"Your data / safety net
|
|
3237
|
+
(never auto-removed)" 全被省略号截掉,而这一列恰好是「这块数据能不能删」的唯一答案。 */
|
|
3238
|
+
.diskUsageScroll th:nth-child(2) { width: 78px; }
|
|
3239
|
+
.diskUsageScroll th:nth-child(3) { width: 86px; }
|
|
3240
|
+
.diskUsageScroll th:nth-child(4) { width: 160px; }
|
|
3241
|
+
|
|
3242
|
+
/* 数值两列不需要省略号:内容永远是「4.8 KB」「235 file(s)」这种短串,
|
|
3243
|
+
一旦被 .tableFixed 的 ellipsis 截成 "265.0 …" 就完全读不出量级。 */
|
|
3244
|
+
.diskUsageScroll td:nth-child(2),
|
|
3245
|
+
.diskUsageScroll td:nth-child(3) {
|
|
3246
|
+
overflow: visible;
|
|
3247
|
+
text-overflow: clip;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
/* 策略列允许换行(其余列是短标签,保持单行省略) */
|
|
3251
|
+
.diskUsageScroll td:nth-child(4) { white-space: normal; }
|
|
3252
|
+
|
|
3253
|
+
/* 第一列也允许换行(2026-11-01):内容列剩 186px,"Sync config & workbook copies" 这类
|
|
3254
|
+
长名字一律被截成「Sync config & work…」—— 而这一列就是「这是哪块数据」的唯一线索。
|
|
3255
|
+
表格行高由 .tableFixed 的 min-height 兜住,换行后仍是两行可读,不会塌成 4px。 */
|
|
3256
|
+
.diskUsageScroll td:nth-child(1) { white-space: normal; }
|
|
3257
|
+
|
|
3258
|
+
/* 卡内小节之间留出间隔(2026-11-01):表格滚动区下方紧贴的「Legacy data」分组标签
|
|
3259
|
+
原本与最后一行数据只差 2px,读起来像表格的又一行数据。 */
|
|
3260
|
+
.diskUsageCard .groupLabel {
|
|
3261
|
+
margin-top: 10px;
|
|
3123
3262
|
}
|
|
3124
3263
|
|
|
3125
3264
|
.activityHeader {
|
|
@@ -3138,24 +3277,49 @@ body {
|
|
|
3138
3277
|
}
|
|
3139
3278
|
|
|
3140
3279
|
.activityRows {
|
|
3141
|
-
/* v3
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3280
|
+
/* v3 曾让卡片吃掉首页剩余高度(.fillCard)并让列表伸展。2026-11-01 改为限高内滚:
|
|
3281
|
+
撑满那一刻,8 行记录下方会空出约 150px 纯背景,读起来是「还在加载」,而不是
|
|
3282
|
+
「这里本来就这么高」(app-screens §4.2 / §10:空得零散像没做完)。
|
|
3283
|
+
现在卡片高度随内容,行数超过上限时本容器内滚 —— 长列表纪律与 .planScroll 一致。 */
|
|
3284
|
+
max-height: 460px;
|
|
3145
3285
|
min-height: 0;
|
|
3146
3286
|
overflow-y: auto;
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3287
|
+
/* 2026-11-01 视觉 pass:去掉列表自带的 1px 外框与 bg-base 深底。
|
|
3288
|
+
卡已经是 .card(一层 1px 外框 + 表面色),列表再套一层 = 双圈边框;
|
|
3289
|
+
而 bg-base 比卡片表面深一档,8 行记录下方的空余区被读成「表格还没加载完」
|
|
3290
|
+
(app-screens §4.2:卡内 >40px 的空带读作未完成)。现在列表直接坐在卡片表面上,
|
|
3291
|
+
只有行分隔线划分行。滚动条槽固定,避免滚动条出现时整行宽度跳一下。 */
|
|
3292
|
+
scrollbar-gutter: stable;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
/* 列头:12px 灰、句首大写、无底色、无下划线(app-screens §7.2:11 张有表头的表里 0 张有
|
|
3296
|
+
填充带,8 张没有标头下划线)。三列与 .activityRow 同一份 grid 定义,否则列头与数据错位。 */
|
|
3297
|
+
.activityHead {
|
|
3298
|
+
display: grid;
|
|
3299
|
+
/* 四列:时间 / 动作 / 详情 / 结果。第二列定宽(96px = 最长 kind 文案 "Manual backup"),
|
|
3300
|
+
列头与行是两个独立的 grid 容器,只有定宽才能让表头压在数据上方对齐。 */
|
|
3301
|
+
grid-template-columns: 72px 96px minmax(0, 1fr) auto;
|
|
3302
|
+
align-items: center;
|
|
3303
|
+
gap: 10px;
|
|
3304
|
+
padding: 0 10px 6px;
|
|
3305
|
+
font-size: var(--cm-fs-table);
|
|
3306
|
+
line-height: 1.25;
|
|
3307
|
+
color: var(--dsw-alias-label-tertiary);
|
|
3308
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l1);
|
|
3150
3309
|
}
|
|
3151
3310
|
|
|
3311
|
+
.activityHead > :last-child {
|
|
3312
|
+
justify-self: end;
|
|
3313
|
+
}
|
|
3152
3314
|
|
|
3153
3315
|
.activityRow {
|
|
3154
3316
|
display: grid;
|
|
3155
|
-
grid-template-columns: 72px minmax(0, 1fr) auto;
|
|
3317
|
+
grid-template-columns: 72px 96px minmax(0, 1fr) auto;
|
|
3156
3318
|
align-items: center;
|
|
3157
3319
|
gap: 10px;
|
|
3158
|
-
|
|
3320
|
+
/* 行高 36 → 40(app-screens §7.1:单行数据行 36-53px,中位 45);
|
|
3321
|
+
密度三件套同动 —— 只抬行高不抬字号,符合 §10「别只缩字号」的反面。 */
|
|
3322
|
+
padding: 10px 10px;
|
|
3159
3323
|
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
|
3160
3324
|
font-size: var(--cm-fs-table);
|
|
3161
3325
|
line-height: 1.5;
|
|
@@ -3176,11 +3340,13 @@ body {
|
|
|
3176
3340
|
}
|
|
3177
3341
|
|
|
3178
3342
|
.activityKind {
|
|
3179
|
-
|
|
3343
|
+
min-width: 0;
|
|
3180
3344
|
line-height: 1.5;
|
|
3181
3345
|
color: var(--dsw-alias-label-tertiary);
|
|
3182
3346
|
font-size: var(--cm-fs-meta);
|
|
3183
3347
|
white-space: nowrap;
|
|
3348
|
+
overflow: hidden;
|
|
3349
|
+
text-overflow: ellipsis;
|
|
3184
3350
|
}
|
|
3185
3351
|
|
|
3186
3352
|
.activitySummary {
|
|
@@ -3220,9 +3386,44 @@ body {
|
|
|
3220
3386
|
}
|
|
3221
3387
|
|
|
3222
3388
|
/* —— 备份位置卡:四列事实网格(标签上、值下,tabular-nums 对齐) —— */
|
|
3389
|
+
/* 卡内小节分隔(2026-11-01):同一张卡里的第二块内容不再套 Card,改用顶线 + 上下 10px。
|
|
3390
|
+
0.5px + border-l2 与 css 里既有的 4 处卡内分节一致(DESIGN.md:行分隔 0.5px、外框 1px)。 */
|
|
3391
|
+
/* 同步内容选择行(2026-11-01):原为页面上独立的一张卡,卡内只有「9 selected + Choose…」,
|
|
3392
|
+
右侧整片留白(app-screens §4.2 卡内死空间)。并入动作卡后一行放下:标题 · 计数 · 按钮 · ⓘ。
|
|
3393
|
+
groupLabel 的 8px 块级下边距在这里归零,行内只靠 gap。 */
|
|
3394
|
+
.sectionPickerRow {
|
|
3395
|
+
display: flex;
|
|
3396
|
+
align-items: center;
|
|
3397
|
+
gap: 8px;
|
|
3398
|
+
flex-wrap: wrap;
|
|
3399
|
+
margin-bottom: 8px;
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
.sectionPickerRow > .groupLabel {
|
|
3403
|
+
margin-bottom: 0;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
/* 「选择」按钮贴右(2026-11-01 用户要求):整行读成「左描述 / 右按钮」,
|
|
3407
|
+
与设置页其余行(左文案 + 右控件)同构;此前它紧跟在计数后面,行右半是空的。
|
|
3408
|
+
窄到放不下时随 flex-wrap 掉到下一行,仍然靠右。 */
|
|
3409
|
+
.sectionPickerRow > .pickerBtn {
|
|
3410
|
+
margin-left: auto;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
.syncSubBlock {
|
|
3414
|
+
margin-top: 10px;
|
|
3415
|
+
padding-top: 10px;
|
|
3416
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3223
3419
|
.factGrid {
|
|
3224
3420
|
display: grid;
|
|
3225
|
-
|
|
3421
|
+
/* 列数跟内容走(2026-11-01):原来写死 4 列,而事实行实际只有「上次同步 / 可同步分区」
|
|
3422
|
+
两项 —— 内容只占左边一半,右边是一列永远空着的轨道,整张卡被读成「右边还没加载完」。
|
|
3423
|
+
app-screens §4.2 把「grid 不适配数据造成的死空间」列为最常见的版面失败。
|
|
3424
|
+
auto-fit + 最小列宽让两格平分整行;只剩一格时(无 statusInfo)它独占整行也不会挤成窄条。
|
|
3425
|
+
远端 URL 那格仍是 gridColumn: 1 / -1,跨满全部自动列。 */
|
|
3426
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
3226
3427
|
gap: 6px 12px;
|
|
3227
3428
|
}
|
|
3228
3429
|
|
|
@@ -3248,18 +3449,49 @@ body {
|
|
|
3248
3449
|
text-overflow: ellipsis;
|
|
3249
3450
|
}
|
|
3250
3451
|
|
|
3452
|
+
/* 上次同步:从事实格移进徽章行行尾(2026-11-01)。删掉「可同步分区」那格之后,
|
|
3453
|
+
事实格首行只剩一格,右侧留出 ~500px 空带(DESIGN §9 #20 panel-void)。
|
|
3454
|
+
单条时间事实跟在徽章后面读起来是一句话,也省掉一整行高度。 */
|
|
3455
|
+
.factInline {
|
|
3456
|
+
font-size: var(--cm-fs-meta);
|
|
3457
|
+
color: var(--dsw-alias-label-tertiary);
|
|
3458
|
+
white-space: nowrap;
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
/* 卡标题行:标题在左、单条状态事实贴右(2026-11-01)。564px 画布下徽章行已被三枚徽章
|
|
3462
|
+
占满,那条时间事实只能掉成孤零零的第二行;而标题行右侧一直是空的。 */
|
|
3463
|
+
.cardTitleRow {
|
|
3464
|
+
display: flex;
|
|
3465
|
+
align-items: baseline;
|
|
3466
|
+
gap: 8px;
|
|
3467
|
+
margin-bottom: 8px;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
.cardTitleRow > .groupLabel {
|
|
3471
|
+
margin-bottom: 0;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3251
3474
|
/* —— 分区构成卡:两列网格行(名称 | 条目数 | 体积) ——
|
|
3252
3475
|
用 auto-fit,窄容器(弹窗内的分区构成)自动退化为单列,
|
|
3253
3476
|
宽容器(总览「分区构成」卡 / 导出页「本次将导出」数据块)仍是两列。 */
|
|
3254
3477
|
.sectionGrid {
|
|
3255
3478
|
display: grid;
|
|
3256
|
-
|
|
3479
|
+
/* 每列至少要装得下「分区名 + 计数 + 大小」三个字段(2026-11-01):
|
|
3480
|
+
原先 210px 的列在两列布局里只剩 ~107px 给名字,11px 等宽下只放得下 16 个字符 ——
|
|
3481
|
+
"Config Manager settings"、"Agent Instructions" 全被截成前半截,
|
|
3482
|
+
而右半边页面下方空着一大片(app-screens §4.2 的死空间)。
|
|
3483
|
+
260px 起跳后 540px 画布上刚好只排一列,名字完整、计数与大小仍共用同一条右边缘。 */
|
|
3484
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
3257
3485
|
gap: 0 24px;
|
|
3258
3486
|
}
|
|
3259
3487
|
|
|
3260
3488
|
.sectionRow {
|
|
3261
3489
|
display: grid;
|
|
3262
|
-
|
|
3490
|
+
/* 计数列定宽(2026-11-01):原来是 auto —— 每行按自己的内容算宽,
|
|
3491
|
+
于是 "19 item(s)" 与 "941 item(s)" 的右边缘相差 20px,一列数字看起来
|
|
3492
|
+
像没对齐(app-screens §7.4:一列数字共用一条右边缘)。72px 容得下
|
|
3493
|
+
"941 items" 这种最长形态,zh 侧「941 项」更短。 */
|
|
3494
|
+
grid-template-columns: minmax(0, 1fr) 68px 56px;
|
|
3263
3495
|
align-items: center;
|
|
3264
3496
|
gap: 10px;
|
|
3265
3497
|
padding: 4px 0;
|
|
@@ -3272,6 +3504,14 @@ body {
|
|
|
3272
3504
|
border-bottom: none;
|
|
3273
3505
|
}
|
|
3274
3506
|
|
|
3507
|
+
/* 真·空行(条目与体积都为 0):降权到 tertiary,但**不隐藏** ——
|
|
3508
|
+
「这个分区存在但没有内容」本身是用户要判断的信息(app-screens §13 零值用灰)。 */
|
|
3509
|
+
.sectionRowEmpty .sectionName,
|
|
3510
|
+
.sectionRowEmpty .sectionCount,
|
|
3511
|
+
.sectionRowEmpty .sectionSize {
|
|
3512
|
+
color: var(--dsw-alias-label-tertiary);
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3275
3515
|
.sectionName {
|
|
3276
3516
|
min-width: 0;
|
|
3277
3517
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
@@ -4103,6 +4343,7 @@ body {
|
|
|
4103
4343
|
flex: none;
|
|
4104
4344
|
display: inline-flex;
|
|
4105
4345
|
align-items: center;
|
|
4346
|
+
gap: 4px;
|
|
4106
4347
|
}
|
|
4107
4348
|
|
|
4108
4349
|
/* 导入日志面板 */
|