home-hosted 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -58,6 +58,8 @@ pnpm dev # tsx-watch panel :6000 + the stock UI's Vite
58
58
  pnpm dev --ui noc-console # the same, for another UI; --port/--ui-port override
59
59
  pnpm run build # dist/cli.js + the stock UI (uis/stock/dist)
60
60
  pnpm run build:uis # every UI under uis/, zipped into uis/dist/ (release assets)
61
+ pnpm run build:locales # i18n/sheets/*.csv -> the git-ignored i18n/generated/*.json (also
62
+ # runs on install and before every UI build)
61
63
  pnpm run quickcheck # eslint + tsc + vue-tsc + the doc links — NOT the tests
62
64
  pnpm exec vitest run # `pnpm test` is vitest in watch mode
63
65
  pnpm run check # ← THE GATE: quickcheck + vitest run --coverage. Run before every
@@ -123,7 +125,8 @@ These prevent defects if broken. The *why* for the non-obvious ones is in
123
125
  an old panel keeps serving, so a required field blanks the app.
124
126
  - **UIs move together.** A change to `uis/stock` — or to a shared contract it reads — lands in every
125
127
  other UI under `uis/`, and each altered UI raises its `ui.json` version; `scripts/stamp-uis.mjs`
126
- **refuses** a commit that changed a shipped UI without raising it (`uis/<name>/test/**` is exempt).
128
+ **refuses** a commit that changed a shipped UI without raising it (a UI's `test/` and its own
129
+ `README.md` are exempt — neither is in the built asset).
127
130
  `src/shared/**` is a UI build input, so a runtime contract needs the bump too — the hook does not
128
131
  inspect that commit.
129
132
  - **Paths.** `dataRoot` is state, under `dataRoot/.hh`; `projectDir` is the base for relative entry
package/dist/cli.js CHANGED
@@ -709,7 +709,7 @@ function parseBind(value) {
709
709
  const parsed = bindSchema(value);
710
710
  return parsed instanceof type.errors ? null : parsed;
711
711
  }
712
- var bindSchema, workspaceIdSchema, workspaceSchema, workspaceCreateSchema, workspaceRenameSchema, portSchema, onPortConflictSchema, onPortConflictDefaultSchema, restartSchema, httpCheckSchema, resourcesSchema, healthSchema, stopSchema, bootstrapSchema, bootstrapOrNullSchema, labelOrNullSchema, logBufferLinesSchema, serverSchema, authSchema, telegramSchema, notificationsSchema, logsSchema, tlsSchema, hostSchema, ddnsRecordTypeSchema, ddnsAccountSchema, ddnsDomainSchema, ddnsIpv4Schema, ddnsIpv6Schema, ddnsConfigSchema, ddnsRecordStateSchema, ddnsRecordViewSchema, ddnsStatusSchema, ddnsProviderFieldSchema, ddnsProviderInfoSchema, ddnsViewSchema, ddnsCredentialsSchema, backupsSchema, controlSchema, defaultsSchema, restartPatchSchema, httpCheckPatchSchema, resourcesPatchSchema, healthPatchSchema, stopPatchSchema, authPatchSchema, telegramPatchSchema, notificationsPatchSchema, hostPatchSchema, backupsPatchSchema, logsPatchSchema, tlsPatchSchema, controlPatchSchema, defaultsPatchSchema, editableFields, serverPatchSchema, serverCreateSchema, nannySpecSchema, nannyExitSchema, nannyStateSchema, settingsPatchSchema, workspaceSettingsPatchSchema, authStatusSchema, tlsStatusSchema, proxyEngineSchema, proxyTargetSchema, proxyTlsModeSchema, proxyRouteSchema, proxyCertificateSchema, proxyCertificateStateSchema, proxyDns01Schema, proxyConfigSchema, proxyEngineSourceSchema, proxyEngineStatusSchema, proxyEngineInfoSchema, proxyRouteStatusSchema, proxyRouteViewSchema, proxyRunStateSchema, proxyStatusSchema, proxyCertificateViewSchema, proxyDnsAccountViewSchema, proxyViewSchema, proxyRoutePatchSchema, proxyPatchSchema, proxyCertificateUploadSchema, proxyEngineInstallSchema, telegramStatusSchema, telegramChatsSchema, notificationViewSchema, sessionViewSchema, loginSchema, passwordValueSchema, passwordSchema, serverStatusSchema, healthStateSchema, portStateSchema, logStreamSchema, logLineSchema, historyEventSchema, serverHistorySchema, processResourcesSchema, hostDiskSchema, hostViewSchema, backupFileSchema, backupEntrySchema, backupsViewSchema, restoreItemSchema, restorePlanSchema, backupCreateSchema, restoreRequestSchema, serverViewSchema, healthResponseSchema, controlViewSchema, uiMetaSchema, uiStatusSchema, workspaceViewSchema, appStateSchema, logQuerySchema, freePortResultSchema, logHistoryQuerySchema, logFileInfoSchema, logServerViewSchema, logServersViewSchema, notificationActionSchema, telegramTokenSchema, apiErrorSchema, tlsUploadSchema, settingsViewSchema, settingsSavedSchema, workspaceSettingsViewSchema, workspaceSettingsSavedSchema;
712
+ var bindSchema, workspaceIdSchema, workspaceSchema, workspaceCreateSchema, workspaceRenameSchema, portSchema, onPortConflictSchema, onPortConflictDefaultSchema, restartSchema, httpCheckSchema, resourcesSchema, healthSchema, stopSchema, bootstrapSchema, bootstrapOrNullSchema, labelOrNullSchema, logBufferLinesSchema, serverSchema, authSchema, telegramSchema, notificationsSchema, logsSchema, tlsSchema, hostSchema, ddnsRecordTypeSchema, ddnsAccountSchema, ddnsDomainSchema, ddnsIpv4Schema, ddnsIpv6Schema, ddnsConfigSchema, ddnsRecordStateSchema, ddnsRecordViewSchema, ddnsStatusSchema, ddnsProviderFieldSchema, ddnsProviderInfoSchema, ddnsViewSchema, ddnsCredentialsSchema, backupsSchema, controlSchema, defaultsSchema, restartPatchSchema, httpCheckPatchSchema, resourcesPatchSchema, healthPatchSchema, stopPatchSchema, authPatchSchema, telegramPatchSchema, notificationsPatchSchema, hostPatchSchema, backupsPatchSchema, logsPatchSchema, tlsPatchSchema, controlPatchSchema, defaultsPatchSchema, editableFields, serverPatchSchema, serverCreateSchema, nannySpecSchema, nannyExitSchema, nannyStateSchema, settingsPatchSchema, workspaceSettingsPatchSchema, authStatusSchema, tlsStatusSchema, proxyEngineSchema, proxyTargetSchema, proxyTlsModeSchema, proxyRouteSchema, proxyCertificateSchema, proxyCertificateStateSchema, proxyDns01Schema, proxyConfigSchema, proxyEngineSourceSchema, proxyEngineStatusSchema, proxyEngineInfoSchema, proxyRouteStatusSchema, proxyRouteViewSchema, proxyRunStateSchema, proxyStatusSchema, proxyCertificateViewSchema, proxyDnsAccountViewSchema, proxyViewSchema, proxyRoutePatchSchema, proxyPatchSchema, proxyCertificateUploadSchema, proxyEngineInstallSchema, telegramStatusSchema, telegramChatsSchema, notificationViewSchema, sessionViewSchema, loginSchema, passwordValueSchema, passwordSchema, serverStatusSchema, healthStateSchema, portStateSchema, logStreamSchema, logLineSchema, historyEventSchema, serverHistorySchema, processResourcesSchema, hostDiskSchema, hostViewSchema, backupFileSchema, backupEntrySchema, backupsViewSchema, restoreItemSchema, restoreSkipSchema, restorePlanSchema, backupCreateSchema, restoreRequestSchema, serverViewSchema, healthResponseSchema, controlViewSchema, uiMetaSchema, uiStatusSchema, workspaceViewSchema, appStateSchema, logQuerySchema, freePortResultSchema, logHistoryQuerySchema, logFileInfoSchema, logServerViewSchema, logServersViewSchema, notificationActionSchema, telegramTokenSchema, apiErrorSchema, tlsUploadSchema, settingsViewSchema, settingsSavedSchema, workspaceSettingsViewSchema, workspaceSettingsSavedSchema;
713
713
  var init_contracts = __esmMin((() => {
714
714
  bindSchema = type("\"local\" | \"lan\" | /^\\d{1,3}(?:\\.\\d{1,3}){3}$/");
715
715
  workspaceIdSchema = type("/^[a-z0-9][a-z0-9_-]*$/");
@@ -1475,14 +1475,26 @@ maxRssBytes: "number.integer >= 0 = 0" }).onUndeclaredKey("reject");
1475
1475
  backupEntrySchema = type({
1476
1476
  /** `global:settings` | `global:secrets` | `global:tls` | `workspace:<id>`. */
1477
1477
  "id": "string",
1478
+ /**
1479
+ * The panel's own English wording. Kept because it is what an older UI reads, and what a
1480
+ * single-language UI wants; a translated UI prefers the `*Key` beside it.
1481
+ */
1478
1482
  "label": "string",
1483
+ /** A catalog key for `label`, with its placeholder values, so a UI can render its own language. */
1484
+ "labelKey?": "string",
1485
+ "labelParams?": "Record<string, string>",
1479
1486
  "kind": "\"settings\" | \"secrets\" | \"tls\" | \"workspace\"",
1480
1487
  "workspaceId?": "string",
1481
1488
  "note": "string | null",
1489
+ /** The same pair for `note`: the panel knows which reason it is, the UI knows the wording. */
1490
+ "noteKey?": "string",
1491
+ "noteParams?": "Record<string, string>",
1482
1492
  /** What this entry would capture; a workspace carries several leaves. */
1483
1493
  "items": type({
1484
1494
  "id": "string",
1485
1495
  "label": "string",
1496
+ "labelKey?": "string",
1497
+ "labelParams?": "Record<string, string>",
1486
1498
  /** `settings`/`servers`/`secrets` for a workspace, `data` for a declared path. */
1487
1499
  "kind": "\"settings\" | \"servers\" | \"secrets\" | \"data\"",
1488
1500
  "path?": "string",
@@ -1490,6 +1502,8 @@ maxRssBytes: "number.integer >= 0 = 0" }).onUndeclaredKey("reject");
1490
1502
  "origin?": "string",
1491
1503
  "included": "boolean",
1492
1504
  "note": "string | null",
1505
+ "noteKey?": "string",
1506
+ "noteParams?": "Record<string, string>",
1493
1507
  "ignoreGenerated?": "boolean"
1494
1508
  }).array()
1495
1509
  });
@@ -1507,27 +1521,45 @@ maxRssBytes: "number.integer >= 0 = 0" }).onUndeclaredKey("reject");
1507
1521
  /** A backup entry or leaf id (`workspace:<id>:servers`, `data:<path>`, ...). */
1508
1522
  "id": "string",
1509
1523
  "label": "string",
1524
+ "labelKey?": "string",
1525
+ "labelParams?": "Record<string, string>",
1510
1526
  "kind": "\"settings\" | \"secrets\" | \"servers\" | \"tls\" | \"data\"",
1511
1527
  "workspaceId?": "string",
1512
1528
  /** false when the current instance does not declare it, or it is not in the archive. */
1513
1529
  "restorable": "boolean",
1514
1530
  /** Echo of the request's selection, so the checkboxes round-trip. */
1515
1531
  "selected": "boolean",
1516
- "note": "string | null"
1532
+ "note": "string | null",
1533
+ "noteKey?": "string",
1534
+ "noteParams?": "Record<string, string>"
1535
+ });
1536
+ restoreSkipSchema = type({
1537
+ "label": "string",
1538
+ "labelKey?": "string",
1539
+ "labelParams?": "Record<string, string>",
1540
+ /** A catalog key for the parenthesis, e.g. `not selected`; absent when there is none. */
1541
+ "reasonKey?": "string",
1542
+ "reasonParams?": "Record<string, string>"
1517
1543
  });
1518
1544
  restorePlanSchema = type({
1519
- dryRun: "boolean",
1520
- encrypted: "boolean",
1545
+ "dryRun": "boolean",
1546
+ "encrypted": "boolean",
1521
1547
  /** The archive needs a password (none or a wrong one was supplied). */
1522
- needsPassword: "boolean",
1523
- items: restoreItemSchema.array(),
1524
- applied: type("string[]"),
1525
- skipped: type("string[]"),
1548
+ "needsPassword": "boolean",
1549
+ "items": restoreItemSchema.array(),
1550
+ "applied": type("string[]"),
1551
+ /**
1552
+ * The ids `applied` names, so a UI resolves each through `items` and renders its own label.
1553
+ * `applied` itself stays: it is what a UI with one language reads.
1554
+ */
1555
+ "appliedIds?": type("string[]"),
1556
+ "skipped": type("string[]"),
1557
+ "skippedItems?": restoreSkipSchema.array(),
1526
1558
  /** Only the panel's own listener needs a restart; its servers are re-read live. */
1527
- restartRequired: "boolean",
1559
+ "restartRequired": "boolean",
1528
1560
  /** The panel re-read the restored config within this same restore. */
1529
- reloaded: "boolean",
1530
- error: "string?"
1561
+ "reloaded": "boolean",
1562
+ "error": "string?"
1531
1563
  });
1532
1564
  backupCreateSchema = type({
1533
1565
  /** Optional: encrypts the archive. Never stored. */
@@ -13666,11 +13698,15 @@ function resolveBackupPaths(servers, includePaths = [], vars) {
13666
13698
  ignoreGenerated: entry.ignoreGenerated
13667
13699
  };
13668
13700
  const note = parent.path === entry.path ? `already declared by ${parent.origin}` : `covered by ${parent.path}`;
13701
+ const noteKey = parent.path === entry.path ? "settings.backup.noteAlreadyDeclared" : "settings.backup.noteCovered";
13702
+ const noteParams = parent.path === entry.path ? { origin: parent.origin } : { path: parent.path };
13669
13703
  return {
13670
13704
  path: entry.path,
13671
13705
  origin: entry.origin,
13672
13706
  included: false,
13673
13707
  note,
13708
+ noteKey,
13709
+ noteParams,
13674
13710
  ignoreGenerated: entry.ignoreGenerated
13675
13711
  };
13676
13712
  });
@@ -13927,7 +13963,9 @@ var init_backups = __esmMin((() => {
13927
13963
  needsPassword: false,
13928
13964
  items: [],
13929
13965
  applied: [],
13966
+ appliedIds: [],
13930
13967
  skipped: [],
13968
+ skippedItems: [],
13931
13969
  restartRequired: false,
13932
13970
  reloaded: false
13933
13971
  };
@@ -13970,7 +14008,13 @@ var init_backups = __esmMin((() => {
13970
14008
  names: entries.map((entry) => entry.name),
13971
14009
  ...password === null ? {} : { password }
13972
14010
  });
13973
- for (const name of extracted.skipped) plan.skipped.push(`${name} (symbolic link, skipped)`);
14011
+ for (const name of extracted.skipped) {
14012
+ plan.skipped.push(`${name} (symbolic link, skipped)`);
14013
+ plan.skippedItems.push({
14014
+ label: name,
14015
+ reasonKey: "settings.restore.reasonSymbolicLink"
14016
+ });
14017
+ }
13974
14018
  const manifestPath = path.join(staging, MANIFEST);
13975
14019
  if (!fs.existsSync(manifestPath)) return {
13976
14020
  ...plan,
@@ -13987,6 +14031,13 @@ var init_backups = __esmMin((() => {
13987
14031
  selected: false
13988
14032
  });
13989
14033
  plan.skipped.push(`${item.label}${item.note === null ? "" : ` (${item.note})`}`);
14034
+ plan.skippedItems.push({
14035
+ label: item.label,
14036
+ ...item.labelKey === void 0 ? {} : { labelKey: item.labelKey },
14037
+ ...item.labelParams === void 0 ? {} : { labelParams: item.labelParams },
14038
+ reasonKey: item.noteKey ?? "settings.restore.reasonUnrestorable",
14039
+ ...item.noteParams === void 0 ? {} : { reasonParams: item.noteParams }
14040
+ });
13990
14041
  return;
13991
14042
  }
13992
14043
  const selected = selectedIds === null || selectedIds.has(item.id) || aliases.some((alias) => selectedIds.has(alias));
@@ -13995,14 +14046,30 @@ var init_backups = __esmMin((() => {
13995
14046
  selected
13996
14047
  });
13997
14048
  if (selected) actions.set(item.id, apply);
13998
- else plan.skipped.push(`${item.label} (not selected)`);
14049
+ else {
14050
+ plan.skipped.push(`${item.label} (not selected)`);
14051
+ plan.skippedItems.push({
14052
+ label: item.label,
14053
+ ...item.labelKey === void 0 ? {} : { labelKey: item.labelKey },
14054
+ ...item.labelParams === void 0 ? {} : { labelParams: item.labelParams },
14055
+ reasonKey: "settings.restore.reasonNotSelected"
14056
+ });
14057
+ }
13999
14058
  };
14000
14059
  const settingsText = readText(path.join(staging, "global", "settings.json"));
14001
14060
  const parsedGlobal = readableGlobalSettings(settingsText) ? parseGlobalSettings(JSON.parse(settingsText)).config : null;
14002
- if (settingsText !== null && parsedGlobal === null) plan.skipped.push("Global settings (the archive's settings are not valid)");
14061
+ if (settingsText !== null && parsedGlobal === null) {
14062
+ plan.skipped.push("Global settings (the archive's settings are not valid)");
14063
+ plan.skippedItems.push({
14064
+ label: "Global settings",
14065
+ labelKey: "settings.backup.entryGlobalSettings",
14066
+ reasonKey: "settings.restore.reasonInvalidSettings"
14067
+ });
14068
+ }
14003
14069
  if (parsedGlobal !== null && settingsText !== null) addItem({
14004
14070
  id: "global:settings",
14005
14071
  label: "Global settings",
14072
+ labelKey: "settings.backup.entryGlobalSettings",
14006
14073
  kind: "settings",
14007
14074
  restorable: true,
14008
14075
  note: null
@@ -14013,6 +14080,7 @@ var init_backups = __esmMin((() => {
14013
14080
  if (globalSecrets !== null) addItem({
14014
14081
  id: "global:secrets",
14015
14082
  label: "Global secrets (password, API token)",
14083
+ labelKey: "settings.backup.entryGlobalSecrets",
14016
14084
  kind: "secrets",
14017
14085
  restorable: true,
14018
14086
  note: null
@@ -14025,6 +14093,7 @@ var init_backups = __esmMin((() => {
14025
14093
  addItem({
14026
14094
  id: "global:tls",
14027
14095
  label: "TLS certificate",
14096
+ labelKey: "settings.backup.entryTlsCertificate",
14028
14097
  kind: "tls",
14029
14098
  restorable: true,
14030
14099
  note: null
@@ -14070,10 +14139,16 @@ var init_backups = __esmMin((() => {
14070
14139
  addItem({
14071
14140
  id: `workspace:${id}:${suffix}`,
14072
14141
  label: `${id}: ${label}`,
14142
+ labelKey: "settings.backup.restoreItemLabel",
14143
+ labelParams: {
14144
+ workspace: id,
14145
+ item: suffix
14146
+ },
14073
14147
  kind: suffix,
14074
14148
  workspaceId: id,
14075
14149
  restorable: false,
14076
- note: "unknown workspace"
14150
+ note: "unknown workspace",
14151
+ noteKey: "settings.backup.noteUnknownWorkspace"
14077
14152
  }, null);
14078
14153
  }
14079
14154
  continue;
@@ -14082,6 +14157,11 @@ var init_backups = __esmMin((() => {
14082
14157
  if (readableWorkspaceSettings(settings)) addItem({
14083
14158
  id: `workspace:${id}:settings`,
14084
14159
  label: `${workspace.label}: Settings`,
14160
+ labelKey: "settings.backup.restoreItemLabel",
14161
+ labelParams: {
14162
+ workspace: workspace.label,
14163
+ item: "settings"
14164
+ },
14085
14165
  kind: "settings",
14086
14166
  workspaceId: id,
14087
14167
  restorable: true,
@@ -14089,7 +14169,18 @@ var init_backups = __esmMin((() => {
14089
14169
  }, () => {
14090
14170
  writeFileAtomic(workspace.settingsPath, settings);
14091
14171
  });
14092
- else plan.skipped.push(`${workspace.label}: Settings (the archive's settings are not valid)`);
14172
+ else {
14173
+ plan.skipped.push(`${workspace.label}: Settings (the archive's settings are not valid)`);
14174
+ plan.skippedItems.push({
14175
+ label: `${workspace.label}: Settings`,
14176
+ labelKey: "settings.backup.restoreItemLabel",
14177
+ labelParams: {
14178
+ workspace: workspace.label,
14179
+ item: "settings"
14180
+ },
14181
+ reasonKey: "settings.restore.reasonInvalidSettings"
14182
+ });
14183
+ }
14093
14184
  }
14094
14185
  if (servers !== null) {
14095
14186
  const parsed = readableServersFile(servers);
@@ -14098,6 +14189,11 @@ var init_backups = __esmMin((() => {
14098
14189
  addItem({
14099
14190
  id: `workspace:${id}:servers`,
14100
14191
  label: `${workspace.label}: Servers`,
14192
+ labelKey: "settings.backup.restoreItemLabel",
14193
+ labelParams: {
14194
+ workspace: workspace.label,
14195
+ item: "servers"
14196
+ },
14101
14197
  kind: "servers",
14102
14198
  workspaceId: id,
14103
14199
  restorable: true,
@@ -14105,11 +14201,27 @@ var init_backups = __esmMin((() => {
14105
14201
  }, () => {
14106
14202
  writeFileAtomic(workspace.serversPath, servers);
14107
14203
  });
14108
- } else plan.skipped.push(`${workspace.label}: Servers (the archive's servers are not valid)`);
14204
+ } else {
14205
+ plan.skipped.push(`${workspace.label}: Servers (the archive's servers are not valid)`);
14206
+ plan.skippedItems.push({
14207
+ label: `${workspace.label}: Servers`,
14208
+ labelKey: "settings.backup.restoreItemLabel",
14209
+ labelParams: {
14210
+ workspace: workspace.label,
14211
+ item: "servers"
14212
+ },
14213
+ reasonKey: "settings.restore.reasonInvalidServers"
14214
+ });
14215
+ }
14109
14216
  }
14110
14217
  if (secrets !== null) addItem({
14111
14218
  id: `workspace:${id}:secrets`,
14112
14219
  label: `${workspace.label}: Secrets`,
14220
+ labelKey: "settings.backup.restoreItemLabel",
14221
+ labelParams: {
14222
+ workspace: workspace.label,
14223
+ item: "secrets"
14224
+ },
14113
14225
  kind: "secrets",
14114
14226
  workspaceId: id,
14115
14227
  restorable: true,
@@ -14179,21 +14291,27 @@ var init_backups = __esmMin((() => {
14179
14291
  const declaredByBackup = archiveAll.some((candidate) => candidate.origin === origin && (workspace === void 0 || candidate.workspace === workspace));
14180
14292
  addItem({
14181
14293
  ...base,
14182
- note: declaredByBackup ? "declared by the backup's config, which is not being restored" : "not declared by this config, nor by the backup"
14294
+ note: declaredByBackup ? "declared by the backup's config, which is not being restored" : "not declared by this config, nor by the backup",
14295
+ noteKey: declaredByBackup ? "settings.backup.noteDeclaredByBackup" : "settings.backup.noteNotDeclared"
14183
14296
  }, null, aliases);
14184
14297
  continue;
14185
14298
  }
14186
14299
  if (!fs.existsSync(from)) {
14187
14300
  addItem({
14188
14301
  ...base,
14189
- note: "missing from the archive"
14302
+ note: "missing from the archive",
14303
+ noteKey: "settings.backup.noteMissingFromArchive"
14190
14304
  }, null, aliases);
14191
14305
  continue;
14192
14306
  }
14193
14307
  addItem({
14194
14308
  ...base,
14195
14309
  restorable: true,
14196
- note: target.path === archivedPath ? null : `restored from ${archivedPath}`
14310
+ ...target.path === archivedPath ? { note: null } : {
14311
+ note: `restored from ${archivedPath}`,
14312
+ noteKey: "settings.backup.noteRestoredFrom",
14313
+ noteParams: { path: archivedPath }
14314
+ }
14197
14315
  }, () => fs.cpSync(from, target.path, {
14198
14316
  recursive: true,
14199
14317
  force: true,
@@ -14207,12 +14325,14 @@ var init_backups = __esmMin((() => {
14207
14325
  }
14208
14326
  if (!options.confirm) {
14209
14327
  plan.applied = [...actions.keys()].map((id) => labelOf(plan, id));
14328
+ plan.appliedIds = [...actions.keys()];
14210
14329
  return plan;
14211
14330
  }
14212
14331
  for (const [id, apply] of actions) {
14213
14332
  apply();
14214
14333
  plan.applied.push(labelOf(plan, id));
14215
14334
  }
14335
+ plan.appliedIds = [...actions.keys()];
14216
14336
  if (this.options.onRestored !== void 0 && [...actions.keys()].some((id) => RELOADABLE.test(id))) {
14217
14337
  plan.reloaded = true;
14218
14338
  this.options.onRestored();
@@ -14239,26 +14359,36 @@ var init_backups = __esmMin((() => {
14239
14359
  }
14240
14360
  /** Selectable entries: the three global slices, then one per workspace. */
14241
14361
  entries(sources) {
14362
+ const missing = (path, key) => {
14363
+ const absent = this.missingNote(path);
14364
+ return absent === null ? { note: null } : {
14365
+ note: absent,
14366
+ noteKey: key
14367
+ };
14368
+ };
14242
14369
  const entries = [
14243
14370
  {
14244
14371
  id: "global:settings",
14245
14372
  label: "Global settings",
14373
+ labelKey: "settings.backup.entryGlobalSettings",
14246
14374
  kind: "settings",
14247
- note: this.missingNote(sources.globalSettingsPath),
14375
+ ...missing(sources.globalSettingsPath, "settings.backup.noteMissing"),
14248
14376
  items: []
14249
14377
  },
14250
14378
  {
14251
14379
  id: "global:secrets",
14252
14380
  label: "Global secrets (password, API token)",
14381
+ labelKey: "settings.backup.entryGlobalSecrets",
14253
14382
  kind: "secrets",
14254
- note: this.missingNote(sources.globalSecretsPath),
14383
+ ...missing(sources.globalSecretsPath, "settings.backup.noteMissing"),
14255
14384
  items: []
14256
14385
  },
14257
14386
  {
14258
14387
  id: "global:tls",
14259
14388
  label: "TLS certificate",
14389
+ labelKey: "settings.backup.entryTlsCertificate",
14260
14390
  kind: "tls",
14261
- note: this.missingNote(sources.tlsDir),
14391
+ ...missing(sources.tlsDir, "settings.backup.noteMissing"),
14262
14392
  items: []
14263
14393
  }
14264
14394
  ];
@@ -14274,27 +14404,37 @@ var init_backups = __esmMin((() => {
14274
14404
  }
14275
14405
  workspaceItems(workspace, includePaths) {
14276
14406
  const id = workspace.id;
14407
+ const note = (path) => {
14408
+ const absent = this.missingNote(path);
14409
+ return absent === null ? { note: null } : {
14410
+ note: absent,
14411
+ noteKey: "settings.backup.noteMissing"
14412
+ };
14413
+ };
14277
14414
  const items = [
14278
14415
  {
14279
14416
  id: `workspace:${id}:settings`,
14280
14417
  label: "Settings",
14418
+ labelKey: "settings.backup.leafSettings",
14281
14419
  kind: "settings",
14282
14420
  included: true,
14283
- note: this.missingNote(workspace.settingsPath)
14421
+ ...note(workspace.settingsPath)
14284
14422
  },
14285
14423
  {
14286
14424
  id: `workspace:${id}:servers`,
14287
14425
  label: "Servers",
14426
+ labelKey: "settings.backup.leafServers",
14288
14427
  kind: "servers",
14289
14428
  included: true,
14290
- note: this.missingNote(workspace.serversPath)
14429
+ ...note(workspace.serversPath)
14291
14430
  },
14292
14431
  {
14293
14432
  id: `workspace:${id}:secrets`,
14294
14433
  label: "Secrets",
14434
+ labelKey: "settings.backup.leafSecrets",
14295
14435
  kind: "secrets",
14296
14436
  included: true,
14297
- note: this.missingNote(workspace.secretsPath)
14437
+ ...note(workspace.secretsPath)
14298
14438
  }
14299
14439
  ];
14300
14440
  for (const declared of this.declaredData(workspace.servers, includePaths)) items.push({
@@ -14305,6 +14445,8 @@ var init_backups = __esmMin((() => {
14305
14445
  origin: declared.origin,
14306
14446
  included: declared.included,
14307
14447
  note: declared.note,
14448
+ ...declared.noteKey === void 0 ? {} : { noteKey: declared.noteKey },
14449
+ ...declared.noteParams === void 0 ? {} : { noteParams: declared.noteParams },
14308
14450
  ignoreGenerated: declared.ignoreGenerated
14309
14451
  });
14310
14452
  return items;
@@ -14320,7 +14462,8 @@ var init_backups = __esmMin((() => {
14320
14462
  if (entry.included && isInside(entry.path, archiveDir)) return {
14321
14463
  ...entry,
14322
14464
  included: false,
14323
- note: "contains the backup directory"
14465
+ note: "contains the backup directory",
14466
+ noteKey: "settings.backup.noteContainsBackupDir"
14324
14467
  };
14325
14468
  return entry;
14326
14469
  });