dsh-config-manager 0.1.39 → 0.1.41

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 (50) hide show
  1. package/lib/client.d.ts +90 -8
  2. package/lib/client.js +1904 -1086
  3. package/lib/client.js.map +1 -1
  4. package/lib/core/cache-cleaner.d.ts +39 -0
  5. package/lib/core/cache-cleaner.js +164 -0
  6. package/lib/core/cache-cleaner.js.map +1 -0
  7. package/lib/index.js +172 -3
  8. package/lib/index.js.map +1 -1
  9. package/lib/market/github-repos.d.ts +19 -8
  10. package/lib/market/github-repos.js +62 -33
  11. package/lib/market/github-repos.js.map +1 -1
  12. package/lib/market/my-repo.d.ts +111 -3
  13. package/lib/market/my-repo.js +309 -26
  14. package/lib/market/my-repo.js.map +1 -1
  15. package/lib/market/prepare.js +3 -2
  16. package/lib/market/prepare.js.map +1 -1
  17. package/lib/market/types.d.ts +2 -0
  18. package/lib/security/secret-scanner.d.ts +18 -3
  19. package/lib/security/secret-scanner.js +144 -20
  20. package/lib/security/secret-scanner.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/client/common/ConfirmDialog.tsx +135 -0
  23. package/src/client/config-manager.module.css +101 -0
  24. package/src/client/market/MarketPanel.tsx +138 -45
  25. package/src/client/market/MyConfigsView.tsx +515 -132
  26. package/src/client/market/disclaimer.test.ts +57 -0
  27. package/src/client/market/disclaimer.ts +43 -0
  28. package/src/client/market/market-api.test.ts +3 -0
  29. package/src/client/market/market-locales.ts +37 -13
  30. package/src/client/market/my-configs-api.ts +20 -2
  31. package/src/client/market/my-configs-view.test.ts +137 -2
  32. package/src/client/market/my-configs-view.ts +174 -2
  33. package/src/client/run-store.test.ts +40 -2
  34. package/src/client/run-store.ts +70 -10
  35. package/src/client/sync/SyncConfirmView.tsx +28 -22
  36. package/src/client/sync/SyncSettingsView.tsx +175 -65
  37. package/src/client/sync/sync-api.ts +19 -0
  38. package/src/client/sync/sync-locales.ts +14 -2
  39. package/src/core/cache-cleaner.test.ts +333 -0
  40. package/src/core/cache-cleaner.ts +196 -0
  41. package/src/index.ts +165 -5
  42. package/src/market/github-repos.test.ts +99 -8
  43. package/src/market/github-repos.ts +67 -35
  44. package/src/market/my-repo.test.ts +218 -27
  45. package/src/market/my-repo.ts +403 -30
  46. package/src/market/prepare.test.ts +71 -1
  47. package/src/market/prepare.ts +3 -2
  48. package/src/market/types.ts +2 -0
  49. package/src/security/secret-scanner.ts +139 -22
  50. package/src/security/security.test.ts +111 -2
package/lib/client.js CHANGED
@@ -1880,25 +1880,6 @@ function computeSyncButtons(busy, remoteReady, t = zhUiT) {
1880
1880
  pullLabel: busy === "pull" ? t("sync.pulling") : busy === "sync" ? t("sync.syncing") : t("sync.pullLabel")
1881
1881
  };
1882
1882
  }
1883
- /** 状态行渲染模型:加载 / 未配置 / 就绪(凭据 + 上次同步 + 通道)/ 错误 */
1884
- function computeSyncStatus(statusInfo, loading, error, t = zhUiT) {
1885
- if (loading) return {
1886
- kind: "loading",
1887
- text: t("sync.statusLoading")
1888
- };
1889
- if (error !== null) return {
1890
- kind: "error",
1891
- text: error
1892
- };
1893
- if (statusInfo === null || !statusInfo.configured) return {
1894
- kind: "unconfigured",
1895
- text: t("sync.statusUnconfigured")
1896
- };
1897
- return {
1898
- kind: "ready",
1899
- text: `${(statusInfo.transport?.type === "webdav" ? statusInfo.webdav?.passwordConfigured ?? false : statusInfo.credentialConfigured) ? t("sync.credConfigured") : t("sync.credMissing")} · ${statusInfo.lastSyncAt !== void 0 && statusInfo.lastSyncAt !== "" ? t("sync.lastSync", { time: formatDateTime$1(statusInfo.lastSyncAt) }) : t("sync.neverSynced")}${statusInfo.transport !== void 0 ? ` · ${statusInfo.transport.type}${statusInfo.transport.ref !== "" ? `/${statusInfo.transport.ref}` : ""}` : ""}`
1900
- };
1901
- }
1902
1883
  /** ISO-8601 → 本地可读时间(YYYY-MM-DD HH:mm;非法输入原样返回) */
1903
1884
  function formatDateTime$1(iso) {
1904
1885
  const d = new Date(iso);
@@ -2212,6 +2193,7 @@ function defaultSyncState() {
2212
2193
  pushReport: null,
2213
2194
  pullReport: null,
2214
2195
  confirmSession: null,
2196
+ confirmDecisions: null,
2215
2197
  lastRestoreId: null,
2216
2198
  error: null,
2217
2199
  loadError: null
@@ -2229,7 +2211,10 @@ function defaultMarketState() {
2229
2211
  error: null,
2230
2212
  loadError: null,
2231
2213
  myItems: null,
2232
- myItemsError: null
2214
+ myItemsError: null,
2215
+ myWizard: null,
2216
+ myInstall: null,
2217
+ myConfirmDeleteId: null
2233
2218
  };
2234
2219
  }
2235
2220
  function defaultSnapshotsState() {
@@ -2283,6 +2268,7 @@ function toSyncStoreSlice(s) {
2283
2268
  pushReport: s.pushReport,
2284
2269
  pullReport: s.pullReport,
2285
2270
  confirmSession: s.confirmSession,
2271
+ confirmDecisions: s.confirmDecisions,
2286
2272
  lastRestoreId: s.lastRestoreId,
2287
2273
  error: s.error,
2288
2274
  loadError: s.loadError
@@ -2301,7 +2287,10 @@ function toMarketStoreSlice(s) {
2301
2287
  error: s.error,
2302
2288
  loadError: s.loadError,
2303
2289
  myItems: s.myItems,
2304
- myItemsError: s.myItemsError
2290
+ myItemsError: s.myItemsError,
2291
+ myWizard: s.myWizard,
2292
+ myInstall: s.myInstall,
2293
+ myConfirmDeleteId: s.myConfirmDeleteId
2305
2294
  };
2306
2295
  }
2307
2296
  /** 从快照面板状态提取切片(结构兼容:传入 PanelState 亦可)。 */
@@ -2319,11 +2308,13 @@ function toSnapshotsStoreSlice(s) {
2319
2308
  * 与不可序列化的实例字段(conflictCollector),以及同步面板的凭据字段
2320
2309
  * (token/webdavPassword/encryptPassword/encryptPasswordConfirm/decryptPassword ——
2321
2310
  * 含 byChannel 内每通道的加密/解密密码)与瞬态字段(busy/savingConfig —— 刷新后
2322
- * 回复空闲,不把「进行中」状态带到新页面),其余原样落入 sessionStorage。
2311
+ * 回复空闲,不把「进行中」状态带到新页面);导出面板的结果字段(result/downloaded)
2312
+ * 与进行中/进度(running/progress/runId)同为内存切片瞬态一并剔除(刷新/关闭 DSH
2313
+ * 后不残留上次导出报告),其余原样落入 sessionStorage。
2323
2314
  * 这是 sessionStorage 的唯一写入路径 —— 敏感值在此被硬性隔离。
2324
2315
  */
2325
2316
  function toPersistedState(state) {
2326
- const { password: _password, passwordConfirm: _passwordConfirm, ...exportRest } = state.export;
2317
+ const { password: _password, passwordConfirm: _passwordConfirm, result: _result, downloaded: _downloaded, running: _running, progress: _progress, runId: _runId, ...exportRest } = state.export;
2327
2318
  const { secretInputs: _secretInputs, decryptPassword: _decryptPassword, decryptRefs: _decryptRefs, archiveUnlocked: _archiveUnlocked, conflictCollector: _conflictCollector, ...importRest } = state.import;
2328
2319
  const { token: _token, webdavPassword: _webdavPassword, busy: _busy, savingConfig: _savingConfig, ...syncRest } = state.sync;
2329
2320
  const stripChannelSensitive = (c) => {
@@ -2565,7 +2556,12 @@ var RunStore = class {
2565
2556
  ...defaultExportState(),
2566
2557
  ...parsed.export,
2567
2558
  password: "",
2568
- passwordConfirm: ""
2559
+ passwordConfirm: "",
2560
+ result: null,
2561
+ downloaded: false,
2562
+ running: false,
2563
+ progress: null,
2564
+ runId: null
2569
2565
  },
2570
2566
  import: {
2571
2567
  ...defaultImportState(),
@@ -2900,7 +2896,7 @@ var RunStore = class {
2900
2896
  const runStore = new RunStore();
2901
2897
  //#endregion
2902
2898
  //#region \0config-manager-css:/home/runner/work/dsh-config-manager/dsh-config-manager/src/client/config-manager.module.css.js
2903
- const css = "._367UGW_section{height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;font-size:13px;display:flex}._367UGW_sectionHeader{flex:none;align-items:center;gap:10px;display:flex}._367UGW_viewTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:1;gap:2px;display:flex}._367UGW_viewTab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}._367UGW_viewTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_viewTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_sectionBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow-y:auto}._367UGW_viewBody{flex-direction:column;gap:12px;padding:4px 2px 16px;display:flex}._367UGW_sectionTitleBlock{flex-direction:column;gap:2px;display:flex}._367UGW_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:15px;font-weight:700}._367UGW_sectionSubtitle{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}._367UGW_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;align-items:center;gap:6px;padding:6px 14px;font-size:13px;font-weight:600;display:inline-flex}._367UGW_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._367UGW_primaryButton:disabled{opacity:.5;cursor:default}._367UGW_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;align-items:center;gap:6px;padding:5px 12px;font-size:12px;display:inline-flex}._367UGW_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._367UGW_ghostButton:disabled{opacity:.45;cursor:default}._367UGW_dangerButton{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._367UGW_dangerButton:hover:not(:disabled){background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-foreground)}._367UGW_dangerButton:disabled{opacity:.45;cursor:default}._367UGW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}._367UGW_badgeInfo{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_badgeOk{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_badgeWarn{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_badgeError{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}._367UGW_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_banner[data-kind=warn]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}._367UGW_field{flex-direction:column;gap:5px;display:flex}._367UGW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._367UGW_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._367UGW_input:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_input::placeholder{color:var(--dsw-alias-label-tertiary)}._367UGW_input:disabled{opacity:.55}._367UGW_select{-webkit-appearance:none;appearance:none;color:var(--dsw-alias-label-primary);background-color:var(--dsw-specific-input-major);background-image:linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-secondary) 50%), linear-gradient(135deg, var(--dsw-alias-label-secondary) 50%, transparent 50%);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background-position:calc(100% - 17px) calc(50% - 1px),calc(100% - 12px) calc(50% - 1px);background-repeat:no-repeat;background-size:5px 5px,5px 5px;border-radius:8px;outline:none;max-width:100%;padding:7px 30px 7px 10px;font-family:inherit;font-size:13px}._367UGW_select:hover:not(:disabled){border-color:var(--dsw-alias-border-l1)}._367UGW_select:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_select:disabled{opacity:.55;cursor:default}._367UGW_select option{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._367UGW_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px;line-height:1.5}._367UGW_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._367UGW_checkboxRow{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:flex-start;gap:8px;font-size:13px;display:flex}._367UGW_checkboxRow input{flex:none;margin-top:2px}._367UGW_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}._367UGW_secretFields{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;padding-top:4px;display:grid}._367UGW_hiddenFile{display:none}._367UGW_modeTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}._367UGW_modeTab{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px;font-size:12.5px}._367UGW_modeTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_modeTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_modeHint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}._367UGW_groupList{flex-direction:column;gap:10px;display:flex}._367UGW_groupCard{gap:8px}._367UGW_groupHeader{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}._367UGW_groupLabel{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:700}._367UGW_groupNote{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_groupItems{flex-direction:column;gap:6px;display:flex}._367UGW_categoryItem{flex-wrap:wrap;align-items:center;gap:8px;display:inline-flex}._367UGW_categoryName{font-weight:500}._367UGW_categoryDesc{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_optionsCard{gap:8px}._367UGW_optionsHeader{color:var(--dsw-alias-label-primary);letter-spacing:.02em;margin-bottom:2px;font-size:12px;font-weight:600}._367UGW_warnList{margin:4px 0 0;padding-left:18px}._367UGW_actionRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_progressBlock{flex-direction:column;flex:none;gap:6px;display:flex}._367UGW_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}._367UGW_progressLabel{font-weight:600}._367UGW_progressDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;max-width:320px;overflow:hidden}._367UGW_progressBadge{font-variant-numeric:tabular-nums;white-space:nowrap;border-radius:999px;align-items:center;gap:2px;padding:1px 8px;font-size:11px;line-height:18px;display:inline-flex}._367UGW_progressBadgeSection{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent);color:var(--dsw-alias-state-business-primary)}._367UGW_progressBadgeCount{background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent);color:var(--dsw-alias-state-info-primary)}._367UGW_progressPercent{font-variant-numeric:tabular-nums;margin-left:auto}._367UGW_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}._367UGW_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}._367UGW_progressBarDone{background:var(--dsw-alias-state-success-primary)}._367UGW_progressIndeterminate{width:40%;animation:1s ease-in-out infinite _367UGW_dshCmIndeterminate}@keyframes _367UGW_dshCmIndeterminate{0%{margin-left:-40%}to{margin-left:100%}}._367UGW_reportView{flex-direction:column;gap:10px;display:flex}._367UGW_statRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}._367UGW_reportText{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:260px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_reportFooter{justify-content:flex-end;gap:8px;display:flex}._367UGW_rollbackBox{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_rollbackBox ._367UGW_reportText{border-color:var(--dsw-alias-border-l2)}._367UGW_errorBanner{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_errorTitle{color:var(--dsw-alias-state-error-primary);font-size:13px;font-weight:700}._367UGW_errorReason{white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorAction{color:var(--dsw-alias-label-secondary);font-size:12.5px}._367UGW_errorActionLabel{color:var(--dsw-alias-state-business-primary)}._367UGW_errorItem{color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorFooter{justify-content:flex-end;display:flex}._367UGW_errorList{flex-direction:column;gap:4px;display:flex}._367UGW_errorLine{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-state-error-primary);border-radius:6px;margin:0;padding:6px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictList{flex-direction:column;gap:10px;display:flex}._367UGW_conflictItem{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_conflictHead{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._367UGW_conflictId{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:600}._367UGW_severityError{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);border-radius:999px;padding:0 6px;font-size:10.5px}._367UGW_conflictDetail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);border-radius:6px;margin:0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictOptions{flex-wrap:wrap;gap:14px;display:flex}._367UGW_pathMappingList{flex-direction:column;gap:8px;display:flex}._367UGW_pathRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;padding:10px 12px;display:grid}._367UGW_pathOld,._367UGW_pathNew{flex-direction:column;gap:4px;min-width:0;display:flex}._367UGW_pathValue{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:hidden}._367UGW_pathIssueKind{color:var(--dsw-alias-state-warn-primary);font-size:10.5px}._367UGW_secretsList{flex-direction:column;gap:10px;display:flex}._367UGW_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:22px 12px;font-size:12.5px}._367UGW_spinnerWrap{align-items:center;gap:6px;display:inline-flex}._367UGW_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite _367UGW_dshCmSpin;display:inline-block}._367UGW_spinnerLabel{color:var(--dsw-alias-label-secondary);font-size:12px}@keyframes _367UGW_dshCmSpin{to{transform:rotate(360deg)}}._367UGW_snapshotList{flex-direction:column;gap:2px;display:flex}._367UGW_snapshotRowHeader,._367UGW_snapshotRow{grid-template-columns:minmax(120px,1.2fr) minmax(140px,1.6fr) 90px 56px 56px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;display:grid}._367UGW_snapshotRowHeader{color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l1);font-size:11px}._367UGW_snapshotRow{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px}._367UGW_snapshotRow:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_snapshotRow[data-active]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent)}._367UGW_warnText{color:var(--dsw-alias-state-warn-primary)}._367UGW_reportList{color:var(--dsw-alias-label-primary);flex-direction:column;gap:3px;margin:8px 0 4px;padding-left:18px;font-size:12px;display:flex}._367UGW_kindTag{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:1px 6px;font-size:10.5px;display:inline-block}._367UGW_planScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 8px;overflow-y:auto}._367UGW_planScroll ._367UGW_reportList{margin-top:6px}._367UGW_reportScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:220px;padding:2px 10px 6px;overflow-y:auto}._367UGW_reportScroll ._367UGW_reportList{margin-top:4px}._367UGW_confirmScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_confirmScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_pullScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_pullScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_rowActions{gap:8px;margin-top:10px;display:flex}._367UGW_diffScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:240px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_aboutLinkRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_aboutAuthor{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:4px 8px;font-size:13px;text-decoration:none;display:inline-flex}._367UGW_aboutAuthor:hover{background:var(--dsw-alias-interactive-bg-hover)}";
2899
+ const css = "._367UGW_section{height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;font-size:13px;display:flex}._367UGW_sectionHeader{flex:none;align-items:center;gap:10px;display:flex}._367UGW_viewTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:1;gap:2px;display:flex}._367UGW_viewTab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}._367UGW_viewTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_viewTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_sectionBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow-y:auto}._367UGW_viewBody{flex-direction:column;gap:12px;padding:4px 2px 16px;display:flex}._367UGW_sectionTitleBlock{flex-direction:column;gap:2px;display:flex}._367UGW_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:15px;font-weight:700}._367UGW_sectionSubtitle{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}._367UGW_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;align-items:center;gap:6px;padding:6px 14px;font-size:13px;font-weight:600;display:inline-flex}._367UGW_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._367UGW_primaryButton:disabled{opacity:.5;cursor:default}._367UGW_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;align-items:center;gap:6px;padding:5px 12px;font-size:12px;display:inline-flex}._367UGW_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._367UGW_ghostButton:disabled{opacity:.45;cursor:default}._367UGW_dangerButton{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._367UGW_dangerButton:hover:not(:disabled){background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-foreground)}._367UGW_dangerButton:disabled{opacity:.45;cursor:default}._367UGW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}._367UGW_badgeInfo{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_badgeOk{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_badgeWarn{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_badgeError{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}._367UGW_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_banner[data-kind=warn]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}._367UGW_wizardCard{gap:14px}._367UGW_wizardCard ._367UGW_field{gap:8px}._367UGW_field{flex-direction:column;gap:5px;display:flex}._367UGW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._367UGW_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._367UGW_input:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_input::placeholder{color:var(--dsw-alias-label-tertiary)}._367UGW_input:disabled{opacity:.55}._367UGW_select{-webkit-appearance:none;appearance:none;color:var(--dsw-alias-label-primary);background-color:var(--dsw-specific-input-major);background-image:linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-secondary) 50%), linear-gradient(135deg, var(--dsw-alias-label-secondary) 50%, transparent 50%);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background-position:calc(100% - 17px) calc(50% - 1px),calc(100% - 12px) calc(50% - 1px);background-repeat:no-repeat;background-size:5px 5px,5px 5px;border-radius:8px;outline:none;max-width:100%;padding:7px 30px 7px 10px;font-family:inherit;font-size:13px}._367UGW_select:hover:not(:disabled){border-color:var(--dsw-alias-border-l1)}._367UGW_select:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_select:disabled{opacity:.55;cursor:default}._367UGW_select option{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._367UGW_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px;line-height:1.5}._367UGW_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._367UGW_checkboxRow{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:flex-start;gap:8px;font-size:13px;display:flex}._367UGW_checkboxRow input{flex:none;margin-top:2px}._367UGW_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}._367UGW_secretFields{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;padding-top:4px;display:grid}._367UGW_hiddenFile{display:none}._367UGW_modeTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}._367UGW_modeTab{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px;font-size:12.5px}._367UGW_modeTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_modeTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_modeHint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}._367UGW_groupList{flex-direction:column;gap:10px;display:flex}._367UGW_groupCard{gap:8px}._367UGW_groupHeader{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}._367UGW_groupLabel{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:700}._367UGW_groupNote{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_groupItems{flex-direction:column;gap:6px;display:flex}._367UGW_categoryItem{flex-wrap:wrap;align-items:center;gap:8px;display:inline-flex}._367UGW_categoryName{font-weight:500}._367UGW_categoryDesc{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_optionsCard{gap:8px}._367UGW_optionsHeader{color:var(--dsw-alias-label-primary);letter-spacing:.02em;margin-bottom:2px;font-size:12px;font-weight:600}._367UGW_warnList{margin:4px 0 0;padding-left:18px}._367UGW_actionRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_progressBlock{flex-direction:column;flex:none;gap:6px;display:flex}._367UGW_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}._367UGW_progressLabel{font-weight:600}._367UGW_progressDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;max-width:320px;overflow:hidden}._367UGW_progressBadge{font-variant-numeric:tabular-nums;white-space:nowrap;border-radius:999px;align-items:center;gap:2px;padding:1px 8px;font-size:11px;line-height:18px;display:inline-flex}._367UGW_progressBadgeSection{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent);color:var(--dsw-alias-state-business-primary)}._367UGW_progressBadgeCount{background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent);color:var(--dsw-alias-state-info-primary)}._367UGW_progressPercent{font-variant-numeric:tabular-nums;margin-left:auto}._367UGW_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}._367UGW_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}._367UGW_progressBarDone{background:var(--dsw-alias-state-success-primary)}._367UGW_progressIndeterminate{width:40%;animation:1s ease-in-out infinite _367UGW_dshCmIndeterminate}@keyframes _367UGW_dshCmIndeterminate{0%{margin-left:-40%}to{margin-left:100%}}._367UGW_reportView{flex-direction:column;gap:10px;display:flex}._367UGW_statRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}._367UGW_reportText{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:260px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_reportFooter{justify-content:flex-end;gap:8px;display:flex}._367UGW_rollbackBox{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_rollbackBox ._367UGW_reportText{border-color:var(--dsw-alias-border-l2)}._367UGW_errorBanner{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_errorTitle{color:var(--dsw-alias-state-error-primary);font-size:13px;font-weight:700}._367UGW_errorReason{white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorAction{color:var(--dsw-alias-label-secondary);font-size:12.5px}._367UGW_errorActionLabel{color:var(--dsw-alias-state-business-primary)}._367UGW_errorItem{color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorFooter{justify-content:flex-end;display:flex}._367UGW_errorList{flex-direction:column;gap:4px;display:flex}._367UGW_errorLine{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-state-error-primary);border-radius:6px;margin:0;padding:6px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictList{flex-direction:column;gap:10px;display:flex}._367UGW_conflictItem{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_conflictHead{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._367UGW_conflictId{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:600}._367UGW_severityError{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);border-radius:999px;padding:0 6px;font-size:10.5px}._367UGW_conflictDetail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);border-radius:6px;margin:0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictOptions{flex-wrap:wrap;gap:14px;display:flex}._367UGW_pathMappingList{flex-direction:column;gap:8px;display:flex}._367UGW_pathRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;padding:10px 12px;display:grid}._367UGW_pathOld,._367UGW_pathNew{flex-direction:column;gap:4px;min-width:0;display:flex}._367UGW_pathValue{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:hidden}._367UGW_pathIssueKind{color:var(--dsw-alias-state-warn-primary);font-size:10.5px}._367UGW_secretsList{flex-direction:column;gap:10px;display:flex}._367UGW_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:22px 12px;font-size:12.5px}._367UGW_spinnerWrap{align-items:center;gap:6px;display:inline-flex}._367UGW_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite _367UGW_dshCmSpin;display:inline-block}._367UGW_spinnerLabel{color:var(--dsw-alias-label-secondary);font-size:12px}@keyframes _367UGW_dshCmSpin{to{transform:rotate(360deg)}}._367UGW_snapshotList{flex-direction:column;gap:2px;display:flex}._367UGW_snapshotRowHeader,._367UGW_snapshotRow{grid-template-columns:minmax(120px,1.2fr) minmax(140px,1.6fr) 90px 56px 56px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;display:grid}._367UGW_snapshotRowHeader{color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l1);font-size:11px}._367UGW_snapshotRow{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px}._367UGW_snapshotRow:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_snapshotRow[data-active]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent)}._367UGW_warnText{color:var(--dsw-alias-state-warn-primary)}._367UGW_reportList{color:var(--dsw-alias-label-primary);flex-direction:column;gap:3px;margin:8px 0 4px;padding-left:18px;font-size:12px;display:flex}._367UGW_kindTag{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:1px 6px;font-size:10.5px;display:inline-block}._367UGW_planScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 8px;overflow-y:auto}._367UGW_planScroll ._367UGW_reportList{margin-top:6px}._367UGW_reportScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:220px;padding:2px 10px 6px;overflow-y:auto}._367UGW_reportScroll ._367UGW_reportList{margin-top:4px}._367UGW_confirmScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_confirmScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_pullScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_pullScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_rowActions{gap:8px;margin-top:10px;display:flex}._367UGW_diffScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:240px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_aboutLinkRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_aboutAuthor{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:4px 8px;font-size:13px;text-decoration:none;display:inline-flex}._367UGW_aboutAuthor:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_dialogMask{z-index:100;background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, transparent);justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}._367UGW_dialogCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:min(420px,100%);max-height:80vh;box-shadow:0 8px 24px color-mix(in srgb, var(--dsw-alias-bg-base) 40%, transparent);border-radius:10px;flex-direction:column;gap:10px;padding:14px;display:flex}._367UGW_dialogHeader{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._367UGW_dialogBody{overscroll-behavior:contain;max-height:240px;color:var(--dsw-alias-label-primary);white-space:pre-wrap;font-size:13px;line-height:1.6;overflow-y:auto}._367UGW_dialogWide{width:min(560px,100%)}._367UGW_dialogBodyScroll{overscroll-behavior:contain;max-height:70vh;color:var(--dsw-alias-label-primary);white-space:normal;font-size:13px;line-height:1.6;overflow-y:auto}._367UGW_dialogHeaderRow{justify-content:space-between;align-items:center;gap:10px;display:flex}._367UGW_dialogClose{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:2px 8px;font-size:16px;line-height:1}._367UGW_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-hover)}";
2904
2900
  const tagId = "dsh-config-manager/config-manager.module.css";
2905
2901
  if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"dsh-config-manager/config-manager.module.css\"]") === null) {
2906
2902
  const tag = document.createElement("style");
@@ -2910,111 +2906,120 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
2910
2906
  document.head.appendChild(tag);
2911
2907
  }
2912
2908
  var config_manager_module_css_default = {
2913
- "warnText": "_367UGW_warnText",
2914
- "progressPercent": "_367UGW_progressPercent",
2915
- "planScroll": "_367UGW_planScroll",
2916
- "pullScroll": "_367UGW_pullScroll",
2917
- "pathIssueKind": "_367UGW_pathIssueKind",
2918
- "reportScroll": "_367UGW_reportScroll",
2919
- "primaryButton": "_367UGW_primaryButton",
2920
- "progressMeta": "_367UGW_progressMeta",
2921
- "errorLine": "_367UGW_errorLine",
2922
- "progressBadgeSection": "_367UGW_progressBadgeSection",
2909
+ "secretFields": "_367UGW_secretFields",
2910
+ "warnList": "_367UGW_warnList",
2923
2911
  "reportView": "_367UGW_reportView",
2924
- "progressBadge": "_367UGW_progressBadge",
2925
- "snapshotRow": "_367UGW_snapshotRow",
2926
- "groupList": "_367UGW_groupList",
2927
- "badgeInfo": "_367UGW_badgeInfo",
2928
- "statRow": "_367UGW_statRow",
2929
- "sectionSubtitle": "_367UGW_sectionSubtitle",
2912
+ "categoryDesc": "_367UGW_categoryDesc",
2913
+ "aboutLinkRow": "_367UGW_aboutLinkRow",
2914
+ "dialogClose": "_367UGW_dialogClose",
2915
+ "dialogHeaderRow": "_367UGW_dialogHeaderRow",
2916
+ "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
2917
+ "formError": "_367UGW_formError",
2918
+ "primaryButton": "_367UGW_primaryButton",
2919
+ "dshCmSpin": "_367UGW_dshCmSpin",
2920
+ "progressBar": "_367UGW_progressBar",
2921
+ "groupHeader": "_367UGW_groupHeader",
2930
2922
  "select": "_367UGW_select",
2931
- "errorReason": "_367UGW_errorReason",
2932
- "badgeError": "_367UGW_badgeError",
2933
- "checkboxRow": "_367UGW_checkboxRow",
2934
- "hiddenFile": "_367UGW_hiddenFile",
2935
- "progressLabel": "_367UGW_progressLabel",
2936
- "spinner": "_367UGW_spinner",
2923
+ "optionsHeader": "_367UGW_optionsHeader",
2924
+ "progressMeta": "_367UGW_progressMeta",
2925
+ "progressIndeterminate": "_367UGW_progressIndeterminate",
2926
+ "reportText": "_367UGW_reportText",
2927
+ "planScroll": "_367UGW_planScroll",
2928
+ "errorTitle": "_367UGW_errorTitle",
2929
+ "sectionHeader": "_367UGW_sectionHeader",
2937
2930
  "reportList": "_367UGW_reportList",
2938
- "warnList": "_367UGW_warnList",
2939
- "aboutLinkRow": "_367UGW_aboutLinkRow",
2940
- "banner": "_367UGW_banner",
2941
- "badgeOk": "_367UGW_badgeOk",
2942
- "conflictItem": "_367UGW_conflictItem",
2943
- "errorBanner": "_367UGW_errorBanner",
2944
- "conflictId": "_367UGW_conflictId",
2931
+ "conflictHead": "_367UGW_conflictHead",
2932
+ "progressBadgeSection": "_367UGW_progressBadgeSection",
2933
+ "rollbackBox": "_367UGW_rollbackBox",
2945
2934
  "secretsList": "_367UGW_secretsList",
2946
- "sectionTitleBlock": "_367UGW_sectionTitleBlock",
2935
+ "sectionTitle": "_367UGW_sectionTitle",
2936
+ "hiddenFile": "_367UGW_hiddenFile",
2937
+ "errorLine": "_367UGW_errorLine",
2947
2938
  "input": "_367UGW_input",
2948
- "spinnerLabel": "_367UGW_spinnerLabel",
2949
- "progressBarDone": "_367UGW_progressBarDone",
2950
- "categoryDesc": "_367UGW_categoryDesc",
2951
- "sectionBody": "_367UGW_sectionBody",
2952
- "badge": "_367UGW_badge",
2953
- "groupItems": "_367UGW_groupItems",
2939
+ "conflictOptions": "_367UGW_conflictOptions",
2940
+ "viewBody": "_367UGW_viewBody",
2941
+ "dialogBodyScroll": "_367UGW_dialogBodyScroll",
2942
+ "errorActionLabel": "_367UGW_errorActionLabel",
2943
+ "pathValue": "_367UGW_pathValue",
2944
+ "pathNew": "_367UGW_pathNew",
2954
2945
  "progressTrack": "_367UGW_progressTrack",
2946
+ "wizardCard": "_367UGW_wizardCard",
2947
+ "snapshotRowHeader": "_367UGW_snapshotRowHeader",
2948
+ "progressBadge": "_367UGW_progressBadge",
2949
+ "pathMappingList": "_367UGW_pathMappingList",
2950
+ "snapshotRow": "_367UGW_snapshotRow",
2951
+ "dialogMask": "_367UGW_dialogMask",
2952
+ "categoryItem": "_367UGW_categoryItem",
2953
+ "sectionBody": "_367UGW_sectionBody",
2955
2954
  "conflictDetail": "_367UGW_conflictDetail",
2956
- "errorActionLabel": "_367UGW_errorActionLabel",
2957
- "kindTag": "_367UGW_kindTag",
2958
- "progressBlock": "_367UGW_progressBlock",
2959
- "badgeWarn": "_367UGW_badgeWarn",
2960
- "groupNote": "_367UGW_groupNote",
2961
- "groupHeader": "_367UGW_groupHeader",
2962
- "actionRow": "_367UGW_actionRow",
2963
- "optionsCard": "_367UGW_optionsCard",
2964
- "pathOld": "_367UGW_pathOld",
2965
- "errorItem": "_367UGW_errorItem",
2955
+ "progressBadgeCount": "_367UGW_progressBadgeCount",
2966
2956
  "snapshotList": "_367UGW_snapshotList",
2967
- "pathNew": "_367UGW_pathNew",
2968
2957
  "confirmScroll": "_367UGW_confirmScroll",
2969
- "conflictList": "_367UGW_conflictList",
2970
- "categoryName": "_367UGW_categoryName",
2971
- "groupLabel": "_367UGW_groupLabel",
2972
- "errorList": "_367UGW_errorList",
2973
- "card": "_367UGW_card",
2974
- "sectionTitle": "_367UGW_sectionTitle",
2975
- "optionsHeader": "_367UGW_optionsHeader",
2976
2958
  "aboutAuthor": "_367UGW_aboutAuthor",
2977
- "progressBadgeCount": "_367UGW_progressBadgeCount",
2978
- "fieldLabel": "_367UGW_fieldLabel",
2979
- "reportText": "_367UGW_reportText",
2980
- "severityError": "_367UGW_severityError",
2981
- "formError": "_367UGW_formError",
2982
- "pathMappingList": "_367UGW_pathMappingList",
2983
- "empty": "_367UGW_empty",
2984
- "spinnerWrap": "_367UGW_spinnerWrap",
2985
- "field": "_367UGW_field",
2986
- "secretFields": "_367UGW_secretFields",
2987
- "rollbackBox": "_367UGW_rollbackBox",
2988
2959
  "rowActions": "_367UGW_rowActions",
2989
- "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
2990
- "ghostButton": "_367UGW_ghostButton",
2991
- "diffScroll": "_367UGW_diffScroll",
2992
- "viewTab": "_367UGW_viewTab",
2993
- "modeTab": "_367UGW_modeTab",
2994
- "radioLabel": "_367UGW_radioLabel",
2995
- "groupCard": "_367UGW_groupCard",
2960
+ "dialogCard": "_367UGW_dialogCard",
2961
+ "groupLabel": "_367UGW_groupLabel",
2962
+ "modeHint": "_367UGW_modeHint",
2963
+ "actionRow": "_367UGW_actionRow",
2964
+ "statRow": "_367UGW_statRow",
2996
2965
  "reportFooter": "_367UGW_reportFooter",
2997
- "sectionHeader": "_367UGW_sectionHeader",
2998
- "pathValue": "_367UGW_pathValue",
2999
- "progressIndeterminate": "_367UGW_progressIndeterminate",
3000
- "dangerButton": "_367UGW_dangerButton",
2966
+ "errorBanner": "_367UGW_errorBanner",
2967
+ "diffScroll": "_367UGW_diffScroll",
2968
+ "badgeOk": "_367UGW_badgeOk",
2969
+ "optionsCard": "_367UGW_optionsCard",
3001
2970
  "hint": "_367UGW_hint",
3002
- "modeHint": "_367UGW_modeHint",
3003
- "categoryItem": "_367UGW_categoryItem",
2971
+ "card": "_367UGW_card",
2972
+ "badgeWarn": "_367UGW_badgeWarn",
2973
+ "pullScroll": "_367UGW_pullScroll",
2974
+ "pathIssueKind": "_367UGW_pathIssueKind",
2975
+ "ghostButton": "_367UGW_ghostButton",
2976
+ "conflictId": "_367UGW_conflictId",
2977
+ "severityError": "_367UGW_severityError",
2978
+ "radioLabel": "_367UGW_radioLabel",
2979
+ "checkboxRow": "_367UGW_checkboxRow",
2980
+ "categoryName": "_367UGW_categoryName",
2981
+ "pathOld": "_367UGW_pathOld",
2982
+ "errorList": "_367UGW_errorList",
2983
+ "conflictList": "_367UGW_conflictList",
2984
+ "progressLabel": "_367UGW_progressLabel",
2985
+ "sectionTitleBlock": "_367UGW_sectionTitleBlock",
2986
+ "section": "_367UGW_section",
2987
+ "viewTabs": "_367UGW_viewTabs",
3004
2988
  "errorFooter": "_367UGW_errorFooter",
2989
+ "dialogWide": "_367UGW_dialogWide",
2990
+ "dialogBody": "_367UGW_dialogBody",
2991
+ "modeTabs": "_367UGW_modeTabs",
2992
+ "progressBlock": "_367UGW_progressBlock",
2993
+ "errorItem": "_367UGW_errorItem",
2994
+ "empty": "_367UGW_empty",
2995
+ "kindTag": "_367UGW_kindTag",
2996
+ "progressPercent": "_367UGW_progressPercent",
2997
+ "fieldLabel": "_367UGW_fieldLabel",
2998
+ "groupNote": "_367UGW_groupNote",
2999
+ "errorReason": "_367UGW_errorReason",
3000
+ "viewTab": "_367UGW_viewTab",
3001
+ "spinner": "_367UGW_spinner",
3002
+ "sectionSubtitle": "_367UGW_sectionSubtitle",
3003
+ "spinnerLabel": "_367UGW_spinnerLabel",
3004
+ "badgeError": "_367UGW_badgeError",
3005
+ "warnText": "_367UGW_warnText",
3006
+ "field": "_367UGW_field",
3007
+ "reportScroll": "_367UGW_reportScroll",
3005
3008
  "pathRow": "_367UGW_pathRow",
3009
+ "badgeInfo": "_367UGW_badgeInfo",
3010
+ "modeTab": "_367UGW_modeTab",
3011
+ "progressBarDone": "_367UGW_progressBarDone",
3012
+ "badge": "_367UGW_badge",
3013
+ "groupList": "_367UGW_groupList",
3014
+ "dialogHeader": "_367UGW_dialogHeader",
3006
3015
  "progressDetail": "_367UGW_progressDetail",
3007
- "progressBar": "_367UGW_progressBar",
3008
- "snapshotRowHeader": "_367UGW_snapshotRowHeader",
3009
- "viewBody": "_367UGW_viewBody",
3010
- "modeTabs": "_367UGW_modeTabs",
3011
- "errorTitle": "_367UGW_errorTitle",
3016
+ "spinnerWrap": "_367UGW_spinnerWrap",
3012
3017
  "errorAction": "_367UGW_errorAction",
3013
- "conflictHead": "_367UGW_conflictHead",
3014
- "section": "_367UGW_section",
3015
- "viewTabs": "_367UGW_viewTabs",
3016
- "conflictOptions": "_367UGW_conflictOptions",
3017
- "dshCmSpin": "_367UGW_dshCmSpin"
3018
+ "groupItems": "_367UGW_groupItems",
3019
+ "dangerButton": "_367UGW_dangerButton",
3020
+ "banner": "_367UGW_banner",
3021
+ "groupCard": "_367UGW_groupCard",
3022
+ "conflictItem": "_367UGW_conflictItem"
3018
3023
  };
3019
3024
  //#endregion
3020
3025
  //#region src/client/common/ui.tsx
@@ -5020,6 +5025,7 @@ const SYNC_API = {
5020
5025
  githubStart: "/api/dsh-config-manager/sync/github/start",
5021
5026
  githubPoll: "/api/dsh-config-manager/sync/github/poll",
5022
5027
  githubCancel: "/api/dsh-config-manager/sync/github/cancel",
5028
+ githubValidate: "/api/dsh-config-manager/sync/github/validate",
5023
5029
  history: "/api/dsh-config-manager/sync/history",
5024
5030
  snapshotsList: "/api/dsh-config-manager/sync/snapshots-list",
5025
5031
  sync: "/api/dsh-config-manager/sync/sync",
@@ -5094,6 +5100,11 @@ var SyncApi = class {
5094
5100
  async githubCancel(flowId) {
5095
5101
  return postJson$2(SYNC_API.githubCancel, { flowId }, this.t);
5096
5102
  }
5103
+ /** GitHub token 有效性校验(判定「是否已登录」:token 存在且 GitHub API 接受;
5104
+ * 401 → valid:false 引导重新登录;非 401 错误向上抛,UI 兜底不误判登出) */
5105
+ async githubValidate() {
5106
+ return postJson$2(SYNC_API.githubValidate, {}, this.t);
5107
+ }
5097
5108
  /** 同步历史:列出本地祖先快照 + 自动同步执行记录(按 createdAt 倒序合并)。 */
5098
5109
  async history() {
5099
5110
  return readJson$2(await fetch(SYNC_API.history), this.t);
@@ -5373,15 +5384,20 @@ function AutosyncRow({ entry, t }) {
5373
5384
  * 全部渲染模型来自 ./sync-view.ts 纯函数(node 单测覆盖),组件只做装配 + 交互状态。
5374
5385
  */
5375
5386
  function SyncConfirmView(props) {
5376
- const { api, syncSessionId, snapshotId, items, needsReview, compatibility, t, onCancel, onRollbackDone } = props;
5377
- const [states, setStates] = (0, react.useState)(() => {
5378
- const init = {};
5379
- for (const it of items) init[it.itemId] = {
5387
+ const { api, syncSessionId, snapshotId, items, needsReview, compatibility, t, decisions, onDecisionsChange, onCancel, onRollbackDone } = props;
5388
+ const states = (0, react.useMemo)(() => {
5389
+ const base = {};
5390
+ for (const it of items) base[it.itemId] = {
5380
5391
  adopted: it.defaultAdopt,
5381
5392
  resolution: void 0
5382
5393
  };
5383
- return init;
5384
- });
5394
+ if (decisions === null) return base;
5395
+ for (const [id, d] of Object.entries(decisions)) if (base[id] !== void 0) base[id] = {
5396
+ adopted: d.adopted,
5397
+ resolution: d.resolution
5398
+ };
5399
+ return base;
5400
+ }, [items, decisions]);
5385
5401
  const [phase, setPhase] = (0, react.useState)("idle");
5386
5402
  const [applyResult, setApplyResult] = (0, react.useState)(null);
5387
5403
  const [error, setError] = (0, react.useState)(null);
@@ -5391,46 +5407,46 @@ function SyncConfirmView(props) {
5391
5407
  }))), [items, states]);
5392
5408
  const displayItems = (0, react.useMemo)(() => reviewItems(items), [items]);
5393
5409
  const setAdopted = (itemId, adopted) => {
5394
- setStates((prev) => ({
5395
- ...prev,
5410
+ const existing = states[itemId] ?? {
5411
+ adopted: false,
5412
+ resolution: void 0
5413
+ };
5414
+ onDecisionsChange({
5415
+ ...decisions ?? {},
5396
5416
  [itemId]: {
5397
- ...prev[itemId],
5398
- adopted
5417
+ adopted,
5418
+ resolution: existing.resolution
5399
5419
  }
5400
- }));
5420
+ });
5401
5421
  };
5402
5422
  const setResolution = (itemId, resolution) => {
5403
- setStates((prev) => {
5404
- const existing = prev[itemId] ?? {
5423
+ const existing = states[itemId] ?? {
5424
+ adopted: false,
5425
+ resolution: void 0
5426
+ };
5427
+ onDecisionsChange({
5428
+ ...decisions ?? {},
5429
+ [itemId]: {
5430
+ adopted: existing.adopted,
5431
+ resolution
5432
+ }
5433
+ });
5434
+ };
5435
+ const applyBulkDecision = (bulk) => {
5436
+ if (bulk.length === 0) return;
5437
+ const next = { ...decisions ?? {} };
5438
+ for (const d of bulk) {
5439
+ const existing = states[d.itemId] ?? {
5405
5440
  adopted: false,
5406
5441
  resolution: void 0
5407
5442
  };
5408
- return {
5409
- ...prev,
5410
- [itemId]: {
5411
- ...existing,
5412
- resolution
5413
- }
5443
+ next[d.itemId] = {
5444
+ ...existing,
5445
+ resolution: d.resolution,
5446
+ adopted: d.adopt
5414
5447
  };
5415
- });
5416
- };
5417
- const applyBulkDecision = (decisions) => {
5418
- if (decisions.length === 0) return;
5419
- setStates((prev) => {
5420
- const next = { ...prev };
5421
- for (const d of decisions) {
5422
- const existing = prev[d.itemId] ?? {
5423
- adopted: false,
5424
- resolution: void 0
5425
- };
5426
- next[d.itemId] = {
5427
- ...existing,
5428
- resolution: d.resolution,
5429
- adopted: d.adopt
5430
- };
5431
- }
5432
- return next;
5433
- });
5448
+ }
5449
+ onDecisionsChange(next);
5434
5450
  };
5435
5451
  const runApply = async () => {
5436
5452
  const unresolved = items.find((it) => it.kind === "Conflict" && states[it.itemId]?.adopted === true && states[it.itemId]?.resolution === void 0);
@@ -5735,10 +5751,16 @@ function ApplyResultCard({ result, busy, t, onRollback }) {
5735
5751
  *
5736
5752
  * 独立设置页壳(sectionHeader/close/自身 tab)已移除 —— tab 容器由
5737
5753
  * ConfigManagerSection 统一渲染,本组件只输出内容体:
5738
- * - **通道子 tab 面板**:GitHub(git)/ WebDAV 两个子 tab(modeTabs 样式)切换,
5739
- * 两个通道的配置表单、自动同步、同步模式、是否加密、远端快照**各自独立**;
5754
+ * - **同步通道入口卡**:展示当前通道 + 配置状态 + 凭据徽章;点「配置同步通道」
5755
+ * → 弹出**通道配置弹窗**(弹窗体系与市场操作弹窗一致,DESIGN.md §8.12:
5756
+ * dialogMask + dialogCard dialogWide + dialogHeaderRow + dialogClose +
5757
+ * dialogBodyScroll,零新增样式);
5758
+ * - **通道配置弹窗**:通道子 tab(GitHub(git)/ WebDAV)切换,两个通道的
5759
+ * 配置表单、自动同步、同步模式、是否加密、远端快照**各自独立**;关闭弹窗
5760
+ * = 放弃本次操作(GitHub 登录流程进行中则一并取消,§8.12 约定);
5740
5761
  * - GitHub 子 tab:repoUrl(必填)+ 认证 token(可选,写入 DSH credentials 的提示)
5741
- * + GitHub OAuth device flow 登录(git 可执行文件固定使用系统 PATH 中的 git);
5762
+ * + **GitHub OAuth device flow 登录**(登录块跟随 git 通道配置放在弹窗内:
5763
+ * 未登录/失效时显示;git 可执行文件固定使用系统 PATH 中的 git);
5742
5764
  * - WebDAV 子 tab:url + username + password(密码写入 DSH credentials)+ 常见服务器预设;
5743
5765
  * - 私有仓库强制提示横幅(仅 git 子 tab 常驻);
5744
5766
  * - 推送按钮 → SyncPushReport;拉取按钮 → SyncPullReport.changes 差异摘要;
@@ -5791,9 +5813,11 @@ const initial$2 = {
5791
5813
  pushReport: null,
5792
5814
  pullReport: null,
5793
5815
  confirmSession: null,
5816
+ confirmDecisions: null,
5794
5817
  lastRestoreId: null,
5795
5818
  error: null,
5796
- github: initialGithub
5819
+ github: initialGithub,
5820
+ githubSignedIn: null
5797
5821
  };
5798
5822
  /**
5799
5823
  * 从 runStore 恢复上次的同步 UI 状态(切 tab 回 / 刷新后挂载)。
@@ -5827,6 +5851,7 @@ function initFromStore$1() {
5827
5851
  pushReport: s.pushReport,
5828
5852
  pullReport: s.pullReport,
5829
5853
  confirmSession: s.confirmSession,
5854
+ confirmDecisions: s.confirmDecisions,
5830
5855
  lastRestoreId: s.lastRestoreId,
5831
5856
  error: s.error,
5832
5857
  loadError: s.loadError
@@ -5839,6 +5864,8 @@ function SyncSettingsView({ api, t }) {
5839
5864
  const stateRef = (0, react.useRef)(state);
5840
5865
  /** 挂载守卫:卸载后不再 setState(store 镜像仍执行,异步结果照常落库) */
5841
5866
  const mountedRef = (0, react.useRef)(true);
5867
+ /** 通道配置弹窗开关(瞬态 UI:切 tab/刷新不持久化,弹窗不自动重开;DESIGN.md §8.12 约定) */
5868
+ const [channelOpen, setChannelOpen] = (0, react.useState)(false);
5842
5869
  /**
5843
5870
  * 统一提交入口:更新 stateRef → 挂载时 setState → **总是**镜像进 runStore。
5844
5871
  * 关键:镜像不依赖 effect flush —— 异步操作(push/pull/sync)完成回调在组件
@@ -5936,6 +5963,7 @@ function SyncSettingsView({ api, t }) {
5936
5963
  const activeCh = remembered ?? savedChannel;
5937
5964
  const preset = activeCh === "webdav" ? info.webdav?.url ?? "" : info.repoUrl ?? "";
5938
5965
  if (info.configured && preset.trim() !== "") loadSnapshots(preset, activeCh);
5966
+ validateGithub();
5939
5967
  } catch (err) {
5940
5968
  patch({
5941
5969
  loading: false,
@@ -6079,6 +6107,7 @@ function SyncSettingsView({ api, t }) {
6079
6107
  next.statusInfo = info;
6080
6108
  }
6081
6109
  commit(next);
6110
+ if (payloadToSave.transport !== "webdav" && saved.credentialConfigured) validateGithub();
6082
6111
  } catch (err) {
6083
6112
  patch({
6084
6113
  savingConfig: false,
@@ -6161,9 +6190,13 @@ function SyncSettingsView({ api, t }) {
6161
6190
  }
6162
6191
  const message = githubPollMessage(poll, uiT);
6163
6192
  if (poll.status === "success") {
6164
- patchGithub({
6165
- phase: "success",
6166
- error: null
6193
+ patch({
6194
+ githubSignedIn: true,
6195
+ github: {
6196
+ ...stateRef.current.github,
6197
+ phase: "success",
6198
+ error: null
6199
+ }
6167
6200
  });
6168
6201
  loadStatus();
6169
6202
  } else patchGithub({
@@ -6189,6 +6222,34 @@ function SyncSettingsView({ api, t }) {
6189
6222
  await api.githubCancel(flowId);
6190
6223
  } catch {}
6191
6224
  };
6225
+ /**
6226
+ * 校验 GitHub token 是否有效(「已登录」判定 → 决定登录块显隐)。
6227
+ * 挂载 / 登录成功 / 保存凭据后调用;已确认登录(githubSignedIn===true)时跳过
6228
+ * (避免反复网络调用)。401 → 未登录:显示登录块并提示重新登录;网络等其余
6229
+ * 错误 → 保持现状(不误判登出,已登录用户不被打扰;下次进入页面会再校验)。
6230
+ */
6231
+ const validateGithub = async () => {
6232
+ if (stateRef.current.githubSignedIn === true) return;
6233
+ try {
6234
+ const res = await api.githubValidate();
6235
+ patch({ githubSignedIn: res.configured && res.valid });
6236
+ } catch {}
6237
+ };
6238
+ /** 打开通道配置弹窗:登录态尚未校验时补一次校验(决定 Git 子 tab 登录块显隐)。 */
6239
+ const openChannelDialog = () => {
6240
+ setChannelOpen(true);
6241
+ if (stateRef.current.githubSignedIn === null) validateGithub();
6242
+ };
6243
+ /**
6244
+ * 关闭通道配置弹窗 = 放弃本次操作(§8.12 约定):GitHub 登录流程进行中则取消
6245
+ * (停轮询 + 通知宿主丢弃设备码登记),保存中(savingConfig)时禁止关闭。
6246
+ */
6247
+ const closeChannelDialog = () => {
6248
+ if (stateRef.current.savingConfig) return;
6249
+ const phase = stateRef.current.github.phase;
6250
+ if (phase === "starting" || phase === "waiting" || phase === "polling") runGithubCancel();
6251
+ setChannelOpen(false);
6252
+ };
6192
6253
  const runPush = async () => {
6193
6254
  patch({
6194
6255
  busy: "push",
@@ -6312,6 +6373,7 @@ function SyncSettingsView({ api, t }) {
6312
6373
  busy: "sync",
6313
6374
  error: null,
6314
6375
  confirmSession: null,
6376
+ confirmDecisions: null,
6315
6377
  lastRestoreId: null
6316
6378
  });
6317
6379
  try {
@@ -6331,6 +6393,7 @@ function SyncSettingsView({ api, t }) {
6331
6393
  patch({
6332
6394
  busy: null,
6333
6395
  confirmSession: session,
6396
+ confirmDecisions: null,
6334
6397
  token: "",
6335
6398
  webdavPassword: ""
6336
6399
  });
@@ -6345,7 +6408,10 @@ function SyncSettingsView({ api, t }) {
6345
6408
  };
6346
6409
  /** 用户取消差异确认:清除会话,复位到空闲。 */
6347
6410
  const cancelConfirm = () => {
6348
- patch({ confirmSession: null });
6411
+ patch({
6412
+ confirmSession: null,
6413
+ confirmDecisions: null
6414
+ });
6349
6415
  };
6350
6416
  /** 从 SyncConfirmView 透传的一键回滚完成信号。 */
6351
6417
  const onRollbackApplied = () => {
@@ -6395,7 +6461,6 @@ function SyncSettingsView({ api, t }) {
6395
6461
  patch({ error: err instanceof Error ? err.message : String(err) });
6396
6462
  }
6397
6463
  };
6398
- const status = computeSyncStatus(state.statusInfo, state.loading, state.loadError, uiT);
6399
6464
  /** 活动通道的远端地址是否就绪(git=repoUrl 非空;webdav=webdavUrl 非空) */
6400
6465
  const remoteReady = computeRemoteReady(state.channel, state.repoUrl, state.webdavUrl);
6401
6466
  const buttons = computeSyncButtons(state.busy, remoteReady, uiT);
@@ -6418,30 +6483,6 @@ function SyncSettingsView({ api, t }) {
6418
6483
  title: t("section.label"),
6419
6484
  subtitle: t("section.description")
6420
6485
  }),
6421
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6422
- className: config_manager_module_css_default.modeTabs,
6423
- role: "tablist",
6424
- children: channelTabModels(state.channel, state.busy !== null).map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6425
- type: "button",
6426
- role: "tab",
6427
- "aria-selected": tab.active,
6428
- "data-active": tab.active ? "" : void 0,
6429
- className: config_manager_module_css_default.modeTab,
6430
- disabled: tab.disabled,
6431
- onClick: () => {
6432
- switchChannel(tab.channel);
6433
- },
6434
- children: tab.channel === "webdav" ? t("channel.webdav") : t("channel.git")
6435
- }, tab.channel))
6436
- }),
6437
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6438
- className: config_manager_module_css_default.modeHint,
6439
- children: t("channel.perChannelHint")
6440
- }),
6441
- state.channel === "git" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
6442
- kind: "warn",
6443
- children: privateRepoHint(uiT)
6444
- }),
6445
6486
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
6446
6487
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6447
6488
  className: config_manager_module_css_default.groupLabel,
@@ -6449,265 +6490,349 @@ function SyncSettingsView({ api, t }) {
6449
6490
  }),
6450
6491
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6451
6492
  className: config_manager_module_css_default.hint,
6452
- children: t("channel.hint")
6493
+ children: t("channel.openHint")
6453
6494
  }),
6454
- state.channel === "git" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6455
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6456
- className: config_manager_module_css_default.groupLabel,
6457
- children: t("config.title")
6458
- }),
6459
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6460
- className: config_manager_module_css_default.field,
6461
- children: [
6462
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6463
- className: config_manager_module_css_default.fieldLabel,
6464
- children: t("config.repoUrl")
6465
- }),
6466
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6467
- type: "text",
6468
- className: config_manager_module_css_default.input,
6469
- value: state.repoUrl,
6470
- placeholder: "https://github.com/user/private-repo.git",
6471
- disabled: state.busy !== null,
6472
- onChange: (e) => {
6473
- patch({ repoUrl: e.target.value });
6474
- scheduleConfigSave();
6475
- }
6476
- }),
6477
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6478
- className: config_manager_module_css_default.hint,
6479
- children: t("config.repoUrlHint")
6480
- })
6481
- ]
6482
- }),
6483
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6484
- className: config_manager_module_css_default.field,
6485
- children: [
6486
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6487
- className: config_manager_module_css_default.fieldLabel,
6488
- children: [
6489
- t("config.token"),
6490
- " ",
6491
- state.statusInfo?.credentialConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6492
- kind: "ok",
6493
- children: t("config.tokenSaved")
6494
- })
6495
- ]
6496
- }),
6497
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6498
- type: "password",
6499
- className: config_manager_module_css_default.input,
6500
- value: state.token,
6501
- autoComplete: "off",
6502
- placeholder: t("config.tokenPlaceholder"),
6503
- disabled: state.busy !== null,
6504
- onChange: (e) => {
6505
- patch({ token: e.target.value });
6506
- scheduleConfigSave();
6507
- }
6508
- }),
6509
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6510
- className: config_manager_module_css_default.hint,
6511
- children: t("config.tokenHint", { ref: "DSH_CONFIG_MANAGER_SYNC_TOKEN" })
6512
- })
6513
- ]
6514
- }),
6515
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6516
- className: config_manager_module_css_default.groupLabel,
6517
- children: t("github.title")
6518
- }),
6519
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6520
- className: config_manager_module_css_default.hint,
6521
- children: t("github.description")
6522
- }),
6523
- githubView.showCode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6524
- className: config_manager_module_css_default.statRow,
6525
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
6495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6496
+ className: config_manager_module_css_default.statRow,
6497
+ children: [
6498
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6526
6499
  kind: "info",
6527
- children: [
6528
- t("github.userCode"),
6529
- ":",
6530
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: githubView.userCode })
6531
- ]
6532
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6533
- className: config_manager_module_css_default.ghostButton,
6534
- href: githubView.verificationUri,
6535
- target: "_blank",
6536
- rel: "noreferrer",
6537
- style: { textDecoration: "none" },
6538
- children: t("github.openAuth")
6539
- })]
6540
- }),
6541
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6542
- className: config_manager_module_css_default.actionRow,
6543
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6544
- variant: "primary",
6545
- disabled: !githubView.canStart || state.busy !== null,
6546
- onClick: () => {
6547
- runGithubStart();
6548
- },
6549
- children: githubView.startLabel
6550
- }), githubView.canCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6551
- disabled: state.busy !== null,
6552
- onClick: () => {
6553
- runGithubCancel();
6554
- },
6555
- children: t("github.cancel")
6556
- })]
6557
- }),
6558
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6559
- className: config_manager_module_css_default.statRow,
6560
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6561
- kind: githubView.phase === "success" ? "ok" : githubView.phase === "error" ? "error" : "warn",
6562
- children: githubView.statusText
6500
+ children: state.channel === "webdav" ? t("channel.webdav") : t("channel.git")
6501
+ }),
6502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6503
+ kind: remoteReady ? "ok" : "warn",
6504
+ children: remoteReady ? t("channel.configured") : t("channel.notConfigured")
6505
+ }),
6506
+ state.channel === "git" && state.statusInfo?.credentialConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6507
+ kind: "ok",
6508
+ children: t("config.tokenSaved")
6509
+ }),
6510
+ state.channel === "webdav" && state.statusInfo?.webdav?.passwordConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6511
+ kind: "ok",
6512
+ children: t("webdav.passwordSaved")
6563
6513
  })
6564
- }),
6565
- githubView.phase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6566
- className: config_manager_module_css_default.hint,
6567
- children: t("config.tokenHint", { ref: "DSH_CONFIG_MANAGER_SYNC_TOKEN" })
6514
+ ]
6515
+ }),
6516
+ remoteReady && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6517
+ className: config_manager_module_css_default.hint,
6518
+ children: [
6519
+ t("channel.currentUrl"),
6520
+ ":",
6521
+ state.channel === "webdav" ? state.webdavUrl : state.repoUrl
6522
+ ]
6523
+ }),
6524
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6525
+ className: config_manager_module_css_default.actionRow,
6526
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6527
+ variant: "primary",
6528
+ onClick: openChannelDialog,
6529
+ children: t("channel.open")
6568
6530
  })
6569
- ] }),
6570
- state.channel === "webdav" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6571
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6572
- className: config_manager_module_css_default.groupLabel,
6573
- children: t("webdav.title")
6574
- }),
6575
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6576
- className: config_manager_module_css_default.hint,
6577
- children: t("webdav.presetHint")
6578
- }),
6579
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
6580
- className: config_manager_module_css_default.select,
6581
- value: presetIdForUrl(state.webdavUrl),
6582
- disabled: state.busy !== null,
6583
- onChange: (e) => {
6584
- const p = presetById(e.target.value);
6585
- patch({ webdavUrl: p.url });
6586
- scheduleConfigSave();
6587
- },
6588
- children: WEBDAV_PRESETS.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6589
- value: p.id,
6590
- children: p.label
6591
- }, p.id))
6592
- }),
6593
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6594
- className: config_manager_module_css_default.field,
6531
+ })
6532
+ ] }),
6533
+ channelOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6534
+ className: config_manager_module_css_default.dialogMask,
6535
+ onMouseDown: (e) => {
6536
+ if (e.target === e.currentTarget && !state.savingConfig) closeChannelDialog();
6537
+ },
6538
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6539
+ className: `${config_manager_module_css_default.dialogCard} ${config_manager_module_css_default.dialogWide}`,
6540
+ role: "dialog",
6541
+ "aria-modal": "true",
6542
+ "aria-label": t("channel.title"),
6543
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6544
+ className: config_manager_module_css_default.dialogHeaderRow,
6545
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6546
+ className: config_manager_module_css_default.dialogHeader,
6547
+ children: t("channel.title")
6548
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6549
+ type: "button",
6550
+ className: config_manager_module_css_default.dialogClose,
6551
+ "aria-label": t("common.close"),
6552
+ disabled: state.savingConfig,
6553
+ onClick: closeChannelDialog,
6554
+ children: "×"
6555
+ })]
6556
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6557
+ className: config_manager_module_css_default.dialogBodyScroll,
6595
6558
  children: [
6596
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6597
- className: config_manager_module_css_default.fieldLabel,
6598
- children: t("webdav.url")
6599
- }),
6600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6601
- type: "text",
6602
- className: config_manager_module_css_default.input,
6603
- value: state.webdavUrl,
6604
- placeholder: "https://dav.example.com/dav/config",
6605
- disabled: state.busy !== null,
6606
- onChange: (e) => {
6607
- patch({ webdavUrl: e.target.value });
6608
- scheduleConfigSave();
6609
- }
6559
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6560
+ className: config_manager_module_css_default.modeTabs,
6561
+ role: "tablist",
6562
+ children: channelTabModels(state.channel, state.busy !== null || state.savingConfig).map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6563
+ type: "button",
6564
+ role: "tab",
6565
+ "aria-selected": tab.active,
6566
+ "data-active": tab.active ? "" : void 0,
6567
+ className: config_manager_module_css_default.modeTab,
6568
+ disabled: tab.disabled,
6569
+ onClick: () => {
6570
+ switchChannel(tab.channel);
6571
+ },
6572
+ children: tab.channel === "webdav" ? t("channel.webdav") : t("channel.git")
6573
+ }, tab.channel))
6610
6574
  }),
6611
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6612
- className: config_manager_module_css_default.hint,
6613
- children: t("webdav.urlHint")
6614
- })
6615
- ]
6616
- }),
6617
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6618
- className: config_manager_module_css_default.field,
6619
- children: [
6620
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6621
- className: config_manager_module_css_default.fieldLabel,
6622
- children: [
6623
- t("webdav.username"),
6624
- " ",
6625
- state.statusInfo?.webdav?.usernameConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6626
- kind: "ok",
6627
- children: t("config.tokenSaved")
6628
- })
6629
- ]
6575
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6576
+ className: config_manager_module_css_default.modeHint,
6577
+ children: t("channel.perChannelHint")
6630
6578
  }),
6631
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6632
- type: "text",
6633
- className: config_manager_module_css_default.input,
6634
- value: state.webdavUsername,
6635
- autoComplete: "off",
6636
- placeholder: "alice",
6637
- disabled: state.busy !== null,
6638
- onChange: (e) => {
6639
- patch({ webdavUsername: e.target.value });
6640
- scheduleConfigSave();
6641
- }
6579
+ state.channel === "git" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
6580
+ kind: "warn",
6581
+ children: privateRepoHint(uiT)
6642
6582
  }),
6643
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6644
- className: config_manager_module_css_default.hint,
6645
- children: t("webdav.usernameHint")
6646
- })
6647
- ]
6648
- }),
6649
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6650
- className: config_manager_module_css_default.field,
6651
- children: [
6652
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6653
- className: config_manager_module_css_default.fieldLabel,
6654
- children: [
6655
- t("webdav.password"),
6656
- " ",
6657
- state.statusInfo?.webdav?.passwordConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6658
- kind: "ok",
6659
- children: t("webdav.passwordSaved")
6583
+ state.channel === "git" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6585
+ className: config_manager_module_css_default.groupLabel,
6586
+ children: t("config.title")
6587
+ }),
6588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6589
+ className: config_manager_module_css_default.field,
6590
+ children: [
6591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6592
+ className: config_manager_module_css_default.fieldLabel,
6593
+ children: t("config.repoUrl")
6594
+ }),
6595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6596
+ type: "text",
6597
+ className: config_manager_module_css_default.input,
6598
+ value: state.repoUrl,
6599
+ placeholder: "https://github.com/user/private-repo.git",
6600
+ disabled: state.busy !== null,
6601
+ onChange: (e) => {
6602
+ patch({ repoUrl: e.target.value });
6603
+ scheduleConfigSave();
6604
+ }
6605
+ }),
6606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6607
+ className: config_manager_module_css_default.hint,
6608
+ children: t("config.repoUrlHint")
6609
+ })
6610
+ ]
6611
+ }),
6612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6613
+ className: config_manager_module_css_default.field,
6614
+ children: [
6615
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6616
+ className: config_manager_module_css_default.fieldLabel,
6617
+ children: [
6618
+ t("config.token"),
6619
+ " ",
6620
+ state.statusInfo?.credentialConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6621
+ kind: "ok",
6622
+ children: t("config.tokenSaved")
6623
+ })
6624
+ ]
6625
+ }),
6626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6627
+ type: "password",
6628
+ className: config_manager_module_css_default.input,
6629
+ value: state.token,
6630
+ autoComplete: "off",
6631
+ placeholder: t("config.tokenPlaceholder"),
6632
+ disabled: state.busy !== null,
6633
+ onChange: (e) => {
6634
+ patch({ token: e.target.value });
6635
+ scheduleConfigSave();
6636
+ }
6637
+ }),
6638
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6639
+ className: config_manager_module_css_default.hint,
6640
+ children: t("config.tokenHint", { ref: "DSH_CONFIG_MANAGER_SYNC_TOKEN" })
6641
+ })
6642
+ ]
6643
+ }),
6644
+ state.githubSignedIn === false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6645
+ state.statusInfo?.credentialConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
6646
+ kind: "warn",
6647
+ children: t("github.tokenInvalid")
6648
+ }),
6649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6650
+ className: config_manager_module_css_default.groupLabel,
6651
+ children: t("github.title")
6652
+ }),
6653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6654
+ className: config_manager_module_css_default.hint,
6655
+ children: t("github.description")
6656
+ }),
6657
+ githubView.showCode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6658
+ className: config_manager_module_css_default.statRow,
6659
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
6660
+ kind: "info",
6661
+ children: [
6662
+ t("github.userCode"),
6663
+ ":",
6664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: githubView.userCode })
6665
+ ]
6666
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6667
+ className: config_manager_module_css_default.ghostButton,
6668
+ href: githubView.verificationUri,
6669
+ target: "_blank",
6670
+ rel: "noreferrer",
6671
+ style: { textDecoration: "none" },
6672
+ children: t("github.openAuth")
6673
+ })]
6674
+ }),
6675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6676
+ className: config_manager_module_css_default.actionRow,
6677
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6678
+ variant: "primary",
6679
+ disabled: !githubView.canStart || state.busy !== null,
6680
+ onClick: () => {
6681
+ runGithubStart();
6682
+ },
6683
+ children: githubView.startLabel
6684
+ }), githubView.canCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6685
+ disabled: state.busy !== null,
6686
+ onClick: () => {
6687
+ runGithubCancel();
6688
+ },
6689
+ children: t("github.cancel")
6690
+ })]
6691
+ }),
6692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6693
+ className: config_manager_module_css_default.statRow,
6694
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6695
+ kind: githubView.phase === "success" ? "ok" : githubView.phase === "error" ? "error" : "warn",
6696
+ children: githubView.statusText
6697
+ })
6698
+ }),
6699
+ githubView.phase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6700
+ className: config_manager_module_css_default.hint,
6701
+ children: t("config.tokenHint", { ref: "DSH_CONFIG_MANAGER_SYNC_TOKEN" })
6660
6702
  })
6661
- ]
6662
- }),
6663
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6664
- type: "password",
6665
- className: config_manager_module_css_default.input,
6666
- value: state.webdavPassword,
6667
- autoComplete: "off",
6668
- placeholder: t("webdav.passwordPlaceholder"),
6669
- disabled: state.busy !== null,
6670
- onChange: (e) => {
6671
- patch({ webdavPassword: e.target.value });
6672
- scheduleConfigSave();
6673
- }
6703
+ ] })
6704
+ ] }),
6705
+ state.channel === "webdav" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6707
+ className: config_manager_module_css_default.groupLabel,
6708
+ children: t("webdav.title")
6709
+ }),
6710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6711
+ className: config_manager_module_css_default.hint,
6712
+ children: t("webdav.presetHint")
6713
+ }),
6714
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
6715
+ className: config_manager_module_css_default.select,
6716
+ value: presetIdForUrl(state.webdavUrl),
6717
+ disabled: state.busy !== null,
6718
+ onChange: (e) => {
6719
+ const p = presetById(e.target.value);
6720
+ patch({ webdavUrl: p.url });
6721
+ scheduleConfigSave();
6722
+ },
6723
+ children: WEBDAV_PRESETS.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6724
+ value: p.id,
6725
+ children: p.label
6726
+ }, p.id))
6727
+ }),
6728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6729
+ className: config_manager_module_css_default.field,
6730
+ children: [
6731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6732
+ className: config_manager_module_css_default.fieldLabel,
6733
+ children: t("webdav.url")
6734
+ }),
6735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6736
+ type: "text",
6737
+ className: config_manager_module_css_default.input,
6738
+ value: state.webdavUrl,
6739
+ placeholder: "https://dav.example.com/dav/config",
6740
+ disabled: state.busy !== null,
6741
+ onChange: (e) => {
6742
+ patch({ webdavUrl: e.target.value });
6743
+ scheduleConfigSave();
6744
+ }
6745
+ }),
6746
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6747
+ className: config_manager_module_css_default.hint,
6748
+ children: t("webdav.urlHint")
6749
+ })
6750
+ ]
6751
+ }),
6752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6753
+ className: config_manager_module_css_default.field,
6754
+ children: [
6755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6756
+ className: config_manager_module_css_default.fieldLabel,
6757
+ children: [
6758
+ t("webdav.username"),
6759
+ " ",
6760
+ state.statusInfo?.webdav?.usernameConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6761
+ kind: "ok",
6762
+ children: t("config.tokenSaved")
6763
+ })
6764
+ ]
6765
+ }),
6766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6767
+ type: "text",
6768
+ className: config_manager_module_css_default.input,
6769
+ value: state.webdavUsername,
6770
+ autoComplete: "off",
6771
+ placeholder: "alice",
6772
+ disabled: state.busy !== null,
6773
+ onChange: (e) => {
6774
+ patch({ webdavUsername: e.target.value });
6775
+ scheduleConfigSave();
6776
+ }
6777
+ }),
6778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6779
+ className: config_manager_module_css_default.hint,
6780
+ children: t("webdav.usernameHint")
6781
+ })
6782
+ ]
6783
+ }),
6784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6785
+ className: config_manager_module_css_default.field,
6786
+ children: [
6787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6788
+ className: config_manager_module_css_default.fieldLabel,
6789
+ children: [
6790
+ t("webdav.password"),
6791
+ " ",
6792
+ state.statusInfo?.webdav?.passwordConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6793
+ kind: "ok",
6794
+ children: t("webdav.passwordSaved")
6795
+ })
6796
+ ]
6797
+ }),
6798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6799
+ type: "password",
6800
+ className: config_manager_module_css_default.input,
6801
+ value: state.webdavPassword,
6802
+ autoComplete: "off",
6803
+ placeholder: t("webdav.passwordPlaceholder"),
6804
+ disabled: state.busy !== null,
6805
+ onChange: (e) => {
6806
+ patch({ webdavPassword: e.target.value });
6807
+ scheduleConfigSave();
6808
+ }
6809
+ }),
6810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6811
+ className: config_manager_module_css_default.hint,
6812
+ children: t("webdav.passwordHint", { ref: "DSH_CONFIG_MANAGER_SYNC_WEBDAV_PASSWORD" })
6813
+ })
6814
+ ]
6815
+ })
6816
+ ] }),
6817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6818
+ className: config_manager_module_css_default.actionRow,
6819
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6820
+ variant: "primary",
6821
+ disabled: state.busy !== null || state.savingConfig || !remoteReady,
6822
+ onClick: () => {
6823
+ flushConfigSave();
6824
+ },
6825
+ children: state.savingConfig ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.saving") }) : t("config.save")
6826
+ })
6674
6827
  }),
6675
6828
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6676
6829
  className: config_manager_module_css_default.hint,
6677
- children: t("webdav.passwordHint", { ref: "DSH_CONFIG_MANAGER_SYNC_WEBDAV_PASSWORD" })
6830
+ children: t("config.saveHint")
6678
6831
  })
6679
6832
  ]
6680
- })
6681
- ] }),
6682
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6683
- className: config_manager_module_css_default.actionRow,
6684
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6685
- variant: "primary",
6686
- disabled: state.busy !== null || state.savingConfig || !remoteReady,
6687
- onClick: () => {
6688
- flushConfigSave();
6689
- },
6690
- children: state.savingConfig ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.saving") }) : t("config.save")
6691
- })
6692
- }),
6693
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6694
- className: config_manager_module_css_default.hint,
6695
- children: t("config.saveHint")
6833
+ })]
6696
6834
  })
6697
- ] }),
6698
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6699
- className: config_manager_module_css_default.groupLabel,
6700
- children: t("status.title")
6701
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6702
- className: config_manager_module_css_default.statRow,
6703
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6704
- kind: status.kind === "ready" ? "ok" : status.kind === "error" ? "error" : "warn",
6705
- children: status.text
6706
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
6707
- kind: "info",
6708
- children: state.channel === "webdav" ? t("channel.webdav") : t("channel.git")
6709
- })]
6710
- })] }),
6835
+ }),
6711
6836
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
6712
6837
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6713
6838
  className: config_manager_module_css_default.groupLabel,
@@ -6990,6 +7115,10 @@ function SyncSettingsView({ api, t }) {
6990
7115
  needsReview: state.confirmSession.needsReview,
6991
7116
  compatibility: state.confirmSession.compatibility,
6992
7117
  t,
7118
+ decisions: state.confirmDecisions,
7119
+ onDecisionsChange: (d) => {
7120
+ patch({ confirmDecisions: d });
7121
+ },
6993
7122
  onCancel: cancelConfirm,
6994
7123
  onRollbackDone: onRollbackApplied
6995
7124
  }),
@@ -7203,50 +7332,186 @@ const MARKET_UPSTREAM_OWNER = "xiajiajun516";
7203
7332
  /** 官方收录目标仓库名(见 MARKET_UPSTREAM_OWNER) */
7204
7333
  const MARKET_UPSTREAM_REPO = "dsh-config-market";
7205
7334
  //#endregion
7206
- //#region src/client/market/my-configs-view.ts
7335
+ //#region src/client/common/ConfirmDialog.tsx
7207
7336
  /**
7208
- * 「我的配置」区块的客户端纯渲染装配层(m-my-configs-view,node 可测)。
7337
+ * ConfirmDialog —— 确认弹窗(危险/重要操作的二次确认,DESIGN.md §8.11 / §14)。
7209
7338
  *
7210
- * 设计纪律(docs/design/2026-08-20-my-configs-design.md §4.5 / §4.6):
7211
- * - 全部无副作用纯函数,React 壳(MyConfigsView)只装配,不承载业务逻辑;
7212
- * - 输入数据一律由调用方(组件 / api 层)提供 —— 本文件不发起任何请求,不持有凭据;
7213
- * - 录入最小化(§1.2):表单仅 name(预填 zip 文件名,可改)/ description(可选)/
7214
- * categories(可选);id / author / version / updatedAt 均为系统自动生成,
7215
- * 界面以「系统自动」徽章展示(MY_CONFIG_AUTO_FIELDS);
7216
- * - 收录状态三态(§4.5):未收录(本地独有)| PR 待审核(带 PR 链接)| 已收录
7217
- * (官方 dsh-config-market/index.json 含该 id);已收录 > PR 待审核 > 未收录
7218
- * (官方已收录时即使存在 open PR 也以收录为准——PR 已合并后不应再显示待审核);
7219
- * - open PR 匹配按固定分支 `dsh-market-sync/<itemId>`(§2.4 自动 PR 分支约定);
7220
- * - 文案走 src/ui/i18n.ts 的 UiT('myConfigs.*' 键,zh 源 / en 镜像),不硬编码中文。
7339
+ * 背景(2026-08-20):用户要求「删除按钮需要弹窗弹出二次确认」,替代原先的
7340
+ * 「行内两步确认」(已从 DESIGN.md §14 移除,见 t8 设计)。本项目此前无任何
7341
+ * modal / fixed / z-index 元素,本组件是首个浮动层 —— DESIGN.md §15 Anti-pattern #7
7342
+ * 已登记豁免(z-index 100;浮动层必须先设计进 DESIGN.md 的流程满足)。
7221
7343
  *
7222
- * 安全硬约束:本层只做展示推导,不接触 token / 凭据;PR URL 由调用方提供,
7223
- * 渲染前仍过 redact() 兜底(与全站一致)。
7344
+ * 交互约定:
7345
+ * - 受控组件:open=false 时不渲染;open=true 渲染遮罩 + 居中卡片;
7346
+ * - 关闭三途径:遮罩点击(仅 e.target === currentTarget)、Esc 键、取消按钮;
7347
+ * - busy=true(或 onConfirm 返回 Promise 的自管 busy)时禁用一切关闭途径与确认按钮,
7348
+ * 防重复提交;
7349
+ * - 初始焦点在取消按钮(危险确认不默认落破坏性按钮);关闭后还原焦点到触发按钮
7350
+ * (open 前的 document.activeElement);
7351
+ * - 不做完整 focus trap(两按钮场景风险可接受,DESIGN.md §8.11 已注明)。
7352
+ *
7353
+ * 安全:message 由调用方传入(渲染前已 redact 兜底,与全站一致);本组件不触碰任何凭据。
7354
+ * 样式:全部走 --dsw-* token(遮罩 color-mix 半透明、卡片 bg-layer-2 + border-l1),
7355
+ * 仅新增 dialogMask/dialogCard/dialogHeader/dialogBody 四个类(config-manager.module.css)。
7224
7356
  */
7225
7357
  /**
7226
- * 登录状态推导(纯函数):loading 优先;authError 非空(401 等)→ token 失效;
7227
- * 否则按 status.loggedIn 区分已登录 / 未登录。输入由调用方提供。
7358
+ * 确认弹窗:遮罩 + 居中卡片 + 标题/正文/按钮区。
7359
+ * 自管 busy:onConfirm 返回 Promise 时置 busy 直到 resolve(reject 仍关闭 busy,错误由调用方处理)。
7228
7360
  */
7229
- function deriveLoginState(input) {
7230
- if (input.loading) return { kind: "loading" };
7231
- if (input.authFailed) return { kind: "token-invalid" };
7232
- const s = input.status;
7233
- if (s !== null && s.loggedIn) return {
7234
- kind: "logged-in",
7235
- login: s.login ?? "",
7236
- repoUrl: s.repoUrl ?? "",
7237
- repoExists: s.repoExists ?? false
7361
+ function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, danger, busy: busyProp, onConfirm, onCancel, children }) {
7362
+ const [selfBusy, setSelfBusy] = (0, react.useState)(false);
7363
+ const busy = busyProp === true || selfBusy;
7364
+ /** open 前的活动元素(关闭后还原焦点) */
7365
+ const prevFocus = (0, react.useRef)(null);
7366
+ /** 取消按钮 ref(初始焦点) */
7367
+ const cancelRef = (0, react.useRef)(null);
7368
+ (0, react.useEffect)(() => {
7369
+ if (open) {
7370
+ prevFocus.current = document.activeElement;
7371
+ requestAnimationFrame(() => {
7372
+ cancelRef.current?.focus();
7373
+ });
7374
+ return;
7375
+ }
7376
+ if (prevFocus.current !== null && prevFocus.current instanceof HTMLElement) {
7377
+ prevFocus.current.focus();
7378
+ prevFocus.current = null;
7379
+ }
7380
+ }, [open]);
7381
+ (0, react.useEffect)(() => {
7382
+ if (!open || busy) return;
7383
+ const onKey = (e) => {
7384
+ if (e.key === "Escape") onCancel();
7385
+ };
7386
+ window.addEventListener("keydown", onKey);
7387
+ return () => {
7388
+ window.removeEventListener("keydown", onKey);
7389
+ };
7390
+ }, [
7391
+ open,
7392
+ busy,
7393
+ onCancel
7394
+ ]);
7395
+ if (!open) return null;
7396
+ const handleConfirm = () => {
7397
+ if (busy) return;
7398
+ const result = onConfirm();
7399
+ if (result instanceof Promise) {
7400
+ setSelfBusy(true);
7401
+ result.finally(() => {
7402
+ setSelfBusy(false);
7403
+ });
7404
+ }
7238
7405
  };
7239
- return { kind: "logged-out" };
7240
- }
7241
- /** 空表单初值(React 壳 useState 初始 + 测试用)。 */
7242
- const EMPTY_MY_CONFIG_FORM = {
7243
- name: "",
7244
- description: "",
7245
- categories: ""
7246
- };
7247
- /** 表单校验:名称必填(trim 后非空)。描述/类别可选。 */
7248
- function validateMyConfigForm(form, t = zhUiT) {
7249
- return { name: form.name.trim() === "" ? t("myConfigs.form.errorName") : null };
7406
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7407
+ className: config_manager_module_css_default.dialogMask,
7408
+ onMouseDown: (e) => {
7409
+ if (e.target === e.currentTarget && !busy) onCancel();
7410
+ },
7411
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7412
+ className: config_manager_module_css_default.dialogCard,
7413
+ role: "dialog",
7414
+ "aria-modal": "true",
7415
+ "aria-label": title,
7416
+ children: [
7417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7418
+ className: config_manager_module_css_default.dialogHeader,
7419
+ children: title
7420
+ }),
7421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7422
+ className: config_manager_module_css_default.dialogBody,
7423
+ children: [message !== void 0 && message !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message }), children]
7424
+ }),
7425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7426
+ className: config_manager_module_css_default.actionRow,
7427
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7428
+ variant: danger === true ? "danger" : "primary",
7429
+ disabled: busy,
7430
+ onClick: () => {
7431
+ handleConfirm();
7432
+ },
7433
+ children: busy ? "…" : confirmLabel ?? ""
7434
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7435
+ ref: cancelRef,
7436
+ type: "button",
7437
+ className: config_manager_module_css_default.ghostButton,
7438
+ disabled: busy,
7439
+ onClick: onCancel,
7440
+ children: cancelLabel ?? ""
7441
+ })]
7442
+ })
7443
+ ]
7444
+ })
7445
+ });
7446
+ }
7447
+ //#endregion
7448
+ //#region src/client/market/disclaimer.ts
7449
+ /** localStorage key 前缀(按 key 隔离三个操作) */
7450
+ const DISCLAIMER_STORAGE_PREFIX = "dsh-cm-market.disclaimer.";
7451
+ /** 由操作 key 生成 localStorage key。 */
7452
+ function disclaimerStorageKey(key) {
7453
+ return `${DISCLAIMER_STORAGE_PREFIX}${key}`;
7454
+ }
7455
+ /** 读取「不再提示」状态(true = 已勾选,该操作以后不再弹免责;任何异常 → false)。 */
7456
+ function readDisclaimerDismissed(key, storage) {
7457
+ try {
7458
+ return storage.getItem(disclaimerStorageKey(key)) === "1";
7459
+ } catch {
7460
+ return false;
7461
+ }
7462
+ }
7463
+ /** 写入「不再提示」状态(勾选后调用;存储不可用时静默忽略)。 */
7464
+ function writeDisclaimerDismissed(key, storage) {
7465
+ try {
7466
+ storage.setItem(disclaimerStorageKey(key), "1");
7467
+ } catch {}
7468
+ }
7469
+ //#endregion
7470
+ //#region src/client/market/my-configs-view.ts
7471
+ /**
7472
+ * 「我的配置」区块的客户端纯渲染装配层(m-my-configs-view,node 可测)。
7473
+ *
7474
+ * 设计纪律(docs/design/2026-08-20-my-configs-design.md §4.5 / §4.6):
7475
+ * - 全部无副作用纯函数,React 壳(MyConfigsView)只装配,不承载业务逻辑;
7476
+ * - 输入数据一律由调用方(组件 / api 层)提供 —— 本文件不发起任何请求,不持有凭据;
7477
+ * - 录入最小化(§1.2):表单仅 name(预填 zip 文件名,可改)/ description(可选)/
7478
+ * categories(可选);id / author / version / updatedAt 均为系统自动生成,
7479
+ * 界面以「系统自动」徽章展示(MY_CONFIG_AUTO_FIELDS);
7480
+ * - 收录状态三态(§4.5):未收录(本地独有)| PR 待审核(带 PR 链接)| 已收录
7481
+ * (官方 dsh-config-market/index.json 含该 id);已收录 > PR 待审核 > 未收录
7482
+ * (官方已收录时即使存在 open PR 也以收录为准——PR 已合并后不应再显示待审核);
7483
+ * - open PR 匹配按固定分支 `dsh-market-sync/<itemId>`(§2.4 自动 PR 分支约定);
7484
+ * - 文案走 src/ui/i18n.ts 的 UiT('myConfigs.*' 键,zh 源 / en 镜像),不硬编码中文。
7485
+ *
7486
+ * 安全硬约束:本层只做展示推导,不接触 token / 凭据;PR URL 由调用方提供,
7487
+ * 渲染前仍过 redact() 兜底(与全站一致)。
7488
+ */
7489
+ /**
7490
+ * 登录状态推导(纯函数):loading 优先;authError 非空(401 等)→ token 失效;
7491
+ * 否则按 status.loggedIn 区分已登录 / 未登录。输入由调用方提供。
7492
+ */
7493
+ function deriveLoginState(input) {
7494
+ if (input.loading) return { kind: "loading" };
7495
+ if (input.authFailed) return { kind: "token-invalid" };
7496
+ const s = input.status;
7497
+ if (s !== null && s.loggedIn) return {
7498
+ kind: "logged-in",
7499
+ login: s.login ?? "",
7500
+ repoUrl: s.repoUrl ?? "",
7501
+ repoExists: s.repoExists ?? false
7502
+ };
7503
+ return { kind: "logged-out" };
7504
+ }
7505
+ /** 空表单初值(React 壳 useState 初始 + 测试用)。 */
7506
+ const EMPTY_MY_CONFIG_FORM = {
7507
+ name: "",
7508
+ description: "",
7509
+ categories: "",
7510
+ id: ""
7511
+ };
7512
+ /** 表单校验:名称必填(trim 后非空)。描述/类别可选。 */
7513
+ function validateMyConfigForm(form, t = zhUiT) {
7514
+ return { name: form.name.trim() === "" ? t("myConfigs.form.errorName") : null };
7250
7515
  }
7251
7516
  /** 表单是否全部合法(上传按钮可用性)。 */
7252
7517
  function myConfigFormValid(errors) {
@@ -7256,6 +7521,89 @@ function myConfigFormValid(errors) {
7256
7521
  function parseCategories(csv) {
7257
7522
  return csv.split(",").map((c) => c.trim()).filter((c) => c !== "");
7258
7523
  }
7524
+ /** 向导初始状态常量(initialWizard('upload') 语义;调用方应经 initialWizard 获取新对象)。 */
7525
+ const EMPTY_MY_WIZARD = {
7526
+ mode: "upload",
7527
+ step: "select",
7528
+ zipPath: null,
7529
+ fileName: null,
7530
+ validating: false,
7531
+ validated: false,
7532
+ validationError: null,
7533
+ form: { ...EMPTY_MY_CONFIG_FORM },
7534
+ formErrors: { name: null },
7535
+ running: false,
7536
+ result: null,
7537
+ error: null
7538
+ };
7539
+ /** 全量状态 → 持久化切片(忽略 validating/running/formErrors;form 浅拷贝,result 直接引用纯 JSON)。 */
7540
+ function toMyWizardSlice(w) {
7541
+ return {
7542
+ mode: w.mode,
7543
+ step: w.step,
7544
+ zipPath: w.zipPath,
7545
+ fileName: w.fileName,
7546
+ validated: w.validated,
7547
+ validationError: w.validationError,
7548
+ form: { ...w.form },
7549
+ result: w.result,
7550
+ error: w.error
7551
+ };
7552
+ }
7553
+ /**
7554
+ * 持久化切片 → 全量状态(null → 全新向导)。瞬态字段归零(validating/running 恒 false),
7555
+ * formErrors 按 form 经 validateMyConfigForm 重算;form 拷贝新对象(不共享切片引用)。
7556
+ */
7557
+ function restoreMyWizard(slice) {
7558
+ if (slice === null) return {
7559
+ ...EMPTY_MY_WIZARD,
7560
+ form: { ...EMPTY_MY_CONFIG_FORM }
7561
+ };
7562
+ return {
7563
+ mode: slice.mode,
7564
+ step: slice.step,
7565
+ zipPath: slice.zipPath,
7566
+ fileName: slice.fileName,
7567
+ validating: false,
7568
+ validated: slice.validated,
7569
+ validationError: slice.validationError,
7570
+ form: { ...slice.form },
7571
+ formErrors: validateMyConfigForm(slice.form),
7572
+ running: false,
7573
+ result: slice.result,
7574
+ error: slice.error
7575
+ };
7576
+ }
7577
+ /** 指定模式的向导初始状态(form 全新空表;mode/step 之外同 EMPTY_MY_WIZARD)。 */
7578
+ function initialWizard(mode) {
7579
+ return {
7580
+ ...EMPTY_MY_WIZARD,
7581
+ mode,
7582
+ form: { ...EMPTY_MY_CONFIG_FORM }
7583
+ };
7584
+ }
7585
+ /** 全量状态 → 持久化切片(忽略 importing;detail/approvals/importResult 直接引用纯 JSON/布尔表)。 */
7586
+ function toMyInstallSlice(s) {
7587
+ return {
7588
+ itemId: s.itemId,
7589
+ detail: s.detail,
7590
+ approvals: s.approvals,
7591
+ importResult: s.importResult,
7592
+ error: s.error
7593
+ };
7594
+ }
7595
+ /** 持久化切片 → 全量状态(null → 无装回本地会话;importing 恢复后恒 false)。 */
7596
+ function restoreMyInstall(slice) {
7597
+ if (slice === null) return null;
7598
+ return {
7599
+ itemId: slice.itemId,
7600
+ detail: slice.detail,
7601
+ approvals: slice.approvals,
7602
+ importing: false,
7603
+ importResult: slice.importResult,
7604
+ error: slice.error
7605
+ };
7606
+ }
7259
7607
  /** 系统自动字段徽章列表(id/作者/版本/更新时间 → 「系统自动」徽章)。 */
7260
7608
  function autoFieldBadges(t = zhUiT) {
7261
7609
  const autoText = t("myConfigs.autoField");
@@ -7348,12 +7696,6 @@ function summarizeMyItems(views) {
7348
7696
  }
7349
7697
  //#endregion
7350
7698
  //#region src/market/view.ts
7351
- /** 市场状态行文案(status 区块顶部一行)。count<=0 → 未添加提示 */
7352
- function marketStatusText(config, t) {
7353
- const count = Array.isArray(config) ? config.length : config.count;
7354
- if (count <= 0) return t("market.statusUnconfigured");
7355
- return t("market.statusConfigured", { count });
7356
- }
7357
7699
  /** 列表摘要:总条目数 + 缓存徽章计数(供 UI 顶部小结)。无副作用。 */
7358
7700
  function marketListSummary(items, _t) {
7359
7701
  const fresh = items.filter((i) => i.cacheState === "fresh").length;
@@ -7614,23 +7956,14 @@ const initialGithubFlow = {
7614
7956
  interval: 5,
7615
7957
  error: null
7616
7958
  };
7617
- function initialWizard(mode) {
7618
- return {
7619
- mode,
7620
- step: "select",
7621
- zipPath: null,
7622
- fileName: null,
7623
- validating: false,
7624
- validated: false,
7625
- validationError: null,
7626
- form: { ...EMPTY_MY_CONFIG_FORM },
7627
- formErrors: { name: null },
7628
- running: false,
7629
- result: null,
7630
- error: null
7631
- };
7632
- }
7633
- function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsError, onMyItemsChange, onBack }) {
7959
+ /**
7960
+ * 上传/更新向导状态(全量模型在 my-configs-view.ts 的 MyWizardState;本组件持有的是
7961
+ * 持久化切片 myWizard(受控 props),经 restoreMyWizard 恢复全量、toMyWizardSlice 上抛镜像。
7962
+ * 瞬态(validating/running/formErrors)由 restore 重建,切 tab/刷新恢复后为初始态。
7963
+ * 装回本地状态(MyInstallState)同模式:持久化切片 myInstall(受控 props),
7964
+ * 经 restoreMyInstall 恢复全量(importing 瞬态归零)、toMyInstallSlice 上抛镜像。
7965
+ */
7966
+ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsError, onMyItemsChange, myWizard, onMyWizardChange, myInstall, onMyInstallChange, myConfirmDeleteId, onMyConfirmDeleteChange }) {
7634
7967
  const uiT = meApi.t;
7635
7968
  const [status, setStatus] = (0, react.useState)(null);
7636
7969
  const [statusLoading, setStatusLoading] = (0, react.useState)(true);
@@ -7638,9 +7971,134 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
7638
7971
  const [github, setGithub] = (0, react.useState)(initialGithubFlow);
7639
7972
  const githubPollTimer = (0, react.useRef)(null);
7640
7973
  const [listLoading, setListLoading] = (0, react.useState)(false);
7641
- const [wizard, setWizard] = (0, react.useState)(() => initialWizard("upload"));
7642
- const [install, setInstall] = (0, react.useState)(null);
7974
+ const [wizard, setWizard] = (0, react.useState)(() => restoreMyWizard(myWizard));
7975
+ /** 最近一次 wizard 全量(commitWizard 读最新值,避免闭包过期) */
7976
+ const wizardRef = (0, react.useRef)(wizard);
7977
+ /** 收录/下架任务状态(结果卡轮询 /me/listing 的实时结果) */
7978
+ const [listingStatus, setListingStatus] = (0, react.useState)(null);
7979
+ /** 收录/下架任务轮询定时器 */
7980
+ const listingPollTimer = (0, react.useRef)(null);
7981
+ /** 删除确认弹窗目标条目 id(受控:MarketPanel 经 runStore 持有,切 tab/刷新不丢;null = 无确认中的删除) */
7982
+ const confirmDeleteId = myConfirmDeleteId;
7983
+ /** 正在删除的条目 id(行级 spinner + 防重复点击) */
7984
+ const [deletingId, setDeletingId] = (0, react.useState)(null);
7985
+ /** 装回本地状态(受控:切片来自 runStore;瞬态 importing 本地重建) */
7986
+ const [install, setInstall] = (0, react.useState)(() => restoreMyInstall(myInstall));
7987
+ /** 最近一次 install 全量(commitInstall 读最新值,避免闭包过期) */
7988
+ const installRef = (0, react.useRef)(install);
7643
7989
  const fileInput = (0, react.useRef)(null);
7990
+ /** 上传/更新向导弹窗开关(瞬态 UI,不持久化:切 tab 弹窗关闭,数据仍在 runStore) */
7991
+ const [uploadOpen, setUploadOpen] = (0, react.useState)(false);
7992
+ /** 装回本地弹窗开关(同上) */
7993
+ const [installOpen, setInstallOpen] = (0, react.useState)(false);
7994
+ /** 当前展示的免责弹窗操作(null = 无;upload/download/install 三操作分开记「不再提示」) */
7995
+ const [disclaimerKey, setDisclaimerKey] = (0, react.useState)(null);
7996
+ /** 免责弹窗「不再提示」勾选(每次打开重置) */
7997
+ const [dontAsk, setDontAsk] = (0, react.useState)(false);
7998
+ /** localStorage(浏览器环境;免责「不再提示」跨会话持久化) */
7999
+ const storage = window.localStorage;
8000
+ /** 打开上传/更新弹窗(更新模式表单已预填):未勾「不再提示」→ 先弹免责,确认后开弹窗 */
8001
+ const openUpload = () => {
8002
+ if (readDisclaimerDismissed("upload", storage)) {
8003
+ setUploadOpen(true);
8004
+ return;
8005
+ }
8006
+ setDontAsk(false);
8007
+ setDisclaimerKey("upload");
8008
+ };
8009
+ /** 待装回本地的目标条目(免责确认后取用;避免免责流程中闭包过期) */
8010
+ const pendingInstallEntry = (0, react.useRef)(null);
8011
+ /** 打开装回本地弹窗:未勾「不再提示」→ 先弹免责,确认后开弹窗并启动下载 */
8012
+ const openInstall = (entry) => {
8013
+ pendingInstallEntry.current = entry;
8014
+ if (readDisclaimerDismissed("install", storage)) {
8015
+ setInstallOpen(true);
8016
+ runDownload(entry);
8017
+ return;
8018
+ }
8019
+ setDontAsk(false);
8020
+ setDisclaimerKey("install");
8021
+ };
8022
+ /** 关闭上传/更新弹窗:重置向导为初始态(弹窗即会话,关闭即放弃本次操作;
8023
+ * 更新模式也切回「一键上传」入口,避免入口按钮残留 update 态) */
8024
+ const closeUpload = () => {
8025
+ setUploadOpen(false);
8026
+ commitWizard(initialWizard("upload"));
8027
+ setListingStatus(null);
8028
+ };
8029
+ /** 取消更新:放弃本次更新,向导回到默认「一键上传」初始态(清空预填/暂存/校验态),弹窗保持打开 */
8030
+ const cancelUpdate = () => {
8031
+ commitWizard(initialWizard("upload"));
8032
+ setListingStatus(null);
8033
+ };
8034
+ /** 关闭装回本地弹窗:清 install 会话 */
8035
+ const closeInstall = () => {
8036
+ setInstallOpen(false);
8037
+ commitInstall(null);
8038
+ };
8039
+ /** 免责弹窗确认:勾选则记录「不再提示」→ 关闭免责 → 打开对应操作弹窗 */
8040
+ const confirmDisclaimer = () => {
8041
+ const key = disclaimerKey;
8042
+ if (key === null) return;
8043
+ if (dontAsk) writeDisclaimerDismissed(key, storage);
8044
+ setDisclaimerKey(null);
8045
+ if (key === "upload") setUploadOpen(true);
8046
+ else if (key === "install") {
8047
+ const entry = pendingInstallEntry.current;
8048
+ setInstallOpen(true);
8049
+ if (entry !== null && entry !== void 0) runDownload(entry);
8050
+ }
8051
+ };
8052
+ /** 取消免责弹窗:关闭,不打开操作弹窗;若向导处于 update 残留态则重置为上传初始态 */
8053
+ const cancelDisclaimer = () => {
8054
+ setDisclaimerKey(null);
8055
+ if (disclaimerKey === "upload" && wizardRef.current.mode === "update") {
8056
+ commitWizard(initialWizard("upload"));
8057
+ setListingStatus(null);
8058
+ }
8059
+ };
8060
+ /** 免责弹窗文案(MyConfigsView 只触发 upload / install 两种;default 兜底返回空串防误显) */
8061
+ const disclaimerText = () => {
8062
+ switch (disclaimerKey) {
8063
+ case "upload": return t("disclaimer.upload.text");
8064
+ case "install": return t("disclaimer.install.text");
8065
+ default: return "";
8066
+ }
8067
+ };
8068
+ /**
8069
+ * 向导状态统一提交:更新 ref → setState → **总是**镜像切片上抛(MarketPanel 落 runStore)。
8070
+ * 镜像不依赖 effect flush:异步回调在组件已卸载(切走 tab)时也能落库,切回恢复。
8071
+ * 镜像上抛包 try/catch:镜像失败(store 异常)绝不影响本地 UI 更新(防「点击无反应」)。
8072
+ */
8073
+ const commitWizard = (next) => {
8074
+ wizardRef.current = next;
8075
+ setWizard(next);
8076
+ try {
8077
+ onMyWizardChange(toMyWizardSlice(next));
8078
+ } catch {}
8079
+ };
8080
+ const patchWizard = (p) => commitWizard({
8081
+ ...wizardRef.current,
8082
+ ...p
8083
+ });
8084
+ /**
8085
+ * 装回本地状态统一提交:更新 ref → setState → **总是**镜像切片上抛(MarketPanel 落 runStore)。
8086
+ * 镜像不依赖 effect flush:异步回调在组件已卸载(切走 tab)时也能落库,切回恢复。
8087
+ */
8088
+ const commitInstall = (next) => {
8089
+ installRef.current = next;
8090
+ setInstall(next);
8091
+ try {
8092
+ onMyInstallChange(next === null ? null : toMyInstallSlice(next));
8093
+ } catch {}
8094
+ };
8095
+ const patchInstall = (p) => {
8096
+ if (installRef.current === null) return;
8097
+ commitInstall({
8098
+ ...installRef.current,
8099
+ ...p
8100
+ });
8101
+ };
7644
8102
  /** 读取登录态(挂载 / 登录成功 / 状态刷新),返回 status 供后续判断 */
7645
8103
  const loadStatus = async () => {
7646
8104
  setStatusLoading(true);
@@ -7753,138 +8211,222 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
7753
8211
  await syncApi.githubCancel(flowId);
7754
8212
  } catch {}
7755
8213
  };
7756
- /** 步骤 1:选 zip → upload 受控临时区;upload 模式预填 name 为 zip 文件名(可改) */
8214
+ /** 选 zip → upload 受控临时区;upload 模式预填 name 为 zip 文件名(可改);
8215
+ * update 模式(更新直达表单页)留在 form 步骤并自动跑校验 */
7757
8216
  const onPickFile = async (file) => {
7758
8217
  if (file === void 0) return;
7759
- setWizard((w) => ({
7760
- ...w,
8218
+ patchWizard({
7761
8219
  fileName: file.name,
7762
- error: null
7763
- }));
8220
+ error: null,
8221
+ validationError: null
8222
+ });
7764
8223
  try {
7765
8224
  const uploaded = await importApi.upload(file);
7766
- setWizard((w) => ({
7767
- ...w,
8225
+ const base = {
7768
8226
  zipPath: uploaded.zipPath,
8227
+ fileName: file.name
8228
+ };
8229
+ if (wizardRef.current.mode === "update") {
8230
+ patchWizard({
8231
+ ...base,
8232
+ validated: false
8233
+ });
8234
+ await runValidateWith(uploaded.zipPath);
8235
+ } else patchWizard({
8236
+ ...base,
7769
8237
  step: "validate",
7770
- form: w.mode === "upload" ? {
7771
- ...w.form,
8238
+ form: {
8239
+ ...wizardRef.current.form,
7772
8240
  name: file.name.replace(/\.zip$/i, "")
7773
- } : w.form
7774
- }));
8241
+ }
8242
+ });
7775
8243
  } catch (err) {
7776
- setWizard((w) => ({
7777
- ...w,
7778
- error: err instanceof Error ? err.message : String(err)
7779
- }));
8244
+ patchWizard({ error: err instanceof Error ? err.message : String(err) });
7780
8245
  }
7781
8246
  };
7782
8247
  /** 步骤 2:analyzeImport dry-run(零写入)—— 无密钥 + 内容合法才放行;通过后进表单 */
7783
8248
  const runValidate = async () => {
7784
8249
  if (wizard.zipPath === null) return;
7785
- setWizard((w) => ({
7786
- ...w,
7787
- validating: true,
7788
- validationError: null
7789
- }));
8250
+ await runValidateWith(wizard.zipPath);
8251
+ };
8252
+ /** 校验指定 zipPath(update 模式选新 zip 后自动调用;任何异常都落到 validationError 展示,不静默) */
8253
+ const runValidateWith = async (zipPath) => {
7790
8254
  try {
7791
- const analysis = await importApi.analyzeImport(wizard.zipPath);
7792
- if (analysis.secretCount > 0) setWizard((w) => ({
8255
+ patchWizard({
8256
+ validating: true,
8257
+ validationError: null
8258
+ });
8259
+ } catch (err) {
8260
+ setWizard((w) => ({
7793
8261
  ...w,
8262
+ validationError: err instanceof Error ? err.message : String(err)
8263
+ }));
8264
+ return;
8265
+ }
8266
+ try {
8267
+ const analysis = await importApi.analyzeImport(zipPath);
8268
+ if (analysis.secretCount > 0) patchWizard({
7794
8269
  validating: false,
7795
8270
  validated: false,
7796
8271
  validationError: t("myconfigs.upload.validateSecrets")
7797
- }));
7798
- else if (!analysis.valid) setWizard((w) => ({
7799
- ...w,
8272
+ });
8273
+ else if (!analysis.valid) patchWizard({
7800
8274
  validating: false,
7801
8275
  validated: false,
7802
8276
  validationError: t("myconfigs.upload.validateInvalid")
7803
- }));
7804
- else setWizard((w) => ({
7805
- ...w,
8277
+ });
8278
+ else patchWizard({
7806
8279
  validating: false,
7807
8280
  validated: true,
8281
+ validationError: null,
7808
8282
  step: "form"
7809
- }));
8283
+ });
7810
8284
  } catch (err) {
7811
- setWizard((w) => ({
7812
- ...w,
8285
+ patchWizard({
7813
8286
  validating: false,
7814
8287
  validated: false,
7815
8288
  validationError: err instanceof Error ? err.message : String(err)
7816
- }));
8289
+ });
7817
8290
  }
7818
8291
  };
7819
8292
  /** 表单字段更新 + 实时校验(pure 模型) */
7820
8293
  const onFormField = (field, value) => {
7821
8294
  const next = {
7822
- ...wizard.form,
8295
+ ...wizardRef.current.form,
7823
8296
  [field]: value
7824
8297
  };
7825
- setWizard((w) => ({
7826
- ...w,
8298
+ patchWizard({
7827
8299
  form: next,
7828
8300
  formErrors: validateMyConfigForm(next, uiT)
7829
- }));
8301
+ });
7830
8302
  };
7831
8303
  /** 「一键上传 / 一键更新」→ meUpload / meUpdate */
7832
8304
  const runUpload = async () => {
7833
- if (wizard.zipPath === null) return;
7834
- const errs = validateMyConfigForm(wizard.form, uiT);
7835
- setWizard((w) => ({
7836
- ...w,
7837
- formErrors: errs
7838
- }));
8305
+ const w = wizardRef.current;
8306
+ const zipPath = w.zipPath;
8307
+ if (zipPath === null) return;
8308
+ const errs = validateMyConfigForm(w.form, uiT);
8309
+ patchWizard({ formErrors: errs });
7839
8310
  if (!myConfigFormValid(errs)) return;
7840
- const categories = parseCategories(wizard.form.categories);
8311
+ const categories = parseCategories(w.form.categories);
7841
8312
  const form = {
7842
- name: wizard.form.name.trim(),
7843
- ...wizard.form.description.trim() !== "" ? { description: wizard.form.description.trim() } : {},
8313
+ name: w.form.name.trim(),
8314
+ ...w.mode === "update" && w.form.id !== "" ? { id: w.form.id } : {},
8315
+ ...w.form.description.trim() !== "" ? { description: w.form.description.trim() } : {},
7844
8316
  ...categories.length > 0 ? { categories } : {}
7845
8317
  };
7846
- setWizard((w) => ({
7847
- ...w,
8318
+ patchWizard({
7848
8319
  running: true,
7849
8320
  error: null,
7850
8321
  result: null
7851
- }));
8322
+ });
7852
8323
  try {
7853
- const result = wizard.mode === "update" ? await meApi.meUpdate({
7854
- zipPath: wizard.zipPath,
8324
+ const result = w.mode === "update" ? await meApi.meUpdate({
8325
+ zipPath,
7855
8326
  form
7856
8327
  }) : await meApi.meUpload({
7857
- zipPath: wizard.zipPath,
8328
+ zipPath,
7858
8329
  form
7859
8330
  });
7860
- setWizard((w) => ({
7861
- ...w,
8331
+ commitWizard({
8332
+ ...wizardRef.current,
7862
8333
  running: false,
7863
8334
  result
7864
- }));
8335
+ });
8336
+ setListingStatus(null);
8337
+ if (result.ok && result.listing === "pending") startListingPoll(result.itemId);
7865
8338
  loadItems({ silent: true });
7866
8339
  } catch (err) {
7867
- setWizard((w) => ({
7868
- ...w,
8340
+ patchWizard({
7869
8341
  running: false,
7870
8342
  error: err instanceof Error ? err.message : String(err)
7871
- }));
8343
+ });
7872
8344
  }
7873
8345
  };
7874
- /** 行操作:更新 → 打开向导,预填条目信息(name/description/categories) */
8346
+ /** 行操作:更新 → 打开「更新配置」表单页(step='form',预填条目信息;表单内选新 zip 自动校验) */
7875
8347
  const startUpdate = (entry) => {
7876
- setWizard({
8348
+ commitWizard({
7877
8349
  ...initialWizard("update"),
8350
+ step: "form",
7878
8351
  form: {
8352
+ id: entry.id,
7879
8353
  name: entry.name,
7880
8354
  description: entry.description ?? "",
7881
8355
  categories: (entry.categories ?? []).join(", ")
7882
8356
  }
7883
8357
  });
8358
+ setListingStatus(null);
8359
+ };
8360
+ /** 轮询 /me/listing 直到任务终态(done/failed/null);间隔 3s、最多 40 次(≈2 分钟),
8361
+ * 后台 fork 更久时超时停止,用户可稍后手动刷新列表/点「重新收录」 */
8362
+ const startListingPoll = (itemId) => {
8363
+ if (listingPollTimer.current !== null) clearTimeout(listingPollTimer.current);
8364
+ let count = 0;
8365
+ const tick = () => {
8366
+ (async () => {
8367
+ try {
8368
+ const s = await meApi.meListing(itemId);
8369
+ if (s === null) {
8370
+ setListingStatus({
8371
+ itemId,
8372
+ listing: "done",
8373
+ prNumber: null,
8374
+ prUrl: null
8375
+ });
8376
+ return;
8377
+ }
8378
+ setListingStatus(s);
8379
+ if (s.listing !== "pending") return;
8380
+ } catch {}
8381
+ count += 1;
8382
+ if (count >= 40) {
8383
+ listingPollTimer.current = null;
8384
+ return;
8385
+ }
8386
+ listingPollTimer.current = setTimeout(tick, 3e3);
8387
+ })();
8388
+ };
8389
+ tick();
8390
+ };
8391
+ /** 挂载恢复:若持久化的向导结果仍是「收录处理中」(pending),继续轮询(仿 resume 模式,避免刷新后永久 pending 卡死) */
8392
+ (0, react.useEffect)(() => {
8393
+ const w = wizardRef.current;
8394
+ if (w.result !== null && w.result.ok && w.result.listing === "pending") startListingPoll(w.result.itemId);
8395
+ return () => {
8396
+ if (listingPollTimer.current !== null) clearTimeout(listingPollTimer.current);
8397
+ };
8398
+ }, []);
8399
+ /** 重新提交收录(收录失败 / 进程重启丢失后的一键重试)→ 重新轮询状态 */
8400
+ const runRelist = async (itemId) => {
8401
+ try {
8402
+ const s = await meApi.meRelist(itemId);
8403
+ setListingStatus(s);
8404
+ startListingPoll(itemId);
8405
+ } catch (err) {
8406
+ patchWizard({ error: err instanceof Error ? err.message : String(err) });
8407
+ }
8408
+ };
8409
+ /** 删除:调 /me/delete(同步删本地索引+文件;已收录自动后台提下架 PR)→ 刷新列表 */
8410
+ const runDelete = async (entry) => {
8411
+ if (deletingId !== null) return;
8412
+ setDeletingId(entry.id);
8413
+ onMyConfirmDeleteChange(null);
8414
+ try {
8415
+ const result = await meApi.meDelete(entry.id);
8416
+ if (result.ok) {
8417
+ if (result.delisted) patchWizard({ error: t("myconfigs.delete.delistStarted") });
8418
+ else if (result.prNumber !== null) patchWizard({ error: t("myconfigs.delete.prClosed") });
8419
+ loadItems({ silent: true });
8420
+ } else patchWizard({ error: result.error ?? t("common.unknownError") });
8421
+ } catch (err) {
8422
+ patchWizard({ error: err instanceof Error ? err.message : String(err) });
8423
+ } finally {
8424
+ setDeletingId(null);
8425
+ }
7884
8426
  };
7885
8427
  /** 装回本地(复用市场下载 + 逐分区批准链路;条目内容在用户自己的公开仓库,必须带 repo 来源) */
7886
8428
  const runDownload = async (entry) => {
7887
- setInstall({
8429
+ commitInstall({
7888
8430
  itemId: entry.id,
7889
8431
  detail: null,
7890
8432
  approvals: {},
@@ -7894,49 +8436,40 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
7894
8436
  });
7895
8437
  try {
7896
8438
  const detail = await api.download(entry.id, entry.repoUrl);
7897
- setInstall((i) => i !== null ? {
7898
- ...i,
8439
+ if (installRef.current !== null) commitInstall({
8440
+ ...installRef.current,
7899
8441
  detail,
7900
8442
  approvals: defaultApprovals(detail.plan)
7901
- } : i);
8443
+ });
7902
8444
  } catch (err) {
7903
- setInstall((i) => i !== null ? {
7904
- ...i,
7905
- error: err instanceof Error ? err.message : String(err)
7906
- } : i);
8445
+ if (installRef.current !== null) patchInstall({ error: err instanceof Error ? err.message : String(err) });
7907
8446
  }
7908
8447
  };
7909
8448
  const runImport = async () => {
7910
8449
  if (install === null || install.detail === null) return;
7911
8450
  const approvedPlan = buildApprovedPlan(install.detail.plan, install.approvals);
7912
8451
  if (approvedPlan.items.length === 0) {
7913
- setInstall((i) => i !== null ? {
7914
- ...i,
7915
- error: t("detail.noApproval")
7916
- } : i);
8452
+ patchInstall({ error: t("detail.noApproval") });
7917
8453
  return;
7918
8454
  }
7919
- setInstall((i) => i !== null ? {
7920
- ...i,
8455
+ patchInstall({
7921
8456
  importing: true,
7922
8457
  error: null
7923
- } : i);
8458
+ });
7924
8459
  try {
7925
8460
  const executed = await importApi.executeImportPlan(install.detail.zipPath, approvedPlan, {
7926
8461
  confirm: true,
7927
8462
  rollbackOnError: true
7928
8463
  });
7929
- setInstall((i) => i !== null ? {
7930
- ...i,
8464
+ patchInstall({
7931
8465
  importing: false,
7932
8466
  importResult: executed
7933
- } : i);
8467
+ });
7934
8468
  } catch (err) {
7935
- setInstall((i) => i !== null ? {
7936
- ...i,
8469
+ patchInstall({
7937
8470
  importing: false,
7938
8471
  error: err instanceof Error ? err.message : String(err)
7939
- } : i);
8472
+ });
7940
8473
  }
7941
8474
  };
7942
8475
  /** 登录视图(loading / logged-out / logged-in / token-invalid) */
@@ -8061,199 +8594,305 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
8061
8594
  };
8062
8595
  /** 上传 / 更新向导卡片(选 zip → 校验 → 表单 → 结果) */
8063
8596
  const renderWizard = () => {
8064
- const reset = () => setWizard(initialWizard(wizard.mode));
8065
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8066
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8067
- className: config_manager_module_css_default.actionRow,
8068
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8069
- className: config_manager_module_css_default.groupLabel,
8070
- children: wizard.mode === "update" ? t("myconfigs.update.title") : t("myconfigs.upload.title")
8071
- }), wizard.mode === "update" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8072
- kind: "info",
8073
- children: t("myconfigs.update.hint")
8074
- })]
8075
- }),
8076
- wizard.step === "select" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8077
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8078
- className: config_manager_module_css_default.hint,
8079
- children: t("myconfigs.upload.selectHint")
8080
- }),
8081
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8082
- ref: fileInput,
8083
- type: "file",
8084
- accept: ".zip,application/zip",
8085
- className: config_manager_module_css_default.hiddenFile,
8086
- onChange: (e) => {
8087
- const picked = e.target.files?.[0];
8088
- e.target.value = "";
8089
- onPickFile(picked);
8090
- }
8091
- }),
8092
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8093
- className: config_manager_module_css_default.actionRow,
8094
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8095
- variant: "primary",
8096
- disabled: wizard.running,
8097
- onClick: () => {
8098
- fileInput.current?.click();
8099
- },
8100
- children: t("myconfigs.upload.select")
8101
- })
8102
- })
8103
- ] }),
8104
- wizard.step === "validate" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8105
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8106
- className: config_manager_module_css_default.hint,
8107
- children: t("myconfigs.upload.selectHint")
8108
- }),
8109
- wizard.fileName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8110
- className: config_manager_module_css_default.statRow,
8111
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8112
- kind: "info",
8113
- children: t("myconfigs.upload.selected", { name: wizard.fileName })
8114
- })
8115
- }),
8116
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8117
- className: config_manager_module_css_default.actionRow,
8118
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8119
- variant: "primary",
8120
- disabled: wizard.zipPath === null || wizard.validating,
8121
- onClick: () => {
8122
- runValidate();
8123
- },
8124
- children: wizard.validating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.upload.validating") }) : t("myconfigs.upload.validate")
8125
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8126
- disabled: wizard.validating,
8127
- onClick: reset,
8128
- children: t("myconfigs.upload.reselect")
8129
- })]
8130
- }),
8131
- wizard.validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8132
- kind: "error",
8133
- children: redact(wizard.validationError)
8134
- })
8135
- ] }),
8136
- wizard.step === "form" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8137
- wizard.validated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8138
- className: config_manager_module_css_default.statRow,
8139
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8140
- kind: "ok",
8141
- children: t("myconfigs.upload.validateOk")
8142
- })
8143
- }),
8144
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
8145
- label: t("myconfigs.upload.form.name"),
8146
- hint: t("myconfigs.upload.form.nameHint"),
8147
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8148
- className: config_manager_module_css_default.input,
8149
- value: wizard.form.name,
8150
- onChange: (e) => {
8151
- onFormField("name", e.target.value);
8152
- }
8153
- }), wizard.formErrors.name !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8154
- className: config_manager_module_css_default.formError,
8155
- children: redact(wizard.formErrors.name)
8156
- })]
8157
- }),
8158
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
8159
- label: t("myconfigs.upload.form.description"),
8160
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
8161
- className: config_manager_module_css_default.input,
8162
- value: wizard.form.description,
8163
- onChange: (e) => {
8164
- onFormField("description", e.target.value);
8165
- }
8166
- })
8167
- }),
8168
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
8169
- label: t("myconfigs.upload.form.categories"),
8170
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8171
- className: config_manager_module_css_default.input,
8172
- value: wizard.form.categories,
8173
- onChange: (e) => {
8174
- onFormField("categories", e.target.value);
8175
- }
8176
- })
8177
- }),
8178
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8179
- className: config_manager_module_css_default.hint,
8180
- children: t("myconfigs.upload.form.autoHint")
8181
- }),
8182
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8183
- className: config_manager_module_css_default.statRow,
8184
- children: autoBadges.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
8185
- kind: "info",
8186
- children: [
8187
- b.label,
8188
- ":",
8189
- b.autoText
8190
- ]
8191
- }, b.field))
8192
- }),
8193
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8194
- className: config_manager_module_css_default.actionRow,
8195
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8196
- variant: "primary",
8197
- disabled: wizard.validated !== true || wizard.running || !myConfigFormValid(wizard.formErrors),
8198
- onClick: () => {
8199
- runUpload();
8200
- },
8201
- children: wizard.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: wizard.mode === "update" ? t("myconfigs.update.running") : t("myconfigs.upload.running") }) : wizard.mode === "update" ? t("myconfigs.update.run") : t("myconfigs.upload.run")
8202
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8203
- disabled: wizard.running,
8204
- onClick: reset,
8205
- children: t("myconfigs.upload.reselect")
8206
- })]
8207
- })
8208
- ] }),
8209
- wizard.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8210
- kind: "error",
8211
- children: redact(wizard.error)
8212
- }),
8213
- wizard.result !== null && (wizard.result.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8214
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8215
- className: config_manager_module_css_default.groupLabel,
8216
- children: t("myconfigs.result.title")
8217
- }),
8218
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8219
- className: config_manager_module_css_default.statRow,
8220
- children: [
8221
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8222
- kind: "ok",
8223
- children: t("myconfigs.result.version", { version: wizard.result.version })
8224
- }),
8225
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8226
- kind: "info",
8227
- children: t("myconfigs.result.sha256", { hash: wizard.result.sha256 })
8228
- }),
8229
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8597
+ /** PR 链接(优先实时任务状态;收录完成后由轮询补上,或直接取同步结果) */
8598
+ const prLink = (() => {
8599
+ const live = listingStatus;
8600
+ const url = live !== null && live.prUrl !== null && live.prUrl !== "" ? live.prUrl : wizardRef.current.result?.prUrl ?? null;
8601
+ if (url === null || url === "") return null;
8602
+ const number = live !== null && live.prNumber !== null ? live.prNumber : wizardRef.current.result?.prNumber;
8603
+ return {
8604
+ url,
8605
+ label: number !== null && number !== void 0 ? t("myconfigs.result.pr", { number: String(number) }) : t("myconfigs.result.openPr")
8606
+ };
8607
+ })();
8608
+ /** 重置:update 模式轻量重置(只清 zip/校验,**保留预填表单**);upload 模式完全重置 */
8609
+ const reset = () => {
8610
+ const w = wizardRef.current;
8611
+ if (w.mode === "update") commitWizard({
8612
+ ...initialWizard("update"),
8613
+ step: "form",
8614
+ form: { ...w.form }
8615
+ });
8616
+ else commitWizard(initialWizard("upload"));
8617
+ setListingStatus(null);
8618
+ };
8619
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8620
+ className: config_manager_module_css_default.dialogMask,
8621
+ onMouseDown: (e) => {
8622
+ if (e.target === e.currentTarget && !wizard.running && !wizard.validating) closeUpload();
8623
+ },
8624
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8625
+ className: `${config_manager_module_css_default.dialogCard} ${config_manager_module_css_default.dialogWide}`,
8626
+ role: "dialog",
8627
+ "aria-modal": "true",
8628
+ "aria-label": wizard.mode === "update" ? t("myconfigs.update.title") : t("myconfigs.upload.title"),
8629
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8630
+ className: config_manager_module_css_default.dialogHeaderRow,
8631
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8632
+ className: config_manager_module_css_default.dialogHeader,
8633
+ children: [wizard.mode === "update" ? t("myconfigs.update.title") : t("myconfigs.upload.title"), wizard.mode === "update" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8230
8634
  kind: "info",
8231
- children: t("myconfigs.result.sections", { sections: wizard.result.sections.join(", ") })
8232
- })
8233
- ]
8234
- }),
8235
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8236
- className: config_manager_module_css_default.actionRow,
8635
+ children: t("myconfigs.update.hint")
8636
+ })]
8637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8638
+ type: "button",
8639
+ className: config_manager_module_css_default.dialogClose,
8640
+ "aria-label": t("common.close"),
8641
+ disabled: wizard.running || wizard.validating,
8642
+ onClick: closeUpload,
8643
+ children: "×"
8644
+ })]
8645
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8646
+ className: config_manager_module_css_default.dialogBodyScroll,
8237
8647
  children: [
8238
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8239
- kind: "info",
8240
- children: t("myconfigs.result.repo")
8241
- }),
8242
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8243
- href: wizard.result.repoUrl,
8244
- children: t("myconfigs.result.openRepo")
8648
+ wizard.step === "select" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8650
+ className: config_manager_module_css_default.hint,
8651
+ children: t("myconfigs.upload.selectHint")
8652
+ }),
8653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8654
+ ref: fileInput,
8655
+ type: "file",
8656
+ accept: ".zip,application/zip",
8657
+ className: config_manager_module_css_default.hiddenFile,
8658
+ onChange: (e) => {
8659
+ const picked = e.target.files?.[0];
8660
+ e.target.value = "";
8661
+ onPickFile(picked);
8662
+ }
8663
+ }),
8664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8665
+ className: config_manager_module_css_default.actionRow,
8666
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8667
+ variant: "primary",
8668
+ disabled: wizard.running,
8669
+ onClick: () => {
8670
+ fileInput.current?.click();
8671
+ },
8672
+ children: t("myconfigs.upload.select")
8673
+ })
8674
+ })
8675
+ ] }),
8676
+ wizard.step === "validate" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8678
+ className: config_manager_module_css_default.hint,
8679
+ children: t("myconfigs.upload.selectHint")
8680
+ }),
8681
+ wizard.fileName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8682
+ className: config_manager_module_css_default.statRow,
8683
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8684
+ kind: "info",
8685
+ children: t("myconfigs.upload.selected", { name: wizard.fileName })
8686
+ })
8687
+ }),
8688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8689
+ className: config_manager_module_css_default.actionRow,
8690
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8691
+ variant: "primary",
8692
+ disabled: wizard.zipPath === null || wizard.validating,
8693
+ onClick: () => {
8694
+ runValidate();
8695
+ },
8696
+ children: wizard.validating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.upload.validating") }) : t("myconfigs.upload.validate")
8697
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8698
+ disabled: wizard.validating,
8699
+ onClick: reset,
8700
+ children: t("myconfigs.upload.reselect")
8701
+ })]
8702
+ }),
8703
+ wizard.validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8704
+ kind: "error",
8705
+ children: redact(wizard.validationError)
8706
+ })
8707
+ ] }),
8708
+ wizard.step === "form" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8709
+ wizard.mode === "update" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8711
+ className: config_manager_module_css_default.hint,
8712
+ children: t("myconfigs.update.zipHint")
8713
+ }),
8714
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8715
+ ref: fileInput,
8716
+ type: "file",
8717
+ accept: ".zip,application/zip",
8718
+ className: config_manager_module_css_default.hiddenFile,
8719
+ onChange: (e) => {
8720
+ const picked = e.target.files?.[0];
8721
+ e.target.value = "";
8722
+ onPickFile(picked);
8723
+ }
8724
+ }),
8725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8726
+ className: config_manager_module_css_default.actionRow,
8727
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8728
+ variant: "primary",
8729
+ disabled: wizard.validating || wizard.running,
8730
+ onClick: () => {
8731
+ fileInput.current?.click();
8732
+ },
8733
+ children: wizard.fileName !== null && wizard.zipPath !== null ? t("myconfigs.upload.selected", { name: wizard.fileName }) : t("myconfigs.update.selectZip")
8734
+ }), wizard.zipPath !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8735
+ disabled: wizard.validating || wizard.running,
8736
+ onClick: reset,
8737
+ children: t("myconfigs.upload.reselect")
8738
+ })]
8739
+ }),
8740
+ wizard.validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8741
+ kind: "error",
8742
+ children: redact(wizard.validationError)
8743
+ })
8744
+ ] }),
8745
+ wizard.validated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8746
+ className: config_manager_module_css_default.statRow,
8747
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8748
+ kind: "ok",
8749
+ children: t("myconfigs.upload.validateOk")
8750
+ })
8751
+ }),
8752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
8753
+ label: t("myconfigs.upload.form.name"),
8754
+ hint: t("myconfigs.upload.form.nameHint"),
8755
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8756
+ className: config_manager_module_css_default.input,
8757
+ value: wizard.form.name,
8758
+ onChange: (e) => {
8759
+ onFormField("name", e.target.value);
8760
+ }
8761
+ }), wizard.formErrors.name !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8762
+ className: config_manager_module_css_default.formError,
8763
+ children: redact(wizard.formErrors.name)
8764
+ })]
8765
+ }),
8766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
8767
+ label: t("myconfigs.upload.form.description"),
8768
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
8769
+ className: config_manager_module_css_default.input,
8770
+ value: wizard.form.description,
8771
+ onChange: (e) => {
8772
+ onFormField("description", e.target.value);
8773
+ }
8774
+ })
8775
+ }),
8776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
8777
+ label: t("myconfigs.upload.form.categories"),
8778
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8779
+ className: config_manager_module_css_default.input,
8780
+ value: wizard.form.categories,
8781
+ onChange: (e) => {
8782
+ onFormField("categories", e.target.value);
8783
+ }
8784
+ })
8785
+ }),
8786
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8787
+ className: config_manager_module_css_default.hint,
8788
+ children: t("myconfigs.upload.form.autoHint")
8789
+ }),
8790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8791
+ className: config_manager_module_css_default.statRow,
8792
+ children: autoBadges.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
8793
+ kind: "info",
8794
+ children: [
8795
+ b.label,
8796
+ ":",
8797
+ b.autoText
8798
+ ]
8799
+ }, b.field))
8800
+ }),
8801
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8802
+ className: config_manager_module_css_default.actionRow,
8803
+ children: [
8804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8805
+ variant: "primary",
8806
+ disabled: wizard.validated !== true || wizard.running || wizard.zipPath === null || !myConfigFormValid(wizard.formErrors),
8807
+ onClick: () => {
8808
+ runUpload();
8809
+ },
8810
+ children: wizard.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: wizard.mode === "update" ? t("myconfigs.update.running") : t("myconfigs.upload.running") }) : wizard.mode === "update" ? t("myconfigs.update.run") : t("myconfigs.upload.run")
8811
+ }),
8812
+ wizard.mode === "update" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8813
+ disabled: wizard.running || wizard.validating,
8814
+ onClick: cancelUpdate,
8815
+ children: t("common.cancel")
8816
+ }),
8817
+ wizard.mode === "upload" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8818
+ disabled: wizard.running,
8819
+ onClick: reset,
8820
+ children: t("myconfigs.upload.reselect")
8821
+ })
8822
+ ]
8823
+ })
8824
+ ] }),
8825
+ wizard.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8826
+ kind: "error",
8827
+ children: redact(wizard.error)
8245
8828
  }),
8246
- wizard.result.prUrl !== null && wizard.result.prUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8247
- href: wizard.result.prUrl,
8248
- children: wizard.result.prNumber !== null ? t("myconfigs.result.pr", { number: String(wizard.result.prNumber) }) : t("myconfigs.result.openPr")
8249
- })
8829
+ wizard.result !== null && (wizard.result.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8831
+ className: config_manager_module_css_default.groupLabel,
8832
+ children: t("myconfigs.result.title")
8833
+ }),
8834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8835
+ className: config_manager_module_css_default.statRow,
8836
+ children: [
8837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8838
+ kind: "ok",
8839
+ children: t("myconfigs.result.version", { version: wizard.result.version })
8840
+ }),
8841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8842
+ kind: "info",
8843
+ children: t("myconfigs.result.sha256", { hash: wizard.result.sha256 })
8844
+ }),
8845
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8846
+ kind: "info",
8847
+ children: t("myconfigs.result.sections", { sections: wizard.result.sections.join(", ") })
8848
+ })
8849
+ ]
8850
+ }),
8851
+ wizard.result.listing === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8852
+ className: config_manager_module_css_default.statRow,
8853
+ children: listingStatus !== null && listingStatus.listing === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8854
+ kind: "error",
8855
+ children: t("myconfigs.result.listingFailed")
8856
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8857
+ variant: "danger",
8858
+ onClick: () => {
8859
+ runRelist(wizard.result.itemId);
8860
+ },
8861
+ children: t("myconfigs.result.relist")
8862
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8863
+ kind: "info",
8864
+ children: t("myconfigs.result.listingPending")
8865
+ })
8866
+ }),
8867
+ listingStatus !== null && listingStatus.listing === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8868
+ kind: "error",
8869
+ children: redact(listingStatus.error ?? t("common.unknownError"))
8870
+ }),
8871
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8872
+ className: config_manager_module_css_default.actionRow,
8873
+ children: [
8874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8875
+ kind: "info",
8876
+ children: t("myconfigs.result.repo")
8877
+ }),
8878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8879
+ href: wizard.result.repoUrl,
8880
+ children: t("myconfigs.result.openRepo")
8881
+ }),
8882
+ prLink !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8883
+ href: prLink.url,
8884
+ children: prLink.label
8885
+ })
8886
+ ]
8887
+ })
8888
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8889
+ kind: "error",
8890
+ children: redact(wizard.result.error ?? ((wizard.result.warnings ?? []).join(" · ") || t("common.unknownError")))
8891
+ }))
8250
8892
  ]
8251
- })
8252
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8253
- kind: "error",
8254
- children: redact(wizard.result.error ?? ((wizard.result.warnings ?? []).join(" · ") || t("common.unknownError")))
8255
- }))
8256
- ] });
8893
+ })]
8894
+ })
8895
+ });
8257
8896
  };
8258
8897
  /** 已上传列表(条目卡片 + 状态徽章 + 行操作) */
8259
8898
  const renderList = () => {
@@ -8338,13 +8977,14 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
8338
8977
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8339
8978
  onClick: () => {
8340
8979
  startUpdate(entry);
8980
+ openUpload();
8341
8981
  },
8342
8982
  children: t("myconfigs.item.update")
8343
8983
  }),
8344
8984
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8345
8985
  disabled: install !== null && install.itemId === view.id && install.detail === null,
8346
8986
  onClick: () => {
8347
- runDownload(entry);
8987
+ openInstall(entry);
8348
8988
  },
8349
8989
  children: t("myconfigs.item.install")
8350
8990
  }),
@@ -8355,6 +8995,14 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
8355
8995
  badge.kind === "warn" && badge.prUrl !== void 0 && badge.prUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8356
8996
  href: badge.prUrl,
8357
8997
  children: t("myconfigs.item.openPr")
8998
+ }),
8999
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9000
+ variant: "danger",
9001
+ disabled: deletingId !== null,
9002
+ onClick: () => {
9003
+ onMyConfirmDeleteChange(view.id);
9004
+ },
9005
+ children: t("myconfigs.delete.run")
8358
9006
  })
8359
9007
  ]
8360
9008
  })]
@@ -8369,114 +9017,127 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
8369
9017
  const approvalList = detail !== null ? approvalRows(detail.plan, install.approvals) : [];
8370
9018
  const approvalSummary = detail !== null ? approvedAdapterSummary(detail.plan, install.approvals) : null;
8371
9019
  const detailView = detail !== null ? marketDetailView(detail, detail.repo ?? entryRepoUrl(install.itemId), true, uiT) : null;
8372
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8373
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8374
- className: config_manager_module_css_default.actionRow,
8375
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8376
- className: config_manager_module_css_default.groupLabel,
8377
- children: [
8378
- t("detail.title"),
8379
- ":",
8380
- install.itemId
8381
- ]
8382
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8383
- onClick: () => {
8384
- setInstall(null);
8385
- },
8386
- children: t("detail.back")
8387
- })]
8388
- }),
8389
- install.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8390
- kind: "error",
8391
- children: redact(install.error)
8392
- }),
8393
- detail === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8394
- className: config_manager_module_css_default.statRow,
8395
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") })
8396
- }),
8397
- detail !== null && detailView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8398
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8399
- kind: "warn",
8400
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.needReview") })
8401
- }),
8402
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8403
- className: config_manager_module_css_default.statRow,
8404
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8405
- kind: detailView.badge.valid ? "ok" : "error",
8406
- children: detailView.badge.statusText
8407
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8408
- kind: "info",
8409
- children: detailView.badge.sectionsText
9020
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9021
+ className: config_manager_module_css_default.dialogMask,
9022
+ onMouseDown: (e) => {
9023
+ if (e.target === e.currentTarget && !install.importing) closeInstall();
9024
+ },
9025
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9026
+ className: `${config_manager_module_css_default.dialogCard} ${config_manager_module_css_default.dialogWide}`,
9027
+ role: "dialog",
9028
+ "aria-modal": "true",
9029
+ "aria-label": t("detail.title"),
9030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9031
+ className: config_manager_module_css_default.dialogHeaderRow,
9032
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9033
+ className: config_manager_module_css_default.dialogHeader,
9034
+ children: [
9035
+ t("detail.title"),
9036
+ ":",
9037
+ install.itemId
9038
+ ]
9039
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9040
+ type: "button",
9041
+ className: config_manager_module_css_default.dialogClose,
9042
+ "aria-label": t("common.close"),
9043
+ disabled: install.importing,
9044
+ onClick: closeInstall,
9045
+ children: "×"
8410
9046
  })]
8411
- }),
8412
- approvalList.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8413
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8414
- className: config_manager_module_css_default.groupLabel,
8415
- children: t("detail.approval.title")
8416
- }),
8417
- approvalSummary !== null && approvalSummary.highRiskTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8418
- kind: "warn",
8419
- children: t("detail.approval.highRiskHint")
8420
- }),
8421
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8422
- className: config_manager_module_css_default.conflictList,
8423
- children: approvalList.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
8424
- checked: row.approved,
8425
- onChange: (checked) => {
8426
- setInstall((i) => i !== null ? {
8427
- ...i,
8428
- approvals: {
8429
- ...i.approvals,
8430
- [row.adapter]: checked
8431
- }
8432
- } : i);
8433
- },
8434
- label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
9047
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9048
+ className: config_manager_module_css_default.dialogBodyScroll,
9049
+ children: [
9050
+ install.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9051
+ kind: "error",
9052
+ children: redact(install.error)
9053
+ }),
9054
+ detail === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9055
+ className: config_manager_module_css_default.statRow,
9056
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") })
9057
+ }),
9058
+ detail !== null && detailView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
9059
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9060
+ kind: "warn",
9061
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.needReview") })
9062
+ }),
9063
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9064
+ className: config_manager_module_css_default.statRow,
9065
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9066
+ kind: detailView.badge.valid ? "ok" : "error",
9067
+ children: detailView.badge.statusText
9068
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9069
+ kind: "info",
9070
+ children: detailView.badge.sectionsText
9071
+ })]
9072
+ }),
9073
+ approvalList.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8435
9074
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8436
- className: config_manager_module_css_default.conflictId,
8437
- children: row.adapter
9075
+ className: config_manager_module_css_default.groupLabel,
9076
+ children: t("detail.approval.title")
8438
9077
  }),
8439
- " ",
8440
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8441
- kind: row.highRisk ? "warn" : "info",
8442
- children: row.highRisk ? t("detail.approval.requiresApproval") : t("detail.approval.safe")
9078
+ approvalSummary !== null && approvalSummary.highRiskTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9079
+ kind: "warn",
9080
+ children: t("detail.approval.highRiskHint")
8443
9081
  }),
8444
- " ",
8445
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8446
- kind: "info",
8447
- children: row.label
9082
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9083
+ className: config_manager_module_css_default.conflictList,
9084
+ children: approvalList.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
9085
+ checked: row.approved,
9086
+ onChange: (checked) => {
9087
+ if (installRef.current !== null) patchInstall({ approvals: {
9088
+ ...installRef.current.approvals,
9089
+ [row.adapter]: checked
9090
+ } });
9091
+ },
9092
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
9093
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9094
+ className: config_manager_module_css_default.conflictId,
9095
+ children: row.adapter
9096
+ }),
9097
+ " ",
9098
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9099
+ kind: row.highRisk ? "warn" : "info",
9100
+ children: row.highRisk ? t("detail.approval.requiresApproval") : t("detail.approval.safe")
9101
+ }),
9102
+ " ",
9103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9104
+ kind: "info",
9105
+ children: row.label
9106
+ })
9107
+ ] })
9108
+ }, row.adapter))
9109
+ }),
9110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9111
+ className: config_manager_module_css_default.statRow,
9112
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9113
+ kind: approvalSummary !== null && approvalSummary.canImport ? "ok" : "warn",
9114
+ children: approvalSummary !== null ? t("detail.approval.count", {
9115
+ selected: String(approvalSummary.selected),
9116
+ total: String(approvalSummary.total)
9117
+ }) : ""
9118
+ })
8448
9119
  })
8449
- ] })
8450
- }, row.adapter))
8451
- }),
8452
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8453
- className: config_manager_module_css_default.statRow,
8454
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8455
- kind: approvalSummary !== null && approvalSummary.canImport ? "ok" : "warn",
8456
- children: approvalSummary !== null ? t("detail.approval.count", {
8457
- selected: String(approvalSummary.selected),
8458
- total: String(approvalSummary.total)
8459
- }) : ""
9120
+ ] }),
9121
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9122
+ className: config_manager_module_css_default.actionRow,
9123
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9124
+ variant: "primary",
9125
+ disabled: install.importing || approvalSummary !== null && !approvalSummary.canImport,
9126
+ onClick: () => {
9127
+ runImport();
9128
+ },
9129
+ children: install.importing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("detail.import")
9130
+ })
9131
+ })
9132
+ ] }),
9133
+ install.importResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
9134
+ kind: install.importResult.ok ? "ok" : "error",
9135
+ children: [install.importResult.ok ? `导入完成:${install.importResult.executed.filter((e) => e.status === "ok").length} 项写入` : `导入失败(${install.importResult.executed.filter((e) => e.status === "failed").length} 项失败)`, install.importResult.needsRestart && " · 部分改动需重启 DSH 后生效"]
8460
9136
  })
8461
- })
8462
- ] }),
8463
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8464
- className: config_manager_module_css_default.actionRow,
8465
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8466
- variant: "primary",
8467
- disabled: install.importing || approvalSummary !== null && !approvalSummary.canImport,
8468
- onClick: () => {
8469
- runImport();
8470
- },
8471
- children: install.importing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("detail.import")
8472
- })
8473
- })
8474
- ] }),
8475
- install.importResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
8476
- kind: install.importResult.ok ? "ok" : "error",
8477
- children: [install.importResult.ok ? `导入完成:${install.importResult.executed.filter((e) => e.status === "ok").length} 项写入` : `导入失败(${install.importResult.executed.filter((e) => e.status === "failed").length} 项失败)`, install.importResult.needsRestart && " · 部分改动需重启 DSH 后生效"]
9137
+ ]
9138
+ })]
8478
9139
  })
8479
- ] });
9140
+ });
8480
9141
  };
8481
9142
  /** 装回本地详情展示用的仓库 URL(供应链警示来源行;取条目 repoUrl 兜底固定目标仓库) */
8482
9143
  function entryRepoUrl(itemId) {
@@ -8485,23 +9146,65 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
8485
9146
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8486
9147
  className: config_manager_module_css_default.viewBody,
8487
9148
  children: [
8488
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8489
- className: config_manager_module_css_default.actionRow,
8490
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8491
- onClick: onBack,
8492
- children: t("myconfigs.back")
8493
- })
8494
- }),
8495
9149
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
8496
9150
  title: t("myconfigs.tab.myconfigs"),
8497
9151
  subtitle: t("myconfigs.login.hint")
8498
9152
  }),
8499
9153
  renderLoginCard(),
8500
9154
  loginView.kind === "logged-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8501
- renderWizard(),
9155
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9156
+ className: config_manager_module_css_default.actionRow,
9157
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9158
+ className: config_manager_module_css_default.groupLabel,
9159
+ children: t("myconfigs.upload.title")
9160
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9161
+ variant: "primary",
9162
+ onClick: openUpload,
9163
+ children: t("myconfigs.upload.run")
9164
+ })]
9165
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9166
+ className: config_manager_module_css_default.hint,
9167
+ children: t("myconfigs.upload.selectHint")
9168
+ })] }),
8502
9169
  renderList(),
8503
- renderInstall()
8504
- ] })
9170
+ uploadOpen && renderWizard(),
9171
+ installOpen && renderInstall()
9172
+ ] }),
9173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
9174
+ open: disclaimerKey !== null,
9175
+ title: t("disclaimer.title"),
9176
+ message: disclaimerText(),
9177
+ confirmLabel: t("disclaimer.confirm"),
9178
+ cancelLabel: t("common.cancel"),
9179
+ onConfirm: confirmDisclaimer,
9180
+ onCancel: cancelDisclaimer,
9181
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9182
+ className: config_manager_module_css_default.checkboxRow,
9183
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9184
+ type: "checkbox",
9185
+ checked: dontAsk,
9186
+ onChange: (e) => {
9187
+ setDontAsk(e.target.checked);
9188
+ }
9189
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("disclaimer.dontAsk") })]
9190
+ })
9191
+ }),
9192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
9193
+ open: confirmDeleteId !== null,
9194
+ title: t("myconfigs.delete.confirmTitle"),
9195
+ message: t("myconfigs.delete.confirmText"),
9196
+ confirmLabel: t("myconfigs.delete.confirm"),
9197
+ cancelLabel: t("common.cancel"),
9198
+ danger: true,
9199
+ busy: deletingId !== null,
9200
+ onConfirm: async () => {
9201
+ const entry = (myItems ?? []).find((it) => it.id === confirmDeleteId);
9202
+ if (entry !== void 0) await runDelete(entry);
9203
+ },
9204
+ onCancel: () => {
9205
+ onMyConfirmDeleteChange(null);
9206
+ }
9207
+ })
8505
9208
  ]
8506
9209
  });
8507
9210
  }
@@ -8531,9 +9234,11 @@ const initial$1 = {
8531
9234
  subView: "browse",
8532
9235
  myItems: null,
8533
9236
  myItemsError: null,
9237
+ myWizard: null,
9238
+ myInstall: null,
9239
+ myConfirmDeleteId: null,
8534
9240
  loading: true,
8535
9241
  loadError: null,
8536
- market: null,
8537
9242
  refreshing: false,
8538
9243
  browsing: false,
8539
9244
  items: [],
@@ -8558,6 +9263,9 @@ function initFromStore() {
8558
9263
  subView: s.subView,
8559
9264
  myItems: s.myItems,
8560
9265
  myItemsError: s.myItemsError,
9266
+ myWizard: s.myWizard,
9267
+ myInstall: s.myInstall,
9268
+ myConfirmDeleteId: s.myConfirmDeleteId,
8561
9269
  search: s.search,
8562
9270
  category: s.category,
8563
9271
  items: s.items,
@@ -8589,15 +9297,54 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
8589
9297
  ...stateRef.current,
8590
9298
  ...p
8591
9299
  });
9300
+ /** 下载详情弹窗开关(瞬态 UI,不持久化) */
9301
+ const [downloadOpen, setDownloadOpen] = (0, react.useState)(false);
9302
+ /** 当前展示的免责弹窗操作(null = 无) */
9303
+ const [disclaimerKey, setDisclaimerKey] = (0, react.useState)(null);
9304
+ /** 免责弹窗「不再提示」勾选(每次打开重置) */
9305
+ const [dontAsk, setDontAsk] = (0, react.useState)(false);
9306
+ /** localStorage(浏览器环境;免责「不再提示」跨会话持久化) */
9307
+ const storage = window.localStorage;
9308
+ /** 待下载条目(免责确认后取用;避免免责流程中闭包过期) */
9309
+ const pendingDownloadItem = (0, react.useRef)(null);
9310
+ /** 点条目「查看详情」:未勾「不再提示」→ 先弹免责,确认后下载并打开详情弹窗 */
9311
+ const openDownload = (item) => {
9312
+ pendingDownloadItem.current = item;
9313
+ if (readDisclaimerDismissed("download", storage)) {
9314
+ setDownloadOpen(true);
9315
+ runDownload(item);
9316
+ return;
9317
+ }
9318
+ setDontAsk(false);
9319
+ setDisclaimerKey("download");
9320
+ };
9321
+ /** 免责弹窗确认:勾选则记录「不再提示」→ 关闭免责 → 打开下载详情弹窗并启动下载 */
9322
+ const confirmDownloadDisclaimer = () => {
9323
+ if (disclaimerKey !== "download") return;
9324
+ if (dontAsk) writeDisclaimerDismissed("download", storage);
9325
+ setDisclaimerKey(null);
9326
+ const item = pendingDownloadItem.current;
9327
+ setDownloadOpen(true);
9328
+ if (item !== null) runDownload(item);
9329
+ };
9330
+ /** 关闭下载详情弹窗:清 detail(弹窗即会话,关闭即放弃) */
9331
+ const closeDownload = () => {
9332
+ setDownloadOpen(false);
9333
+ patch({
9334
+ detail: null,
9335
+ importResult: null,
9336
+ error: null,
9337
+ approvals: {}
9338
+ });
9339
+ };
8592
9340
  /** 卸载时置挂载守卫 + 最后镜像一次(防止「最后一次改动后立即切 tab」时丢状态)。 */
8593
9341
  (0, react.useEffect)(() => () => {
8594
9342
  mountedRef.current = false;
8595
9343
  runStore.patch({ market: toMarketStoreSlice(stateRef.current) });
8596
9344
  }, []);
8597
- /** 内置市场 URL(单一权威;来自 Host 内置常量) */
9345
+ /** 内置市场 URL(单一权威;来自 Host 内置常量;用于条目来源判定与详情供应链警示) */
8598
9346
  const marketUrl = BUILTIN_MARKET_URL;
8599
- const official = isOfficialMarket(marketUrl);
8600
- /** 挂载时读取内置市场状态 */
9347
+ /** 挂载时读取内置市场状态;返回响应供「首次打开自动更新」判据(bootAutoRefreshed) */
8601
9348
  const loadStatus = (0, react.useCallback)(async () => {
8602
9349
  patch({
8603
9350
  loading: true,
@@ -8605,19 +9352,30 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
8605
9352
  });
8606
9353
  try {
8607
9354
  const info = await api.status();
8608
- patch({
8609
- loading: false,
8610
- market: info.markets[0] ?? null
8611
- });
9355
+ patch({ loading: false });
9356
+ return info;
8612
9357
  } catch (err) {
8613
9358
  patch({
8614
9359
  loading: false,
8615
9360
  loadError: err instanceof Error ? err.message : String(err)
8616
9361
  });
9362
+ return null;
8617
9363
  }
8618
9364
  }, [api]);
9365
+ /**
9366
+ * 启动后首次打开市场页 → 自动拉取一次最新 index(需求:dsh 启动后第一次打开市场页面自动更新一次市场)。
9367
+ * 判据是 Host 侧进程内存标记 bootAutoRefreshed(dsh 重启后归零;refresh 成功后置位),
9368
+ * 因此「每次打开都刷新」/「刷新失败后下次打开重试」都自然成立;无需客户端持久化。
9369
+ * bootAutoChecked 同步置位防 StrictMode 双执行/竞态重复触发(同一组件实例只自动刷一次)。
9370
+ */
9371
+ const bootAutoChecked = (0, react.useRef)(false);
8619
9372
  (0, react.useEffect)(() => {
8620
- loadStatus();
9373
+ if (bootAutoChecked.current) return;
9374
+ bootAutoChecked.current = true;
9375
+ (async () => {
9376
+ const info = await loadStatus();
9377
+ if (info !== null && info.bootAutoRefreshed !== true) await runRefresh();
9378
+ })();
8621
9379
  }, []);
8622
9380
  /** 拉取内置市场最新 index.json → 重新浏览(缓存状态由 Host 合并) */
8623
9381
  const runRefresh = async () => {
@@ -8714,7 +9472,6 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
8714
9472
  });
8715
9473
  }
8716
9474
  };
8717
- const statusText = marketStatusText({ count: 1 }, uiT);
8718
9475
  const filtered = filterMarketItems(state.items, state.search, state.category);
8719
9476
  const summary = marketListSummary(state.items, uiT);
8720
9477
  const categories = collectCategories(state.items);
@@ -8766,210 +9523,203 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
8766
9523
  myItemsError: error
8767
9524
  });
8768
9525
  },
8769
- onBack: () => {
8770
- patch({ subView: "browse" });
9526
+ myWizard: state.myWizard,
9527
+ onMyWizardChange: (wizard) => {
9528
+ patch({ myWizard: wizard });
9529
+ },
9530
+ myInstall: state.myInstall,
9531
+ onMyInstallChange: (install) => {
9532
+ patch({ myInstall: install });
9533
+ },
9534
+ myConfirmDeleteId: state.myConfirmDeleteId,
9535
+ onMyConfirmDeleteChange: (id) => {
9536
+ patch({ myConfirmDeleteId: id });
8771
9537
  }
8772
9538
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8773
9539
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
8774
9540
  title: t("section.label"),
8775
9541
  subtitle: t("section.description")
8776
9542
  }),
8777
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8778
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8779
- className: config_manager_module_css_default.groupLabel,
8780
- children: t("config.title")
8781
- }),
8782
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8783
- className: config_manager_module_css_default.hint,
8784
- children: t("config.builtinHint")
8785
- }),
8786
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8787
- className: config_manager_module_css_default.statRow,
8788
- style: { paddingTop: 4 },
8789
- children: [
8790
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8791
- kind: "info",
8792
- children: marketUrl
8793
- }),
8794
- official && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8795
- kind: "ok",
8796
- children: t("config.official")
8797
- }),
8798
- !official && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8799
- kind: "warn",
8800
- children: t("config.custom")
8801
- })
8802
- ]
8803
- }),
8804
- state.market !== null && (state.market.name || state.market.itemCount !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8805
- className: config_manager_module_css_default.statRow,
8806
- children: [state.market.name !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8807
- kind: "info",
8808
- children: state.market.name
8809
- }), state.market.itemCount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8810
- kind: "info",
8811
- children: t("list.count", { count: String(state.market.itemCount) })
8812
- })]
8813
- }),
8814
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8815
- className: config_manager_module_css_default.actionRow,
8816
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8817
- variant: "primary",
8818
- disabled: state.refreshing || state.importing,
8819
- onClick: () => {
8820
- runRefresh();
8821
- },
8822
- children: state.refreshing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.refreshing") }) : t("config.refresh")
8823
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8824
- disabled: state.browsing,
8825
- onClick: () => {
8826
- runBrowse();
8827
- },
8828
- children: state.browsing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") }) : t("list.browse")
8829
- })]
8830
- }),
8831
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8832
- className: config_manager_module_css_default.statRow,
8833
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8834
- kind: state.loadError !== null ? "error" : "ok",
8835
- children: statusText
8836
- })
8837
- })
8838
- ] }),
9543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9544
+ className: config_manager_module_css_default.groupLabel,
9545
+ children: t("config.title")
9546
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9547
+ className: config_manager_module_css_default.actionRow,
9548
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9549
+ variant: "primary",
9550
+ disabled: state.refreshing || state.importing,
9551
+ onClick: () => {
9552
+ runRefresh();
9553
+ },
9554
+ children: state.refreshing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.refreshing") }) : t("config.refresh")
9555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9556
+ disabled: state.browsing,
9557
+ onClick: () => {
9558
+ runBrowse();
9559
+ },
9560
+ children: state.browsing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") }) : t("list.browse")
9561
+ })]
9562
+ })] }),
8839
9563
  state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8840
9564
  kind: "error",
8841
9565
  children: state.error
8842
9566
  }),
8843
- state.detail !== null && detailView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8844
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8845
- className: config_manager_module_css_default.actionRow,
8846
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8847
- className: config_manager_module_css_default.groupLabel,
9567
+ downloadOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9568
+ className: config_manager_module_css_default.dialogMask,
9569
+ onMouseDown: (e) => {
9570
+ if (e.target === e.currentTarget && !state.importing) closeDownload();
9571
+ },
9572
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9573
+ className: `${config_manager_module_css_default.dialogCard} ${config_manager_module_css_default.dialogWide}`,
9574
+ role: "dialog",
9575
+ "aria-modal": "true",
9576
+ "aria-label": t("detail.title"),
9577
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9578
+ className: config_manager_module_css_default.dialogHeaderRow,
9579
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9580
+ className: config_manager_module_css_default.dialogHeader,
9581
+ children: [
9582
+ t("detail.title"),
9583
+ ":",
9584
+ state.detail !== null ? state.detail.name : state.downloadingId ?? ""
9585
+ ]
9586
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9587
+ type: "button",
9588
+ className: config_manager_module_css_default.dialogClose,
9589
+ "aria-label": t("common.close"),
9590
+ disabled: state.importing,
9591
+ onClick: closeDownload,
9592
+ children: "×"
9593
+ })]
9594
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9595
+ className: config_manager_module_css_default.dialogBodyScroll,
8848
9596
  children: [
8849
- t("detail.title"),
8850
- ":",
8851
- state.detail.name
8852
- ]
8853
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8854
- onClick: () => {
8855
- patch({ detail: null });
8856
- },
8857
- children: t("detail.back")
8858
- })]
8859
- }),
8860
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8861
- kind: "warn",
8862
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.needReview") })
8863
- }),
8864
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8865
- className: config_manager_module_css_default.warnList,
8866
- children: detailView.warnings.map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
8867
- style: { color: w.kind === "warn" ? "var(--dsw-alias-state-warn-primary)" : void 0 },
8868
- children: w.text
8869
- }, i))
8870
- }),
8871
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8872
- className: config_manager_module_css_default.statRow,
8873
- children: [
8874
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8875
- kind: detailView.badge.statusKind === "ok" ? "ok" : "error",
8876
- children: detailView.badge.statusText
8877
- }),
8878
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8879
- kind: "info",
8880
- children: detailView.badge.sectionsText
8881
- }),
8882
- state.detail.version !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8883
- kind: "info",
8884
- children: t("detail.version", { version: state.detail.version })
8885
- })
8886
- ]
8887
- }),
8888
- detailView.errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8889
- className: config_manager_module_css_default.fieldLabel,
8890
- children: t("detail.errors")
8891
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8892
- className: config_manager_module_css_default.reportScroll,
8893
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8894
- className: config_manager_module_css_default.warnList,
8895
- children: detailView.errors.map((e, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: e }, i))
8896
- })
8897
- })] }),
8898
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8899
- kind: "info",
8900
- children: t("detail.previewHint")
8901
- }),
8902
- detailView.canImport && approvalList.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8903
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8904
- className: config_manager_module_css_default.groupLabel,
8905
- children: t("detail.approval.title")
8906
- }),
8907
- approvalSummary !== null && approvalSummary.highRiskTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8908
- kind: "warn",
8909
- children: t("detail.approval.highRiskHint")
8910
- }),
8911
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8912
- className: config_manager_module_css_default.conflictList,
8913
- children: approvalList.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
8914
- className: config_manager_module_css_default.checkboxRow,
8915
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8916
- type: "checkbox",
8917
- checked: row.approved,
8918
- onChange: (e) => {
8919
- patch({ approvals: {
8920
- ...state.approvals,
8921
- [row.adapter]: e.target.checked
8922
- } });
8923
- }
8924
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
8925
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8926
- className: config_manager_module_css_default.conflictId,
8927
- children: row.adapter
9597
+ state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9598
+ kind: "error",
9599
+ children: redact(state.error)
9600
+ }),
9601
+ state.detail === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9602
+ className: config_manager_module_css_default.statRow,
9603
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") })
9604
+ }),
9605
+ state.detail !== null && detailView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9607
+ kind: "warn",
9608
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.needReview") })
8928
9609
  }),
8929
- " ",
8930
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8931
- kind: row.highRisk ? "warn" : "info",
8932
- children: row.highRisk ? t("detail.approval.requiresApproval") : t("detail.approval.safe")
9610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9611
+ className: config_manager_module_css_default.warnList,
9612
+ children: detailView.warnings.map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
9613
+ style: { color: w.kind === "warn" ? "var(--dsw-alias-state-warn-primary)" : void 0 },
9614
+ children: w.text
9615
+ }, i))
8933
9616
  }),
8934
- " ",
8935
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9618
+ className: config_manager_module_css_default.statRow,
9619
+ children: [
9620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9621
+ kind: detailView.badge.statusKind === "ok" ? "ok" : "error",
9622
+ children: detailView.badge.statusText
9623
+ }),
9624
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9625
+ kind: "info",
9626
+ children: detailView.badge.sectionsText
9627
+ }),
9628
+ state.detail.version !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9629
+ kind: "info",
9630
+ children: t("detail.version", { version: state.detail.version })
9631
+ })
9632
+ ]
9633
+ }),
9634
+ detailView.errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9635
+ className: config_manager_module_css_default.fieldLabel,
9636
+ children: t("detail.errors")
9637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9638
+ className: config_manager_module_css_default.reportScroll,
9639
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9640
+ className: config_manager_module_css_default.warnList,
9641
+ children: detailView.errors.map((e, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: e }, i))
9642
+ })
9643
+ })] }),
9644
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8936
9645
  kind: "info",
8937
- children: row.label
9646
+ children: t("detail.previewHint")
9647
+ }),
9648
+ detailView.canImport && approvalList.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
9649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9650
+ className: config_manager_module_css_default.groupLabel,
9651
+ children: t("detail.approval.title")
9652
+ }),
9653
+ approvalSummary !== null && approvalSummary.highRiskTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9654
+ kind: "warn",
9655
+ children: t("detail.approval.highRiskHint")
9656
+ }),
9657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9658
+ className: config_manager_module_css_default.conflictList,
9659
+ children: approvalList.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9660
+ className: config_manager_module_css_default.checkboxRow,
9661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9662
+ type: "checkbox",
9663
+ checked: row.approved,
9664
+ onChange: (e) => {
9665
+ patch({ approvals: {
9666
+ ...state.approvals,
9667
+ [row.adapter]: e.target.checked
9668
+ } });
9669
+ }
9670
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
9671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9672
+ className: config_manager_module_css_default.conflictId,
9673
+ children: row.adapter
9674
+ }),
9675
+ " ",
9676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9677
+ kind: row.highRisk ? "warn" : "info",
9678
+ children: row.highRisk ? t("detail.approval.requiresApproval") : t("detail.approval.safe")
9679
+ }),
9680
+ " ",
9681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9682
+ kind: "info",
9683
+ children: row.label
9684
+ })
9685
+ ] })]
9686
+ }, row.adapter))
9687
+ }),
9688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9689
+ className: config_manager_module_css_default.statRow,
9690
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
9691
+ kind: approvalSummary !== null && approvalSummary.canImport ? "ok" : "warn",
9692
+ children: approvalSummary !== null ? t("detail.approval.count", {
9693
+ selected: String(approvalSummary.selected),
9694
+ total: String(approvalSummary.total)
9695
+ }) : ""
9696
+ })
9697
+ })
9698
+ ] }),
9699
+ detailView.canImport ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9700
+ className: config_manager_module_css_default.actionRow,
9701
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9702
+ variant: "primary",
9703
+ disabled: state.importing || approvalSummary !== null && !approvalSummary.canImport,
9704
+ onClick: () => {
9705
+ runImport();
9706
+ },
9707
+ children: state.importing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("detail.import")
9708
+ })
9709
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
9710
+ kind: "error",
9711
+ children: t("detail.emptySections")
9712
+ }),
9713
+ state.importResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
9714
+ kind: state.importResult.ok ? "ok" : "error",
9715
+ children: [state.importResult.ok ? `导入完成:${state.importResult.executed.filter((e) => e.status === "ok").length} 项写入` : `导入失败(${state.importResult.executed.filter((e) => e.status === "failed").length} 项失败)`, state.importResult.needsRestart && " · 部分改动需重启 DSH 后生效"]
8938
9716
  })
8939
- ] })]
8940
- }, row.adapter))
8941
- }),
8942
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8943
- className: config_manager_module_css_default.statRow,
8944
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8945
- kind: approvalSummary !== null && approvalSummary.canImport ? "ok" : "warn",
8946
- children: approvalSummary !== null ? t("detail.approval.count", {
8947
- selected: String(approvalSummary.selected),
8948
- total: String(approvalSummary.total)
8949
- }) : ""
8950
- })
8951
- })
8952
- ] }),
8953
- detailView.canImport ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8954
- className: config_manager_module_css_default.actionRow,
8955
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8956
- variant: "primary",
8957
- disabled: state.importing || approvalSummary !== null && !approvalSummary.canImport,
8958
- onClick: () => {
8959
- runImport();
8960
- },
8961
- children: state.importing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("detail.import")
8962
- })
8963
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8964
- kind: "error",
8965
- children: t("detail.emptySections")
8966
- }),
8967
- state.importResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
8968
- kind: state.importResult.ok ? "ok" : "error",
8969
- children: [state.importResult.ok ? `导入完成:${state.importResult.executed.filter((e) => e.status === "ok").length} 项写入` : `导入失败(${state.importResult.executed.filter((e) => e.status === "failed").length} 项失败)`, state.importResult.needsRestart && " · 部分改动需重启 DSH 后生效"]
9717
+ ] })
9718
+ ]
9719
+ })]
8970
9720
  })
8971
- ] }),
8972
- state.detail === null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
9721
+ }),
9722
+ !downloadOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8973
9723
  state.loadError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, { children: t("list.empty") }),
8974
9724
  state.loadError === null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8975
9725
  className: config_manager_module_css_default.statRow,
@@ -9060,14 +9810,35 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
9060
9810
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9061
9811
  disabled: state.downloadingId === it.id,
9062
9812
  onClick: () => {
9063
- runDownload(it);
9813
+ openDownload(it);
9064
9814
  },
9065
9815
  children: state.downloadingId === it.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("list.download")
9066
9816
  })]
9067
9817
  }, it.id);
9068
9818
  })
9069
9819
  })
9070
- ] })
9820
+ ] }),
9821
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
9822
+ open: disclaimerKey === "download",
9823
+ title: t("disclaimer.title"),
9824
+ message: t("disclaimer.download.text"),
9825
+ confirmLabel: t("disclaimer.confirm"),
9826
+ cancelLabel: t("common.cancel"),
9827
+ onConfirm: confirmDownloadDisclaimer,
9828
+ onCancel: () => {
9829
+ setDisclaimerKey(null);
9830
+ },
9831
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9832
+ className: config_manager_module_css_default.checkboxRow,
9833
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9834
+ type: "checkbox",
9835
+ checked: dontAsk,
9836
+ onChange: (e) => {
9837
+ setDontAsk(e.target.checked);
9838
+ }
9839
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("disclaimer.dontAsk") })]
9840
+ })
9841
+ })
9071
9842
  ] })]
9072
9843
  });
9073
9844
  }
@@ -9717,10 +10488,14 @@ const zh$1 = {
9717
10488
  "config.saving": "保存中…",
9718
10489
  "config.saveHint": "表单改动会自动保存(密码/token 安全写入 DSH credentials,不会写入同步文件或日志);也可点击按钮立即保存。",
9719
10490
  "channel.title": "同步通道",
9720
- "channel.hint": "选择远程同步通道:Git 私有仓库或 WebDAV 服务器。",
9721
10491
  "channel.git": "Git 私有仓库",
9722
10492
  "channel.webdav": "WebDAV 服务器",
9723
10493
  "channel.perChannelHint": "GitHub 与 WebDAV 通道的自动同步、同步模式、加密与远端快照各自独立配置。",
10494
+ "channel.open": "配置同步通道",
10495
+ "channel.openHint": "远程同步通过「同步通道」进行:Git 私有仓库或 WebDAV 服务器。点击按钮在弹窗中配置或修改。",
10496
+ "channel.configured": "已配置",
10497
+ "channel.notConfigured": "未配置",
10498
+ "channel.currentUrl": "当前地址",
9724
10499
  "webdav.title": "WebDAV 配置",
9725
10500
  "webdav.url": "服务器地址",
9726
10501
  "webdav.urlHint": "WebDAV 服务器根地址(https://…)。同步快照与索引存放于该地址的 snapshots/ 子目录下。请勿在地址中包含用户名/密码。",
@@ -9736,6 +10511,7 @@ const zh$1 = {
9736
10511
  "github.login": "使用 GitHub 登录",
9737
10512
  "github.retry": "重新登录",
9738
10513
  "github.cancel": "取消",
10514
+ "github.tokenInvalid": "GitHub 登录已失效,请重新登录。",
9739
10515
  "github.userCode": "一次性授权代码",
9740
10516
  "github.openAuth": "打开 GitHub 授权页面",
9741
10517
  "github.clientIdHint": "需要插件配置 githubClientId(GitHub OAuth App 的 client_id)才能使用 GitHub 登录。",
@@ -9880,10 +10656,14 @@ const en$1 = {
9880
10656
  "config.saving": "Saving…",
9881
10657
  "config.saveHint": "Form changes are saved automatically (password/token written securely into DSH credentials, never into sync files or logs); you can also save explicitly with the button.",
9882
10658
  "channel.title": "Sync Channel",
9883
- "channel.hint": "Choose the remote sync channel: Git private repository or WebDAV server.",
9884
10659
  "channel.git": "Git private repo",
9885
10660
  "channel.webdav": "WebDAV server",
9886
10661
  "channel.perChannelHint": "Auto sync, sync mode, encryption and remote snapshots are configured independently for GitHub and WebDAV channels.",
10662
+ "channel.open": "Configure sync channel",
10663
+ "channel.openHint": "Remote sync runs through a sync channel: a private Git repository or a WebDAV server. Click the button to configure or change it in the dialog.",
10664
+ "channel.configured": "Configured",
10665
+ "channel.notConfigured": "Not configured",
10666
+ "channel.currentUrl": "Current URL",
9887
10667
  "webdav.title": "WebDAV Configuration",
9888
10668
  "webdav.url": "Server URL",
9889
10669
  "webdav.urlHint": "WebDAV server root URL (https://…). Sync snapshots and the index are stored under a snapshots/ subdirectory of that URL. Do not include a username/password in the URL.",
@@ -9899,6 +10679,7 @@ const en$1 = {
9899
10679
  "github.login": "Sign in with GitHub",
9900
10680
  "github.retry": "Sign in again",
9901
10681
  "github.cancel": "Cancel",
10682
+ "github.tokenInvalid": "GitHub sign-in is no longer valid — please sign in again.",
9902
10683
  "github.userCode": "One-time code",
9903
10684
  "github.openAuth": "Open GitHub authorization page",
9904
10685
  "github.clientIdHint": "Requires the githubClientId plugin config (the client_id of your GitHub OAuth App).",
@@ -10121,7 +10902,10 @@ const MY_CONFIGS_API = {
10121
10902
  status: "/api/dsh-config-manager/me/status",
10122
10903
  upload: "/api/dsh-config-manager/me/upload",
10123
10904
  items: "/api/dsh-config-manager/me/items",
10124
- update: "/api/dsh-config-manager/me/update"
10905
+ update: "/api/dsh-config-manager/me/update",
10906
+ listing: "/api/dsh-config-manager/me/listing",
10907
+ relist: "/api/dsh-config-manager/me/relist",
10908
+ delete: "/api/dsh-config-manager/me/delete"
10125
10909
  };
10126
10910
  /** 「我的配置」请求超时(ms):上传/更新含 git clone/push/fork/PR,与 market/sync 对齐量级 */
10127
10911
  const MY_CONFIGS_TIMEOUT_MS = 300 * 1e3;
@@ -10181,6 +10965,18 @@ var MyConfigsApi = class {
10181
10965
  async meUpdate(payload) {
10182
10966
  return postJson(MY_CONFIGS_API.update, payload, this.t);
10183
10967
  }
10968
+ /** 查询收录/下架任务状态(结果卡轮询;任务表未命中时 Host 回退 GitHub 实况推导;无任务无实况 → null) */
10969
+ async meListing(itemId) {
10970
+ return postJson(MY_CONFIGS_API.listing, { itemId }, this.t);
10971
+ }
10972
+ /** 重新提交收录(失败/重启丢失后重试;幂等复用已存在 fork/open PR) */
10973
+ async meRelist(itemId) {
10974
+ return postJson(MY_CONFIGS_API.relist, { itemId }, this.t);
10975
+ }
10976
+ /** 删除条目(同步删本地索引+文件;已收录自动后台提下架 PR;待审核自动关闭收录 PR) */
10977
+ async meDelete(itemId) {
10978
+ return postJson(MY_CONFIGS_API.delete, { itemId }, this.t);
10979
+ }
10184
10980
  };
10185
10981
  //#endregion
10186
10982
  //#region src/client/market/market-locales.ts
@@ -10199,14 +10995,8 @@ const zh = {
10199
10995
  "section.label": "配置市场",
10200
10996
  "section.description": "浏览并下载社区共享的配置(公开 Git 仓库;下载内容一律视为不可信,导入前必经校验与确认)",
10201
10997
  "config.title": "市场仓库",
10202
- "config.builtinHint": "内置官方市场,只读、不可编辑(由创建者维护的公开 Git 仓库)。",
10203
- "config.official": "内置官方市场",
10204
- "config.custom": "自定义市场地址",
10205
- "config.repoUrlHint": "内置公开 Git 仓库地址。市场只读拉取(绝不推送)。",
10206
10998
  "config.refresh": "拉取最新",
10207
10999
  "config.refreshing": "拉取中…",
10208
- "config.addedAt": "更新于:{time}",
10209
- "config.itemCount": "{count} 个条目",
10210
11000
  "list.empty": "内置市场尚未加载。请先「拉取最新」。",
10211
11001
  "list.browse": "浏览",
10212
11002
  "list.loading": "正在读取市场…",
@@ -10273,6 +11063,8 @@ const zh = {
10273
11063
  "myconfigs.update.hint": "版本将自动 +1,已预填原条目信息(名称可改)。",
10274
11064
  "myconfigs.update.run": "一键更新",
10275
11065
  "myconfigs.update.running": "更新中…",
11066
+ "myconfigs.update.zipHint": "更新需要新的配置包:选择新 zip 后自动校验,通过后即可一键更新。",
11067
+ "myconfigs.update.selectZip": "选择新 ZIP…",
10276
11068
  "myconfigs.result.title": "上传/更新完成",
10277
11069
  "myconfigs.result.version": "版本 {version}",
10278
11070
  "myconfigs.result.sha256": "SHA-256:{hash}",
@@ -10281,6 +11073,9 @@ const zh = {
10281
11073
  "myconfigs.result.openRepo": "打开仓库",
10282
11074
  "myconfigs.result.pr": "收录 PR #{number}",
10283
11075
  "myconfigs.result.openPr": "查看 PR",
11076
+ "myconfigs.result.listingPending": "已上传 ✓ 正在后台提交收录(fork + 收录 PR),稍后列表状态自动更新",
11077
+ "myconfigs.result.listingFailed": "收录失败",
11078
+ "myconfigs.result.relist": "重新提交收录",
10284
11079
  "myconfigs.list.title": "已上传",
10285
11080
  "myconfigs.list.empty": "尚未上传任何配置",
10286
11081
  "myconfigs.list.loading": "正在读取…",
@@ -10290,6 +11085,18 @@ const zh = {
10290
11085
  "myconfigs.item.update": "更新",
10291
11086
  "myconfigs.item.install": "装回本地",
10292
11087
  "myconfigs.item.openPr": "查看收录 PR",
11088
+ "myconfigs.delete.run": "删除",
11089
+ "myconfigs.delete.confirm": "确认删除",
11090
+ "myconfigs.delete.confirmTitle": "删除条目",
11091
+ "myconfigs.delete.confirmText": "确认删除?该操作会从配置仓库永久删除该条目的索引与文件(不可恢复)",
11092
+ "myconfigs.delete.delistStarted": "已删除本地条目,并自动提交「下架 PR」(官方市场移除需人工合并,期间市场可能仍显示该条目)",
11093
+ "myconfigs.delete.prClosed": "已删除本地条目,并关闭该条目的待审核收录 PR",
11094
+ "disclaimer.title": "免责声明",
11095
+ "disclaimer.upload.text": "你即将把配置上传到你的公开仓库(<login>/dsh-configs)并提交官方市场收录申请。\n\n上传后内容将对所有人公开可见,且需经过人工审核才能进入官方市场。请确保:\n· 内容不包含任何真实密钥 / 凭据(环境变量引用如 $VAR 是安全的);\n· 内容不包含个人隐私或本地环境信息;\n· 你有权分享这些配置。",
11096
+ "disclaimer.download.text": "你即将从公共网络市场下载配置并导入本地。\n\n下载内容属于不可信输入:未经官方审核、可能包含风险(安装插件 / 写入文件 / 修改设置)。\n· 导入前会逐项展示并校验,高风险分区默认不导入、需逐项批准;\n· 导入会先自动创建快照,失败可回滚;\n· 请核对来源与内容后再确认导入。",
11097
+ "disclaimer.install.text": "你即将把配置从你的公开仓库装回本地。\n\n该内容属于你此前上传的配置,但同样会经过安全校验与逐分区批准:\n· 高风险分区默认不导入、需逐项批准;\n· 导入前自动创建快照,失败可回滚。",
11098
+ "disclaimer.confirm": "我已了解,继续",
11099
+ "disclaimer.dontAsk": "不再提示(该操作以后不再显示免责声明)",
10293
11100
  "myconfigs.error.loadStatus": "登录状态读取失败",
10294
11101
  "myconfigs.error.loadItems": "已上传列表读取失败",
10295
11102
  "common.cancel": "取消",
@@ -10302,14 +11109,8 @@ const en = {
10302
11109
  "section.label": "Config Marketplace",
10303
11110
  "section.description": "Browse and download community-shared configs (public Git repos; downloaded content is always untrusted — validated and confirmed before import)",
10304
11111
  "config.title": "Market Repository",
10305
- "config.builtinHint": "Built-in official market — read-only and not editable (a public Git repo maintained by the creator).",
10306
- "config.official": "Built-in official market",
10307
- "config.custom": "Custom market URL",
10308
- "config.repoUrlHint": "Built-in public Git repository URL. Markets are read-only (never pushed).",
10309
11112
  "config.refresh": "Refresh",
10310
11113
  "config.refreshing": "Refreshing…",
10311
- "config.addedAt": "Updated: {time}",
10312
- "config.itemCount": "{count} item(s)",
10313
11114
  "list.empty": "The built-in market has not loaded yet. Click \"Refresh\" first.",
10314
11115
  "list.browse": "Browse",
10315
11116
  "list.loading": "Reading market…",
@@ -10376,6 +11177,8 @@ const en = {
10376
11177
  "myconfigs.update.hint": "The version will be bumped automatically (+1); item info is prefilled (name editable).",
10377
11178
  "myconfigs.update.run": "Update",
10378
11179
  "myconfigs.update.running": "Updating…",
11180
+ "myconfigs.update.zipHint": "Updating needs a new config zip: pick one and it will be validated automatically, then you can update.",
11181
+ "myconfigs.update.selectZip": "Choose new ZIP…",
10379
11182
  "myconfigs.result.title": "Upload/update complete",
10380
11183
  "myconfigs.result.version": "version {version}",
10381
11184
  "myconfigs.result.sha256": "SHA-256: {hash}",
@@ -10384,6 +11187,9 @@ const en = {
10384
11187
  "myconfigs.result.openRepo": "Open repo",
10385
11188
  "myconfigs.result.pr": "Listing PR #{number}",
10386
11189
  "myconfigs.result.openPr": "View PR",
11190
+ "myconfigs.result.listingPending": "Uploaded ✓ submitting listing in the background (fork + PR) — list status will update shortly",
11191
+ "myconfigs.result.listingFailed": "Listing failed",
11192
+ "myconfigs.result.relist": "Resubmit listing",
10387
11193
  "myconfigs.list.title": "Uploaded",
10388
11194
  "myconfigs.list.empty": "No configs uploaded yet",
10389
11195
  "myconfigs.list.loading": "Loading…",
@@ -10393,6 +11199,18 @@ const en = {
10393
11199
  "myconfigs.item.update": "Update",
10394
11200
  "myconfigs.item.install": "Install locally",
10395
11201
  "myconfigs.item.openPr": "View listing PR",
11202
+ "myconfigs.delete.run": "Delete",
11203
+ "myconfigs.delete.confirm": "Confirm delete",
11204
+ "myconfigs.delete.confirmTitle": "Delete item",
11205
+ "myconfigs.delete.confirmText": "Delete? This permanently removes the item index and files from your config repo (not recoverable)",
11206
+ "myconfigs.delete.delistStarted": "Local item deleted; a \"delist PR\" has been submitted (removal from the official market needs human merge; the item may still appear in the market meanwhile)",
11207
+ "myconfigs.delete.prClosed": "Local item deleted; its pending listing PR has been closed",
11208
+ "disclaimer.title": "Disclaimer",
11209
+ "disclaimer.upload.text": "You are about to upload configs to your public repo (<login>/dsh-configs) and submit a listing request to the official market.\n\nUploaded content becomes publicly visible and requires human review before entering the official market. Please ensure:\n· No real secrets / credentials are included (env-var references like $VAR are safe);\n· No personal privacy or local environment info is included;\n· You have the right to share these configs.",
11210
+ "disclaimer.download.text": "You are about to download configs from a public network market and import them locally.\n\nDownloaded content is untrusted input: not officially reviewed and may carry risks (installing plugins / writing files / changing settings).\n· Everything is validated and shown before import; high-risk sections are not imported by default and require individual approval;\n· A snapshot is auto-created before import and rollback is available on failure;\n· Please verify the source and content before confirming the import.",
11211
+ "disclaimer.install.text": "You are about to reinstall configs from your public repo to this machine.\n\nThis is content you uploaded before, but it still goes through the same validation and per-section approval:\n· High-risk sections are not imported by default and require individual approval;\n· A snapshot is auto-created before import and rollback is available on failure.",
11212
+ "disclaimer.confirm": "I understand, continue",
11213
+ "disclaimer.dontAsk": "Don't ask again (no more disclaimers for this action)",
10396
11214
  "myconfigs.error.loadStatus": "Failed to read sign-in status",
10397
11215
  "myconfigs.error.loadItems": "Failed to read uploaded items",
10398
11216
  "common.cancel": "Cancel",