dsh-config-manager 0.1.78 → 0.1.79
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/lib/adapters/credentials.d.ts +6 -2
- package/lib/adapters/credentials.js +19 -3
- package/lib/adapters/file-collection.js +2 -1
- package/lib/adapters/mcp.d.ts +48 -2
- package/lib/adapters/mcp.js +265 -3
- package/lib/adapters/messages.d.ts +3 -0
- package/lib/adapters/messages.js +7 -0
- package/lib/adapters/plugin-files.js +2 -1
- package/lib/adapters/plugins.js +105 -20
- package/lib/adapters/pnpm-workspace.d.ts +32 -0
- package/lib/adapters/pnpm-workspace.js +113 -2
- package/lib/adapters/prompts.d.ts +6 -2
- package/lib/adapters/prompts.js +17 -2
- package/lib/adapters/providers.d.ts +19 -2
- package/lib/adapters/providers.js +59 -3
- package/lib/adapters/settings.d.ts +6 -2
- package/lib/adapters/settings.js +13 -2
- package/lib/adapters/skills.d.ts +25 -1
- package/lib/adapters/skills.js +60 -0
- package/lib/adapters/ui.d.ts +6 -2
- package/lib/adapters/ui.js +13 -2
- package/lib/adapters/units.d.ts +18 -0
- package/lib/adapters/units.js +39 -0
- package/lib/adapters/workspaces.js +2 -1
- package/lib/client.d.ts +400 -112
- package/lib/client.js +2845 -1524
- package/lib/core/backup.js +20 -0
- package/lib/core/mcp-connector-store.d.ts +159 -0
- package/lib/core/mcp-connector-store.js +445 -0
- package/lib/core/messages-core.d.ts +2 -0
- package/lib/core/messages-core.js +4 -0
- package/lib/core/messages.d.ts +20 -0
- package/lib/core/messages.js +44 -0
- package/lib/core/phase3-host.js +18 -1
- package/lib/core/plugin-cli.d.ts +22 -2
- package/lib/core/plugin-cli.js +22 -3
- package/lib/core/recovery-orchestrator.d.ts +3 -1
- package/lib/core/recovery-orchestrator.js +75 -16
- package/lib/core/rollback-activity.d.ts +89 -0
- package/lib/core/rollback-activity.js +350 -0
- package/lib/core/rollback-ledger.d.ts +0 -9
- package/lib/core/rollback-ledger.js +37 -0
- package/lib/core/rollback-merge.d.ts +22 -2
- package/lib/core/rollback-merge.js +63 -13
- package/lib/core/rollback-point.d.ts +93 -0
- package/lib/core/rollback-point.js +420 -101
- package/lib/core/rollback-undo-journal.d.ts +123 -0
- package/lib/core/rollback-undo-journal.js +517 -0
- package/lib/core/self-update.js +2 -4
- package/lib/core/text-fidelity.d.ts +89 -0
- package/lib/core/text-fidelity.js +176 -0
- package/lib/core/types.d.ts +12 -3
- package/lib/core/update-check.js +2 -4
- package/lib/core/validator.d.ts +9 -0
- package/lib/core/validator.js +12 -0
- package/lib/foreign/bundle.js +42 -3
- package/lib/foreign/claude-code.js +358 -26
- package/lib/foreign/claude-sessions.d.ts +51 -8
- package/lib/foreign/claude-sessions.js +111 -13
- package/lib/foreign/read-claude-code.d.ts +33 -4
- package/lib/foreign/read-claude-code.js +446 -65
- package/lib/foreign/read-claude-plugins.d.ts +36 -0
- package/lib/foreign/read-claude-plugins.js +403 -0
- package/lib/foreign/registry.js +25 -2
- package/lib/foreign/truth-table.js +6 -4
- package/lib/foreign/types.d.ts +170 -2
- package/lib/index.d.ts +14 -5
- package/lib/index.js +60 -16
- package/lib/market/git-file-writer.js +7 -1
- package/lib/profiles/dsh-profile-launcher.d.ts +20 -0
- package/lib/profiles/dsh-profile-launcher.js +58 -1
- package/lib/profiles/dsh-profile-shared.d.ts +8 -2
- package/lib/profiles/dsh-profile-shared.js +8 -2
- package/lib/routes/foreign.js +8 -1
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +2 -2
- package/lib/routes/sync.js +173 -9
- package/lib/schema/manifest.d.ts +6 -1
- package/lib/schema/manifest.js +55 -0
- package/lib/schema/types.d.ts +47 -2
- package/lib/sync/autosync-config.d.ts +73 -0
- package/lib/sync/autosync-config.js +89 -0
- package/lib/sync/autosync-scheduler.d.ts +80 -6
- package/lib/sync/autosync-scheduler.js +263 -40
- package/lib/sync/gist/gist-transport.d.ts +15 -1
- package/lib/sync/gist/gist-transport.js +66 -5
- package/lib/sync/gist/messages.d.ts +3 -0
- package/lib/sync/gist/messages.js +7 -0
- package/lib/sync/git/git-transport.d.ts +42 -3
- package/lib/sync/git/git-transport.js +146 -3
- package/lib/sync/merge.d.ts +47 -14
- package/lib/sync/merge.js +242 -222
- package/lib/sync/messages.d.ts +8 -0
- package/lib/sync/messages.js +19 -0
- package/lib/sync/risk.d.ts +19 -2
- package/lib/sync/risk.js +10 -3
- package/lib/sync/s3/messages.d.ts +6 -0
- package/lib/sync/s3/messages.js +13 -0
- package/lib/sync/s3/s3-transport.d.ts +9 -1
- package/lib/sync/s3/s3-transport.js +78 -6
- package/lib/sync/sync-engine.d.ts +193 -4
- package/lib/sync/sync-engine.js +450 -37
- package/lib/sync/sync-history.d.ts +11 -0
- package/lib/sync/sync-history.js +20 -0
- package/lib/sync/sync-state.d.ts +43 -0
- package/lib/sync/sync-state.js +44 -0
- package/lib/sync/sync-watcher.d.ts +76 -0
- package/lib/sync/sync-watcher.js +202 -0
- package/lib/sync/text-merge.d.ts +85 -0
- package/lib/sync/text-merge.js +513 -0
- package/lib/sync/transport.d.ts +48 -0
- package/lib/sync/transport.js +12 -0
- package/lib/sync/watch-scope.d.ts +38 -0
- package/lib/sync/watch-scope.js +95 -0
- package/lib/sync/webdav/webdav-transport.d.ts +16 -2
- package/lib/sync/webdav/webdav-transport.js +128 -11
- package/lib/ui/foreign-view.js +10 -0
- package/lib/ui/i18n.d.ts +8 -0
- package/lib/ui/i18n.js +20 -0
- package/lib/ui/rollback-view.d.ts +12 -0
- package/lib/ui/rollback-view.js +2 -0
- package/lib/ui/sync-settings-view.d.ts +47 -0
- package/lib/ui/sync-settings-view.js +24 -0
- package/lib/ui/types.d.ts +21 -0
- package/lib/utils/env-lock.d.ts +1 -0
- package/lib/utils/env-lock.js +7 -4
- package/package.json +6 -6
- package/src/adapters/credentials.ts +22 -4
- package/src/adapters/file-collection.ts +2 -1
- package/src/adapters/mcp.test.ts +235 -1
- package/src/adapters/mcp.ts +276 -5
- package/src/adapters/messages.ts +7 -0
- package/src/adapters/plugin-files.ts +2 -1
- package/src/adapters/plugins.test.ts +176 -2
- package/src/adapters/plugins.ts +107 -21
- package/src/adapters/pnpm-workspace.test.ts +89 -1
- package/src/adapters/pnpm-workspace.ts +127 -2
- package/src/adapters/prompts.ts +20 -3
- package/src/adapters/providers.ts +62 -4
- package/src/adapters/settings.ts +16 -3
- package/src/adapters/skills.test.ts +141 -0
- package/src/adapters/skills.ts +58 -2
- package/src/adapters/ui.ts +16 -3
- package/src/adapters/units-entries.test.ts +190 -0
- package/src/adapters/units.ts +50 -0
- package/src/adapters/workspaces.ts +2 -1
- package/src/client/about/PluginUpdateDialog.tsx +4 -2
- package/src/client/about/ReleaseNotesDialog.tsx +4 -2
- package/src/client/chat/RollbackReviewDialog.tsx +3 -2
- package/src/client/common/Skeleton.tsx +32 -0
- package/src/client/common/routes.ts +9 -0
- package/src/client/config-manager.module.css +321 -11
- package/src/client/home/HomePanel.tsx +14 -6
- package/src/client/import/ForeignImportView.tsx +3 -1
- package/src/client/import/import-wizard-steps.tsx +2 -1
- package/src/client/library/ArtifactRow.tsx +12 -2
- package/src/client/library/LibraryActions.tsx +6 -3
- package/src/client/library/LibraryPanel.tsx +21 -3
- package/src/client/library/RestorePlanView.tsx +4 -2
- package/src/client/locales.ts +3 -0
- package/src/client/recovery/RecoveryPanel.tsx +54 -7
- package/src/client/recovery/RollbackCard.tsx +7 -2
- package/src/client/recovery/RollbackDiffPane.tsx +3 -2
- package/src/client/recovery/recovery-locales.ts +20 -2
- package/src/client/recovery/recovery-view.test.ts +39 -1
- package/src/client/recovery/recovery-view.ts +23 -1
- package/src/client/run-store.test.ts +1 -1
- package/src/client/run-store.ts +4 -0
- package/src/client/sync/AutosyncCard.tsx +49 -6
- package/src/client/sync/MergeModal.tsx +219 -0
- package/src/client/sync/MergePreviewView.tsx +306 -0
- package/src/client/sync/SyncActivityBar.tsx +31 -0
- package/src/client/sync/SyncConfirmView.tsx +2 -1
- package/src/client/sync/SyncPanel.tsx +130 -87
- package/src/client/sync/SyncProbeRow.tsx +162 -0
- package/src/client/sync/SyncSettingsView.test.ts +27 -2
- package/src/client/sync/WorkDirCard.tsx +44 -6
- package/src/client/sync/history-model.ts +4 -1
- package/src/client/sync/merge-view.test.ts +234 -0
- package/src/client/sync/merge-view.ts +371 -0
- package/src/client/sync/probe-view.test.ts +90 -0
- package/src/client/sync/probe-view.ts +63 -0
- package/src/client/sync/remote-device-view.test.ts +35 -0
- package/src/client/sync/remote-device-view.ts +25 -0
- package/src/client/sync/sync-api.ts +212 -2
- package/src/client/sync/sync-entry.test.ts +78 -0
- package/src/client/sync/sync-entry.ts +53 -0
- package/src/client/sync/sync-locales.ts +234 -2
- package/src/client/sync/sync-view.test.ts +18 -1
- package/src/client/sync/sync-view.ts +20 -0
- package/src/core/backup.test.ts +41 -0
- package/src/core/backup.ts +20 -0
- package/src/core/mcp-connector-store.test.ts +265 -0
- package/src/core/mcp-connector-store.ts +516 -0
- package/src/core/messages-core.ts +4 -0
- package/src/core/messages.ts +46 -0
- package/src/core/phase3-host.ts +18 -1
- package/src/core/phase3-production-integration.test.ts +4 -0
- package/src/core/plugin-cli.test.ts +26 -0
- package/src/core/plugin-cli.ts +30 -4
- package/src/core/recovery-orchestrator.test.ts +104 -5
- package/src/core/recovery-orchestrator.ts +74 -17
- package/src/core/rollback-activity.test.ts +311 -0
- package/src/core/rollback-activity.ts +367 -0
- package/src/core/rollback-cross-session.test.ts +694 -0
- package/src/core/rollback-interrupted.test.ts +137 -0
- package/src/core/rollback-ledger.ts +32 -0
- package/src/core/rollback-merge.test.ts +130 -0
- package/src/core/rollback-merge.ts +78 -14
- package/src/core/rollback-point.ts +468 -91
- package/src/core/rollback-undo-journal.test.ts +508 -0
- package/src/core/rollback-undo-journal.ts +571 -0
- package/src/core/self-update.ts +2 -5
- package/src/core/text-fidelity.test.ts +189 -0
- package/src/core/text-fidelity.ts +197 -0
- package/src/core/types.ts +11 -3
- package/src/core/update-check.ts +2 -5
- package/src/core/validator.ts +14 -0
- package/src/foreign/bundle-origin.test.ts +265 -0
- package/src/foreign/bundle.ts +45 -5
- package/src/foreign/claude-code.test.ts +268 -0
- package/src/foreign/claude-code.ts +406 -25
- package/src/foreign/claude-plugins.test.ts +234 -0
- package/src/foreign/claude-sessions.test.ts +141 -0
- package/src/foreign/claude-sessions.ts +179 -23
- package/src/foreign/file-budget.test.ts +4 -1
- package/src/foreign/read-claude-code.test.ts +320 -0
- package/src/foreign/read-claude-code.ts +490 -71
- package/src/foreign/read-claude-plugins.ts +465 -0
- package/src/foreign/registry.ts +25 -2
- package/src/foreign/session-ir.test.ts +65 -0
- package/src/foreign/truth-table.test.ts +81 -0
- package/src/foreign/truth-table.ts +6 -3
- package/src/foreign/types.ts +162 -2
- package/src/index.facade.test.ts +26 -4
- package/src/index.ts +76 -19
- package/src/market/git-file-writer.test.ts +4 -0
- package/src/market/git-file-writer.ts +7 -1
- package/src/profiles/dsh-profile-launcher.test.ts +68 -3
- package/src/profiles/dsh-profile-launcher.ts +56 -1
- package/src/profiles/dsh-profile-shared.ts +8 -2
- package/src/routes/foreign.ts +8 -1
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +2 -2
- package/src/routes/sync.ts +176 -9
- package/src/schema/manifest-schema.test.ts +87 -2
- package/src/schema/manifest.ts +57 -1
- package/src/schema/types.ts +45 -2
- package/src/sync/autosync-config.test.ts +73 -1
- package/src/sync/autosync-config.ts +124 -0
- package/src/sync/autosync-risk-tier.test.ts +38 -2
- package/src/sync/autosync-scheduler.test.ts +264 -12
- package/src/sync/autosync-scheduler.ts +296 -44
- package/src/sync/gist/gist-conditional-write.test.ts +223 -0
- package/src/sync/gist/gist-transport.ts +73 -7
- package/src/sync/gist/messages.ts +7 -0
- package/src/sync/git/git-probe.test.ts +80 -0
- package/src/sync/git/git-transport.test.ts +185 -0
- package/src/sync/git/git-transport.ts +178 -5
- package/src/sync/merge.test.ts +188 -55
- package/src/sync/merge.ts +282 -224
- package/src/sync/messages.ts +19 -0
- package/src/sync/risk.ts +29 -5
- package/src/sync/s3/messages.ts +13 -0
- package/src/sync/s3/s3-conditional-write.test.ts +330 -0
- package/src/sync/s3/s3-transport.ts +87 -9
- package/src/sync/sync-device-identity.test.ts +267 -0
- package/src/sync/sync-engine.test.ts +379 -0
- package/src/sync/sync-engine.ts +535 -36
- package/src/sync/sync-history.test.ts +16 -1
- package/src/sync/sync-history.ts +26 -0
- package/src/sync/sync-state.test.ts +64 -0
- package/src/sync/sync-state.ts +79 -0
- package/src/sync/sync-watcher.test.ts +197 -0
- package/src/sync/sync-watcher.ts +246 -0
- package/src/sync/text-merge.test.ts +358 -0
- package/src/sync/text-merge.ts +554 -0
- package/src/sync/transport.ts +57 -0
- package/src/sync/watch-scope.test.ts +122 -0
- package/src/sync/watch-scope.ts +120 -0
- package/src/sync/webdav/webdav-conditional-write.test.ts +360 -0
- package/src/sync/webdav/webdav-transport.ts +137 -14
- package/src/ui/foreign-view.test.ts +34 -0
- package/src/ui/foreign-view.ts +10 -0
- package/src/ui/i18n.ts +20 -0
- package/src/ui/rollback-view.ts +14 -0
- package/src/ui/sync-settings-view.test.ts +28 -1
- package/src/ui/sync-settings-view.ts +43 -0
- package/src/ui/types.ts +22 -0
- package/src/utils/env-lock.ts +8 -5
|
@@ -126,3 +126,35 @@ export function SkeletonTable({ rows = 5, label }: SkeletonTableProps) {
|
|
|
126
126
|
</SkeletonRoot>
|
|
127
127
|
)
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
export interface SkeletonRowsProps {
|
|
131
|
+
/** 行数(默认 6) */
|
|
132
|
+
count?: number
|
|
133
|
+
/** 可见文案(已翻译) */
|
|
134
|
+
label?: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 紧凑行骨架:行高与 `.artifactRowHead` 对齐(52px = 20px 图标位 + 标题/说明两行 + 8/10 内边距),
|
|
139
|
+
* 右侧留一个动作位 —— 用于「产物库」「最近活动」这类**一行一条、有分割线**的列表。
|
|
140
|
+
* 为什么单列一种形态:卡片骨架套在这种列表上会把行距撑大(每卡 12px 内边距),
|
|
141
|
+
* 数据到货时整块往下跳;行的形状只能靠行骨架给。
|
|
142
|
+
*/
|
|
143
|
+
export function SkeletonRows({ count = 6, label }: SkeletonRowsProps) {
|
|
144
|
+
return (
|
|
145
|
+
<SkeletonRoot label={label}>
|
|
146
|
+
<div className={css.skeletonRows} aria-hidden="true">
|
|
147
|
+
{indices(count).map((key) => (
|
|
148
|
+
<div key={key} className={css.skeletonRowItem}>
|
|
149
|
+
<span className={css.skeletonBar + ' ' + css.skeletonRowIcon} />
|
|
150
|
+
<span className={css.skeletonRowBody}>
|
|
151
|
+
<span className={css.skeletonBar + ' ' + css.skeletonRowTitle} />
|
|
152
|
+
<span className={css.skeletonBar + ' ' + css.skeletonRowMeta} />
|
|
153
|
+
</span>
|
|
154
|
+
<span className={css.skeletonBar + ' ' + css.skeletonRowAction} />
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
</SkeletonRoot>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
@@ -99,6 +99,15 @@ export const SYNC_API = {
|
|
|
99
99
|
/** issue #79:工作副本显式重建(relink 非破坏 / reclone 需 confirm;写动作过 mutation gate) */
|
|
100
100
|
workdirReset: `${API_BASE}/sync/workdir/reset`,
|
|
101
101
|
rollback: `${API_BASE}/sync/rollback`,
|
|
102
|
+
/** T-ui-merge:远程合并**只读**预览(三方合并 + 冲突清单,零写入)。 */
|
|
103
|
+
mergePreview: `${API_BASE}/sync/merge-preview`,
|
|
104
|
+
/** T-ui-merge:按逐项裁决执行合并(写动作过 mutation gate)。 */
|
|
105
|
+
applyMerge: `${API_BASE}/sync/apply-merge`,
|
|
106
|
+
/**
|
|
107
|
+
* cloud-sync 借鉴项 ②:**只读**连通性自检(凭据 / 端点 / 目标容器 / 目录可达)。
|
|
108
|
+
* 失败回 200 + ok:false —— 「测试连接」是预期会失败的诊断动作(→ routes/sync.ts 的 sync/probe)。
|
|
109
|
+
*/
|
|
110
|
+
probe: `${API_BASE}/sync/probe`,
|
|
102
111
|
} as const;
|
|
103
112
|
|
|
104
113
|
/** 配置市场路由族(内置单市场:浏览 / 下载 / 发布向导;无 add/remove)。 */
|
|
@@ -1096,6 +1096,13 @@ body {
|
|
|
1096
1096
|
justify-content: center;
|
|
1097
1097
|
}
|
|
1098
1098
|
|
|
1099
|
+
/* 加载骨架区:同一高度契约,但内容**顶部对齐**(行列表从上方长出来;居中会让骨架悬在
|
|
1100
|
+
页面正中,数据到货时整块跳走)。必须写在 .libraryListState 之后才覆盖 justify-content。 */
|
|
1101
|
+
.librarySkeletonRegion {
|
|
1102
|
+
justify-content: flex-start;
|
|
1103
|
+
overflow: hidden;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1099
1106
|
.artifactRow {
|
|
1100
1107
|
border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 60%, transparent);
|
|
1101
1108
|
}
|
|
@@ -1589,6 +1596,65 @@ body {
|
|
|
1589
1596
|
flex: 0 0 20%;
|
|
1590
1597
|
}
|
|
1591
1598
|
|
|
1599
|
+
/* 紧凑行骨架:行高/内边距与 .artifactRowHead 对齐(52px),数据到货时行距不跳变。
|
|
1600
|
+
图标位 20px、标题/说明两行、右侧动作位 56px 都只是**形状**,不假装有内容。 */
|
|
1601
|
+
.skeletonRows {
|
|
1602
|
+
display: flex;
|
|
1603
|
+
flex-direction: column;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
.skeletonRowItem {
|
|
1607
|
+
display: flex;
|
|
1608
|
+
align-items: center;
|
|
1609
|
+
gap: 8px;
|
|
1610
|
+
min-height: 52px;
|
|
1611
|
+
box-sizing: border-box;
|
|
1612
|
+
padding: 8px 10px;
|
|
1613
|
+
border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 60%, transparent);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
.skeletonRowItem:last-child {
|
|
1617
|
+
border-bottom: none;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/* 逐行递进延迟:读起来像「正在一行行填」,而不是整块齐闪(与 .skeletonBars 同一手法) */
|
|
1621
|
+
.skeletonRowItem:nth-child(2) .skeletonBar { animation-delay: 60ms; }
|
|
1622
|
+
.skeletonRowItem:nth-child(3) .skeletonBar { animation-delay: 120ms; }
|
|
1623
|
+
.skeletonRowItem:nth-child(4) .skeletonBar { animation-delay: 180ms; }
|
|
1624
|
+
.skeletonRowItem:nth-child(n + 5) .skeletonBar { animation-delay: 240ms; }
|
|
1625
|
+
|
|
1626
|
+
.skeletonRowIcon {
|
|
1627
|
+
flex: none;
|
|
1628
|
+
width: 20px;
|
|
1629
|
+
height: 20px;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.skeletonRowBody {
|
|
1633
|
+
flex: 1 1 auto;
|
|
1634
|
+
min-width: 0;
|
|
1635
|
+
display: flex;
|
|
1636
|
+
flex-direction: column;
|
|
1637
|
+
gap: 6px;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
.skeletonRowTitle { height: 12px; width: 38%; }
|
|
1641
|
+
.skeletonRowMeta { height: 10px; width: 24%; }
|
|
1642
|
+
|
|
1643
|
+
.skeletonRowAction {
|
|
1644
|
+
flex: none;
|
|
1645
|
+
width: 56px;
|
|
1646
|
+
height: 20px;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
/* 指标段的「值」占位(首页状态条):首屏数据没到时用一条骨架替代 <b> 的数字,
|
|
1650
|
+
宽度只够表达「这里会有一个数」,高度与数值行一致。 */
|
|
1651
|
+
.statValueSkeleton {
|
|
1652
|
+
display: inline-block;
|
|
1653
|
+
width: 26px;
|
|
1654
|
+
height: 12px;
|
|
1655
|
+
border-radius: var(--cm-r-1);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1592
1658
|
/* 骨架下方的可见文案(与 Spinner 的 label 同义):加载反馈必须能被读到,不只靠色块 */
|
|
1593
1659
|
.skeletonCaption {
|
|
1594
1660
|
font-size: var(--cm-fs-meta);
|
|
@@ -2297,6 +2363,7 @@ body {
|
|
|
2297
2363
|
|
|
2298
2364
|
/* —— 限高滚动容器(大报告/列表通用) —— */
|
|
2299
2365
|
.planScroll,
|
|
2366
|
+
.mergeScroll,
|
|
2300
2367
|
.reportScroll,
|
|
2301
2368
|
.confirmScroll,
|
|
2302
2369
|
.pullScroll,
|
|
@@ -2351,23 +2418,25 @@ body {
|
|
|
2351
2418
|
left: 50%;
|
|
2352
2419
|
transform: translate(-50%, -50%);
|
|
2353
2420
|
z-index: var(--cm-z-modal);
|
|
2354
|
-
/*
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2421
|
+
/* 弹窗卡片入场:**只淡入,零几何位移**。
|
|
2422
|
+
为什么连 translateY 也去掉(2026-10-21 用户反馈「弹窗出现的时候有时候动画会发生形变」):
|
|
2423
|
+
① scale 会改变可见尺寸(真机「先在偏下出现再闪到中心」的根因,早已去掉);
|
|
2424
|
+
② 4px 上浮虽然不改中心,但它让**每一帧**都落在一个非整数像素位置上 —— 卡片内容
|
|
2425
|
+
(1px 边框、0.5px 分隔线、11~13px 字)在这些亚像素位置上重采样,边框会一会儿粗一点、
|
|
2426
|
+
一会儿糊一点,看起来就是「形变」;帧率抖动时尤其明显(不是每次都复现)。
|
|
2427
|
+
③ 弹窗尺寸由内容 + max-height 决定,入场期间若内滚条出现/消失,宽度还会跳一次 ——
|
|
2428
|
+
这条交给滚动容器上的 scrollbar-gutter: stable 解决(见 .dialogBodyScroll)。
|
|
2429
|
+
纯 opacity 的合成层变化不触碰布局与栅格,按下即最终位置,任何帧率下都不形变。
|
|
2430
|
+
注:keyframes 里的 translate 已移除,静止态定位仍由本类的 transform 负责。 */
|
|
2359
2431
|
animation: dialogIn var(--cm-motion-base) var(--cm-motion-ease-out);
|
|
2360
2432
|
}
|
|
2361
2433
|
|
|
2362
2434
|
@keyframes dialogIn {
|
|
2363
|
-
/*
|
|
2364
|
-
**仍然不加 scale**:卡片靠 translate(-50%,-50%) 自居中,scale 会在动画期间改变可见尺寸 ——
|
|
2365
|
-
那是「先在偏下的位置出现,再闪到中心」的根因(见上条注释)。translateY 只挪起点 4px、
|
|
2366
|
-
终点回到最终位置,第一帧与最后一帧都是同一个中心,不存在回跳。
|
|
2435
|
+
/* 只做透明度:不加 scale、也不加 translateY(理由见 .dialogContentCenter 的注释)。
|
|
2367
2436
|
另注:这条动画此前**根本没跑**过一次 —— `--cm-motion-base` 在 token 迁移时被丢掉,
|
|
2368
2437
|
整个 animation 短手在 computed-value 阶段失效(见 body 里的动效令牌注释)。 */
|
|
2369
|
-
from { opacity: 0;
|
|
2370
|
-
to { opacity: 1;
|
|
2438
|
+
from { opacity: 0; }
|
|
2439
|
+
to { opacity: 1; }
|
|
2371
2440
|
}
|
|
2372
2441
|
|
|
2373
2442
|
/* 关闭退场:Radix Dialog 的 Presence 在 present=false 时**保持节点挂载**并等动画结束
|
|
@@ -2447,6 +2516,8 @@ body {
|
|
|
2447
2516
|
.dialogBodyScroll {
|
|
2448
2517
|
overflow-y: auto;
|
|
2449
2518
|
max-height: 460px;
|
|
2519
|
+
/* 内滚条出现/消失时不再挤压内容宽度(否则弹窗入场那一瞬间会「横向抖一下」)。 */
|
|
2520
|
+
scrollbar-gutter: stable;
|
|
2450
2521
|
}
|
|
2451
2522
|
|
|
2452
2523
|
.dialogWide {
|
|
@@ -6299,4 +6370,243 @@ button.profileRowMain:hover {
|
|
|
6299
6370
|
font-size: var(--cm-fs-meta);
|
|
6300
6371
|
color: var(--dsw-alias-label-secondary);
|
|
6301
6372
|
}
|
|
6373
|
+
/* ==========================================================================
|
|
6374
|
+
T-ui-merge:远程合并裁决(分区行 + 冲突三列对照)
|
|
6375
|
+
纪律:组件规则只写 --cm-*(结构类 --dsw-alias-* 的边框/底/文字层可直用);字号走 --cm-fs-*;
|
|
6376
|
+
间距只取 9 档;圆角走 --cm-r-*;长列表由 .mergeScroll 并进上面的限高内滚组承担。
|
|
6377
|
+
========================================================================== */
|
|
6378
|
+
|
|
6379
|
+
/* 视图外壳:竖直节奏统一由 flex gap 承担,子块不再各自写 margin-top */
|
|
6380
|
+
.mergeView {
|
|
6381
|
+
display: flex;
|
|
6382
|
+
flex-direction: column;
|
|
6383
|
+
gap: 10px;
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
/* 概览:四个等分指标块(数字在上、标签在下;色只表达语义) */
|
|
6387
|
+
.mergeOverview {
|
|
6388
|
+
display: grid;
|
|
6389
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
6390
|
+
gap: 8px;
|
|
6391
|
+
}
|
|
6392
|
+
|
|
6393
|
+
.mergeTile {
|
|
6394
|
+
display: flex;
|
|
6395
|
+
flex-direction: column;
|
|
6396
|
+
gap: 2px;
|
|
6397
|
+
padding: 8px 10px;
|
|
6398
|
+
border: 0.5px solid var(--cm-line-1);
|
|
6399
|
+
border-radius: var(--cm-r-3);
|
|
6400
|
+
background: var(--cm-surface-1);
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6403
|
+
.mergeTile[data-tone='ok'] {
|
|
6404
|
+
border-color: color-mix(in srgb, var(--cm-ok) 40%, transparent);
|
|
6405
|
+
background: color-mix(in srgb, var(--cm-ok) 8%, transparent);
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
.mergeTile[data-tone='info'] {
|
|
6409
|
+
border-color: color-mix(in srgb, var(--cm-info) 40%, transparent);
|
|
6410
|
+
background: color-mix(in srgb, var(--cm-info) 8%, transparent);
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
.mergeTile[data-tone='warn'] {
|
|
6414
|
+
border-color: color-mix(in srgb, var(--cm-warn) 40%, transparent);
|
|
6415
|
+
background: color-mix(in srgb, var(--cm-warn) 8%, transparent);
|
|
6416
|
+
}
|
|
6417
|
+
|
|
6418
|
+
.mergeTile[data-tone='error'] {
|
|
6419
|
+
border-color: color-mix(in srgb, var(--cm-error) 40%, transparent);
|
|
6420
|
+
background: color-mix(in srgb, var(--cm-error) 8%, transparent);
|
|
6421
|
+
}
|
|
6422
|
+
|
|
6423
|
+
.mergeTileValue {
|
|
6424
|
+
font-size: var(--cm-fs-title);
|
|
6425
|
+
font-weight: 600;
|
|
6426
|
+
line-height: 1.25;
|
|
6427
|
+
color: var(--cm-text-1);
|
|
6428
|
+
font-variant-numeric: tabular-nums;
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
.mergeTileLabel {
|
|
6432
|
+
font-size: var(--cm-fs-meta);
|
|
6433
|
+
line-height: 1.25;
|
|
6434
|
+
color: var(--cm-text-2);
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6437
|
+
/* 工具栏:批量裁决 + 重新预览(ⓘ 说明夹在中间,靠 .statusSpacer 推右) */
|
|
6438
|
+
.mergeToolbar {
|
|
6439
|
+
display: flex;
|
|
6440
|
+
flex-wrap: wrap;
|
|
6441
|
+
align-items: center;
|
|
6442
|
+
gap: 6px;
|
|
6443
|
+
}
|
|
6444
|
+
|
|
6445
|
+
.mergeToolbarLabel {
|
|
6446
|
+
font-size: var(--cm-fs-meta);
|
|
6447
|
+
color: var(--cm-text-3);
|
|
6448
|
+
}
|
|
6449
|
+
|
|
6450
|
+
/* 按钮内「图标 + 文字」的组合(按钮本身不带 gap) */
|
|
6451
|
+
.mergeIconLabel {
|
|
6452
|
+
display: inline-flex;
|
|
6453
|
+
align-items: center;
|
|
6454
|
+
gap: 4px;
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
/* 执行结果卡:与分区卡同档表面,坐标是「已经发生的事」 */
|
|
6458
|
+
.mergeResult {
|
|
6459
|
+
display: flex;
|
|
6460
|
+
flex-direction: column;
|
|
6461
|
+
gap: 2px;
|
|
6462
|
+
padding: 10px 12px;
|
|
6463
|
+
border: 0.5px solid var(--cm-line-1);
|
|
6464
|
+
border-radius: var(--cm-r-3);
|
|
6465
|
+
background: var(--cm-surface-1);
|
|
6466
|
+
}
|
|
6467
|
+
|
|
6468
|
+
/* 弹窗底栏的裁决进度(左,长文案允许收窄换行) */
|
|
6469
|
+
.mergeFooterSummary {
|
|
6470
|
+
min-width: 0;
|
|
6471
|
+
font-size: var(--cm-fs-meta);
|
|
6472
|
+
line-height: 1.5;
|
|
6473
|
+
color: var(--cm-text-2);
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6476
|
+
/* 分区卡(.mergeScroll 内逐张):卡片外框 0.5px,相邻卡之间用间距而不是分隔线 */
|
|
6477
|
+
.mergeSection {
|
|
6478
|
+
padding: 10px 12px;
|
|
6479
|
+
border: 0.5px solid var(--cm-line-1);
|
|
6480
|
+
border-radius: var(--cm-r-3);
|
|
6481
|
+
background: var(--cm-surface-1);
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
.mergeSection + .mergeSection {
|
|
6485
|
+
margin-top: 8px;
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6488
|
+
.mergeSectionHead {
|
|
6489
|
+
display: flex;
|
|
6490
|
+
align-items: center;
|
|
6491
|
+
flex-wrap: wrap;
|
|
6492
|
+
gap: 8px;
|
|
6493
|
+
}
|
|
6494
|
+
|
|
6495
|
+
.mergeSectionName {
|
|
6496
|
+
font-size: var(--cm-fs-title);
|
|
6497
|
+
font-weight: 600;
|
|
6498
|
+
color: var(--cm-text-1);
|
|
6499
|
+
}
|
|
6500
|
+
|
|
6501
|
+
.mergeMeta {
|
|
6502
|
+
font-size: var(--cm-fs-meta);
|
|
6503
|
+
color: var(--dsw-alias-label-tertiary);
|
|
6504
|
+
}
|
|
6505
|
+
|
|
6506
|
+
/* 分区级动作行:换行与间距对齐 .actionRow */
|
|
6507
|
+
.mergeActions {
|
|
6508
|
+
display: flex;
|
|
6509
|
+
flex-wrap: wrap;
|
|
6510
|
+
gap: 6px;
|
|
6511
|
+
margin-top: 8px;
|
|
6512
|
+
}
|
|
6513
|
+
|
|
6514
|
+
/* 当前裁决片:沿用原生 Pill 的激活配方(ghost-active 底 + 内描边)+ 加粗 */
|
|
6515
|
+
.mergeActionActive {
|
|
6516
|
+
background: var(--cm-ghost-active-fill);
|
|
6517
|
+
border-color: var(--cm-ghost-active-border);
|
|
6518
|
+
font-weight: 600;
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
/* 单条冲突(只读展示:路径 + 原因 + 三列预览):嵌在分区卡里,用低一档表面区分层级 */
|
|
6522
|
+
.mergeConflictRow {
|
|
6523
|
+
margin-top: 8px;
|
|
6524
|
+
padding: 8px;
|
|
6525
|
+
border: 0.5px solid var(--cm-line-1);
|
|
6526
|
+
border-radius: var(--cm-r-2);
|
|
6527
|
+
background: color-mix(in srgb, var(--cm-text-1) 4%, transparent);
|
|
6528
|
+
}
|
|
6529
|
+
|
|
6530
|
+
.mergeConflictPath {
|
|
6531
|
+
display: flex;
|
|
6532
|
+
align-items: baseline;
|
|
6533
|
+
gap: 6px;
|
|
6534
|
+
min-width: 0;
|
|
6535
|
+
overflow-wrap: anywhere;
|
|
6536
|
+
font-size: var(--cm-fs-table);
|
|
6537
|
+
color: var(--dsw-alias-label-primary);
|
|
6538
|
+
}
|
|
6539
|
+
|
|
6540
|
+
.mergeKind {
|
|
6541
|
+
flex: 0 0 auto;
|
|
6542
|
+
padding: 0 6px;
|
|
6543
|
+
border-radius: var(--cm-r-1);
|
|
6544
|
+
background: color-mix(in srgb, var(--cm-text-1) 8%, transparent);
|
|
6545
|
+
font-size: var(--cm-fs-meta);
|
|
6546
|
+
line-height: 1.5;
|
|
6547
|
+
color: var(--cm-text-2);
|
|
6548
|
+
}
|
|
6549
|
+
|
|
6550
|
+
.mergeConflictReason {
|
|
6551
|
+
margin-top: 4px;
|
|
6552
|
+
font-size: var(--cm-fs-meta);
|
|
6553
|
+
color: var(--dsw-alias-label-tertiary);
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6556
|
+
/* 三列对照:等宽三列(564px 画布下每列约 160px),每列各自限高内滚 */
|
|
6557
|
+
.mergeCols {
|
|
6558
|
+
display: grid;
|
|
6559
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
6560
|
+
gap: 6px;
|
|
6561
|
+
margin-top: 6px;
|
|
6562
|
+
}
|
|
6563
|
+
|
|
6564
|
+
.mergeCol {
|
|
6565
|
+
min-width: 0;
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
.mergeColLabel {
|
|
6569
|
+
display: block;
|
|
6570
|
+
font-size: var(--cm-fs-meta);
|
|
6571
|
+
color: var(--dsw-alias-label-tertiary);
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
.mergeColValue {
|
|
6575
|
+
margin: 0;
|
|
6576
|
+
max-height: 88px;
|
|
6577
|
+
overflow: auto;
|
|
6578
|
+
white-space: pre-wrap;
|
|
6579
|
+
overflow-wrap: anywhere;
|
|
6580
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
6581
|
+
font-size: var(--cm-fs-meta);
|
|
6582
|
+
color: var(--cm-text-2);
|
|
6583
|
+
}
|
|
6584
|
+
|
|
6585
|
+
/* 弹窗里的分区列表:卡片需要内边距(否则边框贴住滚动容器),高度让位给概览 + 底栏。 */
|
|
6586
|
+
.mergeScroll[data-fit='modal'] {
|
|
6587
|
+
padding: 8px;
|
|
6588
|
+
max-height: 46vh;
|
|
6589
|
+
/* 分区列表在弹窗里内滚:预留滚动条槽位,列表从「刚好装下」变成「需要滚动」时宽度不跳。 */
|
|
6590
|
+
scrollbar-gutter: stable;
|
|
6591
|
+
}
|
|
6592
|
+
|
|
6593
|
+
/* —— 同步在途活动条(一键同步:拉远端快照 / 正在同步) ——
|
|
6594
|
+
不定态进度是**状态**而不是装饰:prefers-reduced-motion 下仍保留(见 DESIGN §5)。 */
|
|
6595
|
+
.syncActivity {
|
|
6596
|
+
display: flex;
|
|
6597
|
+
flex-direction: column;
|
|
6598
|
+
gap: 4px;
|
|
6599
|
+
margin-top: 8px;
|
|
6600
|
+
}
|
|
6601
|
+
|
|
6602
|
+
.syncActivityTrack {
|
|
6603
|
+
display: block;
|
|
6604
|
+
}
|
|
6605
|
+
|
|
6606
|
+
.syncActivityLabel {
|
|
6607
|
+
font-size: var(--cm-fs-meta);
|
|
6608
|
+
line-height: 1.5;
|
|
6609
|
+
color: var(--cm-text-2);
|
|
6610
|
+
}
|
|
6611
|
+
|
|
6302
6612
|
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
type OverviewMetricKey,
|
|
47
47
|
} from '../../ui/overview-view.ts'
|
|
48
48
|
import { Badge, Banner, Button, Card, Spinner, StatusDot, Stepper } from '../common/ui.tsx'
|
|
49
|
+
import { SkeletonRows } from '../common/Skeleton.tsx'
|
|
49
50
|
import { BackupIcon, ExportIcon, ImportIcon, SyncIcon } from '../common/Icon.tsx'
|
|
50
51
|
import { CopyButton } from '../common/CopyButton.tsx'
|
|
51
52
|
import { Modal } from '../common/Modal.tsx'
|
|
@@ -355,11 +356,14 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
355
356
|
onClick={() => { navMetric(seg.key) }}
|
|
356
357
|
>
|
|
357
358
|
<span>{seg.label}</span>
|
|
358
|
-
|
|
359
|
-
|
|
359
|
+
{/* 首屏数值未到货时用骨架占位(不再在整条右端挂一颗转圈):
|
|
360
|
+
段本身的位置与宽度保持不变,数据到货只换「值」这一块。 */}
|
|
361
|
+
{loading
|
|
362
|
+
? <span className={css.skeletonBar + ' ' + css.statValueSkeleton} aria-hidden="true" />
|
|
363
|
+
: <b>{seg.value}</b>}
|
|
364
|
+
{!loading && seg.dim !== null && <span className={css.statSegDim}>· {seg.dim}</span>}
|
|
360
365
|
</button>
|
|
361
366
|
))}
|
|
362
|
-
{loading && <span className={css.statSeg}><Spinner /></span>}
|
|
363
367
|
</div>
|
|
364
368
|
|
|
365
369
|
{/* 2. 动作工具栏:立即备份 + 三张「任务域」入口(带走 / 装进 / 回去)。
|
|
@@ -421,9 +425,13 @@ export function HomePanel({ api, syncApi, historyApi, t, syncT, openForeignImpor
|
|
|
421
425
|
<div className={css.activityHeader}>
|
|
422
426
|
<span className={css.activityTitle}>{t('overview.activity.title')}</span>
|
|
423
427
|
</div>
|
|
424
|
-
{
|
|
425
|
-
|
|
426
|
-
|
|
428
|
+
{/* 首屏未完成时**不写空态**:以前这里直接渲染「暂无操作记录」,
|
|
429
|
+
数据没到就说「没有」,与真实情况相反。改为按行列表形状铺骨架。 */}
|
|
430
|
+
{loading
|
|
431
|
+
? <SkeletonRows count={5} label={t('common.loading')} />
|
|
432
|
+
: activity.length === 0
|
|
433
|
+
? <div className={css.activityEmpty}>{data.history === null ? t('overview.activity.unavailable') : t('overview.activity.empty')}</div>
|
|
434
|
+
: (
|
|
427
435
|
<div className={css.activityRows}>
|
|
428
436
|
{activity.map((item, i) => {
|
|
429
437
|
const atMs = Date.parse(item.at) || 0
|
|
@@ -30,6 +30,7 @@ import type { ConfigManagerApi, ForeignImportResponse } from '../api.ts'
|
|
|
30
30
|
import type { UiT } from '../../ui/i18n.ts'
|
|
31
31
|
import { foreignLossyRows, foreignPathsText, foreignSourcesViewModel, type ForeignSourceRow } from '../../ui/foreign-view.ts'
|
|
32
32
|
import { Badge, Banner, Button, Card, SectionTitle, Spinner } from '../common/ui.tsx'
|
|
33
|
+
import { SkeletonList } from '../common/Skeleton.tsx'
|
|
33
34
|
import { InfoHint } from '../common/InfoHint.tsx'
|
|
34
35
|
import { ErrorBanner } from '../common/ErrorBanner.tsx'
|
|
35
36
|
import { redact } from '../../security/redaction.ts'
|
|
@@ -187,7 +188,8 @@ export function ForeignImportView({ api, t, uiT, onReady }: ForeignImportViewPro
|
|
|
187
188
|
<div className={css.viewBody}>
|
|
188
189
|
<SectionTitle title={t('foreign.source.title')} />
|
|
189
190
|
<div className={css.marketReviewPage}>
|
|
190
|
-
|
|
191
|
+
{/* 来源卡还没到货:按来源卡(卡片列表)的形状铺骨架,不再只给一个居中转圈 */}
|
|
192
|
+
<SkeletonList count={4} label={t('foreign.source.loading')} />
|
|
191
193
|
</div>
|
|
192
194
|
</div>
|
|
193
195
|
)
|
|
@@ -15,6 +15,7 @@ import type { UiT } from '../../ui/i18n.ts'
|
|
|
15
15
|
import {
|
|
16
16
|
Banner, Button, Card, Checkbox, Empty, SectionTitle, Spinner,
|
|
17
17
|
} from '../common/ui.tsx'
|
|
18
|
+
import { Skeleton } from '../common/Skeleton.tsx'
|
|
18
19
|
import { InfoHint } from '../common/InfoHint.tsx'
|
|
19
20
|
import { ErrorBanner, ErrorList } from '../common/ErrorBanner.tsx'
|
|
20
21
|
import { CopyButton } from '../common/CopyButton.tsx'
|
|
@@ -183,7 +184,7 @@ export function ConsultStage(props: {
|
|
|
183
184
|
return (
|
|
184
185
|
<div className={css.viewBody}>
|
|
185
186
|
<SectionTitle title={t('import.preview.title')} info={<InfoHint text={t('import.consult.hint')} label={t('common.infoHint')} />} />
|
|
186
|
-
{consultLoading && <
|
|
187
|
+
{consultLoading && <Skeleton shape="line" count={5} label={apiT('consult.loading')} />}
|
|
187
188
|
{consultReport !== null && <ConsultCard report={consultReport} t={apiT} />}
|
|
188
189
|
{!consultLoading && consultReport === null && (
|
|
189
190
|
<Banner kind="info">{t('import.consult.unavailable')}</Banner>
|
|
@@ -43,10 +43,16 @@ export interface ArtifactRowViewProps {
|
|
|
43
43
|
onAction: (capability: ArtifactCapability, row: ArtifactRow) => void
|
|
44
44
|
/** 该行有动作在跑:禁用行内按钮(防重复提交) */
|
|
45
45
|
busy?: boolean
|
|
46
|
+
/**
|
|
47
|
+
* cloud-sync 借鉴项 ⑥:该行远端快照的**推送方设备**文案(如「由 <设备名> 推送」)。
|
|
48
|
+
* 只有快照 manifest 真的带 deviceName 时才由调用方给出 —— 缺省 undefined 表示**不渲染**
|
|
49
|
+
* (旧快照没有该字段,绝不显示空串或占位)。
|
|
50
|
+
*/
|
|
51
|
+
deviceLabel?: string | undefined
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export function ArtifactRowView({
|
|
49
|
-
row, t, uiT, formatTime, expanded, onToggle, onAction, busy = false,
|
|
55
|
+
row, t, uiT, formatTime, expanded, onToggle, onAction, busy = false, deviceLabel,
|
|
50
56
|
}: ArtifactRowViewProps) {
|
|
51
57
|
const detailId = useId()
|
|
52
58
|
const primary = row.capabilities[0]
|
|
@@ -77,7 +83,11 @@ export function ArtifactRowView({
|
|
|
77
83
|
</div>
|
|
78
84
|
{/* 元数据是**自由序列**而不是表格列:某一段没有(如快照没有体积)就整段不出现,
|
|
79
85
|
绝不补一个「—」占位 —— 这里没有对齐问题,占位只会变成噪音(§3 第三条) */}
|
|
80
|
-
{row.meta.length > 0
|
|
86
|
+
{(row.meta.length > 0 || deviceLabel !== undefined) && (
|
|
87
|
+
<div className={css.artifactMeta}>
|
|
88
|
+
{[...row.meta, ...(deviceLabel === undefined ? [] : [deviceLabel])].join(' · ')}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
81
91
|
</div>
|
|
82
92
|
{row.badges.length > 0 && (
|
|
83
93
|
<div className={css.artifactBadges}>
|
|
@@ -24,6 +24,7 @@ import type { SyncApi } from '../sync/sync-api.ts'
|
|
|
24
24
|
import { formatDateTimeFull } from '../sync/history-model.ts'
|
|
25
25
|
import type { TranslateNS } from '../client-types.ts'
|
|
26
26
|
import { Banner, Button, Empty, Spinner } from '../common/ui.tsx'
|
|
27
|
+
import { Skeleton, SkeletonList, SkeletonTable } from '../common/Skeleton.tsx'
|
|
27
28
|
import { InfoHint } from '../common/InfoHint.tsx'
|
|
28
29
|
import { ConfirmDialog } from '../common/ConfirmDialog.tsx'
|
|
29
30
|
import { Modal } from '../common/Modal.tsx'
|
|
@@ -380,7 +381,8 @@ export function LibraryActions({ api, syncApi, t, target, onDone, onChanged, onO
|
|
|
380
381
|
{consult !== null && (
|
|
381
382
|
<>
|
|
382
383
|
<div className={css.hint}>{fileNameOf(consult.row)}</div>
|
|
383
|
-
{
|
|
384
|
+
{/* 迁移前咨询:正文块(多段报告)→ 按行铺骨架 */}
|
|
385
|
+
{consult.loading && <Skeleton shape="line" count={5} label={api.t('consult.loading')} />}
|
|
384
386
|
{consult.error !== null && <Banner kind="error">{consult.error}</Banner>}
|
|
385
387
|
{consult.report !== null && <ConsultCard report={consult.report} t={api.t} />}
|
|
386
388
|
</>
|
|
@@ -441,7 +443,8 @@ export function LibraryActions({ api, syncApi, t, target, onDone, onChanged, onO
|
|
|
441
443
|
trailing={<InfoHint text={t('snapshots.selectHint')} label={t('common.infoHint')} />}
|
|
442
444
|
/>
|
|
443
445
|
<Modal.Body scroll>
|
|
444
|
-
{
|
|
446
|
+
{/* 恢复计划预览:结果是一张「文件 → 动作」表 → 按表格铺骨架 */}
|
|
447
|
+
{plan?.loading === true && <SkeletonTable rows={6} label={t('common.loading')} />}
|
|
445
448
|
{plan?.error !== null && plan?.error !== undefined && <Banner kind="error">{plan.error}</Banner>}
|
|
446
449
|
{plan?.plan != null && plan.plan.actions.length === 0 && <Empty>{t('snapshots.noActions')}</Empty>}
|
|
447
450
|
{plan?.plan != null && plan.plan.actions.length > 0 && (
|
|
@@ -511,7 +514,7 @@ export function LibraryActions({ api, syncApi, t, target, onDone, onChanged, onO
|
|
|
511
514
|
<Modal.Header title={t('backupFiles.inspect')} closeLabel={t('common.close')} onClose={() => { setInspect(null) }} />
|
|
512
515
|
<Modal.Body scroll>
|
|
513
516
|
{inspect !== null && <div className={css.hint} data-testid="inspect-backup-name">{inspect.name}</div>}
|
|
514
|
-
{inspect?.loading === true && <
|
|
517
|
+
{inspect?.loading === true && <SkeletonList count={3} label={t('backupFiles.inspectLoading')} />}
|
|
515
518
|
{inspect?.error != null && <Banner kind="error">{inspect.error}</Banner>}
|
|
516
519
|
{inspect !== null && !inspect.loading && inspect.result === null && inspect.error === null && <Empty>{t('backupFiles.inspectEmpty')}</Empty>}
|
|
517
520
|
{inspect?.result != null && <BackupInspectView result={inspect.result} t={t} />}
|
|
@@ -21,8 +21,10 @@ import type { SyncApi, SyncSnapshotLite } from '../sync/sync-api.ts'
|
|
|
21
21
|
import type { MarketApi } from '../market/market-api.ts'
|
|
22
22
|
import type { TranslateNS } from '../client-types.ts'
|
|
23
23
|
import { formatDateTimeFull } from '../sync/history-model.ts'
|
|
24
|
+
import { deviceLabelsById } from '../sync/remote-device-view.ts'
|
|
24
25
|
import { runStore, type LibraryStoreSlice } from '../run-store.ts'
|
|
25
|
-
import { Badge, Button, Card, Empty
|
|
26
|
+
import { Badge, Button, Card, Empty } from '../common/ui.tsx'
|
|
27
|
+
import { SkeletonRows } from '../common/Skeleton.tsx'
|
|
26
28
|
import { redact } from '../../security/redaction.ts'
|
|
27
29
|
import { InfoHint } from '../common/InfoHint.tsx'
|
|
28
30
|
import { ArtifactRowView } from './ArtifactRow.tsx'
|
|
@@ -139,6 +141,19 @@ export function LibraryPanel({ api, syncApi, marketApi, t, uiT, onAction, onOpen
|
|
|
139
141
|
() => filterArtifacts(rows, { kind: lib.sourceFilter, text: lib.query, sort: lib.sort }),
|
|
140
142
|
[rows, lib.sourceFilter, lib.query, lib.sort],
|
|
141
143
|
)
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* cloud-sync 借鉴项 ⑥:远端快照 id → 「由 <设备名> 推送」文案。
|
|
147
|
+
* **只有**快照 manifest 真的带 deviceName 的行才进表:旧快照没有该字段,界面就不显示设备行
|
|
148
|
+
* (绝不渲染空串占位,也绝不把 undefined 变成文案)。
|
|
149
|
+
*/
|
|
150
|
+
const deviceLabelOf = useMemo(
|
|
151
|
+
() => deviceLabelsById(
|
|
152
|
+
remote.status === 'ready' ? remote.items : [],
|
|
153
|
+
(device) => t('library.remoteDevice', { device }),
|
|
154
|
+
),
|
|
155
|
+
[remote, t],
|
|
156
|
+
)
|
|
142
157
|
const counts = countByKind(rows)
|
|
143
158
|
const failedSources = [snapshots, files, remote, market].filter((s) => s.status === 'error').length
|
|
144
159
|
|
|
@@ -216,8 +231,10 @@ export function LibraryPanel({ api, syncApi, marketApi, t, uiT, onAction, onOpen
|
|
|
216
231
|
{/* 还有来源在路上(本机两源已就绪但为空)时也走加载态:绝不先给「暂无产物」的结论。
|
|
217
232
|
与空态共用同一个 .libraryListState 契约(守卫要求恰好两处:加载态 + 空态)。 */}
|
|
218
233
|
{(loading || (anySourcePending && filtered.length === 0)) && (
|
|
219
|
-
<div className={css.libraryListState}>
|
|
220
|
-
|
|
234
|
+
<div className={css.libraryListState + ' ' + css.librarySkeletonRegion}>
|
|
235
|
+
{/* 骨架:行数按一屏可见行给(8 行 ≈ 564×720 画布的列表区),行高与 .artifactRowHead 对齐,
|
|
236
|
+
数据到货时列表从同一位置长出来,不再「空页面 + 居中转圈」整块跳变。 */}
|
|
237
|
+
<SkeletonRows count={8} label={t('common.loading')} />
|
|
221
238
|
</div>
|
|
222
239
|
)}
|
|
223
240
|
|
|
@@ -246,6 +263,7 @@ export function LibraryPanel({ api, syncApi, marketApi, t, uiT, onAction, onOpen
|
|
|
246
263
|
runStore.patch({ library: { expandedKey: lib.expandedKey === row.key ? null : row.key } })
|
|
247
264
|
}}
|
|
248
265
|
onAction={onAction}
|
|
266
|
+
deviceLabel={row.kind === 'remote-snapshot' ? deviceLabelOf.get(row.ref.remoteId ?? '') : undefined}
|
|
249
267
|
/>
|
|
250
268
|
))}
|
|
251
269
|
</ul>
|
|
@@ -19,7 +19,8 @@ import type { TranslateNS } from '../client-types.ts'
|
|
|
19
19
|
import { toRestorePlanView, withLoadedStatus, type RestorePlanRow } from '../../ui/restore-plan-view.ts'
|
|
20
20
|
import { DiffTable } from '../common/DiffTable.tsx'
|
|
21
21
|
import { formatBytes } from '../../ui/report.ts'
|
|
22
|
-
import { Badge, Banner
|
|
22
|
+
import { Badge, Banner } from '../common/ui.tsx'
|
|
23
|
+
import { SkeletonTable } from '../common/Skeleton.tsx'
|
|
23
24
|
import { ExpandChevron } from '../common/Icon.tsx'
|
|
24
25
|
import { Collapse } from '../common/Motion.tsx'
|
|
25
26
|
import css from '../config-manager.module.css'
|
|
@@ -104,7 +105,8 @@ function reasonLabel(t: TranslateNS<'config-manager'>, reason: SnapshotFileDiffR
|
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
function DiffPane({ state, t }: { state: DiffState; t: TranslateNS<'config-manager'> }) {
|
|
107
|
-
|
|
108
|
+
// 差异是「逐行文档」形状:加载时按表格铺骨架,与 DiffTable 的行高同一节奏
|
|
109
|
+
if (state.status === 'loading') return <SkeletonTable rows={6} label={t('snapshots.plan.loadingDiff')} />
|
|
108
110
|
if (state.status === 'error') return <Banner kind="error">{redact(state.message)}</Banner>
|
|
109
111
|
const diff = state.diff
|
|
110
112
|
if (diff.reason !== undefined) return <div className={css.hint}>{reasonLabel(t, diff.reason, diff)}</div>
|
package/src/client/locales.ts
CHANGED
|
@@ -827,6 +827,8 @@ export const zh = {
|
|
|
827
827
|
'library.kind.snapshot': '本机快照',
|
|
828
828
|
'library.kind.backupFile': '备份文件',
|
|
829
829
|
'library.kind.remote': '远端快照',
|
|
830
|
+
// cloud-sync 借鉴项 ⑥:快照的推送方设备(仅当快照 manifest 带 deviceName 时才渲染;旧快照不显示占位)
|
|
831
|
+
'library.remoteDevice': '由 {device} 推送',
|
|
830
832
|
'library.kind.market': '市场配置',
|
|
831
833
|
'library.title': '产物库',
|
|
832
834
|
'library.refresh': '刷新',
|
|
@@ -1769,6 +1771,7 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
1769
1771
|
'library.kind.snapshot': 'Local snapshot',
|
|
1770
1772
|
'library.kind.backupFile': 'Backup file',
|
|
1771
1773
|
'library.kind.remote': 'Remote snapshot',
|
|
1774
|
+
'library.remoteDevice': 'Pushed by {device}',
|
|
1772
1775
|
'library.kind.market': 'Market config',
|
|
1773
1776
|
'library.title': 'Artifact library',
|
|
1774
1777
|
'library.refresh': 'Refresh',
|