dsh-config-manager 0.1.71 → 0.1.72

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.
Files changed (548) hide show
  1. package/README.md +111 -96
  2. package/README.zh-CN.md +207 -86
  3. package/lib/adapters/agent-instructions.js +1 -1
  4. package/lib/adapters/file-collection.js +1 -1
  5. package/lib/adapters/index.d.ts +1 -1
  6. package/lib/adapters/index.js +1 -1
  7. package/lib/adapters/link-report.d.ts +1 -0
  8. package/lib/adapters/link-report.js +27 -3
  9. package/lib/adapters/messages.d.ts +18 -0
  10. package/lib/adapters/messages.js +50 -0
  11. package/lib/adapters/plugin-files.js +3 -2
  12. package/lib/adapters/plugins.js +4 -3
  13. package/lib/adapters/prompts.d.ts +1 -0
  14. package/lib/adapters/prompts.js +7 -1
  15. package/lib/adapters/self.d.ts +7 -1
  16. package/lib/adapters/self.js +1 -1
  17. package/lib/adapters/sessions.d.ts +1 -0
  18. package/lib/adapters/sessions.js +4 -2
  19. package/lib/adapters/ui.d.ts +2 -2
  20. package/lib/adapters/ui.js +25 -25
  21. package/lib/adapters/workspaces.d.ts +1 -0
  22. package/lib/adapters/workspaces.js +7 -3
  23. package/lib/cli/actions.d.ts +9 -1
  24. package/lib/cli/actions.js +5 -2
  25. package/lib/cli/help.js +23 -10
  26. package/lib/cli/index.d.ts +23 -0
  27. package/lib/cli/index.js +90 -32
  28. package/lib/cli/sessions-inspect.js +3 -1
  29. package/lib/cli/sessions-repair.js +25 -6
  30. package/lib/cli/web/page.d.ts +12 -0
  31. package/lib/cli/web/page.js +21 -12
  32. package/lib/cli/web/routes.js +2 -2
  33. package/lib/cli/web/server.js +4 -4
  34. package/lib/client.d.ts +1349 -490
  35. package/lib/client.js +43047 -36738
  36. package/lib/core/backup-plan.js +30 -17
  37. package/lib/core/backup-verify.js +24 -23
  38. package/lib/core/backup.d.ts +5 -0
  39. package/lib/core/backup.js +51 -25
  40. package/lib/core/boot-rescue.js +4 -3
  41. package/lib/core/boot-safety.js +2 -1
  42. package/lib/core/command-rollback.d.ts +178 -0
  43. package/lib/core/command-rollback.js +289 -0
  44. package/lib/core/consult-source.js +7 -6
  45. package/lib/core/disk-usage.d.ts +74 -10
  46. package/lib/core/disk-usage.js +67 -4
  47. package/lib/core/engine-locale.d.ts +87 -0
  48. package/lib/core/engine-locale.js +125 -0
  49. package/lib/core/exporter.js +3 -3
  50. package/lib/core/fork-reconcile.d.ts +128 -0
  51. package/lib/core/fork-reconcile.js +150 -0
  52. package/lib/core/journal.js +6 -5
  53. package/lib/core/local-plugin-host.js +3 -2
  54. package/lib/core/local-plugin-pack.js +11 -6
  55. package/lib/core/messages-core.d.ts +292 -0
  56. package/lib/core/messages-core.js +615 -0
  57. package/lib/core/messages.d.ts +60 -2
  58. package/lib/core/messages.js +116 -13
  59. package/lib/core/migration-consult.js +21 -20
  60. package/lib/core/migration-history.js +19 -5
  61. package/lib/core/model-tools.d.ts +0 -22
  62. package/lib/core/model-tools.js +50 -27
  63. package/lib/core/phase3-host.js +30 -5
  64. package/lib/core/plugin-cli.js +18 -19
  65. package/lib/core/reconcile.d.ts +2 -14
  66. package/lib/core/reconcile.js +87 -17
  67. package/lib/core/recovery-orchestrator.js +19 -18
  68. package/lib/core/reinstall.d.ts +4 -0
  69. package/lib/core/reinstall.js +26 -21
  70. package/lib/core/restore.js +20 -17
  71. package/lib/core/rollback-ignore-rules.d.ts +31 -0
  72. package/lib/core/rollback-ignore-rules.js +140 -0
  73. package/lib/core/rollback-point.d.ts +1208 -0
  74. package/lib/core/rollback-point.js +2993 -0
  75. package/lib/core/run-registry.js +6 -5
  76. package/lib/core/session-health.d.ts +15 -0
  77. package/lib/core/session-meta.d.ts +42 -2
  78. package/lib/core/session-meta.js +140 -1
  79. package/lib/core/session-title.d.ts +28 -0
  80. package/lib/core/session-title.js +185 -0
  81. package/lib/core/startup-barrier.d.ts +24 -0
  82. package/lib/core/startup-barrier.js +22 -0
  83. package/lib/core/transaction-coordinator.js +4 -3
  84. package/lib/core/types.d.ts +18 -1
  85. package/lib/core/validator.js +5 -4
  86. package/lib/core/verify-recovery.js +31 -30
  87. package/lib/foreign/bundle.js +3 -2
  88. package/lib/foreign/continue.js +2 -1
  89. package/lib/foreign/crush.js +2 -1
  90. package/lib/foreign/gemini.js +2 -1
  91. package/lib/foreign/goose.js +2 -1
  92. package/lib/foreign/kilocode.js +2 -1
  93. package/lib/foreign/kimi.js +2 -1
  94. package/lib/foreign/messages.d.ts +15 -0
  95. package/lib/foreign/messages.js +40 -0
  96. package/lib/foreign/mimocode.js +2 -1
  97. package/lib/foreign/openclaw.js +2 -1
  98. package/lib/foreign/opencode.js +2 -1
  99. package/lib/foreign/pi.js +2 -1
  100. package/lib/foreign/qoder.js +2 -1
  101. package/lib/foreign/qwen.js +2 -1
  102. package/lib/foreign/registry.js +7 -6
  103. package/lib/foreign/teleagent.js +2 -1
  104. package/lib/foreign/workbuddy.js +2 -1
  105. package/lib/foreign/zcode.js +2 -1
  106. package/lib/foreign/zed.js +2 -1
  107. package/lib/index.d.ts +223 -7
  108. package/lib/index.js +729 -54
  109. package/lib/market/git-file-writer.js +20 -19
  110. package/lib/market/github-repos.js +35 -19
  111. package/lib/market/index-parser.js +31 -30
  112. package/lib/market/messages-market.d.ts +138 -0
  113. package/lib/market/messages-market.js +305 -0
  114. package/lib/market/my-repo.d.ts +2 -2
  115. package/lib/market/my-repo.js +32 -29
  116. package/lib/market/prepare.js +18 -15
  117. package/lib/market/security.js +20 -19
  118. package/lib/market/types.js +2 -1
  119. package/lib/market/url.js +2 -1
  120. package/lib/migrations/index.js +6 -5
  121. package/lib/migrations/messages-migrations.d.ts +32 -0
  122. package/lib/migrations/messages-migrations.js +58 -0
  123. package/lib/profiles/dsh-profile-launcher.js +2 -1
  124. package/lib/profiles/dsh-profile-runtime.js +2 -1
  125. package/lib/profiles/messages.d.ts +7 -0
  126. package/lib/profiles/messages.js +27 -0
  127. package/lib/routes/backup.js +18 -2
  128. package/lib/routes/import.js +3 -3
  129. package/lib/routes/index.d.ts +1 -1
  130. package/lib/routes/kit.d.ts +3 -2
  131. package/lib/routes/kit.js +6 -1
  132. package/lib/routes/market.js +1 -1
  133. package/lib/routes/messages.d.ts +42 -0
  134. package/lib/routes/messages.js +100 -0
  135. package/lib/routes/profiles.js +12 -6
  136. package/lib/routes/recovery.js +378 -114
  137. package/lib/routes/snapshots.js +51 -4
  138. package/lib/routes/sync.js +68 -16
  139. package/lib/schema/config.d.ts +11 -1
  140. package/lib/schema/config.js +52 -20
  141. package/lib/schema/index.d.ts +8 -6
  142. package/lib/schema/index.js +8 -6
  143. package/lib/schema/manifest.js +19 -18
  144. package/lib/schema/messages.d.ts +47 -0
  145. package/lib/schema/messages.js +110 -0
  146. package/lib/schema/section-registry.d.ts +41 -14
  147. package/lib/schema/section-registry.js +35 -29
  148. package/lib/schema/versions.js +6 -5
  149. package/lib/security/encryption.js +16 -15
  150. package/lib/security/integrity.js +8 -7
  151. package/lib/security/messages.d.ts +30 -0
  152. package/lib/security/messages.js +77 -0
  153. package/lib/security/redaction.js +12 -0
  154. package/lib/security/secret-scanner.js +16 -13
  155. package/lib/security/vault.js +2 -1
  156. package/lib/security/zip-security.js +4 -3
  157. package/lib/sync/ancestor.js +2 -1
  158. package/lib/sync/autosync-config.d.ts +11 -0
  159. package/lib/sync/autosync-config.js +12 -0
  160. package/lib/sync/autosync-scheduler.d.ts +11 -1
  161. package/lib/sync/autosync-scheduler.js +115 -76
  162. package/lib/sync/backup-files.js +3 -2
  163. package/lib/sync/backup-scheduler.js +6 -5
  164. package/lib/sync/blob-store.js +2 -1
  165. package/lib/sync/git/git-transport.d.ts +55 -0
  166. package/lib/sync/git/git-transport.js +152 -2
  167. package/lib/sync/github-auth.js +10 -9
  168. package/lib/sync/layout.js +13 -12
  169. package/lib/sync/messages.d.ts +63 -0
  170. package/lib/sync/messages.js +149 -0
  171. package/lib/sync/review-queue.js +6 -5
  172. package/lib/sync/s3/messages.d.ts +7 -0
  173. package/lib/sync/s3/messages.js +16 -0
  174. package/lib/sync/s3/s3-providers.d.ts +4 -1
  175. package/lib/sync/s3/s3-providers.js +5 -5
  176. package/lib/sync/s3/s3-transport.js +1 -1
  177. package/lib/sync/s3/sigv4.js +2 -1
  178. package/lib/sync/snapshot-crypto.js +6 -5
  179. package/lib/sync/snapshot-json.js +3 -2
  180. package/lib/sync/sync-engine.d.ts +37 -15
  181. package/lib/sync/sync-engine.js +67 -37
  182. package/lib/sync/sync-history.js +7 -6
  183. package/lib/sync/sync-selection.d.ts +15 -4
  184. package/lib/sync/sync-selection.js +17 -5
  185. package/lib/sync/webdav/webdav-transport.d.ts +1 -0
  186. package/lib/sync/webdav/webdav-transport.js +4 -1
  187. package/lib/ui/artifact-view.js +4 -3
  188. package/lib/ui/commands.d.ts +1 -1
  189. package/lib/ui/commands.js +10 -0
  190. package/lib/ui/disk-usage-view.d.ts +33 -4
  191. package/lib/ui/disk-usage-view.js +29 -1
  192. package/lib/ui/errors.d.ts +1 -1
  193. package/lib/ui/errors.js +4 -3
  194. package/lib/ui/export-flow.js +24 -20
  195. package/lib/ui/foreign-view.d.ts +5 -1
  196. package/lib/ui/foreign-view.js +26 -2
  197. package/lib/ui/i18n.d.ts +65 -0
  198. package/lib/ui/i18n.js +114 -0
  199. package/lib/ui/import-wizard.d.ts +7 -0
  200. package/lib/ui/import-wizard.js +13 -10
  201. package/lib/ui/progress.d.ts +2 -2
  202. package/lib/ui/progress.js +1 -2
  203. package/lib/ui/report.d.ts +5 -5
  204. package/lib/ui/report.js +6 -6
  205. package/lib/ui/rollback-view.d.ts +756 -0
  206. package/lib/ui/rollback-view.js +724 -0
  207. package/lib/ui/selection-model.d.ts +41 -0
  208. package/lib/ui/selection-model.js +30 -0
  209. package/lib/ui/session-inventory-view.d.ts +97 -3
  210. package/lib/ui/session-inventory-view.js +76 -2
  211. package/lib/ui/session-issue-labels.d.ts +43 -0
  212. package/lib/ui/session-issue-labels.js +48 -0
  213. package/lib/ui/sync-settings-view.d.ts +33 -0
  214. package/lib/ui/sync-settings-view.js +40 -0
  215. package/lib/ui/types.d.ts +8 -3
  216. package/lib/ui/types.js +1 -1
  217. package/lib/utils/atomic-write.js +6 -5
  218. package/lib/utils/bundle-scan.d.ts +11 -1
  219. package/lib/utils/bundle-scan.js +12 -1
  220. package/lib/utils/env-lock.d.ts +10 -0
  221. package/lib/utils/env-lock.js +73 -46
  222. package/lib/utils/json.js +6 -5
  223. package/lib/utils/messages-utils.d.ts +96 -0
  224. package/lib/utils/messages-utils.js +216 -0
  225. package/lib/utils/proxy.js +18 -5
  226. package/lib/utils/recursive-walk.d.ts +22 -0
  227. package/lib/utils/recursive-walk.js +75 -1
  228. package/lib/utils/session-log-repair.js +10 -9
  229. package/lib/utils/zip.js +27 -26
  230. package/package.json +12 -2
  231. package/src/adapters/agent-instructions.ts +1 -1
  232. package/src/adapters/file-collection.test.ts +1 -0
  233. package/src/adapters/file-collection.ts +1 -1
  234. package/src/adapters/files.test.ts +27 -0
  235. package/src/adapters/index.ts +1 -1
  236. package/src/adapters/link-report.test.ts +22 -2
  237. package/src/adapters/link-report.ts +29 -4
  238. package/src/adapters/messages.ts +54 -0
  239. package/src/adapters/plugin-files.ts +3 -2
  240. package/src/adapters/plugins.ts +4 -3
  241. package/src/adapters/prompts.ts +7 -1
  242. package/src/adapters/self.test.ts +26 -0
  243. package/src/adapters/self.ts +8 -2
  244. package/src/adapters/sessions.ts +4 -2
  245. package/src/adapters/settings.test.ts +38 -1
  246. package/src/adapters/ui.test.ts +2 -2
  247. package/src/adapters/ui.ts +27 -26
  248. package/src/adapters/workspaces.ts +7 -3
  249. package/src/cli/actions.test.ts +28 -0
  250. package/src/cli/actions.ts +11 -2
  251. package/src/cli/help.ts +23 -10
  252. package/src/cli/index-guards.test.ts +193 -0
  253. package/src/cli/index.ts +106 -31
  254. package/src/cli/sessions-inspect.test.ts +15 -1
  255. package/src/cli/sessions-inspect.ts +3 -1
  256. package/src/cli/sessions-repair.test.ts +84 -0
  257. package/src/cli/sessions-repair.ts +33 -7
  258. package/src/cli/web/console-route-parity.test.ts +115 -0
  259. package/src/cli/web/page.ts +23 -12
  260. package/src/cli/web/routes.ts +2 -1
  261. package/src/cli/web/server.ts +4 -4
  262. package/src/cli/web/web.test.ts +2 -2
  263. package/src/client/ConfigManagerSection.tsx +30 -2
  264. package/src/client/about/AboutPanel.tsx +2 -2
  265. package/src/client/about/PluginUpdateDialog.tsx +1 -1
  266. package/src/client/about/ReleaseNotesDialog.tsx +2 -2
  267. package/src/client/about/about-view.test.ts +13 -3
  268. package/src/client/about/about-view.ts +10 -6
  269. package/src/client/about/release-notes-view.test.ts +14 -4
  270. package/src/client/about/release-notes-view.ts +4 -2
  271. package/src/client/api.ts +18 -6
  272. package/src/client/chat/ChatEntryView.tsx +391 -0
  273. package/src/client/chat/RollbackDiffTab.tsx +61 -0
  274. package/src/client/chat/RollbackFileIcon.tsx +96 -0
  275. package/src/client/chat/RollbackReviewDialog.tsx +591 -0
  276. package/src/client/chat/chat-entry-view.test.ts +1174 -0
  277. package/src/client/chat/chat-entry-view.ts +758 -0
  278. package/src/client/chat/chat-locales.ts +152 -0
  279. package/src/client/chat/chat-node-registration.ts +64 -0
  280. package/src/client/chat/chat-point-window.ts +173 -0
  281. package/src/client/chat/chat-slots.ts +107 -0
  282. package/src/client/chat/rollback-diff-tab.test.ts +105 -0
  283. package/src/client/chat/rollback-diff-tab.ts +164 -0
  284. package/src/client/chat/rollback-entry-view.test.ts +1469 -0
  285. package/src/client/chat/rollback-entry-view.ts +941 -0
  286. package/src/client/chat/sidebar-slots.ts +37 -0
  287. package/src/client/common/ConfirmDialog.tsx +7 -3
  288. package/src/client/common/ContentPicker.tsx +48 -10
  289. package/src/client/common/DiffTable.tsx +97 -0
  290. package/src/client/common/ErrorBanner.tsx +4 -4
  291. package/src/client/common/Icon.tsx +2 -0
  292. package/src/client/common/Modal.tsx +8 -1
  293. package/src/client/common/ProgressBar.tsx +8 -2
  294. package/src/client/common/ReportView.tsx +4 -4
  295. package/src/client/common/RunsCenter.tsx +1 -0
  296. package/src/client/common/client-route-parity.test.ts +78 -0
  297. package/src/client/common/info-hint-guard.test.ts +46 -13
  298. package/src/client/common/picker-lazy-units.test.ts +42 -0
  299. package/src/client/common/plan-text-redaction.test.ts +17 -6
  300. package/src/client/common/progress-view-edge.test.ts +8 -7
  301. package/src/client/common/progress-view.test.ts +9 -9
  302. package/src/client/common/progress-view.ts +2 -2
  303. package/src/client/common/routes.ts +28 -0
  304. package/src/client/common/ui.tsx +73 -10
  305. package/src/client/config-manager.module.css +996 -21
  306. package/src/client/css-scale-guard.test.ts +30 -0
  307. package/src/client/environment/EnvironmentPanel.tsx +11 -4
  308. package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
  309. package/src/client/export/ExportView.tsx +13 -8
  310. package/src/client/history/HistoryPanel.tsx +4 -3
  311. package/src/client/history/history-api.ts +2 -2
  312. package/src/client/history/history-locales.ts +5 -1
  313. package/src/client/home/HomePanel.tsx +29 -8
  314. package/src/client/import/ForeignImportView.tsx +4 -5
  315. package/src/client/import/ImportWizardView.tsx +5 -3
  316. package/src/client/import/import-wizard-split.test.ts +3 -1
  317. package/src/client/import/import-wizard-steps.tsx +9 -9
  318. package/src/client/index.ts +186 -3
  319. package/src/client/library/LibraryActions.tsx +32 -7
  320. package/src/client/library/LibraryPanel.tsx +23 -3
  321. package/src/client/library/RestorePlanView.tsx +9 -66
  322. package/src/client/locales.ts +54 -5
  323. package/src/client/lucide-icons.d.ts +1 -0
  324. package/src/client/market/MarketPanel.tsx +13 -2
  325. package/src/client/market/MyConfigsView.tsx +5 -4
  326. package/src/client/market/MyConfigsWizard.tsx +10 -8
  327. package/src/client/market/market-api.test.ts +9 -8
  328. package/src/client/market/market-api.ts +2 -2
  329. package/src/client/market/market-locales.ts +3 -0
  330. package/src/client/market/market-view.test.ts +6 -6
  331. package/src/client/market/market-view.ts +3 -3
  332. package/src/client/market/my-configs-api.ts +2 -2
  333. package/src/client/market/my-configs-view.test.ts +23 -22
  334. package/src/client/market/my-configs-view.ts +8 -8
  335. package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
  336. package/src/client/recovery/RecoveryPanel.tsx +442 -283
  337. package/src/client/recovery/RollbackCard.tsx +665 -0
  338. package/src/client/recovery/RollbackDiffPane.tsx +58 -0
  339. package/src/client/recovery/RollbackFileChanges.tsx +163 -0
  340. package/src/client/recovery/incident-api.ts +2 -2
  341. package/src/client/recovery/recovery-api.ts +116 -6
  342. package/src/client/recovery/recovery-locales.test.ts +299 -0
  343. package/src/client/recovery/recovery-locales.ts +488 -149
  344. package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
  345. package/src/client/recovery/rollback-file-changes.test.ts +73 -0
  346. package/src/client/recovery/session-dialog-mount.test.ts +54 -0
  347. package/src/client/run-store.ts +84 -21
  348. package/src/client/sync/AutosyncCard.tsx +25 -1
  349. package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
  350. package/src/client/sync/ChannelConfigDialog.tsx +10 -10
  351. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  352. package/src/client/sync/SecurityOptionsCard.tsx +27 -17
  353. package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
  354. package/src/client/sync/SyncConfirmView.tsx +46 -8
  355. package/src/client/sync/SyncLogList.tsx +1 -1
  356. package/src/client/sync/SyncPanel.tsx +31 -23
  357. package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
  358. package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
  359. package/src/client/sync/SyncSettingsView.test.ts +20 -1
  360. package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
  361. package/src/client/sync/WorkDirCard.tsx +112 -0
  362. package/src/client/sync/history-model.test.ts +42 -9
  363. package/src/client/sync/history-model.ts +30 -15
  364. package/src/client/sync/sync-api.test.ts +57 -33
  365. package/src/client/sync/sync-api.ts +80 -6
  366. package/src/client/sync/sync-locales.ts +104 -3
  367. package/src/client/sync/sync-view-v2.test.ts +65 -13
  368. package/src/client/sync/sync-view.test.ts +107 -52
  369. package/src/client/sync/sync-view.ts +124 -41
  370. package/src/core/backup-plan.test.ts +28 -0
  371. package/src/core/backup-plan.ts +29 -17
  372. package/src/core/backup-verify.ts +24 -23
  373. package/src/core/backup.test.ts +76 -0
  374. package/src/core/backup.ts +55 -25
  375. package/src/core/boot-rescue.ts +4 -3
  376. package/src/core/boot-safety.ts +2 -1
  377. package/src/core/command-rollback.test.ts +536 -0
  378. package/src/core/command-rollback.ts +402 -0
  379. package/src/core/consult-source.ts +7 -6
  380. package/src/core/disk-usage.test.ts +257 -1
  381. package/src/core/disk-usage.ts +134 -14
  382. package/src/core/engine-locale.test.ts +593 -0
  383. package/src/core/engine-locale.ts +175 -0
  384. package/src/core/exporter.ts +3 -3
  385. package/src/core/fork-reconcile.test.ts +279 -0
  386. package/src/core/fork-reconcile.ts +234 -0
  387. package/src/core/journal.ts +6 -5
  388. package/src/core/local-plugin-host.ts +3 -2
  389. package/src/core/local-plugin-pack.ts +11 -6
  390. package/src/core/messages-core.ts +657 -0
  391. package/src/core/messages.ts +123 -17
  392. package/src/core/migration-consult.ts +21 -20
  393. package/src/core/migration-history.ts +19 -5
  394. package/src/core/model-tools.ts +28 -27
  395. package/src/core/phase3-host.ts +21 -5
  396. package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
  397. package/src/core/plugin-cli.ts +18 -19
  398. package/src/core/reconcile.test.ts +117 -0
  399. package/src/core/reconcile.ts +85 -17
  400. package/src/core/recovery-orchestrator.ts +19 -18
  401. package/src/core/reinstall.ts +26 -21
  402. package/src/core/restore.ts +20 -17
  403. package/src/core/rollback-file-diff.test.ts +159 -0
  404. package/src/core/rollback-ignore-rules.ts +126 -0
  405. package/src/core/rollback-point.test.ts +2603 -0
  406. package/src/core/rollback-point.ts +3755 -0
  407. package/src/core/run-registry.ts +6 -5
  408. package/src/core/session-health.ts +15 -0
  409. package/src/core/session-meta.test.ts +117 -1
  410. package/src/core/session-meta.ts +149 -2
  411. package/src/core/session-title.test.ts +127 -0
  412. package/src/core/session-title.ts +189 -0
  413. package/src/core/startup-barrier.test.ts +69 -0
  414. package/src/core/startup-barrier.ts +27 -0
  415. package/src/core/transaction-coordinator.ts +4 -3
  416. package/src/core/types.ts +18 -1
  417. package/src/core/validator.ts +5 -4
  418. package/src/core/verify-recovery.ts +31 -30
  419. package/src/foreign/bundle.ts +3 -2
  420. package/src/foreign/continue.ts +2 -1
  421. package/src/foreign/crush.ts +2 -1
  422. package/src/foreign/file-budget.test.ts +2 -0
  423. package/src/foreign/gemini.ts +2 -1
  424. package/src/foreign/goose.ts +2 -1
  425. package/src/foreign/kilocode.ts +2 -1
  426. package/src/foreign/kimi.ts +2 -1
  427. package/src/foreign/messages.ts +44 -0
  428. package/src/foreign/mimocode.ts +2 -1
  429. package/src/foreign/openclaw.ts +2 -1
  430. package/src/foreign/opencode.ts +2 -1
  431. package/src/foreign/pi.ts +2 -1
  432. package/src/foreign/qoder.ts +2 -1
  433. package/src/foreign/qwen.ts +2 -1
  434. package/src/foreign/registry.ts +7 -6
  435. package/src/foreign/teleagent.ts +2 -1
  436. package/src/foreign/workbuddy.ts +2 -1
  437. package/src/foreign/zcode.ts +2 -1
  438. package/src/foreign/zed.ts +2 -1
  439. package/src/index.facade.test.ts +340 -1
  440. package/src/index.ts +813 -57
  441. package/src/market/git-file-writer.ts +20 -19
  442. package/src/market/github-repos.test.ts +23 -0
  443. package/src/market/github-repos.ts +37 -19
  444. package/src/market/index-parser.ts +31 -30
  445. package/src/market/messages-market.ts +325 -0
  446. package/src/market/my-repo.ts +32 -29
  447. package/src/market/prepare.ts +18 -15
  448. package/src/market/security.ts +20 -19
  449. package/src/market/types.ts +2 -1
  450. package/src/market/url.ts +2 -1
  451. package/src/migrations/index.ts +6 -5
  452. package/src/migrations/messages-migrations.ts +59 -0
  453. package/src/profiles/dsh-profile-launcher.ts +2 -1
  454. package/src/profiles/dsh-profile-runtime.ts +2 -1
  455. package/src/profiles/messages.ts +31 -0
  456. package/src/routes/backup.ts +18 -2
  457. package/src/routes/import.ts +3 -3
  458. package/src/routes/index.ts +1 -1
  459. package/src/routes/kit.ts +9 -3
  460. package/src/routes/market.ts +1 -1
  461. package/src/routes/messages.ts +104 -0
  462. package/src/routes/profiles.ts +12 -6
  463. package/src/routes/recovery.test.ts +405 -1
  464. package/src/routes/recovery.ts +377 -109
  465. package/src/routes/snapshots.ts +51 -4
  466. package/src/routes/sync.ts +67 -16
  467. package/src/schema/config.ts +53 -20
  468. package/src/schema/index.ts +7 -5
  469. package/src/schema/manifest.ts +19 -18
  470. package/src/schema/messages.ts +120 -0
  471. package/src/schema/registry.test.ts +19 -5
  472. package/src/schema/section-registry.ts +64 -39
  473. package/src/schema/versions.ts +6 -5
  474. package/src/security/encryption.ts +16 -15
  475. package/src/security/integrity.ts +8 -7
  476. package/src/security/messages.ts +87 -0
  477. package/src/security/redaction.test.ts +17 -1
  478. package/src/security/redaction.ts +13 -0
  479. package/src/security/secret-scanner.ts +19 -12
  480. package/src/security/security.test.ts +47 -0
  481. package/src/security/vault.ts +2 -1
  482. package/src/security/zip-security.ts +4 -3
  483. package/src/sync/ancestor.ts +2 -1
  484. package/src/sync/autosync-config.ts +21 -0
  485. package/src/sync/autosync-scheduler.test.ts +63 -7
  486. package/src/sync/autosync-scheduler.ts +118 -78
  487. package/src/sync/backup-files.ts +3 -2
  488. package/src/sync/backup-scheduler.ts +6 -5
  489. package/src/sync/blob-store.ts +2 -1
  490. package/src/sync/git/git-transport.test.ts +170 -8
  491. package/src/sync/git/git-transport.ts +176 -2
  492. package/src/sync/github-auth.ts +10 -9
  493. package/src/sync/layout.ts +13 -12
  494. package/src/sync/messages.ts +153 -0
  495. package/src/sync/review-queue.ts +6 -5
  496. package/src/sync/s3/messages.ts +16 -0
  497. package/src/sync/s3/s3-providers.ts +9 -6
  498. package/src/sync/s3/s3-transport.ts +1 -1
  499. package/src/sync/s3/sigv4.ts +3 -1
  500. package/src/sync/snapshot-crypto.ts +6 -5
  501. package/src/sync/snapshot-json.ts +3 -2
  502. package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
  503. package/src/sync/sync-engine.test.ts +226 -8
  504. package/src/sync/sync-engine.ts +103 -41
  505. package/src/sync/sync-history.ts +7 -6
  506. package/src/sync/sync-selection.test.ts +12 -2
  507. package/src/sync/sync-selection.ts +18 -5
  508. package/src/sync/webdav/webdav-transport.ts +4 -1
  509. package/src/ui/artifact-view.ts +4 -3
  510. package/src/ui/commands.test.ts +1 -0
  511. package/src/ui/commands.ts +11 -1
  512. package/src/ui/disk-usage-view.test.ts +113 -6
  513. package/src/ui/disk-usage-view.ts +63 -7
  514. package/src/ui/errors.ts +4 -3
  515. package/src/ui/export-flow.ts +24 -20
  516. package/src/ui/foreign-view.test.ts +43 -0
  517. package/src/ui/foreign-view.ts +30 -3
  518. package/src/ui/i18n.ts +125 -0
  519. package/src/ui/import-wizard.ts +18 -10
  520. package/src/ui/progress.ts +2 -2
  521. package/src/ui/report.ts +7 -6
  522. package/src/ui/rollback-view.test.ts +819 -0
  523. package/src/ui/rollback-view.ts +1339 -0
  524. package/src/ui/selection-model.test.ts +33 -1
  525. package/src/ui/selection-model.ts +53 -0
  526. package/src/ui/session-inventory-view.test.ts +109 -0
  527. package/src/ui/session-inventory-view.ts +138 -3
  528. package/src/ui/session-issue-labels.test.ts +51 -0
  529. package/src/ui/session-issue-labels.ts +70 -0
  530. package/src/ui/sync-settings-view.test.ts +36 -0
  531. package/src/ui/sync-settings-view.ts +49 -0
  532. package/src/ui/types.ts +10 -4
  533. package/src/utils/atomic-write.ts +6 -5
  534. package/src/utils/bundle-scan.test.ts +2 -2
  535. package/src/utils/bundle-scan.ts +12 -1
  536. package/src/utils/env-lock.ts +83 -49
  537. package/src/utils/json.ts +7 -5
  538. package/src/utils/messages-utils.ts +232 -0
  539. package/src/utils/proxy.test.ts +25 -0
  540. package/src/utils/proxy.ts +19 -5
  541. package/src/utils/recursive-walk.test.ts +43 -0
  542. package/src/utils/recursive-walk.ts +75 -1
  543. package/src/utils/session-log-repair.ts +10 -9
  544. package/src/utils/zip.ts +27 -26
  545. package/lib/core/checkpoint.d.ts +0 -362
  546. package/lib/core/checkpoint.js +0 -1309
  547. package/src/core/checkpoint.test.ts +0 -665
  548. package/src/core/checkpoint.ts +0 -1614
@@ -0,0 +1,37 @@
1
+ /**
2
+ * DSH 右侧栏槽位在本仓库的**类型镜像**(只声明我们真的用的那一个位)。
3
+ *
4
+ * 为什么需要镜像:`ctx.slots.register({ name })` 的 name 必须落在 ui-slots 的 `SlotMap` 里,
5
+ * 而本仓库的依赖里没有 `@deepseek-ai/dsh-client-ui-sidebar-right`(与 chat-slots.ts 同一处境)。
6
+ *
7
+ * 镜像来源(逐条核过真机 app.asar 0.2.0-rc.2 的 ui-sidebar-right/client.js):
8
+ * · 正文位 = `sidebar.right.pane.tab`,**keyed / session** 位,key = tab 类型的**实现 id**
9
+ * (自带的文本预览注册 key 就是它的包名;我们的是 `config-manager.rollback-diff`);
10
+ * · 每个正文组件由 seat 注入 `useTabInfo()`(给出 `tab.contentId` = 打开时用的资源地址),
11
+ * 这正是「同一个文件开两个标签各看各的」的依据 —— 我们**不**用全局变量记目标;
12
+ * · 旧版 DSH(0.1.x)没有这个槽位 ⇒ 装配点做形态探测后**不注册**(只丢这一个能力)。
13
+ */
14
+ import type {} from '@deepseek-ai/dsh-client-ui-slots'
15
+
16
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
17
+ interface SlotMap {
18
+ /**
19
+ * 右侧栏一个 pane 的 tab 正文位(keyed / session scope)。
20
+ * 本插件只注册自己的实现 id:`config-manager.rollback-diff`。
21
+ */
22
+ 'sidebar.right.pane.tab': {
23
+ kind: 'keyed'
24
+ scope: 'session'
25
+ keyProps: {
26
+ 'config-manager.rollback-diff': {
27
+ /** 侧栏 seat 注入的 tab 信息钩子(资源地址在这里;拿不到时如实说读不出来)。 */
28
+ useTabInfo?: () => { tab?: { contentId?: unknown } }
29
+ }
30
+ }
31
+ owner: {
32
+ /** 该位由 seat 组合出来的通用 props(我们只读 tab 信息钩子)。 */
33
+ useTabInfo?: () => { tab?: { contentId?: unknown } }
34
+ }
35
+ }
36
+ }
37
+ }
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * 安全:message 由调用方传入(渲染前已 redact 兜底);本组件不触碰任何凭据。
18
18
  */
19
- import { useRef, useState } from 'react'
19
+ import { useId, useRef, useState } from 'react'
20
20
  import { Button } from './ui.tsx'
21
21
  import { Modal } from './Modal.tsx'
22
22
  import css from '../config-manager.module.css'
@@ -60,6 +60,10 @@ export function ConfirmDialog({
60
60
  const handleBackdropClose = backdropClose ?? onCancel
61
61
  /** 取消按钮 ref:Radix 打开时把初始焦点重定向到此(危险确认不默认落破坏性的确认按钮) */
62
62
  const cancelRef = useRef<HTMLButtonElement | null>(null)
63
+ /** 正文说明的 DOM id:交由 Modal 设 aria-describedby。
64
+ * 只设 aria-label 时读屏只会播报标题(如「删除档案」),而**后果说明**不会在打开时被读出。 */
65
+ const messageId = useId()
66
+ const hasMessage = message !== undefined && message !== ''
63
67
 
64
68
  const handleConfirm = (): void => {
65
69
  if (busy) return
@@ -77,10 +81,10 @@ export function ConfirmDialog({
77
81
  }
78
82
 
79
83
  return (
80
- <Modal open={open} onClose={handleBackdropClose} title={title} busy={busy} onOpenAutoFocus={onOpenAutoFocus}>
84
+ <Modal open={open} onClose={handleBackdropClose} title={title} busy={busy} onOpenAutoFocus={onOpenAutoFocus} describedById={hasMessage ? messageId : undefined}>
81
85
  <Modal.Header title={title} />
82
86
  <Modal.Body scroll>
83
- {message !== undefined && message !== '' && <div>{message}</div>}
87
+ {hasMessage && <div id={messageId}>{message}</div>}
84
88
  {children}
85
89
  </Modal.Body>
86
90
  <Modal.Footer>
@@ -7,6 +7,11 @@
7
7
  * 与 SectionComposition 的分工:那个是只读的「分区构成」网格(总览页「分区构成」卡 /
8
8
  * 导出页「本次将导出」数据块共用),这个是可交互的选择器。两者不是替代关系,禁止合并。
9
9
  *
10
+ * 惰性清单(2026-10-12 用户反馈):条目的清单由调用方**逐分区惰性拉取**(导出页只预取已勾选分区)。
11
+ * 所以「未读」是一个必须被表达出来的状态 —— 未读分区照常渲染展开控件,展开时才经 onRequestUnits
12
+ * 触发读取,并且**不显示「已选 0/0」**(那会被读成「这条分区是空的」)。判定全部在
13
+ * src/ui/selection-model.ts(sectionExpandable / sectionCountState),本组件只装配。
14
+ *
10
15
  * 职责边界:本组件**只渲染与转发交互**,全部选择语义(稀疏排除集、原子组联动、
11
16
  * 三态判定、请求参数换算)都在 `src/ui/selection-model.ts` 里,node 可测。
12
17
  */
@@ -17,7 +22,8 @@ import type { TranslateNS } from '../client-types.ts'
17
22
  import {
18
23
  applySessionParentCoupling,
19
24
  applySessionWorkspaceCoupling,
20
- filterSections, groupPickState, groupSelectedCount, groupUnits, tailWeightedEllipsis, pickerSummary, sectionPickState,
25
+ filterSections, groupPickState, groupSelectedCount, groupUnits, tailWeightedEllipsis, pickerSummary,
26
+ sectionCountState, sectionExpandable, sectionPickState,
21
27
  selectAll, selectedUnitCount, toggleSection, toggleUnit, toggleUnitGroup, unitChecked, visibleUnits,
22
28
  type Selection, type SelectionSection, type SelectionUnit, type SessionParentChange, type SessionWorkspaceCouplingFocus,
23
29
  } from '../../ui/selection-model.ts'
@@ -39,7 +45,10 @@ function unitsId(section: SectionId): string {
39
45
  }
40
46
 
41
47
  export interface ContentPickerProps {
42
- /** 分区 + 可勾选单元(由 /export-preview 的 items 派生;units 为空 = 不可细分) */
48
+ /**
49
+ * 分区 + 可勾选单元(由 /export-preview 的 items 派生)。
50
+ * `units` 为空有两种事实:不可细分(整体开关)与**尚未读取**(`unitsUnread === true`,见 SelectionSection)。
51
+ */
43
52
  nodes: SelectionSection[]
44
53
  value: Selection
45
54
  onChange: (next: Selection) => void
@@ -54,6 +63,14 @@ export interface ContentPickerProps {
54
63
  * 并**不显示**会误导的「已选 0/0」—— 引擎对清单缺失的分区按整体导出处理(UI-07)。
55
64
  */
56
65
  failedSections?: SectionId[]
66
+ /**
67
+ * 请求读取某分区的清单(**惰性**;调用方自行去重与并发保护)。
68
+ *
69
+ * 触发时机 = 用户展开一个「尚未读取」的分区。为什么把入口挂在展开动作上:清单只对**已勾选分区**
70
+ * 预取,于是未勾选的分区此前既没有展开控件、也没有条目可选 —— 用户必须先勾上整个分区才能展开与
71
+ * 逐项挑选(2026-10-12 用户反馈)。改成展开即读之后,sessions 这类大分区依然不会被预读。
72
+ */
73
+ onRequestUnits?: (id: SectionId) => void
57
74
  /**
58
75
  * 正在读取清单(任意分区)。true 时在列表上加**遮罩 + 加载动画** ——
59
76
  * 勾选大分区(如 sessions)要读很久,没有反馈会被当成卡死。
@@ -87,7 +104,7 @@ export interface ContentPickerProps {
87
104
 
88
105
  export function ContentPicker({
89
106
  nodes, value, onChange, t, sectionLabel, loading = false, failedSections = [], mode = 'export',
90
- busy = false, pendingSections = [], highRisk, unitBadge,
107
+ busy = false, pendingSections = [], highRisk, unitBadge, onRequestUnits,
91
108
  }: ContentPickerProps) {
92
109
  const [query, setQuery] = useState('')
93
110
  const [expanded, setExpanded] = useState<Record<string, boolean>>({})
@@ -164,6 +181,18 @@ export function ContentPicker({
164
181
  const open = expanded[node.section] === true
165
182
  const picked = selectedUnitCount(value, node)
166
183
  const total = node.units.length === 0 ? node.count : node.units.length
184
+ /** 展开控件:有清单可展开;**清单未读**同样可展开(展开即触发读取,见 onRequestUnits) */
185
+ const expandable = sectionExpandable(node)
186
+ /** 计数区状态(未读**不得**显示 0/0;判定在 ui 纯函数里) */
187
+ const countState = sectionCountState(node, {
188
+ pending: pendingSections.includes(node.section),
189
+ failed: failedSections.includes(node.section),
190
+ })
191
+ /** 展开 = 先按需读清单(仅在「未读且即将展开」时触发一次;收起不读) */
192
+ const onToggleExpand = (): void => {
193
+ if (!open && node.unitsUnread === true) onRequestUnits?.(node.section)
194
+ setExpanded({ ...expanded, [node.section]: !open })
195
+ }
167
196
  /** 本帧要渲染的单元(默认前 N 条;UI-15) */
168
197
  const units = visibleUnits(node.units, showAllUnits[node.section] === true)
169
198
  return (
@@ -172,7 +201,7 @@ export function ContentPicker({
172
201
  {/* 级联树:展开控件在选择框**左边**(点它只展开本节点的子树,不展开别人的)。
173
202
  用原生 button(共享 Button 原语不透传 aria-* 属性,UI-22);图标走**形变层** ——
174
203
  展开态切换本身携带信息,让「变了」可见(common/Icon.tsx 的 ExpandChevron)。 */}
175
- {node.units.length > 0 ? (
204
+ {expandable ? (
176
205
  <button
177
206
  type="button"
178
207
  className={css.iconBtn}
@@ -181,7 +210,7 @@ export function ContentPicker({
181
210
  aria-controls={unitsId(node.section)}
182
211
  aria-label={open ? t('picker.collapse') : t('picker.expand')}
183
212
  title={open ? t('picker.collapse') : t('picker.expand')}
184
- onClick={() => { setExpanded({ ...expanded, [node.section]: !open }) }}
213
+ onClick={onToggleExpand}
185
214
  >
186
215
  <ExpandChevron open={open} />
187
216
  </button>
@@ -209,16 +238,20 @@ export function ContentPicker({
209
238
  {/* 三态:读取中 / 读取失败 / 已选 n/m。
210
239
  没读到清单(在途或失败)时**不要**显示 0/0 —— 那会被读成
211
240
  「这一项没有内容」;失败态必须显式说明「将整体导出」(UI-07)。 */}
212
- {pendingSections.includes(node.section) ? (
241
+ {countState === 'pending' ? (
213
242
  <span className={css.pickerCount}>{t('picker.loadingSection')}</span>
214
- ) : failedSections.includes(node.section) ? (
243
+ ) : countState === 'failed' ? (
215
244
  /* 失败态用 warn 语义色(DESIGN.md「状态即语义」):中性的 .pickerCount
216
245
  会让「读取失败」读起来像一条普通说明,而不是需要用户注意的状态 */
217
246
  <span className={`${css.pickerCount} ${css.warnText}`}>{t('picker.sectionLoadFailed')}</span>
218
- ) : (
247
+ ) : countState === 'count' ? (
219
248
  <span className={css.pickerCount}>
220
249
  {t('picker.selectedOf', { selected: String(picked), total: String(total) })}
221
250
  </span>
251
+ ) : (
252
+ /* 未读(惰性):**不渲染 0/0** —— 「已选 0/0」会被读成「这条分区没有内容」,
253
+ 而事实是「还没去读」。数字等展开(= 触发读取)之后再给。 */
254
+ null
222
255
  )}
223
256
  {node.sizeBytes > 0 && (
224
257
  <span className={`${css.sectionSize} ${css.mono}`}>{formatBytes(node.sizeBytes)}</span>
@@ -257,8 +290,13 @@ export function ContentPicker({
257
290
  onChange={(checked) => { commit(toggleUnit(value, nodes, u.id, checked), focusOf(node.section), { unitId: u.id, checked }) }}
258
291
  label={
259
292
  <span className={css.categoryItem}>
260
- {/* 中段省略(保留尾部时间戳等区分信息)+ title 全文(UI-16,DESIGN.md §9.5) */}
261
- <span className={css.pickerUnitName} title={safeLabel}>{tailWeightedEllipsis(safeLabel, UNIT_NAME_MAX)}</span>
293
+ {/* 空白会话(从未提问过):DSH 自己显示「新会话」—— 这里同样用本地化占位名,
294
+ 目录名仍进 title(悬停可核对)。其余单元照旧中段省略 + title 全文(UI-16,DESIGN.md §9.5)。 */}
295
+ {u.blank === true ? (
296
+ <span className={css.pickerUnitName} title={safeLabel}>{t('picker.blankSession')}</span>
297
+ ) : (
298
+ <span className={css.pickerUnitName} title={safeLabel}>{tailWeightedEllipsis(safeLabel, UNIT_NAME_MAX)}</span>
299
+ )}
262
300
  {u.lockedWith !== undefined && (
263
301
  <span title={t('picker.lockedHint')}><Badge kind="warn">{t('picker.locked')}</Badge></span>
264
302
  )}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 双栏差异表(共享渲染原语)——「快照恢复计划」与「回滚点 · 查看」共用同一副骨架。
3
+ *
4
+ * 为什么是共享原语(AGENTS.md Reuse Before Creating):两处要画的是**同一件事**
5
+ * (hunks → 左右对照:左 = before / 当前,右 = after / 目标),差别只在列头与截断文案;
6
+ * 复制第二份渲染代码必然从视觉与截断口径开始漂移。
7
+ *
8
+ * 分工:行对对齐 / 块头 / 行号列宽全部来自 `src/ui/diff-view.ts`(纯函数、node 单测);本组件只画。
9
+ * 安全:单元格文本是磁盘 / 宿主原文 ⇒ 展示前**必须**过调用方传进来的 sanitize(本该是 redact)。
10
+ */
11
+ import { Fragment } from 'react'
12
+ import { redact } from '../../security/redaction.ts'
13
+ import { countSideRows, maxLineDigits, toSideBySide, type SideBySideRow } from '../../ui/diff-view.ts'
14
+ import type { DiffHunk } from '../../utils/line-diff.ts'
15
+ import css from '../config-manager.module.css'
16
+
17
+ /** 单文件最多渲染多少行对:diff 上限 6000 行,全量渲染会卡住弹窗(数据完整,只是没画出来)。 */
18
+ export const DIFF_ROW_CAP = 400
19
+
20
+ export interface DiffTableProps {
21
+ hunks: readonly DiffHunk[]
22
+ /** 左侧列头(before = 当前) */
23
+ beforeLabel: string
24
+ /** 右侧列头(after = 目标 / 快照) */
25
+ afterLabel: string
26
+ /** 被渲染上限截断时的说明文案(由调用方按自己的字典出话;缺省 = 不提示) */
27
+ truncatedNotice?: (shown: number, total: number) => string
28
+ /** 单元格文本安全化(**缺省就是 redact**:调用方只能换成别的脱敏器,换不成不脱敏) */
29
+ sanitize?: (text: string) => string
30
+ /** 渲染上限(缺省 DIFF_ROW_CAP) */
31
+ rowCap?: number
32
+ }
33
+
34
+ /** 左侧单元格:成对修改 / 纯删除 → 红底;上下文 → 中性。 */
35
+ function leftCellClass(kind: SideBySideRow['kind']): string {
36
+ return kind === 'change' || kind === 'del' ? css.diffCell + ' ' + css.diffCellDel : css.diffCell ?? ''
37
+ }
38
+
39
+ /** 右侧单元格:成对修改 / 纯新增 → 绿底;上下文 → 中性。 */
40
+ function rightCellClass(kind: SideBySideRow['kind']): string {
41
+ return kind === 'change' || kind === 'add' ? css.diffCell + ' ' + css.diffCellAdd : css.diffCell ?? ''
42
+ }
43
+
44
+ /** 左右双栏逐行对照(表头 + 块头 + 行号 + 单元格)。 */
45
+ export function DiffTable({ hunks, beforeLabel, afterLabel, truncatedNotice, sanitize, rowCap = DIFF_ROW_CAP }: DiffTableProps) {
46
+ const cell = sanitize ?? ((text: string): string => redact(text))
47
+ const groups = toSideBySide(hunks)
48
+ const total = countSideRows(groups)
49
+ // 行号列宽 = 实际出现的最大行号位数(1–3/4/5/6+ 位四档 ch),内容列平分剩余空间
50
+ const digits = maxLineDigits(hunks)
51
+ const noColClass = digits <= 3 ? css.diffNoColW3 : digits === 4 ? css.diffNoColW4 : digits === 5 ? css.diffNoColW5 : css.diffNoColW6
52
+ let rendered = 0
53
+ const capped = total > rowCap
54
+ const notice = capped && truncatedNotice !== undefined ? truncatedNotice(rowCap, total) : null
55
+ return (
56
+ <>
57
+ {notice !== null && <div className={css.hint}>{notice}</div>}
58
+ <div className={css.diffScroll}>
59
+ <table className={css.diffTable}>
60
+ <colgroup>
61
+ <col className={noColClass} />
62
+ <col />
63
+ <col className={noColClass} />
64
+ <col />
65
+ </colgroup>
66
+ <thead>
67
+ <tr>
68
+ <th className={css.diffNoHead} colSpan={2}>{beforeLabel}</th>
69
+ <th className={css.diffNoHead} colSpan={2}>{afterLabel}</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ {groups.map((group) => {
74
+ const rows = capped ? group.rows.slice(0, Math.max(0, rowCap - rendered)) : group.rows
75
+ rendered += rows.length
76
+ return (
77
+ <Fragment key={group.header}>
78
+ <tr className={css.diffHunkRow}>
79
+ <td colSpan={4}>{group.header}</td>
80
+ </tr>
81
+ {rows.map((row, index) => (
82
+ <tr key={group.header + '-' + String(index)}>
83
+ <td className={css.diffNo}>{row.left?.oldNo ?? ''}</td>
84
+ <td className={leftCellClass(row.kind)}>{row.left === null ? '' : cell(row.left.text)}</td>
85
+ <td className={css.diffNo}>{row.right?.newNo ?? ''}</td>
86
+ <td className={rightCellClass(row.kind)}>{row.right === null ? '' : cell(row.right.text)}</td>
87
+ </tr>
88
+ ))}
89
+ </Fragment>
90
+ )
91
+ })}
92
+ </tbody>
93
+ </table>
94
+ </div>
95
+ </>
96
+ )
97
+ }
@@ -8,7 +8,7 @@
8
8
  import { useMemo } from 'react'
9
9
  import { toActionableError } from '../../ui/errors.ts'
10
10
  import { redact } from '../../security/redaction.ts'
11
- import { zhUiT, type UiT } from '../../ui/i18n.ts'
11
+ import type { UiT } from '../../ui/i18n.ts'
12
12
  import { Button } from './ui.tsx'
13
13
  import { ArrowRightIcon } from './Icon.tsx'
14
14
  import css from '../config-manager.module.css'
@@ -20,8 +20,8 @@ export interface ErrorBannerProps {
20
20
  onRetry?: () => void
21
21
  /** 重试中的 loading 态 */
22
22
  retrying?: boolean
23
- /** 展示层翻译器(缺省 zh;ErrorBanner 为通用组件,不绑定 settings 命名空间字典) */
24
- t?: UiT
23
+ /** 展示层翻译器(**必传**:英文界面不得回落中文;通用组件,不绑定 settings 命名空间字典) */
24
+ t: UiT
25
25
  }
26
26
 
27
27
  /**
@@ -29,7 +29,7 @@ export interface ErrorBannerProps {
29
29
  * 文本在渲染前再经 redact() 兜底(双保险),Reason 以等宽块展示。
30
30
  * 重试按钮:进行中(retrying)时显示 Spinner 并禁用(防重复点击)。
31
31
  */
32
- export function ErrorBanner({ error, onRetry, retrying, t = zhUiT }: ErrorBannerProps) {
32
+ export function ErrorBanner({ error, onRetry, retrying, t }: ErrorBannerProps) {
33
33
  /**
34
34
  * UI-20:解析结果必须由 `error` **派生**(useMemo),不能是只在挂载时求值一次的 useState ——
35
35
  * 同一横幅上 error 被替换后(如连续两次失败原因不同),标题/原因/建议动作/可重试判定会
@@ -29,6 +29,7 @@ import Copy from 'lucide-react/dist/esm/icons/copy.mjs'
29
29
  import DatabaseBackup from 'lucide-react/dist/esm/icons/database-backup.mjs'
30
30
  import Download from 'lucide-react/dist/esm/icons/download.mjs'
31
31
  import Eye from 'lucide-react/dist/esm/icons/eye.mjs'
32
+ import File from 'lucide-react/dist/esm/icons/file.mjs'
32
33
  import FolderInput from 'lucide-react/dist/esm/icons/folder-input.mjs'
33
34
  import HardDriveDownload from 'lucide-react/dist/esm/icons/hard-drive-download.mjs'
34
35
  import History from 'lucide-react/dist/esm/icons/history.mjs'
@@ -66,6 +67,7 @@ const ICONS = {
66
67
  pencil: Pencil,
67
68
  close: X,
68
69
  delete: Trash2,
70
+ file: File,
69
71
  download: Download,
70
72
  inspect: Search,
71
73
  view: Eye,
@@ -68,6 +68,10 @@ export interface ModalProps {
68
68
  /** Radix 打开时的初始焦点重定向(如 ConfirmDialog 把焦点派发到取消按钮)。
69
69
  * Radix 传入的是可 preventDefault 的 DOM Event。 */
70
70
  onOpenAutoFocus?: (e: Event) => void
71
+ /** 说明文本节点的 id(Radix Content 据此设 aria-describedby):
72
+ * 确认框的「后果说明」必须被屏幕阅读器读到 —— 只设 aria-label 时打开弹窗只播报标题。
73
+ * 传 id 而不是文本,是为了让可见正文本身就是被描述的那一段(不重复渲染、不动视觉)。 */
74
+ describedById?: string
71
75
  /** 卡片额外内联样式(如 ReleaseNotes 的自定义宽度/最大高度);常规布局仍走 CSS 类 */
72
76
  cardStyle?: CSSProperties
73
77
  children?: ReactNode
@@ -76,7 +80,7 @@ export interface ModalProps {
76
80
  /**
77
81
  * 统一弹窗容器(Radix Dialog)。busy 时禁用 Esc 与遮罩关闭。
78
82
  */
79
- export function Modal({ open, onClose, title, wide, busy, onOpenAutoFocus, cardStyle, children }: ModalProps) {
83
+ export function Modal({ open, onClose, title, wide, busy, onOpenAutoFocus, cardStyle, describedById, children }: ModalProps) {
80
84
  /**
81
85
  * Portal 容器解析 —— 三条纪律(顺序即重要性,别改回 useEffect):
82
86
  *
@@ -114,6 +118,9 @@ export function Modal({ open, onClose, title, wide, busy, onOpenAutoFocus, cardS
114
118
  className={`${css.dialogContentCenter} ${css.dialogCard}${wide === true ? ` ${css.dialogWide}` : ''}`}
115
119
  style={cardStyle}
116
120
  aria-label={title}
121
+ // 说明文本(危险确认的后果)必须进无障碍树:Radix 只在存在 Dialog.Description 时
122
+ // 自动接 aria-describedby,而我们的正文由调用方自渲染 —— 因此显式传 id(视觉零变化)。
123
+ aria-describedby={describedById}
117
124
  // 「这是我们自己的弹窗卡片」标记:Select 的弹层据此决定挂载点 —— 弹层**必须**进弹窗内容子树,
118
125
  // 否则会被 radix 的 body{pointer-events:none}、外部交互判定与 RemoveScroll 滚动锁挡死
119
126
  // (2026-10-04 用户反馈「选项点不动」;见 common/Select.tsx 的 resolveMenuHost)。
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { computeProgressView, progressBarMode } from './progress-view.ts'
15
15
  import type { RunProgress } from './progress-view.ts'
16
+ import type { UiT } from '../../ui/i18n.ts'
16
17
  import { redact } from '../../security/redaction.ts'
17
18
  import css from '../config-manager.module.css'
18
19
 
@@ -23,6 +24,11 @@ export interface ProgressBarProps {
23
24
  active: boolean
24
25
  /** 已结束且结论为失败:结束态不得渲染成「成功绿」(导出页不传,行为不变) */
25
26
  failed?: boolean
27
+ /**
28
+ * 展示层翻译器(**必传**:阶段文案要按当前语言渲染;英文界面不得回落中文)。
29
+ * 此前组件内隐式用 zhUiT 缺省值,切到英文后进度文案恒为中文。
30
+ */
31
+ t: UiT
26
32
  }
27
33
 
28
34
  /**
@@ -32,8 +38,8 @@ export interface ProgressBarProps {
32
38
  * 无百分比且在跑 → 不定态动画;**无百分比且已结束 → 静止条**(这条是 bugfix:
33
39
  * 此前「已完成」的任务因为没有百分比也走不定态分支,动画永不停 → 看起来一直在加载)。
34
40
  */
35
- export function ProgressBar({ event, active, failed }: ProgressBarProps) {
36
- const view = computeProgressView(event)
41
+ export function ProgressBar({ event, active, failed, t }: ProgressBarProps) {
42
+ const view = computeProgressView(event, t)
37
43
  const mode = progressBarMode(view, active)
38
44
  const fillClass = active
39
45
  ? css.progressBar
@@ -23,7 +23,7 @@ import {
23
23
  } from '../../ui/report.ts'
24
24
  import type { ImportSectionStat } from '../../ui/types.ts'
25
25
  import { redact } from '../../security/redaction.ts'
26
- import { zhUiT, type UiT } from '../../ui/i18n.ts'
26
+ import type { UiT } from '../../ui/i18n.ts'
27
27
  import { Badge, Button } from './ui.tsx'
28
28
  import css from '../config-manager.module.css'
29
29
 
@@ -37,8 +37,8 @@ export interface ReportViewProps {
37
37
  onDownload?: () => void
38
38
  /** 下载进行中(导出报告场景:下载按钮 spinner + 禁用,防重复下载) */
39
39
  downloadBusy?: boolean
40
- /** 展示层翻译器(缺省 zh;与 ErrorBanner 同策略,不绑定 settings 命名空间) */
41
- t?: UiT
40
+ /** 展示层翻译器(**必传**:英文界面不得回落中文;不绑定 settings 命名空间,故用 UiT) */
41
+ t: UiT
42
42
  /**
43
43
  * 分区显示名(SectionId → 文案)。导出报告的分区清单**必须**传 section-labels 的
44
44
  * sectionLabeler(t)(分区名的单一映射);缺省回退裸 id,只允许用于非 UI 场合
@@ -230,7 +230,7 @@ function ImportSectionTable({ stats, labelOf, t }: {
230
230
  * (ImportWizardView 的 .resultFooter)——原先埋在报告卡底部,被
231
231
  * `flex + overflow:hidden` 的挤压布局裁掉,用户报告「导入完成后没有完成按钮」。
232
232
  */
233
- export function ReportView({ kind, exportReport, importResult, onDownload, downloadBusy = false, t = zhUiT, sectionLabel }: ReportViewProps) {
233
+ export function ReportView({ kind, exportReport, importResult, onDownload, downloadBusy = false, t, sectionLabel }: ReportViewProps) {
234
234
  const importStats = kind === 'import' && importResult !== undefined
235
235
  ? importSectionStats(importResult.executed)
236
236
  : []
@@ -209,6 +209,7 @@ export function RunsCenter({ api, recoveryApi, t, defaultRollbackOnError }: Runs
209
209
  active={card.status === 'running'}
210
210
  // 失败的任务绝不能渲染成「成功绿」(结束态的默认色是 success)
211
211
  failed={card.status === 'failed'}
212
+ t={api.t}
212
213
  />
213
214
  {card.counts !== null && (
214
215
  <div className={css.runsCounts}>
@@ -34,6 +34,7 @@ import {
34
34
  MARKET_API,
35
35
  MY_CONFIGS_API,
36
36
  RECOVERY_API,
37
+ ROLLBACK_POINT_ROUTES,
37
38
  SYNC_API,
38
39
  } from './routes.ts';
39
40
 
@@ -67,8 +68,44 @@ const PREFIX_ROUTED = new Set<string>([
67
68
  // (`segments[0] === 'sessions'` 下的 `repair` / `rollback` 子路径),不新增宿主注册路由条目。
68
69
  RECOVERY_API.sessionsRepair,
69
70
  RECOVERY_API.sessionsRollback,
71
+ // S7:rollback-points(工作区 + 会话回滚,两态)—— 由 recovery prefix 路由内部按 path 分发
72
+ // (`segments[0] === 'rollback-points'` 下的 storage / <id>/{preview,rewind,delete}),
73
+ // 不新增宿主注册路由条目,因此 route-parity 的 79 条快照不变。
74
+ RECOVERY_API.rollbackPoints,
75
+ RECOVERY_API.rollbackPointsStorage,
70
76
  ]);
71
77
 
78
+ /**
79
+ * rollback-points 族的**端点 ↔ RecoveryApi 方法**一一对账(S7 / T2)。
80
+ *
81
+ * `<id>` 是占位符(不是可直接请求的路径),所以这些子路径不进 `RECOVERY_API`(也就不进
82
+ * `clientPaths()` 的宿主字面量对账);但「客户端声明了哪 7 条端点、实现了哪 7 个方法」必须
83
+ * 两边都齐 —— 少了 = 界面点了 404,多了 = 死代码或改错方向。
84
+ */
85
+ const ROLLBACK_POINT_ENDPOINTS: readonly string[] = [
86
+ RECOVERY_API.rollbackPoints,
87
+ RECOVERY_API.rollbackPointsStorage,
88
+ `${RECOVERY_API.rollbackPoints}/<id>`,
89
+ `${RECOVERY_API.rollbackPoints}/<id>/preview`,
90
+ `${RECOVERY_API.rollbackPoints}/<id>/file-diff`,
91
+ `${RECOVERY_API.rollbackPoints}/<id>/rewind`,
92
+ `${RECOVERY_API.rollbackPoints}/<id>/delete`,
93
+ ];
94
+
95
+ /** 客户端 api 文件(对账对象;与 http-usage-guard 的收敛范围同源)。 */
96
+ const RECOVERY_API_FILE = 'src/client/recovery/recovery-api.ts';
97
+
98
+ function readClientSource(rel: string): string {
99
+ return fs.readFileSync(path.join(ROOT, rel), 'utf8');
100
+ }
101
+
102
+ /** 源码里声明的异步方法名(`async name(`)。 */
103
+ function declaredAsyncMethods(source: string): string[] {
104
+ const out: string[] = [];
105
+ for (const match of source.matchAll(/^\s*async\s+([A-Za-z0-9_]+)\s*\(/gm)) out.push(match[1] as string);
106
+ return out;
107
+ }
108
+
72
109
  const ROUTE_LITERAL = /'(\/api\/dsh-config-manager[^']*)'/g;
73
110
 
74
111
  /** 从宿主源码提取路由字面量(行级;注释整行/同行注释后都不采信)。 */
@@ -174,6 +211,47 @@ test('parity-03 prefix 路由(recovery)确有宿主前缀字面量覆盖', (
174
211
  }
175
212
  });
176
213
 
214
+ test('parity-05 rollback-points 族:7 条端点与 RecoveryApi 方法一一对账(两个方向)', () => {
215
+ const entries = Object.values(ROLLBACK_POINT_ROUTES);
216
+ assert.equal(entries.length, 7, 'rollback-points 族必须恰好 7 条端点(6 条冻结契约 + 只读 file-diff)');
217
+ assert.deepEqual(
218
+ entries.map((entry) => entry.path).sort(),
219
+ [...ROLLBACK_POINT_ENDPOINTS].sort(),
220
+ '端点清单必须与冻结契约逐字一致(多一条=死代码,少一条=界面打不出去)',
221
+ );
222
+
223
+ // ① 每条端点都必须是 recovery prefix 下的子路径(\`<id>\` 占位符只能是 id 段,不能拼错层级)
224
+ const literals = hostRouteLiterals();
225
+ for (const entry of entries) {
226
+ assert.ok(
227
+ entry.path === RECOVERY_API.rollbackPoints
228
+ || entry.path === RECOVERY_API.rollbackPointsStorage
229
+ || entry.path.startsWith(RECOVERY_API.rollbackPoints + '/<id>'),
230
+ '端点 ' + entry.path + ' 不在 rollback-points 族下(拼错即 404)',
231
+ );
232
+ assert.ok(prefixCovered(entry.path, literals), '端点 ' + entry.path + ' 没有宿主 recovery 前缀字面量覆盖');
233
+ }
234
+
235
+ // ② 正向:每个方法都必须真的在 RecoveryApi 里实现(名字改了/删了就红)
236
+ const source = readClientSource(RECOVERY_API_FILE);
237
+ const declared = declaredAsyncMethods(source);
238
+ for (const entry of entries) {
239
+ assert.ok(
240
+ declared.includes(entry.method),
241
+ 'RecoveryApi 里没有 ' + entry.method + '() —— 端点 ' + entry.path + ' 缺少实现',
242
+ );
243
+ }
244
+
245
+ // ③ 反向:rollback-points 族的方法不得游离在表外(死代码 / 改错名)
246
+ const tableMethods = new Set(entries.map((entry) => entry.method));
247
+ const familyMethods = declared.filter((name) => /^(rollbackPoint|restoreRollbackPoint|removeRollbackPoint)/.test(name));
248
+ assert.deepEqual(
249
+ [...familyMethods].sort(),
250
+ [...tableMethods].sort(),
251
+ 'rollback-points 族方法与 \`ROLLBACK_POINT_ROUTES\` 必须一一对应(旧 checkpoint* 方法不在本族内)',
252
+ );
253
+ });
254
+
177
255
  test('parity-04 未精确命中的客户端路由必须已在 PREFIX_ROUTED 显式登记', () => {
178
256
  const literals = hostRouteLiterals();
179
257
  const unregistered = clientPaths().filter((p) => !literals.has(p) && !PREFIX_ROUTED.has(p));