dsh-config-manager 0.1.71 → 0.1.73

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 (596) hide show
  1. package/README.md +149 -98
  2. package/README.zh-CN.md +245 -88
  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 +2 -1
  6. package/lib/adapters/index.js +6 -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 +21 -0
  10. package/lib/adapters/messages.js +57 -0
  11. package/lib/adapters/plugin-data.d.ts +114 -0
  12. package/lib/adapters/plugin-data.js +309 -0
  13. package/lib/adapters/plugin-files.js +3 -2
  14. package/lib/adapters/plugins.js +4 -3
  15. package/lib/adapters/prompts.d.ts +1 -0
  16. package/lib/adapters/prompts.js +7 -1
  17. package/lib/adapters/self.d.ts +8 -2
  18. package/lib/adapters/self.js +3 -2
  19. package/lib/adapters/sessions.d.ts +1 -0
  20. package/lib/adapters/sessions.js +4 -2
  21. package/lib/adapters/test-helpers.d.ts +9 -0
  22. package/lib/adapters/test-helpers.js +14 -0
  23. package/lib/adapters/ui.d.ts +2 -2
  24. package/lib/adapters/ui.js +25 -25
  25. package/lib/adapters/workspaces.d.ts +1 -0
  26. package/lib/adapters/workspaces.js +7 -3
  27. package/lib/cli/actions.d.ts +9 -1
  28. package/lib/cli/actions.js +5 -2
  29. package/lib/cli/help.js +23 -10
  30. package/lib/cli/index.d.ts +23 -0
  31. package/lib/cli/index.js +90 -32
  32. package/lib/cli/sessions-inspect.js +3 -1
  33. package/lib/cli/sessions-repair.js +25 -6
  34. package/lib/cli/web/page.d.ts +12 -0
  35. package/lib/cli/web/page.js +21 -12
  36. package/lib/cli/web/routes.js +2 -2
  37. package/lib/cli/web/server.js +4 -4
  38. package/lib/client.d.ts +1442 -494
  39. package/lib/client.js +15326 -8520
  40. package/lib/core/analyzer.d.ts +11 -2
  41. package/lib/core/analyzer.js +31 -8
  42. package/lib/core/backup-plan.d.ts +10 -0
  43. package/lib/core/backup-plan.js +51 -20
  44. package/lib/core/backup-verify.js +24 -23
  45. package/lib/core/backup.d.ts +25 -1
  46. package/lib/core/backup.js +99 -28
  47. package/lib/core/boot-rescue.js +4 -3
  48. package/lib/core/boot-safety.js +2 -1
  49. package/lib/core/command-rollback.d.ts +182 -0
  50. package/lib/core/command-rollback.js +293 -0
  51. package/lib/core/consult-source.js +7 -6
  52. package/lib/core/disk-usage.d.ts +74 -10
  53. package/lib/core/disk-usage.js +67 -4
  54. package/lib/core/engine-locale.d.ts +87 -0
  55. package/lib/core/engine-locale.js +125 -0
  56. package/lib/core/exporter.js +3 -3
  57. package/lib/core/fork-reconcile.d.ts +128 -0
  58. package/lib/core/fork-reconcile.js +150 -0
  59. package/lib/core/journal.js +6 -5
  60. package/lib/core/local-plugin-host.js +3 -2
  61. package/lib/core/local-plugin-pack.js +11 -6
  62. package/lib/core/messages-core.d.ts +294 -0
  63. package/lib/core/messages-core.js +619 -0
  64. package/lib/core/messages.d.ts +61 -2
  65. package/lib/core/messages.js +118 -13
  66. package/lib/core/migration-consult.js +21 -20
  67. package/lib/core/migration-history.js +19 -5
  68. package/lib/core/model-tools.d.ts +0 -22
  69. package/lib/core/model-tools.js +50 -27
  70. package/lib/core/phase3-host.js +30 -5
  71. package/lib/core/plugin-cli.js +18 -19
  72. package/lib/core/plugin-data-registry.d.ts +124 -0
  73. package/lib/core/plugin-data-registry.js +198 -0
  74. package/lib/core/reconcile.d.ts +2 -14
  75. package/lib/core/reconcile.js +87 -17
  76. package/lib/core/recovery-orchestrator.js +19 -18
  77. package/lib/core/reinstall.d.ts +4 -0
  78. package/lib/core/reinstall.js +26 -21
  79. package/lib/core/restore.js +20 -17
  80. package/lib/core/rollback-gate.d.ts +33 -0
  81. package/lib/core/rollback-gate.js +57 -0
  82. package/lib/core/rollback-ignore-rules.d.ts +31 -0
  83. package/lib/core/rollback-ignore-rules.js +140 -0
  84. package/lib/core/rollback-ledger.d.ts +248 -0
  85. package/lib/core/rollback-ledger.js +1282 -0
  86. package/lib/core/rollback-point.d.ts +1391 -0
  87. package/lib/core/rollback-point.js +3503 -0
  88. package/lib/core/rollback.js +6 -4
  89. package/lib/core/run-registry.js +6 -5
  90. package/lib/core/session-health.d.ts +15 -0
  91. package/lib/core/session-meta.d.ts +42 -2
  92. package/lib/core/session-meta.js +140 -1
  93. package/lib/core/session-title.d.ts +28 -0
  94. package/lib/core/session-title.js +185 -0
  95. package/lib/core/startup-barrier.d.ts +24 -0
  96. package/lib/core/startup-barrier.js +22 -0
  97. package/lib/core/transaction-coordinator.js +4 -3
  98. package/lib/core/types.d.ts +37 -1
  99. package/lib/core/validator.js +5 -4
  100. package/lib/core/verify-recovery.js +31 -30
  101. package/lib/foreign/bundle.js +3 -2
  102. package/lib/foreign/continue.js +2 -1
  103. package/lib/foreign/crush.js +2 -1
  104. package/lib/foreign/gemini.js +2 -1
  105. package/lib/foreign/goose.js +2 -1
  106. package/lib/foreign/kilocode.js +2 -1
  107. package/lib/foreign/kimi.js +2 -1
  108. package/lib/foreign/messages.d.ts +15 -0
  109. package/lib/foreign/messages.js +40 -0
  110. package/lib/foreign/mimocode.js +2 -1
  111. package/lib/foreign/openclaw.js +2 -1
  112. package/lib/foreign/opencode.js +2 -1
  113. package/lib/foreign/pi.js +2 -1
  114. package/lib/foreign/qoder.js +2 -1
  115. package/lib/foreign/qwen.js +2 -1
  116. package/lib/foreign/registry.d.ts +1 -1
  117. package/lib/foreign/registry.js +8 -7
  118. package/lib/foreign/teleagent.js +2 -1
  119. package/lib/foreign/workbuddy.js +2 -1
  120. package/lib/foreign/zcode.js +2 -1
  121. package/lib/foreign/zed.js +2 -1
  122. package/lib/index.d.ts +227 -7
  123. package/lib/index.js +799 -54
  124. package/lib/market/git-file-writer.js +20 -19
  125. package/lib/market/github-repos.js +35 -19
  126. package/lib/market/index-parser.js +31 -30
  127. package/lib/market/messages-market.d.ts +138 -0
  128. package/lib/market/messages-market.js +305 -0
  129. package/lib/market/my-repo.d.ts +2 -2
  130. package/lib/market/my-repo.js +32 -29
  131. package/lib/market/prepare.js +18 -15
  132. package/lib/market/security.js +20 -19
  133. package/lib/market/types.js +2 -1
  134. package/lib/market/url.js +2 -1
  135. package/lib/migrations/index.js +6 -5
  136. package/lib/migrations/messages-migrations.d.ts +32 -0
  137. package/lib/migrations/messages-migrations.js +58 -0
  138. package/lib/profiles/dsh-profile-launcher.js +2 -1
  139. package/lib/profiles/dsh-profile-runtime.js +2 -1
  140. package/lib/profiles/messages.d.ts +7 -0
  141. package/lib/profiles/messages.js +27 -0
  142. package/lib/routes/backup.js +18 -2
  143. package/lib/routes/import.js +3 -3
  144. package/lib/routes/index.d.ts +1 -1
  145. package/lib/routes/kit.d.ts +3 -2
  146. package/lib/routes/kit.js +6 -1
  147. package/lib/routes/market.js +1 -1
  148. package/lib/routes/messages.d.ts +43 -0
  149. package/lib/routes/messages.js +102 -0
  150. package/lib/routes/prefs.js +26 -3
  151. package/lib/routes/profiles.js +12 -6
  152. package/lib/routes/recovery.d.ts +21 -0
  153. package/lib/routes/recovery.js +478 -94
  154. package/lib/routes/snapshots.js +51 -4
  155. package/lib/routes/sync.js +79 -17
  156. package/lib/schema/config.d.ts +11 -1
  157. package/lib/schema/config.js +52 -20
  158. package/lib/schema/index.d.ts +8 -6
  159. package/lib/schema/index.js +8 -6
  160. package/lib/schema/manifest.js +19 -18
  161. package/lib/schema/messages.d.ts +47 -0
  162. package/lib/schema/messages.js +110 -0
  163. package/lib/schema/section-registry.d.ts +42 -15
  164. package/lib/schema/section-registry.js +67 -32
  165. package/lib/schema/types.d.ts +1 -1
  166. package/lib/schema/versions.js +6 -5
  167. package/lib/security/encryption.js +16 -15
  168. package/lib/security/integrity.js +8 -7
  169. package/lib/security/messages.d.ts +30 -0
  170. package/lib/security/messages.js +77 -0
  171. package/lib/security/redaction.js +12 -0
  172. package/lib/security/secret-scanner.js +16 -13
  173. package/lib/security/vault.js +2 -1
  174. package/lib/security/zip-security.js +4 -3
  175. package/lib/sync/ancestor.js +2 -1
  176. package/lib/sync/autosync-config.d.ts +11 -0
  177. package/lib/sync/autosync-config.js +12 -0
  178. package/lib/sync/autosync-scheduler.d.ts +11 -1
  179. package/lib/sync/autosync-scheduler.js +115 -76
  180. package/lib/sync/backup-files.js +3 -2
  181. package/lib/sync/backup-scheduler.js +6 -5
  182. package/lib/sync/blob-store.js +2 -1
  183. package/lib/sync/git/git-transport.d.ts +55 -0
  184. package/lib/sync/git/git-transport.js +152 -2
  185. package/lib/sync/github-auth.js +10 -9
  186. package/lib/sync/layout.js +13 -12
  187. package/lib/sync/messages.d.ts +63 -0
  188. package/lib/sync/messages.js +149 -0
  189. package/lib/sync/review-queue.js +6 -5
  190. package/lib/sync/s3/messages.d.ts +7 -0
  191. package/lib/sync/s3/messages.js +16 -0
  192. package/lib/sync/s3/s3-providers.d.ts +4 -1
  193. package/lib/sync/s3/s3-providers.js +5 -5
  194. package/lib/sync/s3/s3-transport.js +1 -1
  195. package/lib/sync/s3/sigv4.js +2 -1
  196. package/lib/sync/snapshot-crypto.js +6 -5
  197. package/lib/sync/snapshot-json.js +3 -2
  198. package/lib/sync/sync-engine.d.ts +58 -22
  199. package/lib/sync/sync-engine.js +119 -48
  200. package/lib/sync/sync-history.js +7 -6
  201. package/lib/sync/sync-selection.d.ts +15 -4
  202. package/lib/sync/sync-selection.js +17 -5
  203. package/lib/sync/ui-prefs.d.ts +22 -0
  204. package/lib/sync/ui-prefs.js +28 -0
  205. package/lib/sync/webdav/webdav-transport.d.ts +1 -0
  206. package/lib/sync/webdav/webdav-transport.js +4 -1
  207. package/lib/ui/artifact-view.js +4 -3
  208. package/lib/ui/commands.d.ts +1 -1
  209. package/lib/ui/commands.js +10 -0
  210. package/lib/ui/diff-view.d.ts +10 -0
  211. package/lib/ui/diff-view.js +17 -0
  212. package/lib/ui/disk-usage-view.d.ts +33 -4
  213. package/lib/ui/disk-usage-view.js +29 -1
  214. package/lib/ui/errors.d.ts +1 -1
  215. package/lib/ui/errors.js +4 -3
  216. package/lib/ui/export-flow.js +26 -20
  217. package/lib/ui/foreign-view.d.ts +5 -1
  218. package/lib/ui/foreign-view.js +26 -2
  219. package/lib/ui/i18n.d.ts +68 -1
  220. package/lib/ui/i18n.js +120 -2
  221. package/lib/ui/import-wizard.d.ts +7 -0
  222. package/lib/ui/import-wizard.js +13 -10
  223. package/lib/ui/progress.d.ts +2 -2
  224. package/lib/ui/progress.js +1 -2
  225. package/lib/ui/report.d.ts +5 -5
  226. package/lib/ui/report.js +6 -6
  227. package/lib/ui/rollback-view.d.ts +815 -0
  228. package/lib/ui/rollback-view.js +738 -0
  229. package/lib/ui/selection-model.d.ts +41 -0
  230. package/lib/ui/selection-model.js +30 -0
  231. package/lib/ui/session-inventory-view.d.ts +97 -3
  232. package/lib/ui/session-inventory-view.js +76 -2
  233. package/lib/ui/session-issue-labels.d.ts +43 -0
  234. package/lib/ui/session-issue-labels.js +48 -0
  235. package/lib/ui/sync-settings-view.d.ts +33 -0
  236. package/lib/ui/sync-settings-view.js +40 -0
  237. package/lib/ui/test-helpers.js +2 -1
  238. package/lib/ui/types.d.ts +8 -3
  239. package/lib/ui/types.js +1 -1
  240. package/lib/utils/atomic-write.js +6 -5
  241. package/lib/utils/bundle-scan.d.ts +11 -1
  242. package/lib/utils/bundle-scan.js +12 -1
  243. package/lib/utils/env-lock.d.ts +10 -0
  244. package/lib/utils/env-lock.js +73 -46
  245. package/lib/utils/json.js +6 -5
  246. package/lib/utils/messages-utils.d.ts +96 -0
  247. package/lib/utils/messages-utils.js +216 -0
  248. package/lib/utils/proxy.js +18 -5
  249. package/lib/utils/recursive-walk.d.ts +22 -0
  250. package/lib/utils/recursive-walk.js +75 -1
  251. package/lib/utils/session-log-repair.js +10 -9
  252. package/lib/utils/zip.js +27 -26
  253. package/package.json +12 -2
  254. package/src/adapters/agent-instructions.ts +1 -1
  255. package/src/adapters/file-collection.test.ts +1 -0
  256. package/src/adapters/file-collection.ts +1 -1
  257. package/src/adapters/files.test.ts +27 -0
  258. package/src/adapters/index.ts +6 -1
  259. package/src/adapters/link-report.test.ts +22 -2
  260. package/src/adapters/link-report.ts +29 -4
  261. package/src/adapters/messages.ts +61 -0
  262. package/src/adapters/plugin-data.test.ts +225 -0
  263. package/src/adapters/plugin-data.ts +351 -0
  264. package/src/adapters/plugin-files.ts +3 -2
  265. package/src/adapters/plugins.ts +4 -3
  266. package/src/adapters/prompts.ts +7 -1
  267. package/src/adapters/self.test.ts +26 -0
  268. package/src/adapters/self.ts +10 -3
  269. package/src/adapters/sessions.ts +4 -2
  270. package/src/adapters/settings.test.ts +38 -1
  271. package/src/adapters/test-helpers.ts +15 -0
  272. package/src/adapters/ui.test.ts +2 -2
  273. package/src/adapters/ui.ts +27 -26
  274. package/src/adapters/workspaces.ts +7 -3
  275. package/src/cli/actions.test.ts +28 -0
  276. package/src/cli/actions.ts +11 -2
  277. package/src/cli/help.ts +23 -10
  278. package/src/cli/index-guards.test.ts +193 -0
  279. package/src/cli/index.ts +106 -31
  280. package/src/cli/sessions-inspect.test.ts +15 -1
  281. package/src/cli/sessions-inspect.ts +3 -1
  282. package/src/cli/sessions-repair.test.ts +84 -0
  283. package/src/cli/sessions-repair.ts +33 -7
  284. package/src/cli/web/console-route-parity.test.ts +115 -0
  285. package/src/cli/web/page.ts +23 -12
  286. package/src/cli/web/routes.ts +2 -1
  287. package/src/cli/web/server.ts +4 -4
  288. package/src/cli/web/web.test.ts +2 -2
  289. package/src/client/ConfigManagerSection.tsx +30 -2
  290. package/src/client/about/AboutPanel.tsx +2 -2
  291. package/src/client/about/PluginUpdateDialog.tsx +1 -1
  292. package/src/client/about/ReleaseNotesDialog.tsx +2 -2
  293. package/src/client/about/about-view.test.ts +13 -3
  294. package/src/client/about/about-view.ts +10 -6
  295. package/src/client/about/release-notes-view.test.ts +14 -4
  296. package/src/client/about/release-notes-view.ts +4 -2
  297. package/src/client/api.ts +18 -6
  298. package/src/client/chat/ChatEntryView.tsx +411 -0
  299. package/src/client/chat/RollbackDiffTab.tsx +108 -0
  300. package/src/client/chat/RollbackFileIcon.tsx +96 -0
  301. package/src/client/chat/RollbackReviewDialog.tsx +607 -0
  302. package/src/client/chat/chat-entry-view.test.ts +1174 -0
  303. package/src/client/chat/chat-entry-view.ts +758 -0
  304. package/src/client/chat/chat-locales.ts +152 -0
  305. package/src/client/chat/chat-node-registration.ts +64 -0
  306. package/src/client/chat/chat-point-window.ts +183 -0
  307. package/src/client/chat/chat-slots.ts +107 -0
  308. package/src/client/chat/rollback-diff-tab.test.ts +105 -0
  309. package/src/client/chat/rollback-diff-tab.ts +174 -0
  310. package/src/client/chat/rollback-entry-view.test.ts +1469 -0
  311. package/src/client/chat/rollback-entry-view.ts +946 -0
  312. package/src/client/chat/sidebar-slots.ts +59 -0
  313. package/src/client/common/ConfirmDialog.tsx +7 -3
  314. package/src/client/common/ContentPicker.tsx +48 -10
  315. package/src/client/common/DiffTable.tsx +133 -0
  316. package/src/client/common/ErrorBanner.tsx +4 -4
  317. package/src/client/common/Icon.tsx +2 -0
  318. package/src/client/common/Modal.tsx +8 -1
  319. package/src/client/common/ProgressBar.tsx +8 -2
  320. package/src/client/common/ReportView.tsx +4 -4
  321. package/src/client/common/RunsCenter.tsx +1 -0
  322. package/src/client/common/client-route-parity.test.ts +84 -0
  323. package/src/client/common/info-hint-guard.test.ts +46 -13
  324. package/src/client/common/picker-lazy-units.test.ts +42 -0
  325. package/src/client/common/plan-text-redaction.test.ts +17 -6
  326. package/src/client/common/progress-view-edge.test.ts +8 -7
  327. package/src/client/common/progress-view.test.ts +9 -9
  328. package/src/client/common/progress-view.ts +2 -2
  329. package/src/client/common/routes.ts +38 -0
  330. package/src/client/common/section-labels.test.ts +1 -0
  331. package/src/client/common/section-labels.ts +2 -0
  332. package/src/client/common/ui.tsx +73 -10
  333. package/src/client/config-manager.module.css +1109 -31
  334. package/src/client/css-scale-guard.test.ts +30 -0
  335. package/src/client/environment/EnvironmentPanel.tsx +11 -4
  336. package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
  337. package/src/client/export/ExportView.tsx +13 -8
  338. package/src/client/history/HistoryPanel.tsx +4 -3
  339. package/src/client/history/history-api.ts +2 -2
  340. package/src/client/history/history-locales.ts +5 -1
  341. package/src/client/home/HomePanel.tsx +29 -8
  342. package/src/client/import/ForeignImportView.tsx +4 -5
  343. package/src/client/import/ImportWizardView.tsx +5 -3
  344. package/src/client/import/import-wizard-split.test.ts +3 -1
  345. package/src/client/import/import-wizard-steps.tsx +9 -9
  346. package/src/client/index.ts +232 -3
  347. package/src/client/library/LibraryActions.tsx +32 -7
  348. package/src/client/library/LibraryPanel.tsx +23 -3
  349. package/src/client/library/RestorePlanView.tsx +9 -66
  350. package/src/client/locales.ts +62 -9
  351. package/src/client/lucide-icons.d.ts +1 -0
  352. package/src/client/market/MarketPanel.tsx +13 -2
  353. package/src/client/market/MyConfigsView.tsx +5 -4
  354. package/src/client/market/MyConfigsWizard.tsx +10 -8
  355. package/src/client/market/market-api.test.ts +9 -8
  356. package/src/client/market/market-api.ts +2 -2
  357. package/src/client/market/market-locales.ts +3 -0
  358. package/src/client/market/market-view.test.ts +6 -6
  359. package/src/client/market/market-view.ts +3 -3
  360. package/src/client/market/my-configs-api.ts +2 -2
  361. package/src/client/market/my-configs-view.test.ts +23 -22
  362. package/src/client/market/my-configs-view.ts +8 -8
  363. package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
  364. package/src/client/recovery/RecoveryPanel.tsx +442 -283
  365. package/src/client/recovery/RollbackCard.tsx +859 -0
  366. package/src/client/recovery/RollbackDiffPane.tsx +69 -0
  367. package/src/client/recovery/RollbackFileChanges.tsx +163 -0
  368. package/src/client/recovery/incident-api.ts +2 -2
  369. package/src/client/recovery/recovery-api.ts +144 -6
  370. package/src/client/recovery/recovery-locales.test.ts +299 -0
  371. package/src/client/recovery/recovery-locales.ts +520 -149
  372. package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
  373. package/src/client/recovery/rollback-enabled.ts +41 -0
  374. package/src/client/recovery/rollback-file-changes.test.ts +114 -0
  375. package/src/client/recovery/session-dialog-mount.test.ts +54 -0
  376. package/src/client/run-store.ts +84 -21
  377. package/src/client/sync/AutosyncCard.tsx +25 -1
  378. package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
  379. package/src/client/sync/ChannelConfigDialog.tsx +10 -10
  380. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  381. package/src/client/sync/SecurityOptionsCard.tsx +27 -17
  382. package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
  383. package/src/client/sync/SyncConfirmView.tsx +46 -8
  384. package/src/client/sync/SyncLogList.tsx +1 -1
  385. package/src/client/sync/SyncPanel.tsx +31 -23
  386. package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
  387. package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
  388. package/src/client/sync/SyncSettingsView.test.ts +20 -1
  389. package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
  390. package/src/client/sync/WorkDirCard.tsx +112 -0
  391. package/src/client/sync/history-model.test.ts +42 -9
  392. package/src/client/sync/history-model.ts +30 -15
  393. package/src/client/sync/sync-api.test.ts +57 -33
  394. package/src/client/sync/sync-api.ts +80 -6
  395. package/src/client/sync/sync-locales.ts +104 -3
  396. package/src/client/sync/sync-view-v2.test.ts +65 -13
  397. package/src/client/sync/sync-view.test.ts +107 -52
  398. package/src/client/sync/sync-view.ts +124 -41
  399. package/src/core/analyzer.ts +31 -9
  400. package/src/core/backup-plan.test.ts +48 -5
  401. package/src/core/backup-plan.ts +51 -20
  402. package/src/core/backup-verify.ts +24 -23
  403. package/src/core/backup.test.ts +76 -0
  404. package/src/core/backup.ts +105 -29
  405. package/src/core/boot-rescue.ts +4 -3
  406. package/src/core/boot-safety.ts +2 -1
  407. package/src/core/command-rollback.test.ts +536 -0
  408. package/src/core/command-rollback.ts +406 -0
  409. package/src/core/consult-source.ts +7 -6
  410. package/src/core/disk-usage.test.ts +257 -1
  411. package/src/core/disk-usage.ts +134 -14
  412. package/src/core/engine-locale.test.ts +593 -0
  413. package/src/core/engine-locale.ts +175 -0
  414. package/src/core/exporter.ts +3 -3
  415. package/src/core/fork-reconcile.test.ts +279 -0
  416. package/src/core/fork-reconcile.ts +234 -0
  417. package/src/core/journal.ts +6 -5
  418. package/src/core/local-plugin-host.ts +3 -2
  419. package/src/core/local-plugin-pack.ts +11 -6
  420. package/src/core/messages-core.ts +661 -0
  421. package/src/core/messages.ts +125 -17
  422. package/src/core/migration-consult.ts +21 -20
  423. package/src/core/migration-history.ts +19 -5
  424. package/src/core/model-tools.ts +28 -27
  425. package/src/core/phase3-host.ts +21 -5
  426. package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
  427. package/src/core/plugin-cli.ts +18 -19
  428. package/src/core/plugin-data-registry.test.ts +113 -0
  429. package/src/core/plugin-data-registry.ts +261 -0
  430. package/src/core/reconcile.test.ts +117 -0
  431. package/src/core/reconcile.ts +85 -17
  432. package/src/core/recovery-orchestrator.ts +19 -18
  433. package/src/core/reinstall.ts +26 -21
  434. package/src/core/restore.ts +20 -17
  435. package/src/core/rollback-file-diff.test.ts +159 -0
  436. package/src/core/rollback-gate.test.ts +66 -0
  437. package/src/core/rollback-gate.ts +61 -0
  438. package/src/core/rollback-ignore-rules.ts +126 -0
  439. package/src/core/rollback-ledger.test.ts +655 -0
  440. package/src/core/rollback-ledger.ts +1314 -0
  441. package/src/core/rollback-point.test.ts +2853 -0
  442. package/src/core/rollback-point.ts +4340 -0
  443. package/src/core/rollback.ts +5 -3
  444. package/src/core/run-registry.ts +6 -5
  445. package/src/core/session-health.ts +15 -0
  446. package/src/core/session-meta.test.ts +117 -1
  447. package/src/core/session-meta.ts +149 -2
  448. package/src/core/session-title.test.ts +127 -0
  449. package/src/core/session-title.ts +189 -0
  450. package/src/core/startup-barrier.test.ts +69 -0
  451. package/src/core/startup-barrier.ts +27 -0
  452. package/src/core/transaction-coordinator.ts +4 -3
  453. package/src/core/types.ts +37 -1
  454. package/src/core/validator.ts +5 -4
  455. package/src/core/verify-recovery.ts +31 -30
  456. package/src/foreign/bundle.ts +3 -2
  457. package/src/foreign/continue.ts +2 -1
  458. package/src/foreign/crush.ts +2 -1
  459. package/src/foreign/file-budget.test.ts +2 -0
  460. package/src/foreign/gemini.ts +2 -1
  461. package/src/foreign/goose.ts +2 -1
  462. package/src/foreign/kilocode.ts +2 -1
  463. package/src/foreign/kimi.ts +2 -1
  464. package/src/foreign/messages.ts +44 -0
  465. package/src/foreign/mimocode.ts +2 -1
  466. package/src/foreign/openclaw.ts +2 -1
  467. package/src/foreign/opencode.ts +2 -1
  468. package/src/foreign/pi.ts +2 -1
  469. package/src/foreign/qoder.ts +2 -1
  470. package/src/foreign/qwen.ts +2 -1
  471. package/src/foreign/registry.test.ts +1 -1
  472. package/src/foreign/registry.ts +8 -7
  473. package/src/foreign/teleagent.ts +2 -1
  474. package/src/foreign/workbuddy.ts +2 -1
  475. package/src/foreign/zcode.ts +2 -1
  476. package/src/foreign/zed.ts +2 -1
  477. package/src/index.facade.test.ts +340 -1
  478. package/src/index.ts +876 -57
  479. package/src/market/git-file-writer.ts +20 -19
  480. package/src/market/github-repos.test.ts +23 -0
  481. package/src/market/github-repos.ts +37 -19
  482. package/src/market/index-parser.ts +31 -30
  483. package/src/market/messages-market.ts +325 -0
  484. package/src/market/my-repo.ts +32 -29
  485. package/src/market/prepare.ts +18 -15
  486. package/src/market/security.ts +20 -19
  487. package/src/market/types.ts +2 -1
  488. package/src/market/url.ts +2 -1
  489. package/src/migrations/index.ts +6 -5
  490. package/src/migrations/messages-migrations.ts +59 -0
  491. package/src/profiles/dsh-profile-launcher.ts +2 -1
  492. package/src/profiles/dsh-profile-runtime.ts +2 -1
  493. package/src/profiles/messages.ts +31 -0
  494. package/src/routes/backup.ts +18 -2
  495. package/src/routes/import.ts +3 -3
  496. package/src/routes/index.ts +1 -1
  497. package/src/routes/kit.ts +9 -3
  498. package/src/routes/market.ts +1 -1
  499. package/src/routes/messages.ts +106 -0
  500. package/src/routes/prefs.ts +31 -2
  501. package/src/routes/profiles.ts +12 -6
  502. package/src/routes/recovery.test.ts +405 -1
  503. package/src/routes/recovery.ts +477 -94
  504. package/src/routes/snapshots.ts +51 -4
  505. package/src/routes/sync.ts +82 -17
  506. package/src/schema/config.ts +53 -20
  507. package/src/schema/index.ts +7 -5
  508. package/src/schema/manifest-schema.test.ts +4 -4
  509. package/src/schema/manifest.ts +19 -18
  510. package/src/schema/messages.ts +120 -0
  511. package/src/schema/registry.test.ts +28 -7
  512. package/src/schema/section-registry.ts +96 -42
  513. package/src/schema/types.ts +2 -1
  514. package/src/schema/versions.ts +6 -5
  515. package/src/security/encryption.ts +16 -15
  516. package/src/security/integrity.ts +8 -7
  517. package/src/security/messages.ts +87 -0
  518. package/src/security/redaction.test.ts +17 -1
  519. package/src/security/redaction.ts +13 -0
  520. package/src/security/secret-scanner.ts +19 -12
  521. package/src/security/security.test.ts +47 -0
  522. package/src/security/vault.ts +2 -1
  523. package/src/security/zip-security.ts +4 -3
  524. package/src/sync/ancestor.ts +2 -1
  525. package/src/sync/autosync-config.ts +21 -0
  526. package/src/sync/autosync-scheduler.test.ts +63 -7
  527. package/src/sync/autosync-scheduler.ts +118 -78
  528. package/src/sync/backup-files.ts +3 -2
  529. package/src/sync/backup-scheduler.ts +6 -5
  530. package/src/sync/blob-store.ts +2 -1
  531. package/src/sync/git/git-transport.test.ts +170 -8
  532. package/src/sync/git/git-transport.ts +176 -2
  533. package/src/sync/github-auth.ts +10 -9
  534. package/src/sync/layout.ts +13 -12
  535. package/src/sync/messages.ts +153 -0
  536. package/src/sync/review-queue.ts +6 -5
  537. package/src/sync/s3/messages.ts +16 -0
  538. package/src/sync/s3/s3-providers.ts +9 -6
  539. package/src/sync/s3/s3-transport.ts +1 -1
  540. package/src/sync/s3/sigv4.ts +3 -1
  541. package/src/sync/snapshot-crypto.ts +6 -5
  542. package/src/sync/snapshot-json.ts +3 -2
  543. package/src/sync/sync-credentials.test.ts +119 -0
  544. package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
  545. package/src/sync/sync-engine.test.ts +226 -8
  546. package/src/sync/sync-engine.ts +154 -52
  547. package/src/sync/sync-history.ts +7 -6
  548. package/src/sync/sync-selection.test.ts +12 -2
  549. package/src/sync/sync-selection.ts +18 -5
  550. package/src/sync/transport.test.ts +1 -0
  551. package/src/sync/ui-prefs.test.ts +44 -1
  552. package/src/sync/ui-prefs.ts +39 -0
  553. package/src/sync/webdav/webdav-transport.ts +4 -1
  554. package/src/ui/artifact-view.ts +4 -3
  555. package/src/ui/commands.test.ts +1 -0
  556. package/src/ui/commands.ts +11 -1
  557. package/src/ui/diff-view.test.ts +9 -1
  558. package/src/ui/diff-view.ts +22 -0
  559. package/src/ui/disk-usage-view.test.ts +113 -6
  560. package/src/ui/disk-usage-view.ts +63 -7
  561. package/src/ui/errors.ts +4 -3
  562. package/src/ui/export-flow.ts +26 -20
  563. package/src/ui/foreign-view.test.ts +43 -0
  564. package/src/ui/foreign-view.ts +30 -3
  565. package/src/ui/i18n.ts +131 -2
  566. package/src/ui/import-wizard.ts +18 -10
  567. package/src/ui/progress.ts +2 -2
  568. package/src/ui/report.ts +7 -6
  569. package/src/ui/rollback-view.test.ts +836 -0
  570. package/src/ui/rollback-view.ts +1405 -0
  571. package/src/ui/selection-model.test.ts +33 -1
  572. package/src/ui/selection-model.ts +53 -0
  573. package/src/ui/session-inventory-view.test.ts +109 -0
  574. package/src/ui/session-inventory-view.ts +138 -3
  575. package/src/ui/session-issue-labels.test.ts +51 -0
  576. package/src/ui/session-issue-labels.ts +70 -0
  577. package/src/ui/sync-settings-view.test.ts +36 -0
  578. package/src/ui/sync-settings-view.ts +49 -0
  579. package/src/ui/test-helpers.ts +2 -1
  580. package/src/ui/types.ts +10 -4
  581. package/src/utils/atomic-write.ts +6 -5
  582. package/src/utils/bundle-scan.test.ts +2 -2
  583. package/src/utils/bundle-scan.ts +12 -1
  584. package/src/utils/env-lock.ts +83 -49
  585. package/src/utils/json.ts +7 -5
  586. package/src/utils/messages-utils.ts +232 -0
  587. package/src/utils/proxy.test.ts +25 -0
  588. package/src/utils/proxy.ts +19 -5
  589. package/src/utils/recursive-walk.test.ts +43 -0
  590. package/src/utils/recursive-walk.ts +75 -1
  591. package/src/utils/session-log-repair.ts +10 -9
  592. package/src/utils/zip.ts +27 -26
  593. package/lib/core/checkpoint.d.ts +0 -362
  594. package/lib/core/checkpoint.js +0 -1309
  595. package/src/core/checkpoint.test.ts +0 -665
  596. package/src/core/checkpoint.ts +0 -1614
@@ -19,7 +19,7 @@ import { Banner } from '../common/ui.tsx'
19
19
  import { toast } from '../common/toast-store.ts'
20
20
  import { redact } from '../../security/redaction.ts'
21
21
  import type { SyncApi } from './sync-api.ts'
22
- import type { SyncChannel } from './sync-view.ts'
22
+ import { SYNC_CHANNEL_LABEL_KEY, type SyncChannel } from './sync-view.ts'
23
23
 
24
24
  export function ChannelClearConfirmDialog({ open, channel, api, t, onCancel, onCleared }: {
25
25
  open: boolean
@@ -30,7 +30,10 @@ export function ChannelClearConfirmDialog({ open, channel, api, t, onCancel, onC
30
30
  /** 成功回调:调用方据此清表单残留、撤销在途自动保存并重拉 status(本组件不碰页面状态) */
31
31
  onCleared: () => void
32
32
  }) {
33
- const channelLabel = channel === 'webdav' ? t('channel.webdav') : t('channel.git')
33
+ // 四个通道一律走穷尽映射(SYNC_CHANNEL_LABEL_KEY: Record<SyncChannel, SyncKey>):
34
+ // 此前写成「webdav ? webdav : git」,在 s3/gist 卡上点「断开配置」时,危险确认框与成功回执
35
+ // 都会把该通道说成「Git 私有仓库」——用户会以为断错了通道而取消(或反之误判已断的通道)。
36
+ const channelLabel = t(SYNC_CHANNEL_LABEL_KEY[channel])
34
37
  return (
35
38
  <ConfirmDialog
36
39
  open={open}
@@ -45,7 +48,7 @@ export function ChannelClearConfirmDialog({ open, channel, api, t, onCancel, onC
45
48
  toast.ok(t('toast.channelCleared', { channel: channelLabel }))
46
49
  onCleared()
47
50
  } catch (err) {
48
- toast.error(`${t('toast.channelClearFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
51
+ toast.error(t('toast.channelClearFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
49
52
  }
50
53
  }}
51
54
  onCancel={onCancel}
@@ -18,6 +18,7 @@ import {
18
18
  cloudIssueKey, cloudSecretRefName, presetById, presetIdForUrl, privateRepoHint,
19
19
  S3_PROVIDER_LABEL_KEY, SYNC_CHANNEL_LABEL_KEY, WEBDAV_PRESETS,
20
20
  } from './sync-view.ts'
21
+ import { GIST_LARGE_SECTION_HINT_KEY } from './sync-view.ts'
21
22
  import type { GithubLoginView } from './sync-view.ts'
22
23
  import { S3_PROVIDERS } from '../../ui/sync-settings-view.ts'
23
24
  import type { CloudFormIssue, SyncFormSnapshot } from '../../ui/sync-settings-view.ts'
@@ -137,6 +138,7 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
137
138
  {t('config.token')}
138
139
  {' '}
139
140
  {statusInfo?.credentialConfigured === true && <Badge kind="ok">{t('config.tokenSaved')}</Badge>}
141
+ <InfoHint text={t('config.tokenHint', { ref: SYNC_CREDENTIAL_REF })} label={t('common.infoHint')} />
140
142
  </span>
141
143
  <input
142
144
  type="password"
@@ -149,7 +151,6 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
149
151
  onFormChange({ token: e.target.value })
150
152
  }}
151
153
  />
152
- <span className={css.hint}>{t('config.tokenHint', { ref: SYNC_CREDENTIAL_REF })}</span>
153
154
  </label>
154
155
 
155
156
  {/* GitHub OAuth 登录(device flow):弹窗内仅 git 通道显示;已登录(token 有效)时整块隐藏 */}
@@ -161,7 +162,7 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
161
162
  <span className={css.groupLabel}>{t('github.title')} <InfoHint text={t('github.description')} label={t('common.infoHint')} /></span>
162
163
  {githubView.showCode && (
163
164
  <div className={css.statRow}>
164
- <Badge kind="info">{t('github.userCode')}:<strong>{githubView.userCode}</strong></Badge>
165
+ <Badge kind="info">{t('github.userCode')}{t('common.labelSeparator')}<strong>{githubView.userCode}</strong></Badge>
165
166
  <a
166
167
  className={css.ghostButton}
167
168
  href={githubView.verificationUri}
@@ -192,9 +193,6 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
192
193
  {githubView.statusText}
193
194
  </Badge>
194
195
  </div>
195
- {githubView.phase === 'error' && (
196
- <span className={css.hint}>{t('config.tokenHint', { ref: SYNC_CREDENTIAL_REF })}</span>
197
- )}
198
196
  </>
199
197
  )}
200
198
  </>
@@ -215,7 +213,7 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
215
213
  const preset = presetById(next)
216
214
  onFormChange({ webdavUrl: preset.url })
217
215
  }}
218
- options={WEBDAV_PRESETS.map((preset) => ({ value: preset.id, label: preset.label }))}
216
+ options={WEBDAV_PRESETS.map((preset) => ({ value: preset.id, label: t(preset.labelKey) }))}
219
217
  />
220
218
  <InfoHint text={t('webdav.presetHint')} label={t('common.infoHint')} />
221
219
  </div>
@@ -257,6 +255,7 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
257
255
  {t('webdav.password')}
258
256
  {' '}
259
257
  {statusInfo?.webdav?.passwordConfigured === true && <Badge kind="ok">{t('webdav.passwordSaved')}</Badge>}
258
+ <InfoHint text={t('webdav.passwordHint', { ref: SYNC_WEBDAV_CREDENTIAL_REF })} label={t('common.infoHint')} />
260
259
  </span>
261
260
  <input
262
261
  type="password"
@@ -269,7 +268,6 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
269
268
  onFormChange({ webdavPassword: e.target.value })
270
269
  }}
271
270
  />
272
- <span className={css.hint}>{t('webdav.passwordHint', { ref: SYNC_WEBDAV_CREDENTIAL_REF })}</span>
273
271
  </label>
274
272
  </>
275
273
  )}
@@ -359,6 +357,7 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
359
357
  {t('cloud.secret')}
360
358
  {' '}
361
359
  {statusInfo?.s3?.secretStored === true && <Badge kind="ok">{t('cloud.secretSaved')}</Badge>}
360
+ <InfoHint text={t('cloud.secretHint', { ref: cloudSecretRef })} label={t('common.infoHint')} />
362
361
  </span>
363
362
  <input
364
363
  type="password"
@@ -369,7 +368,6 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
369
368
  disabled={busy}
370
369
  onChange={(e: ChangeEvent<HTMLInputElement>) => { onFormChange({ s3Secret: e.target.value }) }}
371
370
  />
372
- <span className={css.hint}>{t('cloud.secretHint', { ref: cloudSecretRef })}</span>
373
371
  </label>
374
372
  </>
375
373
  )}
@@ -377,7 +375,9 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
377
375
  {/* gist 通道分支(GitHub Gist:走 REST,token 只写 DSH 凭据槽位) */}
378
376
  {channel === 'gist' && (
379
377
  <>
380
- <span className={css.groupLabel}>{t('cloud.gistTitle')}</span>
378
+ {/* G-34:gist 刻意不外置 blob(一次 GET 全量 / 单文件超限截断 / 文件数上限)⇒ 大分区不适合走 gist;
379
+ 该说明属机制类,收进标题 ⓘ(2026-10-12 用户要求) */}
380
+ <span className={css.groupLabel}>{t('cloud.gistTitle')} <InfoHint text={t(GIST_LARGE_SECTION_HINT_KEY)} label={t('common.infoHint')} /></span>
381
381
  <label className={css.field}>
382
382
  <span className={css.fieldLabel}>{t('cloud.gistId')} <InfoHint text={t('cloud.gistIdHint')} label={t('common.infoHint')} /></span>
383
383
  <input
@@ -417,6 +417,7 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
417
417
  {t('cloud.token')}
418
418
  {' '}
419
419
  {statusInfo?.gist?.secretStored === true && <Badge kind="ok">{t('cloud.tokenSaved')}</Badge>}
420
+ <InfoHint text={t('cloud.tokenHint', { ref: cloudSecretRef })} label={t('common.infoHint')} />
420
421
  </span>
421
422
  <input
422
423
  type="password"
@@ -427,7 +428,6 @@ export function ChannelConfigDialog({ open, onClose, api, t, uiT, form, cloudIss
427
428
  disabled={busy}
428
429
  onChange={(e: ChangeEvent<HTMLInputElement>) => { onFormChange({ gistToken: e.target.value }) }}
429
430
  />
430
- <span className={css.hint}>{t('cloud.tokenHint', { ref: cloudSecretRef })}</span>
431
431
  </label>
432
432
  </>
433
433
  )}
@@ -7,6 +7,7 @@
7
7
  import type { ChangeEvent } from 'react'
8
8
  import type { TranslateNS } from '../client-types.ts'
9
9
  import { Button, Card } from '../common/ui.tsx'
10
+ import { InfoHint } from '../common/InfoHint.tsx'
10
11
  import type { ChannelSyncState } from './sync-view.ts'
11
12
  import type { SyncChannelSettingsPatch } from '../../ui/sync-settings-view.ts'
12
13
  import css from '../config-manager.module.css'
@@ -22,7 +23,7 @@ export function DecryptPasswordCard({ t, settings, onPatchSettings, onPersistDec
22
23
  return (
23
24
  <Card>
24
25
  <label className={css.field}>
25
- <span className={css.fieldLabel}>{t('mode.decryptPassword')}</span>
26
+ <span className={css.fieldLabel}>{t('mode.decryptPassword')} <InfoHint text={t('mode.decryptPasswordHint')} label={t('common.infoHint')} /></span>
26
27
  <input
27
28
  type="password"
28
29
  className={css.input}
@@ -32,7 +33,6 @@ export function DecryptPasswordCard({ t, settings, onPatchSettings, onPersistDec
32
33
  onChange={(e: ChangeEvent<HTMLInputElement>) => { onPatchSettings({ decryptPassword: e.target.value }) }}
33
34
  onBlur={onPersistDecryptPassword}
34
35
  />
35
- <span className={css.hint}>{t('mode.decryptPasswordHint')}</span>
36
36
  </label>
37
37
  {settings.decryptPasswordSaved && (
38
38
  <div className={css.actionRow}>
@@ -7,6 +7,7 @@
7
7
  import type { ChangeEvent } from 'react'
8
8
  import type { TranslateNS } from '../client-types.ts'
9
9
  import { Card, Checkbox } from '../common/ui.tsx'
10
+ import { InfoHint } from '../common/InfoHint.tsx'
10
11
  import type { ChannelSyncState } from './sync-view.ts'
11
12
  import type { SyncChannelSettingsPatch } from '../../ui/sync-settings-view.ts'
12
13
  import css from '../config-manager.module.css'
@@ -26,19 +27,27 @@ export function SecurityOptionsCard({ t, settings, onToggleEncrypt, onToggleIncl
26
27
  return (
27
28
  <Card>
28
29
  <span className={css.groupLabel}>{t('mode.security')}</span>
29
- <Checkbox
30
- checked={settings.encrypt}
31
- onChange={onToggleEncrypt}
32
- label={<span className={css.categoryName}>{t('mode.encrypt')}</span>}
33
- />
34
- <div className={css.hint}>{t('mode.encryptHint')}</div>
35
- {settings.encrypt && settings.encryptPasswordSaved && (
36
- <div className={css.hint}>{t('mode.passwordSavedHint')}</div>
37
- )}
30
+ {/* 说明性文案一律进 ⓘ(2026-10-12 用户要求:卡里只留勾选项与控件)。
31
+ 「加密方式 + 密码存哪 + 自动同步跳过加密快照」两句合成同一颗 ⓘ。 */}
32
+ <div className={css.controlRow}>
33
+ <Checkbox
34
+ checked={settings.encrypt}
35
+ onChange={onToggleEncrypt}
36
+ label={<span className={css.categoryName}>{t('mode.encrypt')}</span>}
37
+ />
38
+ <InfoHint
39
+ text={t('mode.encryptHint') + ' ' + t('mode.encryptAutosyncNotice')}
40
+ label={t('common.infoHint')}
41
+ />
42
+ </div>
38
43
  {settings.encrypt && (
39
44
  <div className={css.secretFields}>
40
45
  <label className={css.field}>
41
- <span className={css.fieldLabel}>{t('mode.password')}</span>
46
+ <span className={css.fieldLabel}>
47
+ {t('mode.password')}
48
+ {' '}
49
+ {settings.encryptPasswordSaved && <InfoHint text={t('mode.passwordSavedHint')} label={t('common.infoHint')} />}
50
+ </span>
42
51
  <input
43
52
  type="password"
44
53
  className={css.input}
@@ -71,13 +80,14 @@ export function SecurityOptionsCard({ t, settings, onToggleEncrypt, onToggleIncl
71
80
  <span className={css.hint}>{t('mode.passwordClearNotice')}</span>
72
81
  </div>
73
82
  )}
74
- <Checkbox
75
- checked={settings.includeSecrets}
76
- onChange={onToggleIncludeSecrets}
77
- label={<span className={css.categoryName}>{t('mode.includeSecrets')}</span>}
78
- />
79
- <div className={css.hint}>{t('mode.includeSecretsHint')}</div>
80
- <span className={css.hint}>{t('mode.encryptAutosyncNotice')}</span>
83
+ <div className={css.controlRow}>
84
+ <Checkbox
85
+ checked={settings.includeSecrets}
86
+ onChange={onToggleIncludeSecrets}
87
+ label={<span className={css.categoryName}>{t('mode.includeSecrets')}</span>}
88
+ />
89
+ <InfoHint text={t('mode.includeSecretsHint')} label={t('common.infoHint')} />
90
+ </div>
81
91
  </Card>
82
92
  )
83
93
  }
@@ -10,7 +10,7 @@ import { ExpandChevron } from '../common/Icon.tsx'
10
10
  import { InfoHint } from '../common/InfoHint.tsx'
11
11
  import type { SyncStatusResponse } from './sync-api.ts'
12
12
  import type { SyncChannel } from './sync-view.ts'
13
- import { formatSyncStatusTimestamp, formatSyncUrlPreview } from '../../ui/sync-settings-view.ts'
13
+ import { formatSyncStatusTimestamp, formatSyncUrlPreview, workdirStateKey, workdirStateBadgeKind } from '../../ui/sync-settings-view.ts'
14
14
  import { needsGitToken, SYNC_CHANNEL_LABEL_KEY } from './sync-view.ts'
15
15
  import css from '../config-manager.module.css'
16
16
 
@@ -38,6 +38,11 @@ export function SyncChannelEntryCard({ t, channel, statusInfo, remoteReady, repo
38
38
  contentId?: string
39
39
  onToggleExpanded?: () => void
40
40
  }) {
41
+ // issue #79:工作副本状态(git 专属)。只在**需要人管**的状态(仍指旧地址 / 读不出)挂徽章 ——
42
+ // 「尚未创建」是首次同步前的正常态,不值得占一个徽章位;完整事实在展开区的「工作副本」卡里。
43
+ const workdirKey = channel === 'git' ? workdirStateKey(statusInfo?.workDir) : null
44
+ const workdirKind = workdirStateBadgeKind(workdirKey)
45
+ const showWorkdirBadge = workdirKind === 'warn'
41
46
  /** 事实行里的「当前远端」预览:按通道取那个真正标识远端的字段(gist 用 id,没有 URL) */
42
47
  const previewRemote = channel === 'webdav'
43
48
  ? webdavUrl
@@ -73,13 +78,20 @@ export function SyncChannelEntryCard({ t, channel, statusInfo, remoteReady, repo
73
78
  {channel === 'gist' && statusInfo?.gist?.secretStored === true && (
74
79
  <Badge kind="ok">{t('cloud.tokenSaved')}</Badge>
75
80
  )}
81
+ {/* issue #79:改仓库地址后工作副本仍指旧地址(下一轮同步会自动重指向)—— 在卡上直接可见 */}
82
+ {showWorkdirBadge && workdirKey !== null && workdirKind !== null && (
83
+ <Badge kind={workdirKind}>{t(workdirKey)}</Badge>
84
+ )}
76
85
  </div>
77
86
  {/* 状态事实行(Workbench:配置状态/上次同步/可同步分区——未配置时也要给硬事实) */}
78
87
  <div className={css.factGrid} style={{ marginTop: 8 }}>
79
88
  <div className={css.factCell}>
80
89
  <span className={css.factLabel}>{t('syncStatus.state')}</span>
81
90
  <span className={css.factValue}>
82
- {statusInfo?.configured === true ? t('channel.configured') : t('channel.notConfigured')}
91
+ {/* 判据必须与徽章同源(本卡自己的 remoteReady):statusInfo.configured 是**全局**语义
92
+ (任一通道配置过即为 true),照它渲染会让「只配了 git」时另外三张卡同时出现
93
+ 「未配置」徽章 +「已配置」事实行。 */}
94
+ {remoteReady ? t('channel.configured') : t('channel.notConfigured')}
83
95
  </span>
84
96
  </div>
85
97
  <div className={css.factCell}>
@@ -22,7 +22,7 @@ import {
22
22
  buildAdoptions, confirmListSummary, hasBulkDecidable, keepLocalAll, kindLabel, reviewItems, severityLabel,
23
23
  summarizeConfirmItems, useRemoteAll, type BulkDecision, type SyncConflictResolution,
24
24
  } from './sync-view.ts'
25
- import type { ApplyItemsResponse } from './sync-api.ts'
25
+ import type { ApplyItemsResponse, SyncRollbackResponse } from './sync-api.ts'
26
26
  import type { TranslateNS } from '../client-types.ts'
27
27
  import type { SyncConfirmDecisions } from '../run-store.ts'
28
28
  import { compatibilityBadgeKind, compatibilityLevel, type CompatibilityLevel } from '../../ui/import-wizard.ts'
@@ -86,6 +86,8 @@ export function SyncConfirmView(props: SyncConfirmViewProps): ReactNode {
86
86
  // 执行阶段:'idle' | 'applying' | 'done' | 'failed' | 'cancelling'
87
87
  const [phase, setPhase] = useState<'idle' | 'applying' | 'done' | 'failed' | 'cancelling'>('idle');
88
88
  const [applyResult, setApplyResult] = useState<ApplyItemsResponse | null>(null);
89
+ /** 一键回滚的诚实结果:full=false 时留在界面列出未恢复项(绝不只弹一句「已回滚」) */
90
+ const [rollbackResult, setRollbackResult] = useState<SyncRollbackResponse | null>(null);
89
91
  const [error, setError] = useState<string | null>(null);
90
92
  /** Phase 7 迁移前咨询:远端快照咨询报告(本地 state,非敏感) */
91
93
  const [consultReport, setConsultReport] = useState<ConsultReport | null>(null);
@@ -155,7 +157,7 @@ export function SyncConfirmView(props: SyncConfirmViewProps): ReactNode {
155
157
  const res = states[it.itemId]?.resolution;
156
158
  if (res !== undefined) resolutions.set(it.itemId, res);
157
159
  }
158
- const adoptions = buildAdoptions(items, adoptedMap, resolutions);
160
+ const adoptions = buildAdoptions(items, adoptedMap, resolutions, api.t);
159
161
  // T1:一键同步的确认页尚无「中止 / 跳过 / 引导」三选一控件(服务端已支持三态校验),
160
162
  // 因此显式传 `guide`:不改变既有写入行为,也避免缺省 abort 把用户挡在门外。
161
163
  const result = await api.applyItems({ syncSessionId, adoptions, sessionFormatDisposition: 'guide' });
@@ -196,13 +198,23 @@ export function SyncConfirmView(props: SyncConfirmViewProps): ReactNode {
196
198
  setPhase('applying');
197
199
  setError(null);
198
200
  try {
199
- await api.rollback({ restoreId });
201
+ const result = await api.rollback({ restoreId });
202
+ const failed = result.failed ?? [];
200
203
  setPhase('done');
201
204
  setApplyResult(null);
202
- // R-04:回滚成功改走 Toast(原 ok Banner 只在「回滚成功」这一条路径出现;
203
- // 操作已完成、用户可离开,不需要停留阅读)。下方「关闭」按钮保留。
204
- toast.ok(t('syncflow.rollbackDone'));
205
- onRollbackDone?.();
205
+ if (result.full === false) {
206
+ // 诚实性(2026-10):部分回滚**绝不**弹「已回滚」Toast —— 留在卡里列出未恢复项
207
+ // 与人工提示(凭据这类无法自动恢复的项会带 manualHint)。
208
+ setRollbackResult(result);
209
+ toast.warn(t('syncflow.rollbackPartialToast', { n: String(failed.length) }));
210
+ // 回滚没完成 ⇒ 不通知上层清空 lastRestoreId:那个回滚点仍然相关,用户可能想重试/排查。
211
+ } else {
212
+ setRollbackResult(null);
213
+ // R-04:回滚成功改走 Toast(原 ok Banner 只在「回滚成功」这一条路径出现;
214
+ // 操作已完成、用户可离开,不需要停留阅读)。下方「关闭」按钮保留。
215
+ toast.ok(t('syncflow.rollbackDone'));
216
+ onRollbackDone?.();
217
+ }
206
218
  } catch (err) {
207
219
  setPhase('failed');
208
220
  setError(err instanceof Error ? err.message : String(err));
@@ -336,6 +348,28 @@ export function SyncConfirmView(props: SyncConfirmViewProps): ReactNode {
336
348
  )}
337
349
  {(phase === 'done' || phase === 'failed') && applyResult === null && (
338
350
  <>
351
+ {/* 诚实性(2026-10):回滚未完成(full=false)时逐条列出未恢复项与人工提示,
352
+ 绝不用「已回滚」这一句话盖过去。完整回滚仍走 Toast(见 runRollback)。 */}
353
+ {rollbackResult !== null && rollbackResult.full === false && (
354
+ <>
355
+ <Banner kind="error">
356
+ {t('syncflow.rollbackIncomplete', { n: String((rollbackResult.failed ?? []).length) })}
357
+ </Banner>
358
+ {(rollbackResult.failed ?? []).length > 0 && (
359
+ <div>
360
+ <span className={css.fieldLabel}>{t('syncflow.rollbackFailedItems')}</span>
361
+ <ul className={css.warnList}>
362
+ {(rollbackResult.failed ?? []).map((f, i) => (
363
+ <li key={i}>
364
+ {redact(f.item)} —— {redact(f.reason)}
365
+ {f.manualHint !== undefined && f.manualHint !== '' ? t('common.parens', { text: redact(f.manualHint) }) : ''}
366
+ </li>
367
+ ))}
368
+ </ul>
369
+ </div>
370
+ )}
371
+ </>
372
+ )}
339
373
  {/* R-04:回滚成功的 ok Banner 已改为 Toast(见 runRollback)。此处仅保留收尾「关闭」按钮;
340
374
  一并消除了原实现在 runApply 抛错(phase=failed 且 applyResult 仍为 null)时
341
375
  误显示「已回滚到应用前」的问题 —— 那种情况下方 error Banner 才是唯一事实。 */}
@@ -418,7 +452,11 @@ function ApplyResultCard({ result, busy, t, onRollback }: ApplyResultCardProps):
418
452
  {/* R-05:成功分支已改走 Toast(见 runApply);此处只保留失败 Banner ——
419
453
  K-06 要求失败后用户停留本卡阅读 warnings 明细并决定是否回滚,
420
454
  自动消失的 Toast 无法承载这个决策入口。 */}
421
- {!ok && <Banner kind="error">{t('syncflow.importFailed')}</Banner>}
455
+ {!ok && (result.rollbackFull === false
456
+ // 诚实性(2026-10):rolledBack=true 但整体回滚未完成时,'syncflow.importFailed'
457
+ //(文案断言「已整体回滚」)是错的 —— 换专用措辞,未恢复项见下方 warnings 列表。
458
+ ? <Banner kind="error">{t('syncflow.importRollbackIncomplete')}</Banner>
459
+ : <Banner kind="error">{t('syncflow.importFailed')}</Banner>)}
422
460
  {/* issue #56:部分成功必须**留在卡里**(Toast 会消失,而用户需要据此判断
423
461
  「我这次到底缺了什么、要不要回滚」)。措辞与事实同强度:不是失败,是 N 项没生效。 */}
424
462
  {ok && ineffective.length > 0 && (
@@ -83,7 +83,7 @@ export function SyncLogList({ api, t }: SyncLogListProps): ReactNode {
83
83
 
84
84
  return (
85
85
  <Card>
86
- <SectionTitle title={t('history.title') + '(' + String(rows.length) + ')'} />
86
+ <SectionTitle title={t('history.title') + t('common.parens', { text: String(rows.length) })} />
87
87
  {/* 头部统计摘要(先扫结论);失败/跳过仅在存在时出现,并给语义色 */}
88
88
  <div className={css.statRow} aria-label={t('history.stats.summary')}>
89
89
  <Badge kind="info">{t('history.stats.total', { count: String(stats.total) })}</Badge>
@@ -62,6 +62,7 @@ import type {
62
62
  import {
63
63
  channelMapOf, channelStateMap, computeGithubLoginView, computeSyncButtons, defaultChannelSyncState,
64
64
  githubPollMessage, isClientChannel, kindLabel, pullReportView, pushPreviewView, pushReportView, readStoredChannel,
65
+ gistSessionsRiskKey,
65
66
  initialSyncSections, normalizeSessionsLimit, severityLabel, syncSectionOptions, writeStoredChannel,
66
67
  } from './sync-view.ts'
67
68
  import type {
@@ -87,6 +88,7 @@ import type { ExportPreviewResponse } from '../api.ts'
87
88
  import { SecurityOptionsCard } from './SecurityOptionsCard.tsx'
88
89
  import { DecryptPasswordCard } from './DecryptPasswordCard.tsx'
89
90
  import { AutosyncCard } from './AutosyncCard.tsx'
91
+ import { WorkDirCard } from './WorkDirCard.tsx'
90
92
  import { Collapse } from '../common/Motion.tsx'
91
93
  import { SyncLogList } from './SyncLogList.tsx'
92
94
  import { UnreadableSnapshotsBanner } from './UnreadableSnapshotsBanner.tsx'
@@ -444,7 +446,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
444
446
  // 未接线的值一律按「无记录」处理并回落 localStorage 记忆 —— 不把未接线通道混进 UI 状态机。
445
447
  const remembered: SyncChannel | null = isClientChannel(info.lastSyncChannel) ? info.lastSyncChannel : readStoredChannel()
446
448
  // 可同步分区目录回填(host adapters 唯一事实源;仅 portable;两通道共用)
447
- const catalog = info.syncSections !== undefined ? syncSectionOptions(info.syncSections) : []
449
+ const catalog = info.syncSections !== undefined ? syncSectionOptions(info.syncSections, uiT) : []
448
450
  // 每通道回填:优先该通道的持久化配置(syncSelectionByChannel / autosyncByChannel);
449
451
  // 无持久化 → 默认模式 + 推荐分区
450
452
  const selByCh = info.syncSelectionByChannel
@@ -505,7 +507,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
505
507
  })
506
508
  } catch (err) {
507
509
  // R-20:读取自动同步状态失败(此前写入页面最底部的共享 error Banner,常滚出视野)
508
- toast.error(`${t('toast.autosyncLoadFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
510
+ toast.error(t('toast.autosyncLoadFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
509
511
  }
510
512
  }
511
513
 
@@ -547,7 +549,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
547
549
  } catch (err) {
548
550
  // M-18/M-21:拉取远端快照失败原先完全静默(下拉留空),用户无法区分
549
551
  // 「远端确实没有快照」与「读取失败」。不阻断主流程,仅以 Toast 如实告知。
550
- toast.error(`${t('toast.snapshotsLoadFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
552
+ toast.error(t('toast.snapshotsLoadFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
551
553
  } finally {
552
554
  loadingSnapshotsRef.current[ch] = false
553
555
  patchChannelState(ch, { loadingSnapshots: false })
@@ -664,7 +666,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
664
666
  }
665
667
  } catch (err) {
666
668
  // R-20:保存失败**始终**提示(无论手动还是自动)——用户的改动没有落盘
667
- toast.error(`${t('toast.configSaveFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
669
+ toast.error(t('toast.configSaveFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
668
670
  patch({ savingConfig: false })
669
671
  } finally {
670
672
  // 保存期间又排入的新改动 → 立即补发(保底,不丢输入)
@@ -871,7 +873,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
871
873
  } catch (err) {
872
874
  // R-20:预览失败(此前写共享 error Banner,渲染在页面最底部而按钮在中部)
873
875
  patch({ busy: null })
874
- toast.error(`${t('toast.pushPreviewFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
876
+ toast.error(t('toast.pushPreviewFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
875
877
  }
876
878
  }
877
879
 
@@ -901,7 +903,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
901
903
  }
902
904
  } catch (err) {
903
905
  patch({ busy: null })
904
- toast.error(`${t('toast.pushFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
906
+ toast.error(t('toast.pushFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
905
907
  }
906
908
  }
907
909
 
@@ -916,11 +918,12 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
916
918
  const report = await api.pull({ ...payload(), ...decrypt })
917
919
  patch({ busy: null, pullReport: report, token: '', webdavPassword: '' })
918
920
  patchChannel({ decryptPassword: '' })
919
- // M-19:拉取终局回执(弹窗关闭后无痕迹)
920
- toast.ok(t('toast.pullDone'))
921
+ // 必须按 report.ok 分流(与 runPush 同口径):校验不通过时宿主仍回 HTTP 200 + ok:false
922
+ if (report.ok) toast.ok(t('toast.pullDone'))
923
+ else toast.error(t('toast.pullFailed'))
921
924
  } catch (err) {
922
925
  patch({ busy: null })
923
- toast.error(`${t('toast.pullFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
926
+ toast.error(t('toast.pullFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
924
927
  }
925
928
  }
926
929
 
@@ -947,7 +950,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
947
950
  })
948
951
  } catch (err) {
949
952
  // R-20/M-22:同步设置持久化失败(此前写共享 error Banner,与刚点的按钮相距整屏)
950
- toast.error(t('toast.selectionSaveFailed') + ':' + redact(err instanceof Error ? err.message : String(err)))
953
+ toast.error(t('toast.selectionSaveFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
951
954
  }
952
955
  }
953
956
 
@@ -1036,7 +1039,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1036
1039
  if (!session.ok) {
1037
1040
  // R-20:一键同步启动失败(宿主明确回报 message,无异常抛出)
1038
1041
  patch({ busy: null })
1039
- toast.error(`${t('toast.syncStartFailed')}:${redact(session.message ?? t('syncflow.syncFailed'))}`)
1042
+ toast.error(t('toast.syncStartFailed') + t('common.labelSeparator') + redact(session.message ?? t('syncflow.syncFailed')))
1040
1043
  return
1041
1044
  }
1042
1045
  patch({ busy: null, confirmSession: session, confirmDecisions: null, token: '', webdavPassword: '' })
@@ -1044,7 +1047,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1044
1047
  void loadSnapshots()
1045
1048
  } catch (err) {
1046
1049
  patch({ busy: null })
1047
- toast.error(`${t('toast.syncStartFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
1050
+ toast.error(t('toast.syncStartFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
1048
1051
  }
1049
1052
  }
1050
1053
 
@@ -1081,7 +1084,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1081
1084
  toast.ok(t('toast.autosyncUpdated'))
1082
1085
  } catch (err) {
1083
1086
  // R-20/M-20:失败必须提示 —— 界面是乐观更新,用户会以为开关已生效
1084
- toast.error(`${t('toast.autosyncUpdateFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
1087
+ toast.error(t('toast.autosyncUpdateFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
1085
1088
  }
1086
1089
  }
1087
1090
 
@@ -1092,7 +1095,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1092
1095
  patchChannelOf(channel, { autosync: updated, autosyncEnabled: updated.enabled, autosyncInterval: updated.interval })
1093
1096
  toast.ok(t('toast.autosyncUpdated'))
1094
1097
  } catch (err) {
1095
- toast.error(`${t('toast.autosyncUpdateFailed')}:${redact(err instanceof Error ? err.message : String(err))}`)
1098
+ toast.error(t('toast.autosyncUpdateFailed') + t('common.labelSeparator') + redact(err instanceof Error ? err.message : String(err)))
1096
1099
  }
1097
1100
  }
1098
1101
 
@@ -1172,9 +1175,9 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1172
1175
  的断行。标题内联沿用 AboutPanel / EnvironmentPanel 的既有写法(零新增样式)。 */}
1173
1176
  <span className={css.groupLabel}>{t('mode.title')} <InfoHint text={t('mode.hint')} label={t('common.infoHint')} /></span>
1174
1177
  <span className={css.categoryDesc}>
1175
- {state.catalog.length === 0
1176
- ? t('common.loading')
1177
- : t('mode.selectedCount', { n: String(cs.syncSections.length) })}
1178
+ {state.loadError !== null ? t('load.failed')
1179
+ : state.catalog.length === 0 ? t('common.loading')
1180
+ : t('mode.selectedCount', { n: String(cs.syncSections.length) })}
1178
1181
  </span>
1179
1182
  <div className={css.actionRowTop}>
1180
1183
  <Button onClick={() => { openSectionPickerFor(channel) }} disabled={busy}>
@@ -1183,6 +1186,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1183
1186
  <InfoHint text={t('mode.persistHint')} label={t('common.infoHint')} />
1184
1187
  </div>
1185
1188
  {cs.syncSections.length === 0 && <Banner kind="warn">{t('mode.atLeastOne')}</Banner>}
1189
+ {gistSessionsRiskKey(channel, cs.syncSections) !== null && <Banner kind="warn">{t(gistSessionsRiskKey(channel, cs.syncSections)!)}</Banner>}
1186
1190
  </Card>
1187
1191
  )}
1188
1192
  {ready && (
@@ -1279,6 +1283,8 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1279
1283
  onClearSavedPassword={() => { clearSavedDecryptPassword(channel) }}
1280
1284
  />
1281
1285
  )}
1286
+ {/* issue #79:工作副本(git 专属;确认弹窗/宿主调用/回执全在 WorkDirCard.tsx) */}
1287
+ {ready && channel === 'git' && <WorkDirCard t={t} api={api} workDir={state.statusInfo?.workDir} busy={busy} onChanged={() => { void loadStatus() }} />}
1282
1288
  {/* issue #59:远端快照列表不完整时的常驻告知(push 报成功但列表为空时,这里是唯一线索)。
1283
1289
  v3:跟着**它所属的通道**走(此前挂页面级 = 显示的是隐式当前通道的列表)。 */}
1284
1290
  {ready && <UnreadableSnapshotsBanner items={cs.unreadableSnapshots} t={t} />}
@@ -1290,14 +1296,14 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1290
1296
  /* ------------------------------------------------ 渲染 */
1291
1297
  return (
1292
1298
  <div className={css.viewBody}>
1293
- <SectionTitle title={t('section.label')} subtitle={t('section.description')} />
1299
+ <SectionTitle title={t('section.label')} info={<InfoHint text={t('section.description')} label={t('common.infoHint')} />} />
1294
1300
 
1295
1301
  {/* M-16:页面级加载失败就地提示(此前 loadError 只写不读,宿主不可达时整页显示
1296
1302
  「未配置」假象)。此处保留可就地重试的 ErrorBanner —— 它是**页面是否可用**
1297
1303
  的前提条件,必须常驻到用户重试成功,不能交给会自动消失的 Toast。 */}
1298
1304
  {state.loadError !== null && (
1299
1305
  <ErrorBanner
1300
- error={`${t('load.failed')}:${redact(state.loadError)}`}
1306
+ error={t('load.failed') + t('common.labelSeparator') + redact(state.loadError)}
1301
1307
  onRetry={() => { void loadStatus() }}
1302
1308
  retrying={state.loading}
1303
1309
  t={api.t}
@@ -1350,6 +1356,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1350
1356
  open={sectionPickerOpen}
1351
1357
  t={t}
1352
1358
  cmT={cmT}
1359
+ uiT={uiT}
1353
1360
  sectionName={sectionName}
1354
1361
  catalog={state.catalog}
1355
1362
  sections={chState.syncSections}
@@ -1390,7 +1397,7 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1390
1397
  closeDisabled={state.busy === 'push'}
1391
1398
  />
1392
1399
  <Modal.Body scroll>
1393
- <PushPreviewCard preview={state.pushPreview.preview} t={t} uiT={uiT} />
1400
+ <PushPreviewCard preview={state.pushPreview.preview} t={t} uiT={uiT} sectionName={sectionName} />
1394
1401
  </Modal.Body>
1395
1402
  <Modal.Footer>
1396
1403
  <Button
@@ -1538,10 +1545,11 @@ export function SyncPanel({ api, t, cmT }: SyncPanelProps) {
1538
1545
  }
1539
1546
 
1540
1547
  /** P0-②:push 预览内容(绑 src/ui/i18n.ts 的 UiT 文案;纯展示,无敏感字段)。 */
1541
- function PushPreviewCard({ preview, t, uiT }: {
1548
+ function PushPreviewCard({ preview, t, uiT, sectionName }: {
1542
1549
  preview: SyncPushPreview | null
1543
1550
  t: TranslateNS<'config-manager-sync'>
1544
1551
  uiT: UiT
1552
+ sectionName: (id: SectionId) => string
1545
1553
  }) {
1546
1554
  const view = pushPreviewView(preview, uiT)
1547
1555
  if (view === null) return null
@@ -1561,8 +1569,8 @@ function PushPreviewCard({ preview, t, uiT }: {
1561
1569
  <ul className={css.reportList}>
1562
1570
  {view.rows.map((row) => (
1563
1571
  <li key={row.section}>
1564
- <span className={css.kindTag}>{row.changed ? 'changed' : 'unchanged'}</span>
1565
- {' '}{row.section} · {row.count}
1572
+ <span className={css.kindTag}>{row.changed ? t('syncflow.pushChanged') : t('syncflow.pushUnchanged')}</span>
1573
+ {' '}{sectionName(row.section)} · {row.count}
1566
1574
  </li>
1567
1575
  ))}
1568
1576
  </ul>
@@ -123,7 +123,7 @@ export function SyncRepositoryPicker({ open, api, t, repoUrl, busy, onPick }: Sy
123
123
  } catch (err) {
124
124
  const detail = err instanceof Error ? err.message : String(err)
125
125
  setCreateError(detail)
126
- toast.error(t('repoPicker.createFailed') + ':' + redact(detail))
126
+ toast.error(t('repoPicker.createFailed') + t('common.labelSeparator') + redact(detail))
127
127
  } finally {
128
128
  setCreating(false)
129
129
  }
@@ -166,12 +166,12 @@ export function SyncRepositoryPicker({ open, api, t, repoUrl, busy, onPick }: Sy
166
166
  {/* 空态 / 失败说明留在原地(不进 ⓘ):用户正卡在这里,说明必须看得见 */}
167
167
  {loaded && privateRepos.length === 0 && <span className={css.hint}>{t('repoPicker.empty')}</span>}
168
168
  {loadError !== null && (
169
- <span className={css.formError}>{t('repoPicker.loadFailed')}({redact(loadError)})</span>
169
+ <span className={css.formError}>{t('repoPicker.loadFailed')}{t('common.parens', { text: redact(loadError) })}</span>
170
170
  )}
171
171
  </div>
172
172
  {createOpen && (
173
173
  <div className={css.field}>
174
- <span className={css.groupLabel}>{t('repoPicker.createTitle')}</span>
174
+ <span className={css.groupLabel}>{t('repoPicker.createTitle')} <InfoHint text={t('repoPicker.privateOnly')} label={t('common.infoHint')} /></span>
175
175
  <label className={css.field}>
176
176
  <span className={css.fieldLabel}>{t('repoPicker.name')}</span>
177
177
  <input
@@ -197,8 +197,6 @@ export function SyncRepositoryPicker({ open, api, t, repoUrl, busy, onPick }: Sy
197
197
  onChange={(e: ChangeEvent<HTMLInputElement>) => { setDescription(e.target.value) }}
198
198
  />
199
199
  </label>
200
- {/* 安全说明常驻可见(不进 ⓘ):新建的仓库恒为私有 */}
201
- <span className={css.hint}>{t('repoPicker.privateOnly')}</span>
202
200
  {nameHint !== null && <span className={css.formError}>{nameHint}</span>}
203
201
  {createError !== null && <span className={css.formError}>{redact(createError)}</span>}
204
202
  <div className={css.actionRowTop}>