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.
- package/README.md +111 -96
- package/README.zh-CN.md +207 -86
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +1 -1
- package/lib/adapters/index.js +1 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +18 -0
- package/lib/adapters/messages.js +50 -0
- package/lib/adapters/plugin-files.js +3 -2
- package/lib/adapters/plugins.js +4 -3
- package/lib/adapters/prompts.d.ts +1 -0
- package/lib/adapters/prompts.js +7 -1
- package/lib/adapters/self.d.ts +7 -1
- package/lib/adapters/self.js +1 -1
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- package/lib/adapters/ui.d.ts +2 -2
- package/lib/adapters/ui.js +25 -25
- package/lib/adapters/workspaces.d.ts +1 -0
- package/lib/adapters/workspaces.js +7 -3
- package/lib/cli/actions.d.ts +9 -1
- package/lib/cli/actions.js +5 -2
- package/lib/cli/help.js +23 -10
- package/lib/cli/index.d.ts +23 -0
- package/lib/cli/index.js +90 -32
- package/lib/cli/sessions-inspect.js +3 -1
- package/lib/cli/sessions-repair.js +25 -6
- package/lib/cli/web/page.d.ts +12 -0
- package/lib/cli/web/page.js +21 -12
- package/lib/cli/web/routes.js +2 -2
- package/lib/cli/web/server.js +4 -4
- package/lib/client.d.ts +1349 -490
- package/lib/client.js +43047 -36738
- package/lib/core/backup-plan.js +30 -17
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +5 -0
- package/lib/core/backup.js +51 -25
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +178 -0
- package/lib/core/command-rollback.js +289 -0
- package/lib/core/consult-source.js +7 -6
- package/lib/core/disk-usage.d.ts +74 -10
- package/lib/core/disk-usage.js +67 -4
- package/lib/core/engine-locale.d.ts +87 -0
- package/lib/core/engine-locale.js +125 -0
- package/lib/core/exporter.js +3 -3
- package/lib/core/fork-reconcile.d.ts +128 -0
- package/lib/core/fork-reconcile.js +150 -0
- package/lib/core/journal.js +6 -5
- package/lib/core/local-plugin-host.js +3 -2
- package/lib/core/local-plugin-pack.js +11 -6
- package/lib/core/messages-core.d.ts +292 -0
- package/lib/core/messages-core.js +615 -0
- package/lib/core/messages.d.ts +60 -2
- package/lib/core/messages.js +116 -13
- package/lib/core/migration-consult.js +21 -20
- package/lib/core/migration-history.js +19 -5
- package/lib/core/model-tools.d.ts +0 -22
- package/lib/core/model-tools.js +50 -27
- package/lib/core/phase3-host.js +30 -5
- package/lib/core/plugin-cli.js +18 -19
- package/lib/core/reconcile.d.ts +2 -14
- package/lib/core/reconcile.js +87 -17
- package/lib/core/recovery-orchestrator.js +19 -18
- package/lib/core/reinstall.d.ts +4 -0
- package/lib/core/reinstall.js +26 -21
- package/lib/core/restore.js +20 -17
- package/lib/core/rollback-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-point.d.ts +1208 -0
- package/lib/core/rollback-point.js +2993 -0
- package/lib/core/run-registry.js +6 -5
- package/lib/core/session-health.d.ts +15 -0
- package/lib/core/session-meta.d.ts +42 -2
- package/lib/core/session-meta.js +140 -1
- package/lib/core/session-title.d.ts +28 -0
- package/lib/core/session-title.js +185 -0
- package/lib/core/startup-barrier.d.ts +24 -0
- package/lib/core/startup-barrier.js +22 -0
- package/lib/core/transaction-coordinator.js +4 -3
- package/lib/core/types.d.ts +18 -1
- package/lib/core/validator.js +5 -4
- package/lib/core/verify-recovery.js +31 -30
- package/lib/foreign/bundle.js +3 -2
- package/lib/foreign/continue.js +2 -1
- package/lib/foreign/crush.js +2 -1
- package/lib/foreign/gemini.js +2 -1
- package/lib/foreign/goose.js +2 -1
- package/lib/foreign/kilocode.js +2 -1
- package/lib/foreign/kimi.js +2 -1
- package/lib/foreign/messages.d.ts +15 -0
- package/lib/foreign/messages.js +40 -0
- package/lib/foreign/mimocode.js +2 -1
- package/lib/foreign/openclaw.js +2 -1
- package/lib/foreign/opencode.js +2 -1
- package/lib/foreign/pi.js +2 -1
- package/lib/foreign/qoder.js +2 -1
- package/lib/foreign/qwen.js +2 -1
- package/lib/foreign/registry.js +7 -6
- package/lib/foreign/teleagent.js +2 -1
- package/lib/foreign/workbuddy.js +2 -1
- package/lib/foreign/zcode.js +2 -1
- package/lib/foreign/zed.js +2 -1
- package/lib/index.d.ts +223 -7
- package/lib/index.js +729 -54
- package/lib/market/git-file-writer.js +20 -19
- package/lib/market/github-repos.js +35 -19
- package/lib/market/index-parser.js +31 -30
- package/lib/market/messages-market.d.ts +138 -0
- package/lib/market/messages-market.js +305 -0
- package/lib/market/my-repo.d.ts +2 -2
- package/lib/market/my-repo.js +32 -29
- package/lib/market/prepare.js +18 -15
- package/lib/market/security.js +20 -19
- package/lib/market/types.js +2 -1
- package/lib/market/url.js +2 -1
- package/lib/migrations/index.js +6 -5
- package/lib/migrations/messages-migrations.d.ts +32 -0
- package/lib/migrations/messages-migrations.js +58 -0
- package/lib/profiles/dsh-profile-launcher.js +2 -1
- package/lib/profiles/dsh-profile-runtime.js +2 -1
- package/lib/profiles/messages.d.ts +7 -0
- package/lib/profiles/messages.js +27 -0
- package/lib/routes/backup.js +18 -2
- package/lib/routes/import.js +3 -3
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +3 -2
- package/lib/routes/kit.js +6 -1
- package/lib/routes/market.js +1 -1
- package/lib/routes/messages.d.ts +42 -0
- package/lib/routes/messages.js +100 -0
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.js +378 -114
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +68 -16
- package/lib/schema/config.d.ts +11 -1
- package/lib/schema/config.js +52 -20
- package/lib/schema/index.d.ts +8 -6
- package/lib/schema/index.js +8 -6
- package/lib/schema/manifest.js +19 -18
- package/lib/schema/messages.d.ts +47 -0
- package/lib/schema/messages.js +110 -0
- package/lib/schema/section-registry.d.ts +41 -14
- package/lib/schema/section-registry.js +35 -29
- package/lib/schema/versions.js +6 -5
- package/lib/security/encryption.js +16 -15
- package/lib/security/integrity.js +8 -7
- package/lib/security/messages.d.ts +30 -0
- package/lib/security/messages.js +77 -0
- package/lib/security/redaction.js +12 -0
- package/lib/security/secret-scanner.js +16 -13
- package/lib/security/vault.js +2 -1
- package/lib/security/zip-security.js +4 -3
- package/lib/sync/ancestor.js +2 -1
- package/lib/sync/autosync-config.d.ts +11 -0
- package/lib/sync/autosync-config.js +12 -0
- package/lib/sync/autosync-scheduler.d.ts +11 -1
- package/lib/sync/autosync-scheduler.js +115 -76
- package/lib/sync/backup-files.js +3 -2
- package/lib/sync/backup-scheduler.js +6 -5
- package/lib/sync/blob-store.js +2 -1
- package/lib/sync/git/git-transport.d.ts +55 -0
- package/lib/sync/git/git-transport.js +152 -2
- package/lib/sync/github-auth.js +10 -9
- package/lib/sync/layout.js +13 -12
- package/lib/sync/messages.d.ts +63 -0
- package/lib/sync/messages.js +149 -0
- package/lib/sync/review-queue.js +6 -5
- package/lib/sync/s3/messages.d.ts +7 -0
- package/lib/sync/s3/messages.js +16 -0
- package/lib/sync/s3/s3-providers.d.ts +4 -1
- package/lib/sync/s3/s3-providers.js +5 -5
- package/lib/sync/s3/s3-transport.js +1 -1
- package/lib/sync/s3/sigv4.js +2 -1
- package/lib/sync/snapshot-crypto.js +6 -5
- package/lib/sync/snapshot-json.js +3 -2
- package/lib/sync/sync-engine.d.ts +37 -15
- package/lib/sync/sync-engine.js +67 -37
- package/lib/sync/sync-history.js +7 -6
- package/lib/sync/sync-selection.d.ts +15 -4
- package/lib/sync/sync-selection.js +17 -5
- package/lib/sync/webdav/webdav-transport.d.ts +1 -0
- package/lib/sync/webdav/webdav-transport.js +4 -1
- package/lib/ui/artifact-view.js +4 -3
- package/lib/ui/commands.d.ts +1 -1
- package/lib/ui/commands.js +10 -0
- package/lib/ui/disk-usage-view.d.ts +33 -4
- package/lib/ui/disk-usage-view.js +29 -1
- package/lib/ui/errors.d.ts +1 -1
- package/lib/ui/errors.js +4 -3
- package/lib/ui/export-flow.js +24 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +65 -0
- package/lib/ui/i18n.js +114 -0
- package/lib/ui/import-wizard.d.ts +7 -0
- package/lib/ui/import-wizard.js +13 -10
- package/lib/ui/progress.d.ts +2 -2
- package/lib/ui/progress.js +1 -2
- package/lib/ui/report.d.ts +5 -5
- package/lib/ui/report.js +6 -6
- package/lib/ui/rollback-view.d.ts +756 -0
- package/lib/ui/rollback-view.js +724 -0
- package/lib/ui/selection-model.d.ts +41 -0
- package/lib/ui/selection-model.js +30 -0
- package/lib/ui/session-inventory-view.d.ts +97 -3
- package/lib/ui/session-inventory-view.js +76 -2
- package/lib/ui/session-issue-labels.d.ts +43 -0
- package/lib/ui/session-issue-labels.js +48 -0
- package/lib/ui/sync-settings-view.d.ts +33 -0
- package/lib/ui/sync-settings-view.js +40 -0
- package/lib/ui/types.d.ts +8 -3
- package/lib/ui/types.js +1 -1
- package/lib/utils/atomic-write.js +6 -5
- package/lib/utils/bundle-scan.d.ts +11 -1
- package/lib/utils/bundle-scan.js +12 -1
- package/lib/utils/env-lock.d.ts +10 -0
- package/lib/utils/env-lock.js +73 -46
- package/lib/utils/json.js +6 -5
- package/lib/utils/messages-utils.d.ts +96 -0
- package/lib/utils/messages-utils.js +216 -0
- package/lib/utils/proxy.js +18 -5
- package/lib/utils/recursive-walk.d.ts +22 -0
- package/lib/utils/recursive-walk.js +75 -1
- package/lib/utils/session-log-repair.js +10 -9
- package/lib/utils/zip.js +27 -26
- package/package.json +12 -2
- package/src/adapters/agent-instructions.ts +1 -1
- package/src/adapters/file-collection.test.ts +1 -0
- package/src/adapters/file-collection.ts +1 -1
- package/src/adapters/files.test.ts +27 -0
- package/src/adapters/index.ts +1 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +54 -0
- package/src/adapters/plugin-files.ts +3 -2
- package/src/adapters/plugins.ts +4 -3
- package/src/adapters/prompts.ts +7 -1
- package/src/adapters/self.test.ts +26 -0
- package/src/adapters/self.ts +8 -2
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- package/src/adapters/ui.test.ts +2 -2
- package/src/adapters/ui.ts +27 -26
- package/src/adapters/workspaces.ts +7 -3
- package/src/cli/actions.test.ts +28 -0
- package/src/cli/actions.ts +11 -2
- package/src/cli/help.ts +23 -10
- package/src/cli/index-guards.test.ts +193 -0
- package/src/cli/index.ts +106 -31
- package/src/cli/sessions-inspect.test.ts +15 -1
- package/src/cli/sessions-inspect.ts +3 -1
- package/src/cli/sessions-repair.test.ts +84 -0
- package/src/cli/sessions-repair.ts +33 -7
- package/src/cli/web/console-route-parity.test.ts +115 -0
- package/src/cli/web/page.ts +23 -12
- package/src/cli/web/routes.ts +2 -1
- package/src/cli/web/server.ts +4 -4
- package/src/cli/web/web.test.ts +2 -2
- package/src/client/ConfigManagerSection.tsx +30 -2
- package/src/client/about/AboutPanel.tsx +2 -2
- package/src/client/about/PluginUpdateDialog.tsx +1 -1
- package/src/client/about/ReleaseNotesDialog.tsx +2 -2
- package/src/client/about/about-view.test.ts +13 -3
- package/src/client/about/about-view.ts +10 -6
- package/src/client/about/release-notes-view.test.ts +14 -4
- package/src/client/about/release-notes-view.ts +4 -2
- package/src/client/api.ts +18 -6
- package/src/client/chat/ChatEntryView.tsx +391 -0
- package/src/client/chat/RollbackDiffTab.tsx +61 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +591 -0
- package/src/client/chat/chat-entry-view.test.ts +1174 -0
- package/src/client/chat/chat-entry-view.ts +758 -0
- package/src/client/chat/chat-locales.ts +152 -0
- package/src/client/chat/chat-node-registration.ts +64 -0
- package/src/client/chat/chat-point-window.ts +173 -0
- package/src/client/chat/chat-slots.ts +107 -0
- package/src/client/chat/rollback-diff-tab.test.ts +105 -0
- package/src/client/chat/rollback-diff-tab.ts +164 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +941 -0
- package/src/client/chat/sidebar-slots.ts +37 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +97 -0
- package/src/client/common/ErrorBanner.tsx +4 -4
- package/src/client/common/Icon.tsx +2 -0
- package/src/client/common/Modal.tsx +8 -1
- package/src/client/common/ProgressBar.tsx +8 -2
- package/src/client/common/ReportView.tsx +4 -4
- package/src/client/common/RunsCenter.tsx +1 -0
- package/src/client/common/client-route-parity.test.ts +78 -0
- package/src/client/common/info-hint-guard.test.ts +46 -13
- package/src/client/common/picker-lazy-units.test.ts +42 -0
- package/src/client/common/plan-text-redaction.test.ts +17 -6
- package/src/client/common/progress-view-edge.test.ts +8 -7
- package/src/client/common/progress-view.test.ts +9 -9
- package/src/client/common/progress-view.ts +2 -2
- package/src/client/common/routes.ts +28 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +996 -21
- package/src/client/css-scale-guard.test.ts +30 -0
- package/src/client/environment/EnvironmentPanel.tsx +11 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
- package/src/client/export/ExportView.tsx +13 -8
- package/src/client/history/HistoryPanel.tsx +4 -3
- package/src/client/history/history-api.ts +2 -2
- package/src/client/history/history-locales.ts +5 -1
- package/src/client/home/HomePanel.tsx +29 -8
- package/src/client/import/ForeignImportView.tsx +4 -5
- package/src/client/import/ImportWizardView.tsx +5 -3
- package/src/client/import/import-wizard-split.test.ts +3 -1
- package/src/client/import/import-wizard-steps.tsx +9 -9
- package/src/client/index.ts +186 -3
- package/src/client/library/LibraryActions.tsx +32 -7
- package/src/client/library/LibraryPanel.tsx +23 -3
- package/src/client/library/RestorePlanView.tsx +9 -66
- package/src/client/locales.ts +54 -5
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +13 -2
- package/src/client/market/MyConfigsView.tsx +5 -4
- package/src/client/market/MyConfigsWizard.tsx +10 -8
- package/src/client/market/market-api.test.ts +9 -8
- package/src/client/market/market-api.ts +2 -2
- package/src/client/market/market-locales.ts +3 -0
- package/src/client/market/market-view.test.ts +6 -6
- package/src/client/market/market-view.ts +3 -3
- package/src/client/market/my-configs-api.ts +2 -2
- package/src/client/market/my-configs-view.test.ts +23 -22
- package/src/client/market/my-configs-view.ts +8 -8
- package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
- package/src/client/recovery/RecoveryPanel.tsx +442 -283
- package/src/client/recovery/RollbackCard.tsx +665 -0
- package/src/client/recovery/RollbackDiffPane.tsx +58 -0
- package/src/client/recovery/RollbackFileChanges.tsx +163 -0
- package/src/client/recovery/incident-api.ts +2 -2
- package/src/client/recovery/recovery-api.ts +116 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +488 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-file-changes.test.ts +73 -0
- package/src/client/recovery/session-dialog-mount.test.ts +54 -0
- package/src/client/run-store.ts +84 -21
- package/src/client/sync/AutosyncCard.tsx +25 -1
- package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
- package/src/client/sync/ChannelConfigDialog.tsx +10 -10
- package/src/client/sync/DecryptPasswordCard.tsx +2 -2
- package/src/client/sync/SecurityOptionsCard.tsx +27 -17
- package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
- package/src/client/sync/SyncConfirmView.tsx +46 -8
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +31 -23
- package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
- package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
- package/src/client/sync/SyncSettingsView.test.ts +20 -1
- package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
- package/src/client/sync/WorkDirCard.tsx +112 -0
- package/src/client/sync/history-model.test.ts +42 -9
- package/src/client/sync/history-model.ts +30 -15
- package/src/client/sync/sync-api.test.ts +57 -33
- package/src/client/sync/sync-api.ts +80 -6
- package/src/client/sync/sync-locales.ts +104 -3
- package/src/client/sync/sync-view-v2.test.ts +65 -13
- package/src/client/sync/sync-view.test.ts +107 -52
- package/src/client/sync/sync-view.ts +124 -41
- package/src/core/backup-plan.test.ts +28 -0
- package/src/core/backup-plan.ts +29 -17
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +55 -25
- package/src/core/boot-rescue.ts +4 -3
- package/src/core/boot-safety.ts +2 -1
- package/src/core/command-rollback.test.ts +536 -0
- package/src/core/command-rollback.ts +402 -0
- package/src/core/consult-source.ts +7 -6
- package/src/core/disk-usage.test.ts +257 -1
- package/src/core/disk-usage.ts +134 -14
- package/src/core/engine-locale.test.ts +593 -0
- package/src/core/engine-locale.ts +175 -0
- package/src/core/exporter.ts +3 -3
- package/src/core/fork-reconcile.test.ts +279 -0
- package/src/core/fork-reconcile.ts +234 -0
- package/src/core/journal.ts +6 -5
- package/src/core/local-plugin-host.ts +3 -2
- package/src/core/local-plugin-pack.ts +11 -6
- package/src/core/messages-core.ts +657 -0
- package/src/core/messages.ts +123 -17
- package/src/core/migration-consult.ts +21 -20
- package/src/core/migration-history.ts +19 -5
- package/src/core/model-tools.ts +28 -27
- package/src/core/phase3-host.ts +21 -5
- package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
- package/src/core/plugin-cli.ts +18 -19
- package/src/core/reconcile.test.ts +117 -0
- package/src/core/reconcile.ts +85 -17
- package/src/core/recovery-orchestrator.ts +19 -18
- package/src/core/reinstall.ts +26 -21
- package/src/core/restore.ts +20 -17
- package/src/core/rollback-file-diff.test.ts +159 -0
- package/src/core/rollback-ignore-rules.ts +126 -0
- package/src/core/rollback-point.test.ts +2603 -0
- package/src/core/rollback-point.ts +3755 -0
- package/src/core/run-registry.ts +6 -5
- package/src/core/session-health.ts +15 -0
- package/src/core/session-meta.test.ts +117 -1
- package/src/core/session-meta.ts +149 -2
- package/src/core/session-title.test.ts +127 -0
- package/src/core/session-title.ts +189 -0
- package/src/core/startup-barrier.test.ts +69 -0
- package/src/core/startup-barrier.ts +27 -0
- package/src/core/transaction-coordinator.ts +4 -3
- package/src/core/types.ts +18 -1
- package/src/core/validator.ts +5 -4
- package/src/core/verify-recovery.ts +31 -30
- package/src/foreign/bundle.ts +3 -2
- package/src/foreign/continue.ts +2 -1
- package/src/foreign/crush.ts +2 -1
- package/src/foreign/file-budget.test.ts +2 -0
- package/src/foreign/gemini.ts +2 -1
- package/src/foreign/goose.ts +2 -1
- package/src/foreign/kilocode.ts +2 -1
- package/src/foreign/kimi.ts +2 -1
- package/src/foreign/messages.ts +44 -0
- package/src/foreign/mimocode.ts +2 -1
- package/src/foreign/openclaw.ts +2 -1
- package/src/foreign/opencode.ts +2 -1
- package/src/foreign/pi.ts +2 -1
- package/src/foreign/qoder.ts +2 -1
- package/src/foreign/qwen.ts +2 -1
- package/src/foreign/registry.ts +7 -6
- package/src/foreign/teleagent.ts +2 -1
- package/src/foreign/workbuddy.ts +2 -1
- package/src/foreign/zcode.ts +2 -1
- package/src/foreign/zed.ts +2 -1
- package/src/index.facade.test.ts +340 -1
- package/src/index.ts +813 -57
- package/src/market/git-file-writer.ts +20 -19
- package/src/market/github-repos.test.ts +23 -0
- package/src/market/github-repos.ts +37 -19
- package/src/market/index-parser.ts +31 -30
- package/src/market/messages-market.ts +325 -0
- package/src/market/my-repo.ts +32 -29
- package/src/market/prepare.ts +18 -15
- package/src/market/security.ts +20 -19
- package/src/market/types.ts +2 -1
- package/src/market/url.ts +2 -1
- package/src/migrations/index.ts +6 -5
- package/src/migrations/messages-migrations.ts +59 -0
- package/src/profiles/dsh-profile-launcher.ts +2 -1
- package/src/profiles/dsh-profile-runtime.ts +2 -1
- package/src/profiles/messages.ts +31 -0
- package/src/routes/backup.ts +18 -2
- package/src/routes/import.ts +3 -3
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +9 -3
- package/src/routes/market.ts +1 -1
- package/src/routes/messages.ts +104 -0
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +377 -109
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +67 -16
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +19 -5
- package/src/schema/section-registry.ts +64 -39
- package/src/schema/versions.ts +6 -5
- package/src/security/encryption.ts +16 -15
- package/src/security/integrity.ts +8 -7
- package/src/security/messages.ts +87 -0
- package/src/security/redaction.test.ts +17 -1
- package/src/security/redaction.ts +13 -0
- package/src/security/secret-scanner.ts +19 -12
- package/src/security/security.test.ts +47 -0
- package/src/security/vault.ts +2 -1
- package/src/security/zip-security.ts +4 -3
- package/src/sync/ancestor.ts +2 -1
- package/src/sync/autosync-config.ts +21 -0
- package/src/sync/autosync-scheduler.test.ts +63 -7
- package/src/sync/autosync-scheduler.ts +118 -78
- package/src/sync/backup-files.ts +3 -2
- package/src/sync/backup-scheduler.ts +6 -5
- package/src/sync/blob-store.ts +2 -1
- package/src/sync/git/git-transport.test.ts +170 -8
- package/src/sync/git/git-transport.ts +176 -2
- package/src/sync/github-auth.ts +10 -9
- package/src/sync/layout.ts +13 -12
- package/src/sync/messages.ts +153 -0
- package/src/sync/review-queue.ts +6 -5
- package/src/sync/s3/messages.ts +16 -0
- package/src/sync/s3/s3-providers.ts +9 -6
- package/src/sync/s3/s3-transport.ts +1 -1
- package/src/sync/s3/sigv4.ts +3 -1
- package/src/sync/snapshot-crypto.ts +6 -5
- package/src/sync/snapshot-json.ts +3 -2
- package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +103 -41
- package/src/sync/sync-history.ts +7 -6
- package/src/sync/sync-selection.test.ts +12 -2
- package/src/sync/sync-selection.ts +18 -5
- package/src/sync/webdav/webdav-transport.ts +4 -1
- package/src/ui/artifact-view.ts +4 -3
- package/src/ui/commands.test.ts +1 -0
- package/src/ui/commands.ts +11 -1
- package/src/ui/disk-usage-view.test.ts +113 -6
- package/src/ui/disk-usage-view.ts +63 -7
- package/src/ui/errors.ts +4 -3
- package/src/ui/export-flow.ts +24 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +125 -0
- package/src/ui/import-wizard.ts +18 -10
- package/src/ui/progress.ts +2 -2
- package/src/ui/report.ts +7 -6
- package/src/ui/rollback-view.test.ts +819 -0
- package/src/ui/rollback-view.ts +1339 -0
- package/src/ui/selection-model.test.ts +33 -1
- package/src/ui/selection-model.ts +53 -0
- package/src/ui/session-inventory-view.test.ts +109 -0
- package/src/ui/session-inventory-view.ts +138 -3
- package/src/ui/session-issue-labels.test.ts +51 -0
- package/src/ui/session-issue-labels.ts +70 -0
- package/src/ui/sync-settings-view.test.ts +36 -0
- package/src/ui/sync-settings-view.ts +49 -0
- package/src/ui/types.ts +10 -4
- package/src/utils/atomic-write.ts +6 -5
- package/src/utils/bundle-scan.test.ts +2 -2
- package/src/utils/bundle-scan.ts +12 -1
- package/src/utils/env-lock.ts +83 -49
- package/src/utils/json.ts +7 -5
- package/src/utils/messages-utils.ts +232 -0
- package/src/utils/proxy.test.ts +25 -0
- package/src/utils/proxy.ts +19 -5
- package/src/utils/recursive-walk.test.ts +43 -0
- package/src/utils/recursive-walk.ts +75 -1
- package/src/utils/session-log-repair.ts +10 -9
- package/src/utils/zip.ts +27 -26
- package/lib/core/checkpoint.d.ts +0 -362
- package/lib/core/checkpoint.js +0 -1309
- package/src/core/checkpoint.test.ts +0 -665
- package/src/core/checkpoint.ts +0 -1614
|
@@ -37,9 +37,16 @@ test('布局守卫:小节用卡片容器 + groupLabel 标题 + snapshotList/re
|
|
|
37
37
|
false,
|
|
38
38
|
'小节标题不得用 .snapshotRowMain(flex:1 + nowrap ⇒ 标题被拉成一行、文字竖排)',
|
|
39
39
|
);
|
|
40
|
+
// 2026-10-12 UI 重构:计划 / 结果从「卡内小节」变成**面板**(.shPanel + .shPanelTitle),
|
|
41
|
+
// 入口标题与 keep 仍在卡内走 .groupLabel。口径不变:标题必须用**专属标题类**,
|
|
42
|
+
// 绝不退回 .snapshotRowMain(flex:1 + nowrap ⇒ 标题被拉成一行、文字竖排)。
|
|
40
43
|
assert.ok(
|
|
41
|
-
(body.match(/css\.groupLabel/g) ?? []).length >=
|
|
42
|
-
'
|
|
44
|
+
(body.match(/css\.groupLabel/g) ?? []).length >= 2,
|
|
45
|
+
'入口标题与 keep 选择必须走 .groupLabel(至少 2 处)',
|
|
46
|
+
);
|
|
47
|
+
assert.ok(
|
|
48
|
+
(body.match(/css\.shPanelTitle/g) ?? []).length >= 2,
|
|
49
|
+
'计划与结果的面板标题必须走 .shPanelTitle(至少 2 处)',
|
|
43
50
|
);
|
|
44
51
|
assert.ok(
|
|
45
52
|
(body.match(/css\.snapshotList/g) ?? []).length >= 3,
|
|
@@ -50,3 +57,126 @@ test('布局守卫:小节用卡片容器 + groupLabel 标题 + snapshotList/re
|
|
|
50
57
|
'长列表必须限高内滚(.snapshotList + .reportScroll)',
|
|
51
58
|
);
|
|
52
59
|
});
|
|
60
|
+
|
|
61
|
+
const ROLLBACK_CARD_PATH = 'src/client/recovery/RollbackCard.tsx';
|
|
62
|
+
|
|
63
|
+
/* ---------------------------------------------- 回滚点时间线(2026-10-11 布局重构) */
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 源码级守卫:回滚点台账必须用**会话分组卡 + 两行式行卡**,不得退回通用 `.actionRow` 字墙。
|
|
67
|
+
*
|
|
68
|
+
* 为什么(用户 2026-10-11 反馈「工作区 + 对话回滚面板的 UI 太丑」):旧实现每行都是
|
|
69
|
+
* `.actionRow[data-inline]`(通用横向操作行)—— 时间 / 回合 / 来源徽章 / 文件数 / 会话游标 / note /
|
|
70
|
+
* 两颗实体按钮**同权重**横排,564px 画布下换行位置随机,元信息与按钮混成一片 11px 灰字;
|
|
71
|
+
* 长句(「回退前救援点(可回退、不可删)」「无会话游标(只能回退工作区)」)逐行重复整句。
|
|
72
|
+
* 这类缺陷**类型检查、纯函数单测、bundle 护栏全都抓不到**(与本文件头上那条同源),只能靠源码守卫。
|
|
73
|
+
*
|
|
74
|
+
* 四条判据:
|
|
75
|
+
* ① 结构类必须在位:分组卡 / 分组头 / 组标题 / 行列表 / 行卡 / 主行 / meta 行;
|
|
76
|
+
* ② 行内不得再出现 `.actionRow[data-inline]`(旧字墙形态必须绝迹);
|
|
77
|
+
* ③ 长句必须有**短标签键**进屏(完整句仍进 title,事实一个字都不少);
|
|
78
|
+
* ④ 台账与预览两个长列表仍限高内滚。
|
|
79
|
+
*/
|
|
80
|
+
test('布局守卫:回滚点时间线 = 分组卡 + 两行式行卡(禁止退回 actionRow 字墙)', () => {
|
|
81
|
+
const src = readFileSync(ROLLBACK_CARD_PATH, 'utf8');
|
|
82
|
+
for (const cls of [
|
|
83
|
+
'rollbackGroup', 'rollbackGroupHead', 'rollbackGroupTitle',
|
|
84
|
+
'rollbackPoints', 'rollbackPoint', 'rollbackPointTop', 'rollbackPointMeta',
|
|
85
|
+
]) {
|
|
86
|
+
assert.ok(src.includes('css.' + cls), '回滚卡必须用 .' + cls + '(会话分组卡 + 两行式行卡)');
|
|
87
|
+
}
|
|
88
|
+
assert.equal(
|
|
89
|
+
/css\.actionRow[^\n]*data-inline/.test(src),
|
|
90
|
+
false,
|
|
91
|
+
'行渲染不得退回 .actionRow[data-inline](旧字墙形态:元信息与按钮同权重横排)',
|
|
92
|
+
);
|
|
93
|
+
for (const key of ['rollback.kind.guardShort', 'rollback.row.sessionShort', 'rollback.row.workspaceShort']) {
|
|
94
|
+
assert.ok(src.includes(key), '短标签键必须进屏(完整句进 title):' + key);
|
|
95
|
+
}
|
|
96
|
+
assert.ok(
|
|
97
|
+
src.includes('css.planScroll') && src.includes('css.reportScroll'),
|
|
98
|
+
'台账与预览列表必须限高内滚(.planScroll / .reportScroll)',
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/* ------------------- 2026-10-12:两块诊断卡退出折叠 + 台账整块进弹窗 */
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 用户 2026-10-12 的两条要求(类型检查 / 纯函数单测 / bundle 护栏都抓不到渲染结构,故仍是源码守卫):
|
|
106
|
+
* ①「会话体检」与「工作区 + 会话回滚」**不再可展开/收回** —— 卡头不得再出现折叠开关或折叠容器,
|
|
107
|
+
* 摘要与主操作(体检入口 / 台账入口)常驻可见;
|
|
108
|
+
* ② 回滚台账整块搬进**本页弹窗**(按会话分组),且与体检弹窗同一条挂载纪律:只在打开时挂载。
|
|
109
|
+
* 谁把折叠加回来、或把台账塞回卡内正文,这里直接红灯。
|
|
110
|
+
*/
|
|
111
|
+
test('两块诊断卡退出折叠:卡头无 FoldToggle/Collapse,回滚台账在条件挂载的弹窗内', () => {
|
|
112
|
+
const rollback = readFileSync(ROLLBACK_CARD_PATH, 'utf8');
|
|
113
|
+
const sessions = functionBody('SessionHealthCard');
|
|
114
|
+
|
|
115
|
+
for (const [name, src] of [['RollbackCard', rollback], ['SessionHealthCard', sessions]] as const) {
|
|
116
|
+
assert.equal(src.includes('<FoldToggle'), false, name + ' 不得再渲染折叠开关(用户要求退出折叠)');
|
|
117
|
+
assert.equal(src.includes('<Collapse'), false, name + ' 不得再用折叠容器(内容常驻可见)');
|
|
118
|
+
assert.equal(src.includes('aria-expanded'), false, name + ' 卡内不得再有展开/收回语义');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 卡上只留「打开台账」这一颗入口按钮;台账本体在 {open && …} 条件挂载的弹窗里
|
|
122
|
+
assert.ok(rollback.includes("t('rollback.dialog.open')"), '卡上必须留一颗打开台账的按钮');
|
|
123
|
+
assert.match(
|
|
124
|
+
rollback,
|
|
125
|
+
/\{open && \(\s*<Modal open[\s\S]{0,400}title=\{t\('rollback\.dialog\.title'\)\}/,
|
|
126
|
+
'台账必须进弹窗,且只在打开时挂载(关闭即卸载)',
|
|
127
|
+
);
|
|
128
|
+
assert.ok(rollback.includes('<Modal.Body scroll>'), '台账列表必须在弹窗正文内(限高内滚,不撑长页面)');
|
|
129
|
+
// 体检入口同样常驻卡上(不再是「收起后要展开才看得到」)
|
|
130
|
+
assert.ok(sessions.includes("t('sessions.dialog.open')"), '体检入口必须常驻卡上');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/* ------------------- 2026-10-12:台账弹窗左右分栏 + 刷新不抖 + 预览弹窗留白 */
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 用户 2026-10-12 的三条要求(同样只有源码守卫能钉住渲染结构):
|
|
137
|
+
* ① 台账弹窗 = **左栏选对话 / 右栏看该对话的回滚点**(左栏默认新 → 旧,顺序由 rollbackPointGroups 定死);
|
|
138
|
+
* ② 「刷新」**不得**把已有内容换成 loading 空态(旧实现让弹窗高度骤变 ⇒ 用户反馈「整页抖动」);
|
|
139
|
+
* ③ 预览弹窗正文必须包在 `Modal.Body` 里(否则文字与按钮贴边 —— 用户截图实证)。
|
|
140
|
+
*/
|
|
141
|
+
test('台账弹窗分栏 / 刷新不抖 / 预览留白(2026-10-12 用户要求)', () => {
|
|
142
|
+
const src = readFileSync(ROLLBACK_CARD_PATH, 'utf8');
|
|
143
|
+
for (const cls of ['rollbackLedger', 'rollbackSessions', 'rollbackSessionItem', 'rollbackSessionId', 'rollbackSessionMeta', 'rollbackLedgerMain']) {
|
|
144
|
+
assert.ok(src.includes('css.' + cls), '分栏必须用 .' + cls + '(左栏会话列表 + 右栏回滚点)');
|
|
145
|
+
}
|
|
146
|
+
assert.ok(src.includes("aria-label={t('rollback.sessions.title')}"), '左栏必须带**已翻译**的无障碍名');
|
|
147
|
+
assert.ok(src.includes('activeGroup.rows.map('), '右栏渲染的必须是**当前选中对话**的回滚点(不是全部会话平铺)');
|
|
148
|
+
assert.match(src, /const activeGroup = groups\.find\(/, '选中的键失效时必须回落到第一条(绝不显示空右栏)');
|
|
149
|
+
// ② 刷新:保留内容,只把按钮置为在途
|
|
150
|
+
assert.ok(src.includes('load({ keep: true })'), '刷新按钮必须走 keep 语义(保留旧内容)');
|
|
151
|
+
assert.match(src, /const keep = options\?\.keep === true && list !== null/, 'keep 只在已有内容时成立(首屏仍走 loading)');
|
|
152
|
+
assert.ok(src.includes('loading={refreshing}'), '刷新在途只体现在按钮上,不动正文');
|
|
153
|
+
// ③ 预览弹窗正文留白(两个弹窗的正文都必须包在 Modal.Body 里)
|
|
154
|
+
assert.ok((src.match(/<Modal.Body scroll>/g) ?? []).length >= 2, '台账与预览两个弹窗的正文都必须包在 Modal.Body 里');
|
|
155
|
+
// ④ 弹窗高度**锁死**(2026-10-12 用户第二条反馈:切换对话 / 选到点数很少的会话时不得变高变矮)
|
|
156
|
+
const cssSheet = readFileSync('src/client/config-manager.module.css', 'utf8');
|
|
157
|
+
const ledgerRule = /\.rollbackLedger \{[^}]*\}/.exec(cssSheet)?.[0] ?? '';
|
|
158
|
+
assert.match(ledgerRule, /height: 340px/, '分栏容器必须锁死高度(否则弹窗重排 = 整页抖动)');
|
|
159
|
+
const sessionsRule = /\.rollbackSessions \{[^}]*\}/.exec(cssSheet)?.[0] ?? '';
|
|
160
|
+
assert.match(sessionsRule, /height: 100%/, '左栏必须填满分栏高度(不得再用 max-height 随内容变化)');
|
|
161
|
+
assert.ok(src.includes('css.rollbackPointsScroll'), '右栏列表必须用 .rollbackPointsScroll 吃掉剩余高度并内滚');
|
|
162
|
+
// ⑤ 会话显示**标题**(拿不到标题才回落 id —— 绝不编造)
|
|
163
|
+
assert.ok(src.includes('group.title ?? group.sessionId'), '左栏必须优先显示对话标题,拿不到才回落会话 id');
|
|
164
|
+
assert.ok(src.includes('activeGroup.title ?? activeGroup.sessionId'), '右栏组头同样优先显示标题');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/* ------------------- 2026-10-12:会话体检卡删掉「预扫描说明」文案 */
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 用户 2026-10-12 要求删掉卡上那行描述(「打开后扫描本机存量会话:哪些读不出来……(扫描只读)。」)——
|
|
171
|
+
* 卡上只留真正的信息:扫描摘要与主操作。类型检查/模型单测都抓不到「文案又被加回来」,
|
|
172
|
+
* 故仍用源码守卫钉住:卡内不得再渲染该键,字典(zh/en)里也不得再有它。
|
|
173
|
+
*/
|
|
174
|
+
test('会话体检卡不再渲染预扫描说明文案(用户 2026-10-12 要求)', () => {
|
|
175
|
+
const sessions = functionBody('SessionHealthCard');
|
|
176
|
+
assert.equal(sessions.includes('sessions.dialog.hint'), false, '预扫描说明文案不得再回到卡上');
|
|
177
|
+
// 摘要行必须保留,且只在真有扫描结果时渲染(首屏不得留空行)
|
|
178
|
+
assert.ok(sessions.includes("t('sessions.dialog.lastScan'"), '扫描摘要必须保留');
|
|
179
|
+
assert.match(sessions, /summary !== null && response !== null &&/, '摘要只在有结果时渲染');
|
|
180
|
+
const locales = readFileSync('src/client/recovery/recovery-locales.ts', 'utf8');
|
|
181
|
+
assert.equal(locales.includes("'sessions.dialog.hint'"), false, '字典里不得留下已删文案(zh 与 en 都要删)');
|
|
182
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 回退预览文件清单(共享组件)的源码级守卫。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要:清单的版式与诚实边界(图标 | 目录弱化 + 基名 | 右对齐统计;删除行写「删除」;
|
|
5
|
+
* 行数不可信写「行数未统计」,绝不写 0)**类型检查与行为单测都抓不到** —— React 壳不跑组件框架,
|
|
6
|
+
* 而它现在被两处调用:聊天里的回退预览与设置页回滚卡的预览弹窗(2026-10-12 用户要求
|
|
7
|
+
* 「环境页的回退预览也要一眼看清被改动的文件」)。谁把这两处又各自写一份、或把「删除」写成
|
|
8
|
+
* +0 −N,这里直接红灯。
|
|
9
|
+
*/
|
|
10
|
+
import test from 'node:test'
|
|
11
|
+
import assert from 'node:assert/strict'
|
|
12
|
+
import { readFileSync } from 'node:fs'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
import { dirname, join } from 'node:path'
|
|
15
|
+
|
|
16
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
const CHANGES = join(HERE, 'RollbackFileChanges.tsx')
|
|
18
|
+
const CARD = join(HERE, 'RollbackCard.tsx')
|
|
19
|
+
const DIALOG = join(HERE, '../chat/RollbackReviewDialog.tsx')
|
|
20
|
+
|
|
21
|
+
test('清单结构:容器 + 两类行 + 图标 + 目录弱化 + 右对齐统计(源级)', () => {
|
|
22
|
+
const src = readFileSync(CHANGES, 'utf8')
|
|
23
|
+
assert.match(src, /className=\{css\.rollbackFileList\}/, '清单必须包进 .rollbackFileList 容器')
|
|
24
|
+
assert.equal([...src.matchAll(/role="listitem"/g)].length, 2, '两类行(改动 / 删除)都必须是 listitem')
|
|
25
|
+
assert.match(src, /splitRollbackPath\(file\.path\)/, '文件行必须切出目录前缀(只画基名时读不出「在哪」)')
|
|
26
|
+
assert.match(src, /className=\{css\.rollbackFileDir\}/, '目录段必须有独立的弱化类')
|
|
27
|
+
assert.match(src, /className=\{css\.rollbackFileStat\}/, '±行数必须包在紧跟文件名的统计块里')
|
|
28
|
+
assert.match(src, /RollbackFileIcon/, '文件名前面必须有文件类型图标')
|
|
29
|
+
assert.match(src, /render=\{renderFileIcon\}/, '图标的外部来源(dsh-better-sidebar)必须接到每一行')
|
|
30
|
+
assert.match(src, /data-kind=\{previewFileStatusKnown\(file\.status\) \? file\.status : undefined\}/, '语义色条只在形状可判定时上色(读不出就不画)')
|
|
31
|
+
assert.equal([...src.matchAll(/data-off/g)].length, 0, '共享清单里不得有整块降透明的 data-off(旧做法)')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('诚实边界:删除写「删除」、行数不可信写「行数未统计」(源级)', () => {
|
|
35
|
+
const src = readFileSync(CHANGES, 'utf8')
|
|
36
|
+
assert.match(src, /rollback\.preview\.file\.deleted/, '整文件删除必须写「删除」而不是 +0 −N')
|
|
37
|
+
assert.match(src, /rollback\.preview\.file\.unknown/, '行数不可信必须写「行数未统计」')
|
|
38
|
+
assert.match(src, /if \(status === 'delete'\)/, '删除只由引擎给的 status 判定(不猜、不从行数反推)')
|
|
39
|
+
assert.doesNotMatch(src, /added === 0\s*[?&=|)]/, '绝不从 added === 0 反推删除(清空成 0 行与删除是两件事)')
|
|
40
|
+
assert.doesNotMatch(src, /chat\.preview/, '字典键由调用方给(组件只收已翻译文本),不得自己引聊天字典')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('点文件名看差异:路径那一格是按钮,且只走调用方注入的通道(源级)', () => {
|
|
44
|
+
const src = readFileSync(CHANGES, 'utf8')
|
|
45
|
+
assert.equal([...src.matchAll(/css\.fileViewBtn/g)].length, 0, '尾部那颗独立「查看」按钮必须整体消失')
|
|
46
|
+
const buttons = src.match(/<button[\s\S]{0,400}?<\/button>/g) ?? []
|
|
47
|
+
const pathBtn = buttons.find((block) => block.includes('css.rollbackFilePath'))
|
|
48
|
+
assert.ok(pathBtn !== undefined, '路径那一格本身必须是 <button>(点文件名即跳转)')
|
|
49
|
+
assert.ok(pathBtn.includes('view.onView(file.path)'), '点文件名必须走注入的唯一打开入口(组件不自己找通道)')
|
|
50
|
+
assert.ok(pathBtn.includes('disabled={view.disabled}'), '忙态 / 无可写目标时必须禁用(判据由调用方给)')
|
|
51
|
+
assert.ok(pathBtn.includes('view.hint(file.path)'), '无障碍名必须带这个文件的全路径(同名按钮一屏好几个)')
|
|
52
|
+
const removals = src.slice(src.indexOf('removals.map'))
|
|
53
|
+
assert.doesNotMatch(removals, /<button/, '删除行不是按钮:它的差异读不出来,点它不该有反应')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('两处调用同一份渲染:聊天预览 + 设置页回滚卡预览(源级)', () => {
|
|
57
|
+
const dialog = readFileSync(DIALOG, 'utf8')
|
|
58
|
+
const card = readFileSync(CARD, 'utf8')
|
|
59
|
+
assert.match(dialog, /<RollbackFileChanges/, '聊天预览必须用共享清单')
|
|
60
|
+
assert.match(dialog, /renderFileIcon=\{renderFileIcon\}/, '聊天侧必须把外部图标来源传下去')
|
|
61
|
+
assert.match(dialog, /onView: view,/, '聊天侧点文件名必须走同一个 view()(唯一打开入口)')
|
|
62
|
+
assert.match(dialog, /readRollbackPreviewRemovals\(/, '聊天侧删除行必须取自引擎的删除集 removals')
|
|
63
|
+
assert.doesNotMatch(dialog, /css\.rollbackFileList/, '聊天侧不得再自己画一份清单')
|
|
64
|
+
// 设置页回滚卡:同一份渲染 + 同一对读法 + 同一个查看通道(内嵌差异面板)
|
|
65
|
+
assert.match(card, /<RollbackFileChanges/, '设置页回滚卡必须用共享清单')
|
|
66
|
+
assert.match(card, /readRollbackPreviewFiles\(preview\?\.workspace\)/, '清单必须取自引擎 preview.workspace.files')
|
|
67
|
+
assert.match(card, /readRollbackPreviewRemovals\(preview\?\.workspace\)/, '删除行必须取自引擎的删除集 removals')
|
|
68
|
+
assert.match(card, /rollbackPreviewScope\(previewFiles, previewRemovals\)/, '汇总必须走 ui 层唯一实现(口径与文件行同源)')
|
|
69
|
+
assert.match(card, /view=\{fileView\}/, '查看通道必须显式接上(宿主没能力时不给假入口)')
|
|
70
|
+
assert.match(card, /RollbackDiffPane state=\{inlineDiff\.state\}/, '设置页没有右侧栏:差异必须在弹窗内画出来')
|
|
71
|
+
assert.match(card, /css\.planScroll/, '几百个文件的清单必须限高内滚')
|
|
72
|
+
assert.doesNotMatch(card, /rollbackPreviewRows\(/, '预览弹窗不再画工作区/会话计数面板(与聊天预览同一屏)')
|
|
73
|
+
})
|
|
@@ -55,4 +55,58 @@ test('T15:会话体检的卡片与容器必须留白(真机反馈「挤在
|
|
|
55
55
|
// 行间距
|
|
56
56
|
const list = /\.snapshotList \{[^}]*\}/.exec(css)?.[0] ?? '';
|
|
57
57
|
assert.match(list, /gap: 10px/, '行间距必须加大');
|
|
58
|
+
// 2026-10-12 UI 重构:新的行卡与摘要卡同样不许贴边(旧类只覆盖了 .snapshotRow)
|
|
59
|
+
const shRow = /\.shRow \{[^}]*\}/.exec(css)?.[0] ?? '';
|
|
60
|
+
assert.match(shRow, /padding: 8px 10px/, '会话行卡(.shRow)必须自带内边距');
|
|
61
|
+
const shSummary = /\.shSummary \{[^}]*\}/.exec(css)?.[0] ?? '';
|
|
62
|
+
assert.match(shSummary, /padding: 12px 14px/, '摘要卡(.shSummary)必须自带内边距');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* T16:会话体检弹窗的**结构**(2026-10-12 用户反馈「会话体检的 UI 太丑」后新增)。
|
|
67
|
+
*
|
|
68
|
+
* 旧形态的三处根因:① 摘要是四颗同款徽章,看不出哪个数重点;② 每行是「徽章 + 会话 id + 问题 +
|
|
69
|
+
* 版本/体积 + 两颗文字按钮」同排的横向 flex,564px 画布下换行位置随机;③ 机制说明(布局归位 3 段、
|
|
70
|
+
* 离线修复 1 段)常驻成 4~6 行散文。类型检查 / 纯函数单测 / bundle 护栏都抓不到「渲染结构」,
|
|
71
|
+
* 故与 T13 同规格用源码守卫钉住(改回字墙即红灯)。
|
|
72
|
+
*/
|
|
73
|
+
test('T16:会话体检弹窗 = 数字摘要 + 区块头 + 两行式行卡(禁止退回字墙)', () => {
|
|
74
|
+
const src = fs.readFileSync(PANEL, 'utf8');
|
|
75
|
+
for (const cls of [
|
|
76
|
+
'shSummary', 'shMetric', 'shSeverity', 'shSectionHead',
|
|
77
|
+
'shRow', 'shRowTop', 'shRowMeta', 'shRowActions', 'shPanel', 'shCommands',
|
|
78
|
+
]) {
|
|
79
|
+
assert.ok(src.includes('css.' + cls), '弹窗必须用 .' + cls + '(摘要 / 区块头 / 两行式行卡 / 面板容器)');
|
|
80
|
+
}
|
|
81
|
+
// ① 主操作进标题行(旧实现是正文左上角孤零零一颗按钮,要先滚过摘要才找得到)
|
|
82
|
+
assert.ok(src.includes('<Modal.Header'), '弹窗标题行必须显式装配(主操作与 ⓘ 都挂在它上面)');
|
|
83
|
+
assert.ok(src.includes('css.shHeaderTrailing'), '标题行 trailing 必须有自己的容器类');
|
|
84
|
+
// ② 数据口径(未验证 / 被隐藏的正常会话 / 超上限 / 读不出条目)必须有专属容器,不散落成正文 .hint
|
|
85
|
+
assert.ok(src.includes('css.shNotes'), '数据口径必须有专属容器(.shNotes)');
|
|
86
|
+
// ③ ZIP 入口不再是文字按钮:图标按钮 + 已翻译的 aria-label,禁用原因走 title(不静默消失)
|
|
87
|
+
assert.equal(
|
|
88
|
+
/t\('sessions\.zip\.action'\)\}\s*<\/Button>/.test(src),
|
|
89
|
+
false,
|
|
90
|
+
'下载 ZIP 不得再是文字按钮(图标按钮 + aria-label + title)',
|
|
91
|
+
);
|
|
92
|
+
assert.ok(src.includes("label={t('sessions.zip.action')}"), 'ZIP 图标按钮必须带已翻译的无障碍名');
|
|
93
|
+
// ④ 长句只进 title:完整严重级句不得再进屏当徽章文案(短标签走 severityShortKey)
|
|
94
|
+
assert.ok(src.includes('row.severityShortKey'), '行徽章必须用短标签键(完整句进 title)');
|
|
95
|
+
assert.ok(src.includes('title={t(row.severityKey)}'), '完整严重级句必须仍在 title 里(事实一个字不少)');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* T17:会话体检的行首必须显示**对话标题**(2026-10-12 用户反馈:列表只有 `session-20d2722c-…`,
|
|
100
|
+
* 同屏十几条里认不出哪条是自己的对话)。宿主没给标题时回落目录名,绝不编造。
|
|
101
|
+
*/
|
|
102
|
+
test('T17:会话行首 = 对话标题(缺省回落目录名),目录名退到 meta 行仍可见', () => {
|
|
103
|
+
const src = fs.readFileSync(PANEL, 'utf8');
|
|
104
|
+
assert.ok(
|
|
105
|
+
src.includes("row.title ?? (row.blank ? t('sessions.blank') : row.sessionId)"),
|
|
106
|
+
'行首主文本必须是标题(缺省:空白会话显示「新会话」,其余回落 sessionId)',
|
|
107
|
+
);
|
|
108
|
+
assert.ok(src.includes('title={row.title ?? row.unitId}'), '全文进 title(标题已是脱敏后的文本)');
|
|
109
|
+
assert.ok(src.includes('title={row.unitId}>{row.sessionId}'), '目录名仍需常驻(修复 / 下载要核对的主键)');
|
|
110
|
+
// 空白会话(从未提问):DSH 自己显示「新会话」—— 不得拿 uuid 当标题
|
|
111
|
+
assert.ok(src.includes("row.blank ? t('sessions.blank')"), '空白会话必须显示本地化的「新会话」占位名');
|
|
58
112
|
});
|
package/src/client/run-store.ts
CHANGED
|
@@ -41,6 +41,9 @@ import {
|
|
|
41
41
|
type SessionFormatDisposition,
|
|
42
42
|
} from '../ui/session-format-disposition.ts'
|
|
43
43
|
import { renderExportReport } from '../ui/report.ts'
|
|
44
|
+
// 模块级单例没有「调用方」可以传 UiT:直接用**活**翻译器(随 DSH 语言变化,非 zh 一律 en)。
|
|
45
|
+
// 此前这里的提示文案是硬编码中文,英文界面下原样透出。
|
|
46
|
+
import { liveUiT } from '../ui/i18n.ts'
|
|
44
47
|
import type { FlowPhase } from '../ui/flow.ts'
|
|
45
48
|
import type { ImportStep } from '../ui/types.ts'
|
|
46
49
|
import { isQuickRecommended } from '../ui/types.ts'
|
|
@@ -108,6 +111,9 @@ export interface StoreStorage {
|
|
|
108
111
|
/** sessionStorage 键。 */
|
|
109
112
|
export const STATE_KEY = 'dsh.cfgMgr.state.v1'
|
|
110
113
|
|
|
114
|
+
/** 高频进度 patch 的落盘合并窗口(ms):轮询每 500ms 一次,合并到 1s 一次。 */
|
|
115
|
+
const SAVE_DEBOUNCE_MS = 1000
|
|
116
|
+
|
|
111
117
|
/**
|
|
112
118
|
* 导出页的初始勾选 = **推荐分区**(可迁移、非设备专属),之后由内容选择器调整。
|
|
113
119
|
* 判据与 ExportFlow.quickSelection() 共用同一个 isQuickRecommended —— 一处规则,两个调用点。
|
|
@@ -1054,13 +1060,17 @@ export function toPersistedState(state: StoreState): PersistedState {
|
|
|
1054
1060
|
},
|
|
1055
1061
|
sync: {
|
|
1056
1062
|
channel: sync.channel,
|
|
1057
|
-
repoUrl
|
|
1058
|
-
|
|
1063
|
+
// 同步地址与报告也过值级脱敏:repoUrl/webdavUrl 里若被粘贴进 userinfo 凭据
|
|
1064
|
+
// (https://oauth2:<token>@host/…),落 sessionStorage 就等于把凭据写进浏览器。
|
|
1065
|
+
// 宿主 validateRepoUrl/validateWebDavUrl 本来就拒绝这种地址,所以这里只影响掩码,不改合法输入。
|
|
1066
|
+
// webdavUsername 是**非敏感**字段(保留可读),刻意不掩(掩了会让回填的表单变 ***)。
|
|
1067
|
+
repoUrl: redactPersistedValue(sync.repoUrl),
|
|
1068
|
+
webdavUrl: redactPersistedValue(sync.webdavUrl),
|
|
1059
1069
|
webdavUsername: sync.webdavUsername,
|
|
1060
1070
|
byChannel: channelMapOf((ch) => pickChannel(sync.byChannel[ch])),
|
|
1061
|
-
pushReport: sync.pushReport,
|
|
1062
|
-
pullReport: sync.pullReport,
|
|
1063
|
-
pushPreview: sync.pushPreview,
|
|
1071
|
+
pushReport: redactPersistedValue(sync.pushReport),
|
|
1072
|
+
pullReport: redactPersistedValue(sync.pullReport),
|
|
1073
|
+
pushPreview: redactPersistedValue(sync.pushPreview),
|
|
1064
1074
|
confirmSession: redactPersistedValue(sync.confirmSession),
|
|
1065
1075
|
confirmDecisions: sync.confirmDecisions,
|
|
1066
1076
|
lastRestoreId: sync.lastRestoreId,
|
|
@@ -1456,6 +1466,50 @@ export class RunStore {
|
|
|
1456
1466
|
this.notify()
|
|
1457
1467
|
}
|
|
1458
1468
|
|
|
1469
|
+
/**
|
|
1470
|
+
* 落盘节流(默认 ~1s 合并):导入/同步期间 watchRunning 每 500ms 一次 patch,每次都
|
|
1471
|
+
* `JSON.stringify(toPersistedState(state))` 整份序列化 + 写 sessionStorage —— 大 plan 下这是
|
|
1472
|
+
* 可观测的开销。合并窗口内的多次 patch 只落盘一次;`pagehide` 前强制 flush(见 saveSoon)。
|
|
1473
|
+
*/
|
|
1474
|
+
private saveTimer: ReturnType<typeof setTimeout> | null = null
|
|
1475
|
+
/** 是否已就落盘失败提示过一次(配额满时只提示一次,绝不每 patch 弹)。 */
|
|
1476
|
+
private saveFailureNotified = false
|
|
1477
|
+
/** 落盘失败的可见通道(壳层注入本地化文案;缺省 null = 与改造前一致:静默)。 */
|
|
1478
|
+
private saveErrorNotifier: (() => void) | null = null
|
|
1479
|
+
/** pagehide 只绑一次。 */
|
|
1480
|
+
private flushBound = false
|
|
1481
|
+
|
|
1482
|
+
/** 壳层注入「落盘失败」的提示通道(本地化文案由壳层给,store 不持有词典)。 */
|
|
1483
|
+
setSaveErrorNotifier(fn: (() => void) | null): void {
|
|
1484
|
+
this.saveErrorNotifier = fn
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/** 合并窗口内的落盘请求(首次调用排定一次)。 */
|
|
1488
|
+
private saveSoon(): void {
|
|
1489
|
+
this.bindFlushOnHide()
|
|
1490
|
+
if (this.saveTimer !== null) return
|
|
1491
|
+
this.saveTimer = setTimeout(() => {
|
|
1492
|
+
this.saveTimer = null
|
|
1493
|
+
this.save()
|
|
1494
|
+
}, SAVE_DEBOUNCE_MS)
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/** 立即落盘(pagehide / 需要确定性写盘时调用)。 */
|
|
1498
|
+
flushSave(): void {
|
|
1499
|
+
if (this.saveTimer !== null) {
|
|
1500
|
+
clearTimeout(this.saveTimer)
|
|
1501
|
+
this.saveTimer = null
|
|
1502
|
+
}
|
|
1503
|
+
this.save()
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
private bindFlushOnHide(): void {
|
|
1507
|
+
if (this.flushBound || typeof window === 'undefined') return
|
|
1508
|
+
this.flushBound = true
|
|
1509
|
+
// 关页/切后台前把合并窗口内的改动落盘(否则节流窗口里的最后一次改动会丢)
|
|
1510
|
+
window.addEventListener('pagehide', () => { this.flushSave() })
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1459
1513
|
/** 浅合并补丁并立即持久化(保存走白名单,敏感字段不落盘)。 */
|
|
1460
1514
|
patch(patchObj: StorePatch): void {
|
|
1461
1515
|
this.state = {
|
|
@@ -1473,7 +1527,11 @@ export class RunStore {
|
|
|
1473
1527
|
task: patchObj.task !== undefined ? patchObj.task : this.state.task,
|
|
1474
1528
|
}
|
|
1475
1529
|
this.notify()
|
|
1476
|
-
|
|
1530
|
+
// **只合并导入进度**(轮询每 500ms 一次,载荷含最多 500 行日志 —— 这才是真正的开销)。
|
|
1531
|
+
// 其余 patch(含 export.progress)一律立即落盘:既有语义与既有测试都按「patch 后已同步持久化」断言,
|
|
1532
|
+
// 扩大合并范围会静默改变那些行为。
|
|
1533
|
+
if (patchObj.import?.progress !== undefined) this.saveSoon()
|
|
1534
|
+
else this.save()
|
|
1477
1535
|
}
|
|
1478
1536
|
|
|
1479
1537
|
/** 把 ImportWizard 控制器的 snapshot() 镜像进 store(wizard 动作后调用)。 */
|
|
@@ -1688,7 +1746,12 @@ export class RunStore {
|
|
|
1688
1746
|
try {
|
|
1689
1747
|
this.storage.setItem(STATE_KEY, JSON.stringify(toPersistedState(this.state)))
|
|
1690
1748
|
} catch {
|
|
1691
|
-
//
|
|
1749
|
+
// 存储满/不可用:状态仍完整存在于内存,但**刷新即丢** —— 首次失败时经壳层注入的通道提示一次
|
|
1750
|
+
//(未注入则与改造前一致:静默)。绝不每次 patch 都弹(配额满时 patch 很频繁)。
|
|
1751
|
+
if (!this.saveFailureNotified) {
|
|
1752
|
+
this.saveFailureNotified = true
|
|
1753
|
+
try { this.saveErrorNotifier?.() } catch { /* 提示失败不影响主流程 */ }
|
|
1754
|
+
}
|
|
1692
1755
|
}
|
|
1693
1756
|
}
|
|
1694
1757
|
|
|
@@ -1757,7 +1820,7 @@ export class RunStore {
|
|
|
1757
1820
|
running: false,
|
|
1758
1821
|
progress: null,
|
|
1759
1822
|
runId: null,
|
|
1760
|
-
error: '
|
|
1823
|
+
error: liveUiT('run.exportResultLost'),
|
|
1761
1824
|
},
|
|
1762
1825
|
})
|
|
1763
1826
|
}
|
|
@@ -1787,7 +1850,7 @@ export class RunStore {
|
|
|
1787
1850
|
conflictCollector: null,
|
|
1788
1851
|
selectedFileName: null,
|
|
1789
1852
|
errors: [],
|
|
1790
|
-
error: '
|
|
1853
|
+
error: liveUiT('run.importResultLost'),
|
|
1791
1854
|
},
|
|
1792
1855
|
})
|
|
1793
1856
|
}
|
|
@@ -1806,7 +1869,7 @@ export class RunStore {
|
|
|
1806
1869
|
this.patch({
|
|
1807
1870
|
snapshots: {
|
|
1808
1871
|
running: false,
|
|
1809
|
-
actionError: '
|
|
1872
|
+
actionError: liveUiT('run.restoreResultLost'),
|
|
1810
1873
|
},
|
|
1811
1874
|
})
|
|
1812
1875
|
}
|
|
@@ -1825,7 +1888,7 @@ export class RunStore {
|
|
|
1825
1888
|
this.patch({
|
|
1826
1889
|
recovery: {
|
|
1827
1890
|
running: false,
|
|
1828
|
-
actionError: '
|
|
1891
|
+
actionError: liveUiT('run.restoreResultLost'),
|
|
1829
1892
|
},
|
|
1830
1893
|
})
|
|
1831
1894
|
}
|
|
@@ -1955,7 +2018,7 @@ export class RunStore {
|
|
|
1955
2018
|
export: {
|
|
1956
2019
|
running: false,
|
|
1957
2020
|
progress: { stage: 'done', step: 1, total: 1 },
|
|
1958
|
-
result: { ...result, text: renderExportReport(result.report) },
|
|
2021
|
+
result: { ...result, text: renderExportReport(result.report, liveUiT) },
|
|
1959
2022
|
downloaded: false,
|
|
1960
2023
|
},
|
|
1961
2024
|
})
|
|
@@ -1966,7 +2029,7 @@ export class RunStore {
|
|
|
1966
2029
|
progress: null,
|
|
1967
2030
|
result: null,
|
|
1968
2031
|
downloaded: false,
|
|
1969
|
-
error: state.error ?? '
|
|
2032
|
+
error: state.error ?? liveUiT('run.exportUnavailable'),
|
|
1970
2033
|
},
|
|
1971
2034
|
})
|
|
1972
2035
|
}
|
|
@@ -1983,7 +2046,7 @@ export class RunStore {
|
|
|
1983
2046
|
})
|
|
1984
2047
|
} else {
|
|
1985
2048
|
this.patch({
|
|
1986
|
-
snapshots: { running: false, actionError: state.error ?? '
|
|
2049
|
+
snapshots: { running: false, actionError: state.error ?? liveUiT('run.restoreUnavailable') },
|
|
1987
2050
|
})
|
|
1988
2051
|
}
|
|
1989
2052
|
return
|
|
@@ -2033,15 +2096,15 @@ export class RunStore {
|
|
|
2033
2096
|
const restored = wizard.snapshot().step
|
|
2034
2097
|
if (restored !== 'importing') stepAfterFailure = restored
|
|
2035
2098
|
const internals = wizard as unknown as WizardInternals
|
|
2036
|
-
internals.errors = [...internals.errors, state.error ?? '
|
|
2099
|
+
internals.errors = [...internals.errors, state.error ?? liveUiT('report.importFailed')]
|
|
2037
2100
|
}
|
|
2038
2101
|
this.patch({
|
|
2039
2102
|
import: {
|
|
2040
2103
|
step: stepAfterFailure,
|
|
2041
2104
|
running: false,
|
|
2042
2105
|
progress: null,
|
|
2043
|
-
error: state.error ?? '
|
|
2044
|
-
errors: [...this.state.import.errors, state.error ?? '
|
|
2106
|
+
error: state.error ?? liveUiT('report.importFailed'),
|
|
2107
|
+
errors: [...this.state.import.errors, state.error ?? liveUiT('report.importFailed')],
|
|
2045
2108
|
},
|
|
2046
2109
|
})
|
|
2047
2110
|
}
|
|
@@ -2055,7 +2118,7 @@ export class RunStore {
|
|
|
2055
2118
|
running: false,
|
|
2056
2119
|
progress: null,
|
|
2057
2120
|
runId: null,
|
|
2058
|
-
error: '
|
|
2121
|
+
error: liveUiT('run.progressLost'),
|
|
2059
2122
|
},
|
|
2060
2123
|
})
|
|
2061
2124
|
} else if (kind === 'restore') {
|
|
@@ -2064,7 +2127,7 @@ export class RunStore {
|
|
|
2064
2127
|
this.patch({
|
|
2065
2128
|
snapshots: {
|
|
2066
2129
|
running: false,
|
|
2067
|
-
actionError: '
|
|
2130
|
+
actionError: liveUiT('run.restoreProgressLost'),
|
|
2068
2131
|
},
|
|
2069
2132
|
})
|
|
2070
2133
|
} else if (kind === 'recovery') {
|
|
@@ -2073,7 +2136,7 @@ export class RunStore {
|
|
|
2073
2136
|
this.patch({
|
|
2074
2137
|
recovery: {
|
|
2075
2138
|
running: false,
|
|
2076
|
-
actionError: '
|
|
2139
|
+
actionError: liveUiT('run.restoreProgressLost'),
|
|
2077
2140
|
},
|
|
2078
2141
|
})
|
|
2079
2142
|
} else {
|
|
@@ -2088,7 +2151,7 @@ export class RunStore {
|
|
|
2088
2151
|
conflictCollector: null,
|
|
2089
2152
|
selectedFileName: null,
|
|
2090
2153
|
errors: [],
|
|
2091
|
-
error: '
|
|
2154
|
+
error: liveUiT('run.progressLost'),
|
|
2092
2155
|
},
|
|
2093
2156
|
})
|
|
2094
2157
|
}
|
|
@@ -11,8 +11,10 @@ import { Badge, Card } from '../common/ui.tsx'
|
|
|
11
11
|
import { Select } from '../common/Select.tsx'
|
|
12
12
|
import { InfoHint } from '../common/InfoHint.tsx'
|
|
13
13
|
import {
|
|
14
|
-
autosyncIntervalMs, autosyncStatusText, computeAutosyncCountdown, formatIntervalDuration,
|
|
14
|
+
autosyncIntervalMs, autosyncSkipDetail, autosyncStatusText, computeAutosyncCountdown, formatIntervalDuration,
|
|
15
15
|
} from './sync-view.ts'
|
|
16
|
+
// 展示文本渲染前统一脱敏(与 ErrorBanner/ReportView 同规矩)
|
|
17
|
+
import { redact } from '../../security/redaction.ts'
|
|
16
18
|
import type { AutosyncInterval } from './sync-api.ts'
|
|
17
19
|
import type { ChannelSyncState } from './sync-view.ts'
|
|
18
20
|
import { autosyncIntervalKey } from '../../ui/sync-settings-view.ts'
|
|
@@ -44,6 +46,12 @@ export function AutosyncCard({ t, uiT, settings, busy, onToggleAutosync, onUpdat
|
|
|
44
46
|
const autosyncCountdownMs = settings.autosync !== null && settings.autosync.elapsedMs >= 0
|
|
45
47
|
? computeAutosyncCountdown(settings.autosync.elapsedMs, autosyncIntervalMs(settings.autosync.interval))
|
|
46
48
|
: null
|
|
49
|
+
// issue #79:本轮为什么没同步 / 失败了什么,必须看得见 —— 此前只写进 info 级日志
|
|
50
|
+
// (默认日志级别 warn),界面恒无提示,用户只能去翻 sync-autosync.json。
|
|
51
|
+
const skipDetail = settings.autosync !== null ? autosyncSkipDetail(settings.autosync, t) : null
|
|
52
|
+
const lastMessage = settings.autosync?.lastRunMessage !== undefined && settings.autosync.lastRunMessage !== ''
|
|
53
|
+
? redact(settings.autosync.lastRunMessage)
|
|
54
|
+
: null
|
|
47
55
|
return (
|
|
48
56
|
<Card>
|
|
49
57
|
<span className={css.groupLabel}>{t('autosync.title')} <InfoHint text={t('autosync.description')} label={t('common.infoHint')} /></span>
|
|
@@ -78,6 +86,22 @@ export function AutosyncCard({ t, uiT, settings, busy, onToggleAutosync, onUpdat
|
|
|
78
86
|
</Badge>
|
|
79
87
|
)}
|
|
80
88
|
</div>
|
|
89
|
+
{(skipDetail !== null || lastMessage !== null) && (
|
|
90
|
+
<div className={css.factGrid} style={{ marginTop: 8 }}>
|
|
91
|
+
{skipDetail !== null && (
|
|
92
|
+
<div className={css.factCell} style={{ gridColumn: '1 / -1' }}>
|
|
93
|
+
<span className={css.factLabel}>{t('autosync.skipReasonLabel')}</span>
|
|
94
|
+
<span className={css.factValue}>{skipDetail}</span>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
{lastMessage !== null && (
|
|
98
|
+
<div className={css.factCell} style={{ gridColumn: '1 / -1' }}>
|
|
99
|
+
<span className={css.factLabel}>{t('autosync.lastMessage')}</span>
|
|
100
|
+
<span className={css.factValue}>{lastMessage}</span>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
81
105
|
</Card>
|
|
82
106
|
)
|
|
83
107
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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(
|
|
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}
|