dsh-config-manager 0.2.4 → 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 +1263 -1192
- 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 +10 -0
- package/lib/sync/git/git-transport.js +72 -14
- 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/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 +3 -2
- package/src/sync/git/git-transport.test.ts +168 -7
- package/src/sync/git/git-transport.ts +70 -14
- 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
|
@@ -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
|
/* 导入日志面板 */
|
|
@@ -440,37 +440,43 @@ export function EnvironmentPanel({ api, t, recoveryApi, recoveryT, incidentApi }
|
|
|
440
440
|
{/* —— 运行状态:当前运行中的 profile + 本插件启动的实例(可打开 / 可停止) —— */}
|
|
441
441
|
<Card className={css.card}>
|
|
442
442
|
<div className={css.groupLabel}>{t('profiles.running.title')}</div>
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
</div>
|
|
449
|
-
{/* 空态只给状态,不解释机制:「启动」按钮就在每行,端口/开浏览器属该按钮的 title */}
|
|
443
|
+
{/* 2026-11-01:原来「当前:web」一行 + 运行行「web · :3277 · 当前实例」把同一个事实
|
|
444
|
+
说了两遍,而两行的值起点一个在 110px、一个在 12px(kvKey 88px 只在前者生效),
|
|
445
|
+
左边缘对不齐 —— app-screens §7.4「一个事实只在一处、一个左边缘」。
|
|
446
|
+
现在只有一种行:kvKey 列放档案名(与空态那格同宽同起点,两种状态边缘一致),
|
|
447
|
+
kvValue 列放端口与来源。 */}
|
|
450
448
|
{state.running.length === 0 && (
|
|
451
|
-
<div className={css.
|
|
452
|
-
<
|
|
449
|
+
<div className={css.kvRow}>
|
|
450
|
+
<span className={css.kvKey}>{t('profiles.current')}</span>
|
|
451
|
+
<span className={css.kvValue}>
|
|
452
|
+
{state.current === null ? '—' : <span className={css.mono}>{state.current}</span>}
|
|
453
|
+
<Badge kind="info">{t('profiles.running.none')}</Badge>
|
|
454
|
+
</span>
|
|
453
455
|
</div>
|
|
454
456
|
)}
|
|
455
457
|
{state.running.map((record) => (
|
|
456
|
-
<div key={record.name} className={css.
|
|
458
|
+
<div key={record.name} className={css.kvRow}>
|
|
459
|
+
<span className={css.kvKey + ' ' + css.mono} title={record.name}>{record.name}</span>
|
|
457
460
|
<span className={css.kvValue}>
|
|
458
|
-
|
|
459
|
-
{record.
|
|
460
|
-
{' · '}{t(record.current ? 'profiles.running.originCurrent' : record.owned ? 'profiles.running.originOwned' : 'profiles.running.originExternal')}
|
|
461
|
+
{record.port !== null && <>{t('profiles.running.port', { port: record.port })}{' · '}</>}
|
|
462
|
+
{t(record.current ? 'profiles.running.originCurrent' : record.owned ? 'profiles.running.originOwned' : 'profiles.running.originExternal')}
|
|
461
463
|
</span>
|
|
462
464
|
{record.url !== null && <Button size="sm" href={record.url}>{t('profiles.running.open')}</Button>}
|
|
463
|
-
{/*
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
465
|
+
{/* 当前实例不能停自己(进程会死在响应途中):不渲染按钮,也不给禁用按钮。
|
|
466
|
+
disabled 的按钮在真实数据里只读作「这里有个坏掉的东西」(app-screens §11);
|
|
467
|
+
这一行本来已经写着「当前实例」,缺的不是提示而是控件。 */}
|
|
468
|
+
{!record.current && (
|
|
469
|
+
<Button
|
|
470
|
+
size="sm"
|
|
471
|
+
variant="ghost"
|
|
472
|
+
disabled={state.stopping !== null}
|
|
473
|
+
title={record.owned ? t('profiles.stop.hint') : t('profiles.stop.hintExternal')}
|
|
474
|
+
loading={state.stopping === record.name}
|
|
475
|
+
onClick={() => { patch({ stopTargetName: record.name, error: null }) }}
|
|
476
|
+
>
|
|
477
|
+
{t('profiles.stop')}
|
|
478
|
+
</Button>
|
|
479
|
+
)}
|
|
474
480
|
</div>
|
|
475
481
|
))}
|
|
476
482
|
</Card>
|
|
@@ -493,12 +499,14 @@ export function EnvironmentPanel({ api, t, recoveryApi, recoveryT, incidentApi }
|
|
|
493
499
|
)}
|
|
494
500
|
</Banner>
|
|
495
501
|
)}
|
|
496
|
-
{
|
|
497
|
-
|
|
502
|
+
{/* 启动**就绪**只走 toast(2026-11-01 用户要求):doLaunch 成功那一刻已经发过
|
|
503
|
+
「已启动档案 X(端口 N)」,而这条横幅把同一句再说一遍,还占掉一整块绿色面板
|
|
504
|
+
——「打开 / 停止」在下面「运行状态」卡里本来就有(record.url 的「打开」按钮),
|
|
505
|
+
日志路径只在**没就绪**时需要,那时才留在横幅里。 */}
|
|
506
|
+
{launch !== null && launchState(launch) !== 'ready' && (
|
|
507
|
+
<Banner kind="warn">
|
|
498
508
|
<div>
|
|
499
|
-
{
|
|
500
|
-
? t('profiles.launch.started', { name: launch.name, port: launch.port })
|
|
501
|
-
: t('profiles.launch.pending', { name: launch.name, port: launch.port, seconds: Math.round(DSH_PROFILE_LAUNCH_TIMEOUT_MS / 1000) })}
|
|
509
|
+
{t('profiles.launch.pending', { name: launch.name, port: launch.port, seconds: Math.round(DSH_PROFILE_LAUNCH_TIMEOUT_MS / 1000) })}
|
|
502
510
|
</div>
|
|
503
511
|
<div className={css.kvRow}>
|
|
504
512
|
<span className={css.kvKey}>{t('profiles.launch.log')}</span>
|
|
@@ -508,14 +516,6 @@ export function EnvironmentPanel({ api, t, recoveryApi, recoveryT, incidentApi }
|
|
|
508
516
|
<div key={warning} className={css.hint}>{t(launchWarningKey(warning))}</div>
|
|
509
517
|
))}
|
|
510
518
|
<div className={css.actionRow}>
|
|
511
|
-
{launch.url !== null && (
|
|
512
|
-
<>
|
|
513
|
-
<Button size="sm" variant="primary" href={launch.url}>{t('profiles.launch.open')}</Button>
|
|
514
|
-
<Button size="sm" onClick={() => { copyText(launch.url ?? '', t, 'profiles.launch.copiedUrl') }}>
|
|
515
|
-
{t('profiles.launch.copyUrl')}
|
|
516
|
-
</Button>
|
|
517
|
-
</>
|
|
518
|
-
)}
|
|
519
519
|
<Button size="sm" variant="ghost" onClick={() => { patch({ launchResult: null }) }}>{t('common.close')}</Button>
|
|
520
520
|
</div>
|
|
521
521
|
</Banner>
|
|
@@ -610,11 +610,18 @@ export function EnvironmentPanel({ api, t, recoveryApi, recoveryT, incidentApi }
|
|
|
610
610
|
<span className={css.profileRowTitle}>
|
|
611
611
|
<span className={`${css.mono} ${css.profileRowName}`}>{profile.name}</span>
|
|
612
612
|
<span className={css.badgeRow}>
|
|
613
|
-
{
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
613
|
+
{/* 2026-11-01:这两个徽章原来并排写着「Running」与「·3277 running」——
|
|
614
|
+
同一个词三格内出现两次,还要额外占 60px 宽(正是把行摘要挤到截断的
|
|
615
|
+
一部分)。isCurrent 已经由 ok 徽章说清,第二个徽章只需要回答「在哪个
|
|
616
|
+
端口上」,所以文案收成 ':3277'。非当前实例(外部/本插件启动)没有
|
|
617
|
+
状态徽章,那时保留 'running' 字样。 */}
|
|
618
|
+
{profile.isCurrent
|
|
619
|
+
? <Badge kind="ok">{t('profiles.current')}</Badge>
|
|
620
|
+
: runningRecord !== undefined && runningRecord.port === null
|
|
621
|
+
? <Badge kind="info">{t('profiles.running.badgePlain')}</Badge>
|
|
622
|
+
: null}
|
|
623
|
+
{runningRecord !== undefined && runningRecord.port !== null && (
|
|
624
|
+
<Badge kind="info">{t('profiles.running.portBadge', { port: runningRecord.port })}</Badge>
|
|
618
625
|
)}
|
|
619
626
|
{/* 残留行必须自报「未完成的副本」——不得伪装成普通「自定义」档案(t50);非残留行走原形态键 */}
|
|
620
627
|
<Badge kind={incomplete ? 'warn' : 'info'}>{t(profileShapeLabelKey(profile))}</Badge>
|
|
@@ -646,9 +653,11 @@ export function EnvironmentPanel({ api, t, recoveryApi, recoveryT, incidentApi }
|
|
|
646
653
|
)
|
|
647
654
|
: (
|
|
648
655
|
<>
|
|
656
|
+
{/* 2026-11-01:这里原来还跟着「node_modules present · patch hot reload」
|
|
657
|
+
两段 —— 它们与详情弹窗里的同名徽章重复(EnvironmentPanel 详情区),
|
|
658
|
+
而三颗动作按钮已经把这一行压到尾部截断。行摘要只留三个计数,
|
|
659
|
+
那两段在点开详情时仍然逐字可见。 */}
|
|
649
660
|
{t('profiles.row.summary', { bundles: facts.bundles, patch: facts.patchEntries, deps: facts.deps })}
|
|
650
|
-
{' · '}{facts.hasNodeModules ? t('profiles.nodeModules.yes') : t('profiles.nodeModules.no')}
|
|
651
|
-
{' · '}{profile.patchReload === 'startup' ? t('profiles.patchReload.startup') : t('profiles.patchReload.live')}
|
|
652
661
|
{profile.updatedAtMs !== null && ` · ${t('profiles.updatedAt', { time: formatProfileTime(profile.updatedAtMs) })}`}
|
|
653
662
|
</>
|
|
654
663
|
)}
|
|
@@ -656,9 +665,10 @@ export function EnvironmentPanel({ api, t, recoveryApi, recoveryT, incidentApi }
|
|
|
656
665
|
</button>
|
|
657
666
|
<span className={css.actionRow} data-inline>
|
|
658
667
|
{/* cross-F3:残留行不给启动/停止那一格(generic 必然 notLaunchable);删除恒在 */}
|
|
659
|
-
{
|
|
660
|
-
|
|
661
|
-
|
|
668
|
+
{/* 2026-11-01:原来 current 这一格放一颗 disabled 的「运行中」按钮 —— 标题旁
|
|
669
|
+
已经有 ok 徽章写着同一句话,动作区却因此常年挂着一个按不动的方块。
|
|
670
|
+
「当前档案没有启动/停止动作」这件事的正确表达是**没有动作**,
|
|
671
|
+
不是一颗永远灰着的按钮。reactor: app-screens §11 disabled ≠ 可用提示。 */}
|
|
662
672
|
{caps.launchOrStop && rowAction === 'stop' && runningRecord !== undefined && (
|
|
663
673
|
<Button
|
|
664
674
|
size="sm"
|
|
@@ -224,7 +224,9 @@ export function DiskUsageCard({ api, infoHintLabel, onBackupsChanged }: DiskUsag
|
|
|
224
224
|
<span className={css.cellMeta}>
|
|
225
225
|
{row.unreadable ? t('diskUsage.unreadable') : row.size + ' · ' + row.files}
|
|
226
226
|
</span>
|
|
227
|
-
|
|
227
|
+
{/* 行尾的 protected 标签已删(2026-11-01):本组上方的说明文字就是
|
|
228
|
+
「no longer read · never auto-deleted · not part of the total above」,
|
|
229
|
+
逐行再贴一遍只会把这一行挤到溢出(实测最后一段跑出右边界)。 */}
|
|
228
230
|
</div>
|
|
229
231
|
))}
|
|
230
232
|
</>
|
|
@@ -381,7 +381,9 @@ export function ExportView({ api, t }: ExportViewProps) {
|
|
|
381
381
|
>
|
|
382
382
|
{t('export.run')}
|
|
383
383
|
</Button>
|
|
384
|
-
|
|
384
|
+
{/* 不再 size="sm"(2026-11-01):同一工具栏里 28px 与 24px 混排,右边缘参差、
|
|
385
|
+
基线也对不齐(app-screens §8「同排控件同高」)。次要性由 ghost 承担,不由高度承担。 */}
|
|
386
|
+
<Button loading={invLoading} disabled={running} title={t('picker.title')} onClick={openPicker}>
|
|
385
387
|
{invLoading ? null : <PreviewIcon size={13} />} {t('picker.title')}
|
|
386
388
|
</Button>
|
|
387
389
|
{/* 工具栏「模式提示」属「机制怎么工作 + 省事提示」(MOVE 类):收进 ⓘ,
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'
|
|
25
25
|
import type { ReactNode } from 'react'
|
|
26
26
|
import type { SnapshotMeta } from '../../core/restore.ts'
|
|
27
|
+
import { midEllipsis } from '../../ui/mid-ellipsis.ts'
|
|
27
28
|
import type { BackupScheduleStatus } from '../../ui/backup-schedule.ts'
|
|
28
29
|
import { backupRunOutcome, type BackupSkipReason } from '../../ui/backup-schedule.ts'
|
|
29
30
|
import type { BackupFileMeta } from '../../sync/backup-files.ts'
|
|
@@ -432,7 +433,7 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
432
433
|
{emptyState ? (
|
|
433
434
|
/* 首用空态:只交代顺序(备份 → 导出 → 导入),动作全交给上方工具栏 ——
|
|
434
435
|
此前这里又放了一遍「立即备份 / 远程同步」,等于同一屏两个入口。 */
|
|
435
|
-
<Card className={
|
|
436
|
+
<Card className={css.activityCard}>
|
|
436
437
|
<span className={css.groupLabel}>{t('overview.empty.title')}</span>
|
|
437
438
|
<div className={css.emptySteps}>
|
|
438
439
|
<Stepper
|
|
@@ -446,8 +447,11 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
446
447
|
</Card>
|
|
447
448
|
) : (
|
|
448
449
|
<>
|
|
449
|
-
{/* 3.
|
|
450
|
-
|
|
450
|
+
{/* 3. 最近活动表(**首页最后一个数据块**)。2026-11-01:不再用 .fillCard 撑满视口 ——
|
|
451
|
+
8 行记录撑到 400px 后,卡内下方留出约 150px 纯空区,被读成「表格还没加载完」
|
|
452
|
+
(app-screens §4.2:卡内 >40px 空带读作未完成)。现在卡片高度随内容,
|
|
453
|
+
行数多时由 .activityRows 的 max-height 内滚;页底留白属于页面层级,读起来是完整的。 */}
|
|
454
|
+
<Card className={css.activityCard}>
|
|
451
455
|
<div className={css.activityHeader}>
|
|
452
456
|
<span className={css.activityTitle}>{t('overview.activity.title')}</span>
|
|
453
457
|
</div>
|
|
@@ -458,6 +462,15 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
458
462
|
: activity.length === 0
|
|
459
463
|
? <div className={css.activityEmpty}>{data.history === null ? t('overview.activity.unavailable') : t('overview.activity.empty')}</div>
|
|
460
464
|
: (
|
|
465
|
+
<>
|
|
466
|
+
{/* 列头:14 行记录只有「时间 / 类型·摘要 / 结果」三列,没有名字时
|
|
467
|
+
用户不知道右边那一列的绿点是什么(app-screens §7.2:有表头才读得懂列)。 */}
|
|
468
|
+
<div className={css.activityHead}>
|
|
469
|
+
<span>{t('overview.activity.colTime')}</span>
|
|
470
|
+
<span>{t('overview.activity.colEvent')}</span>
|
|
471
|
+
<span>{t('overview.activity.colDetail')}</span>
|
|
472
|
+
<span>{t('overview.activity.colResult')}</span>
|
|
473
|
+
</div>
|
|
461
474
|
<div className={css.activityRows}>
|
|
462
475
|
{activity.map((item, i) => {
|
|
463
476
|
const atMs = Date.parse(item.at) || 0
|
|
@@ -467,10 +480,16 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
467
480
|
<span className={css.activityTime} title={atMs > 0 ? new Date(atMs).toLocaleString() : undefined}>
|
|
468
481
|
{renderRelTime(atMs, t)}
|
|
469
482
|
</span>
|
|
483
|
+
{/* kind 独立成列(2026-11-01):原先它挤在摘要行里,「Import · 」的
|
|
484
|
+
长前缀把真正的区分信息(文件名尾部时间戳)挤出可视宽度,
|
|
485
|
+
中段省略再准也被前缀吃掉。 */}
|
|
486
|
+
<span className={css.activityKind} title={kindText}>{kindText}</span>
|
|
470
487
|
<span className={css.activitySummary}>
|
|
471
|
-
|
|
488
|
+
{/* 中段省略而不是尾部(DESIGN.md §9-5):摘要里的区分信息在尾部
|
|
489
|
+
—— 文件名时间戳后缀、项数 —— 尾部截断会让两条记录看起来一样。
|
|
490
|
+
52 是 HomePanel 历史上的取值,换实现不改观感。title 保留全文。 */}
|
|
472
491
|
<span className={css.activitySummaryText} title={displaySummary(item.summary, t)}>
|
|
473
|
-
{displaySummary(item.summary, t)}
|
|
492
|
+
{midEllipsis(displaySummary(item.summary, t), 34)}
|
|
474
493
|
</span>
|
|
475
494
|
<CopyButton text={item.summary} label={t('overview.activity.copy')} t={t} />
|
|
476
495
|
</span>
|
|
@@ -479,6 +498,7 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
479
498
|
)
|
|
480
499
|
})}
|
|
481
500
|
</div>
|
|
501
|
+
</>
|
|
482
502
|
)}
|
|
483
503
|
</Card>
|
|
484
504
|
</>
|
|
@@ -67,7 +67,6 @@ import css from '../config-manager.module.css'
|
|
|
67
67
|
export interface ImportWizardViewProps {
|
|
68
68
|
api: ConfigManagerApi
|
|
69
69
|
t: TranslateNS<'config-manager'>
|
|
70
|
-
uiT?: UiT; onOpenForeignImport?: () => void // 可选:外部来源入口(见 SelectStep.foreignEntry)
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
/* 兼容性等级 → 字典键的映射统一在 ../common/compat-label.ts(同步确认页共用同一份,
|
|
@@ -432,7 +431,7 @@ function ResultStep(props: {
|
|
|
432
431
|
)
|
|
433
432
|
}
|
|
434
433
|
|
|
435
|
-
function ImportWizardBody({ api, t
|
|
434
|
+
function ImportWizardBody({ api, t }: ImportWizardViewProps) {
|
|
436
435
|
const {
|
|
437
436
|
imp,
|
|
438
437
|
wizard,
|
|
@@ -511,7 +510,6 @@ function ImportWizardBody({ api, t, uiT, onOpenForeignImport }: ImportWizardView
|
|
|
511
510
|
onReset={resetWizard}
|
|
512
511
|
apiT={api.t}
|
|
513
512
|
t={t}
|
|
514
|
-
foreignEntry={uiT !== undefined && onOpenForeignImport !== undefined ? { uiT, onOpen: onOpenForeignImport } : undefined}
|
|
515
513
|
/>
|
|
516
514
|
)
|
|
517
515
|
}
|