@reddoorla/maintenance 0.58.0 → 0.59.0

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 (67) hide show
  1. package/dist/airtable-YZ6VMSQZ.js +9 -0
  2. package/dist/{announce-JO67XZLZ.js → announce-Q2DXP5OO.js} +3 -3
  3. package/dist/{bump-deps-6D4VCAFQ.js → bump-deps-RYHQIKW4.js} +2 -2
  4. package/dist/{chunk-4PY4I5OR.js → chunk-5PHCZ6L5.js} +11 -2
  5. package/dist/chunk-5PHCZ6L5.js.map +1 -0
  6. package/dist/{chunk-IDRBAF2H.js → chunk-6CSCLBAA.js} +2 -2
  7. package/dist/{chunk-XO2RUYVM.js → chunk-HB7QPECR.js} +2 -2
  8. package/dist/{chunk-RYECSIEA.js → chunk-HPSGCDWY.js} +3 -3
  9. package/dist/chunk-HPSGCDWY.js.map +1 -0
  10. package/dist/{chunk-HPNGCRTK.js → chunk-KC4HU7BC.js} +2 -2
  11. package/dist/{chunk-PKIYAWA4.js → chunk-L4ULXHIN.js} +7 -2
  12. package/dist/chunk-L4ULXHIN.js.map +1 -0
  13. package/dist/{chunk-IJW4DS2S.js → chunk-LMXIZITU.js} +3 -3
  14. package/dist/chunk-LMXIZITU.js.map +1 -0
  15. package/dist/{chunk-5E3OW6YN.js → chunk-TLZ4V2BE.js} +2 -2
  16. package/dist/{chunk-S7WKPRID.js → chunk-TZN2G6FD.js} +2 -2
  17. package/dist/cli/bin.js +13 -13
  18. package/dist/cli/commands/audit.js +6 -6
  19. package/dist/{convert-to-pnpm-JYUUUDDP.js → convert-to-pnpm-KNCXU6FM.js} +2 -2
  20. package/dist/{digest-RUQ42QXL.js → digest-RVIOHUXR.js} +2 -2
  21. package/dist/{github-signals-JXHRHDAG.js → github-signals-NCDBYSVV.js} +4 -4
  22. package/dist/index.js +109 -16
  23. package/dist/index.js.map +1 -1
  24. package/dist/{init-6LJU72F2.js → init-KD56UENX.js} +4 -4
  25. package/dist/{launch-6ZB3QA2I.js → launch-35KSDHYO.js} +13 -7
  26. package/dist/launch-35KSDHYO.js.map +1 -0
  27. package/dist/{lighthouse-airtable-FSGOUVMQ.js → lighthouse-airtable-N3KCBPVA.js} +3 -3
  28. package/dist/{onboard-UM6UKX2V.js → onboard-SCZ3UKZ2.js} +2 -2
  29. package/dist/{orchestrate-GEHLKE64.js → orchestrate-D72PB6NG.js} +3 -3
  30. package/dist/{renovate-dispatch-L5HWCNRY.js → renovate-dispatch-P2FAJTQK.js} +2 -2
  31. package/dist/{report-3YVSMRFE.js → report-CN56TGMK.js} +5 -5
  32. package/dist/{self-updating-J7U7WIP3.js → self-updating-HLMF7KEG.js} +2 -2
  33. package/dist/{svelte-codemods-BW4M4CDF.js → svelte-codemods-HNITLWQM.js} +2 -2
  34. package/dist/{sync-configs-ZYKZCP4C.js → sync-configs-BZXBYKTI.js} +2 -2
  35. package/dist/{upgrade-UAXREMED.js → upgrade-6YDUELFA.js} +2 -2
  36. package/dist/{websites-EEZVZXQT.js → websites-IYP7IGLU.js} +6 -4
  37. package/dist/{write-audits-to-airtable-LUFY424L.js → write-audits-to-airtable-VDGHCZGQ.js} +4 -4
  38. package/package.json +1 -1
  39. package/dist/airtable-E5F5FLGD.js +0 -9
  40. package/dist/chunk-4PY4I5OR.js.map +0 -1
  41. package/dist/chunk-IJW4DS2S.js.map +0 -1
  42. package/dist/chunk-PKIYAWA4.js.map +0 -1
  43. package/dist/chunk-RYECSIEA.js.map +0 -1
  44. package/dist/launch-6ZB3QA2I.js.map +0 -1
  45. /package/dist/{airtable-E5F5FLGD.js.map → airtable-YZ6VMSQZ.js.map} +0 -0
  46. /package/dist/{announce-JO67XZLZ.js.map → announce-Q2DXP5OO.js.map} +0 -0
  47. /package/dist/{bump-deps-6D4VCAFQ.js.map → bump-deps-RYHQIKW4.js.map} +0 -0
  48. /package/dist/{chunk-IDRBAF2H.js.map → chunk-6CSCLBAA.js.map} +0 -0
  49. /package/dist/{chunk-XO2RUYVM.js.map → chunk-HB7QPECR.js.map} +0 -0
  50. /package/dist/{chunk-HPNGCRTK.js.map → chunk-KC4HU7BC.js.map} +0 -0
  51. /package/dist/{chunk-5E3OW6YN.js.map → chunk-TLZ4V2BE.js.map} +0 -0
  52. /package/dist/{chunk-S7WKPRID.js.map → chunk-TZN2G6FD.js.map} +0 -0
  53. /package/dist/{convert-to-pnpm-JYUUUDDP.js.map → convert-to-pnpm-KNCXU6FM.js.map} +0 -0
  54. /package/dist/{digest-RUQ42QXL.js.map → digest-RVIOHUXR.js.map} +0 -0
  55. /package/dist/{github-signals-JXHRHDAG.js.map → github-signals-NCDBYSVV.js.map} +0 -0
  56. /package/dist/{init-6LJU72F2.js.map → init-KD56UENX.js.map} +0 -0
  57. /package/dist/{lighthouse-airtable-FSGOUVMQ.js.map → lighthouse-airtable-N3KCBPVA.js.map} +0 -0
  58. /package/dist/{onboard-UM6UKX2V.js.map → onboard-SCZ3UKZ2.js.map} +0 -0
  59. /package/dist/{orchestrate-GEHLKE64.js.map → orchestrate-D72PB6NG.js.map} +0 -0
  60. /package/dist/{renovate-dispatch-L5HWCNRY.js.map → renovate-dispatch-P2FAJTQK.js.map} +0 -0
  61. /package/dist/{report-3YVSMRFE.js.map → report-CN56TGMK.js.map} +0 -0
  62. /package/dist/{self-updating-J7U7WIP3.js.map → self-updating-HLMF7KEG.js.map} +0 -0
  63. /package/dist/{svelte-codemods-BW4M4CDF.js.map → svelte-codemods-HNITLWQM.js.map} +0 -0
  64. /package/dist/{sync-configs-ZYKZCP4C.js.map → sync-configs-BZXBYKTI.js.map} +0 -0
  65. /package/dist/{upgrade-UAXREMED.js.map → upgrade-6YDUELFA.js.map} +0 -0
  66. /package/dist/{websites-EEZVZXQT.js.map → websites-IYP7IGLU.js.map} +0 -0
  67. /package/dist/{write-audits-to-airtable-LUFY424L.js.map → write-audits-to-airtable-VDGHCZGQ.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  ACTIVE_STATUSES,
6
6
  listWebsites,
7
7
  siteSlug
8
- } from "./chunk-PKIYAWA4.js";
8
+ } from "./chunk-L4ULXHIN.js";
9
9
 
10
10
  // src/inventory/airtable.ts
11
11
  function fromAirtableBase(base, opts = {}) {
@@ -46,4 +46,4 @@ function fromAirtableBase(base, opts = {}) {
46
46
  export {
47
47
  fromAirtableBase
48
48
  };
49
- //# sourceMappingURL=chunk-5E3OW6YN.js.map
49
+ //# sourceMappingURL=chunk-TLZ4V2BE.js.map
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  siteSlug,
19
19
  updateAnalyticsHealth
20
- } from "./chunk-PKIYAWA4.js";
20
+ } from "./chunk-L4ULXHIN.js";
21
21
  import {
22
22
  defaultCredentialsPath
23
23
  } from "./chunk-6VAL7XJT.js";
@@ -441,4 +441,4 @@ export {
441
441
  fetchGaUsers,
442
442
  fetchSearch
443
443
  };
444
- //# sourceMappingURL=chunk-S7WKPRID.js.map
444
+ //# sourceMappingURL=chunk-TZN2G6FD.js.map
package/dist/cli/bin.js CHANGED
@@ -104,7 +104,7 @@ cli.command("sync-configs [site]", "Sync canonical configs into a site.").option
104
104
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
105
105
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
106
106
  async (site, opts) => runOrExit(
107
- async () => (await import("../sync-configs-ZYKZCP4C.js")).runSyncConfigsCommand(site, opts),
107
+ async () => (await import("../sync-configs-BZXBYKTI.js")).runSyncConfigsCommand(site, opts),
108
108
  opts
109
109
  )
110
110
  );
@@ -113,7 +113,7 @@ cli.command("bump-deps [site]", "Bump dependencies.").option("--group <group>",
113
113
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
114
114
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
115
115
  async (site, opts) => runOrExit(
116
- async () => (await import("../bump-deps-6D4VCAFQ.js")).runBumpDepsCommand(site, opts),
116
+ async () => (await import("../bump-deps-RYHQIKW4.js")).runBumpDepsCommand(site, opts),
117
117
  opts
118
118
  )
119
119
  );
@@ -122,7 +122,7 @@ cli.command(
122
122
  "Bootstrap a repo to keep itself updated (CI + Renovate + auto-merge)."
123
123
  ).option("--dry", "List what would be enabled without writing or opening PRs").option("--fleet <inventory>", 'Inventory file (.json or .mjs/.js), or "airtable"').option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
124
124
  async (site, opts) => runOrExit(
125
- async () => (await import("../self-updating-J7U7WIP3.js")).runSelfUpdatingCommand(site, opts),
125
+ async () => (await import("../self-updating-HLMF7KEG.js")).runSelfUpdatingCommand(site, opts),
126
126
  opts
127
127
  )
128
128
  );
@@ -131,7 +131,7 @@ cli.command("upgrade <upgrade> [site]", "Run a named upgrade recipe (svelte-4-to
131
131
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
132
132
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
133
133
  async (upgrade, site, opts) => runOrExit(
134
- async () => (await import("../upgrade-UAXREMED.js")).runUpgradeCommand(upgrade, site, opts),
134
+ async () => (await import("../upgrade-6YDUELFA.js")).runUpgradeCommand(upgrade, site, opts),
135
135
  opts
136
136
  )
137
137
  );
@@ -143,7 +143,7 @@ cli.command(
143
143
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
144
144
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
145
145
  async (site, opts) => runOrExit(
146
- async () => (await import("../convert-to-pnpm-JYUUUDDP.js")).runConvertToPnpmCommand(site, opts),
146
+ async () => (await import("../convert-to-pnpm-KNCXU6FM.js")).runConvertToPnpmCommand(site, opts),
147
147
  opts
148
148
  )
149
149
  );
@@ -152,7 +152,7 @@ cli.command("svelte-codemods [site]", "Apply Svelte 5 gotcha codemods to an alre
152
152
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
153
153
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
154
154
  async (site, opts) => runOrExit(
155
- async () => (await import("../svelte-codemods-BW4M4CDF.js")).runSvelteCodemodsCommand(site, opts),
155
+ async () => (await import("../svelte-codemods-HNITLWQM.js")).runSvelteCodemodsCommand(site, opts),
156
156
  opts
157
157
  )
158
158
  );
@@ -164,7 +164,7 @@ cli.command(
164
164
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
165
165
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
166
166
  async (site, opts) => runOrExit(
167
- async () => (await import("../onboard-UM6UKX2V.js")).runOnboardCommand(site, opts),
167
+ async () => (await import("../onboard-SCZ3UKZ2.js")).runOnboardCommand(site, opts),
168
168
  opts
169
169
  )
170
170
  );
@@ -175,14 +175,14 @@ cli.command(
175
175
  "--fleet <inventory>",
176
176
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
177
177
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
178
- async (site, opts) => runOrExit(async () => (await import("../init-6LJU72F2.js")).runInitCommand(site, opts), opts)
178
+ async (site, opts) => runOrExit(async () => (await import("../init-KD56UENX.js")).runInitCommand(site, opts), opts)
179
179
  );
180
180
  cli.command(
181
181
  "launch <site>",
182
182
  "Bootstrap + first-audit a site, then draft its launch email for approval."
183
183
  ).action(
184
184
  async (site, opts) => runOrExit(
185
- async () => (await import("../launch-6ZB3QA2I.js")).runLaunchCommand(site, opts),
185
+ async () => (await import("../launch-35KSDHYO.js")).runLaunchCommand(site, opts),
186
186
  opts
187
187
  )
188
188
  );
@@ -191,7 +191,7 @@ cli.command(
191
191
  "Draft the monthly-report announcement email for maintenance sites (all, or one) for approval."
192
192
  ).action(
193
193
  async (site, opts) => runOrExit(
194
- async () => (await import("../announce-JO67XZLZ.js")).runAnnounceCommand(site, opts),
194
+ async () => (await import("../announce-Q2DXP5OO.js")).runAnnounceCommand(site, opts),
195
195
  opts
196
196
  )
197
197
  );
@@ -206,7 +206,7 @@ cli.command("report [site]", "Draft or send maintenance/testing reports.").optio
206
206
  "Email the operator one daily digest of reports ready for approval (skips when empty)."
207
207
  ).action(
208
208
  async (site, opts) => runOrExit(
209
- async () => (await import("../report-3YVSMRFE.js")).runReportCommand(site, opts),
209
+ async () => (await import("../report-CN56TGMK.js")).runReportCommand(site, opts),
210
210
  opts
211
211
  )
212
212
  );
@@ -215,7 +215,7 @@ cli.command(
215
215
  "Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write Airtable."
216
216
  ).option("--fleet", "Run across every site in the Airtable inventory.").option("--write-airtable", "Write each site's signals back to its Websites row.").action(
217
217
  async (opts) => runOrExit(
218
- async () => (await import("../github-signals-JXHRHDAG.js")).runGitHubSignalsCommand({
218
+ async () => (await import("../github-signals-NCDBYSVV.js")).runGitHubSignalsCommand({
219
219
  fleet: opts.fleet,
220
220
  writeAirtable: opts.writeAirtable
221
221
  }),
@@ -233,7 +233,7 @@ cli.command(
233
233
  "Trigger Renovate on fleet sites the security sweep flagged with critical/high vulns."
234
234
  ).option("--fleet", "Run across every active, repo-backed site in the Airtable inventory.").action(
235
235
  async (opts) => runOrExit(
236
- async () => (await import("../renovate-dispatch-L5HWCNRY.js")).runRenovateDispatchCommand({
236
+ async () => (await import("../renovate-dispatch-P2FAJTQK.js")).runRenovateDispatchCommand({
237
237
  fleet: opts.fleet
238
238
  }),
239
239
  opts
@@ -5,11 +5,11 @@ import {
5
5
  } from "../../chunk-4NAOR44V.js";
6
6
  import {
7
7
  resolveSites
8
- } from "../../chunk-IDRBAF2H.js";
8
+ } from "../../chunk-6CSCLBAA.js";
9
9
  import {
10
10
  ALL_AUDIT_NAMES,
11
11
  runOneAudit
12
- } from "../../chunk-4PY4I5OR.js";
12
+ } from "../../chunk-5PHCZ6L5.js";
13
13
  import "../../chunk-LKGVSM2O.js";
14
14
  import "../../chunk-FDIFTIXC.js";
15
15
  import "../../chunk-HVOOCK6L.js";
@@ -215,9 +215,9 @@ ${skipNotice}`;
215
215
  let writeBackFailed = false;
216
216
  if (opts.writeAirtable !== void 0) {
217
217
  const { openBase, readAirtableConfig } = await import("../../client-EPVO3FEH.js");
218
- const { listWebsites } = await import("../../websites-EEZVZXQT.js");
218
+ const { listWebsites } = await import("../../websites-IYP7IGLU.js");
219
219
  if (opts.fleet !== void 0) {
220
- const { writeFleetAuditsToAirtable, formatFleetWriteSummary } = await import("../../write-audits-to-airtable-LUFY424L.js");
220
+ const { writeFleetAuditsToAirtable, formatFleetWriteSummary } = await import("../../write-audits-to-airtable-VDGHCZGQ.js");
221
221
  const base = openBase(readAirtableConfig());
222
222
  const websites = await listWebsites(base);
223
223
  const fleetWrite = await writeFleetAuditsToAirtable({ base, websites, results });
@@ -226,8 +226,8 @@ ${skipNotice}`;
226
226
 
227
227
  ${formatFleetWriteSummary(fleetWrite)}`;
228
228
  } else {
229
- const { resolveSlugFromCwd } = await import("../../lighthouse-airtable-FSGOUVMQ.js");
230
- const { writeAuditsToAirtable } = await import("../../write-audits-to-airtable-LUFY424L.js");
229
+ const { resolveSlugFromCwd } = await import("../../lighthouse-airtable-N3KCBPVA.js");
230
+ const { writeAuditsToAirtable } = await import("../../write-audits-to-airtable-VDGHCZGQ.js");
231
231
  const slug = typeof opts.writeAirtable === "string" && opts.writeAirtable.length > 0 ? opts.writeAirtable : await resolveSlugFromCwd(cwd);
232
232
  let writeSummary = null;
233
233
  await new Listr(
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-4NAOR44V.js";
6
6
  import {
7
7
  resolveSites
8
- } from "./chunk-IDRBAF2H.js";
8
+ } from "./chunk-6CSCLBAA.js";
9
9
  import {
10
10
  convertToPnpm
11
11
  } from "./chunk-2X322ZES.js";
@@ -48,4 +48,4 @@ async function runConvertToPnpmCommand(site, opts) {
48
48
  export {
49
49
  runConvertToPnpmCommand
50
50
  };
51
- //# sourceMappingURL=convert-to-pnpm-JYUUUDDP.js.map
51
+ //# sourceMappingURL=convert-to-pnpm-KNCXU6FM.js.map
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  listWebsites,
11
11
  siteSlug
12
- } from "./chunk-PKIYAWA4.js";
12
+ } from "./chunk-L4ULXHIN.js";
13
13
  import {
14
14
  openBase,
15
15
  readAirtableConfig
@@ -410,4 +410,4 @@ export {
410
410
  renderDigestHtml,
411
411
  runDigest
412
412
  };
413
- //# sourceMappingURL=digest-RUQ42QXL.js.map
413
+ //# sourceMappingURL=digest-RVIOHUXR.js.map
@@ -3,8 +3,8 @@ import {
3
3
  } from "./chunk-PZQ3WED2.js";
4
4
  import {
5
5
  formatFleetWriteSummary
6
- } from "./chunk-IJW4DS2S.js";
7
- import "./chunk-RYECSIEA.js";
6
+ } from "./chunk-LMXIZITU.js";
7
+ import "./chunk-HPSGCDWY.js";
8
8
  import {
9
9
  makeGitHub
10
10
  } from "./chunk-J4ECCFRT.js";
@@ -13,7 +13,7 @@ import {
13
13
  listWebsites,
14
14
  siteSlug,
15
15
  updateGitHubSignals
16
- } from "./chunk-PKIYAWA4.js";
16
+ } from "./chunk-L4ULXHIN.js";
17
17
  import {
18
18
  openBase,
19
19
  readAirtableConfig
@@ -113,4 +113,4 @@ export {
113
113
  githubSignalsExitCode,
114
114
  runGitHubSignalsCommand
115
115
  };
116
- //# sourceMappingURL=github-signals-JXHRHDAG.js.map
116
+ //# sourceMappingURL=github-signals-NCDBYSVV.js.map
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  DEFAULT_INIT_STEPS,
3
3
  a11yFixturesPage,
4
4
  init
5
- } from "./chunk-XO2RUYVM.js";
5
+ } from "./chunk-HB7QPECR.js";
6
6
  import {
7
7
  svelteCodemods
8
8
  } from "./chunk-FUD4I2ZW.js";
@@ -16,11 +16,11 @@ import {
16
16
  } from "./chunk-WVRPOXKH.js";
17
17
  import {
18
18
  draftReportForSite
19
- } from "./chunk-S7WKPRID.js";
19
+ } from "./chunk-TZN2G6FD.js";
20
20
  import "./chunk-6LIWDIXU.js";
21
21
  import {
22
22
  sendApprovedReports
23
- } from "./chunk-HPNGCRTK.js";
23
+ } from "./chunk-KC4HU7BC.js";
24
24
  import "./chunk-OGGRFBL4.js";
25
25
  import {
26
26
  BLURRED_CID,
@@ -60,7 +60,7 @@ import {
60
60
  runAudits,
61
61
  runAuditsAcross,
62
62
  securityAudit
63
- } from "./chunk-4PY4I5OR.js";
63
+ } from "./chunk-5PHCZ6L5.js";
64
64
  import "./chunk-LKGVSM2O.js";
65
65
  import {
66
66
  fromJsonFile,
@@ -71,12 +71,12 @@ import "./chunk-XXTZBPUY.js";
71
71
  import "./chunk-XTK5VIQB.js";
72
72
  import {
73
73
  fromAirtableBase
74
- } from "./chunk-5E3OW6YN.js";
74
+ } from "./chunk-TLZ4V2BE.js";
75
75
  import "./chunk-U5SZ4FOP.js";
76
76
  import {
77
77
  SEVERITY_RANK,
78
78
  siteSlug
79
- } from "./chunk-PKIYAWA4.js";
79
+ } from "./chunk-L4ULXHIN.js";
80
80
  import "./chunk-HN6I2XWI.js";
81
81
  import "./chunk-WEKL2HYJ.js";
82
82
  import "./chunk-6VAL7XJT.js";
@@ -232,6 +232,17 @@ var SUBMISSION_STATUS_SCRIPT = `document.querySelectorAll("button.subm-status").
232
232
  });
233
233
  });`;
234
234
 
235
+ // src/dashboard/site-details.ts
236
+ var SITE_STATUS_OPTIONS = [
237
+ "in development",
238
+ "launch period",
239
+ "maintenance",
240
+ "hosting",
241
+ "probably not our problem",
242
+ "deprecated"
243
+ ];
244
+ var FREQ_OPTIONS = ["None", "Monthly", "Quarterly", "Yearly"];
245
+
235
246
  // src/dashboard/render.ts
236
247
  var DASH = "\u2014";
237
248
  function scoreTile(label, value) {
@@ -380,21 +391,44 @@ function detailRow(label, value) {
380
391
  const display = typeof value === "string" && value.trim().length > 0 ? escapeHtml(value.trim()) : DASH;
381
392
  return `<div class="detail"><dt>${escapeHtml(label)}</dt><dd>${display}</dd></div>`;
382
393
  }
394
+ function savedSpan(field) {
395
+ return `<span class="detail-saved" data-for="${field}"></span>`;
396
+ }
397
+ function selectRow(label, field, options, current, url) {
398
+ const inList = current !== null && options.includes(current);
399
+ const opts = options.map(
400
+ (o) => `<option value="${escapeHtml(o)}"${o === current ? " selected" : ""}>${escapeHtml(o)}</option>`
401
+ ).join("");
402
+ const placeholder = inList ? "" : `<option value="" disabled selected hidden>\u2014 select \u2014</option>`;
403
+ return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><select id="detail-${field}" data-detail-field="${field}" data-details-url="${url}">${placeholder}${opts}</select>${savedSpan(field)}</dd></div>`;
404
+ }
405
+ function inputRow(label, field, value, url) {
406
+ return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="text" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" value="${escapeHtml(value ?? "")}" />${savedSpan(field)}</dd></div>`;
407
+ }
408
+ function textareaRow(label, field, value, url) {
409
+ return `<div class="detail wide"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><textarea id="detail-${field}" data-detail-field="${field}" data-details-url="${url}">${escapeHtml(value ?? "")}</textarea>${savedSpan(field)}</dd></div>`;
410
+ }
383
411
  function siteDetailsSection(site) {
412
+ const url = `/api/sites/${escapeHtml(siteSlug(site.name))}/details`;
384
413
  const lastCommit = site.lastCommitAt ? `${relativeTimeFromNow(site.lastCommitAt)}` : null;
385
414
  const rows = [
386
- detailRow("Maintenance cadence", site.maintenanceFreq),
387
- detailRow("Testing cadence", site.testingFreq),
388
- detailRow("Report recipients (To)", site.reportRecipientsTo),
389
- detailRow("Report recipients (CC)", site.reportRecipientsCc),
390
- detailRow("Point of contact", site.pointOfContact),
391
- detailRow("GA4 property", site.ga4PropertyId),
392
- detailRow("Search query", site.searchQuery),
393
- detailRow("Git repo", site.gitRepo),
415
+ selectRow("Status", "status", SITE_STATUS_OPTIONS, site.status, url),
416
+ selectRow("Maintenance cadence", "maintenanceFreq", FREQ_OPTIONS, site.maintenanceFreq, url),
417
+ selectRow("Testing cadence", "testingFreq", FREQ_OPTIONS, site.testingFreq, url),
418
+ inputRow("Report recipients (To)", "reportRecipientsTo", site.reportRecipientsTo, url),
419
+ inputRow("Report recipients (CC)", "reportRecipientsCc", site.reportRecipientsCc, url),
420
+ inputRow("Point of contact", "pointOfContact", site.pointOfContact, url),
421
+ inputRow("GA4 property", "ga4PropertyId", site.ga4PropertyId, url),
422
+ inputRow("Search query", "searchQuery", site.searchQuery, url),
423
+ inputRow("Git repo", "gitRepo", site.gitRepo, url),
424
+ textareaRow("Copy \u2014 Intro", "copyIntro", site.copyIntro, url),
425
+ textareaRow("Copy \u2014 Contact", "copyContact", site.copyContact, url),
426
+ textareaRow("Copy \u2014 Footer", "copyFooter", site.copyFooter, url),
394
427
  detailRow("Last commit", lastCommit)
395
428
  ].join("");
429
+ const triggerBtn = site.gitRepo?.trim() ? `<button class="trigger-renovate" data-trigger-url="/api/sites/${escapeHtml(siteSlug(site.name))}/trigger-renovate">Trigger Renovate</button>` : "";
396
430
  return `<div class="section site-details">
397
- <h2>Site details</h2>
431
+ <h2>Site details ${triggerBtn}</h2>
398
432
  <dl class="details">${rows}</dl>
399
433
  </div>`;
400
434
  }
@@ -445,8 +479,13 @@ button.approve:disabled { opacity: 0.6; cursor: default; }
445
479
  .setup-missing { color: #a65a00; }
446
480
  .details { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.5rem 1.5rem; margin: 0; }
447
481
  .detail { display: flex; flex-direction: column; }
482
+ .detail.wide { grid-column: 1 / -1; }
448
483
  .detail dt { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: #999; }
449
484
  .detail dd { margin: 0; }
485
+ .detail dd input, .detail dd select, .detail dd textarea { width: 100%; box-sizing: border-box; font: inherit; padding: 0.25rem 0.4rem; border: 1px solid #ccc; border-radius: 4px; background: transparent; color: inherit; }
486
+ .detail dd textarea { min-height: 3.5rem; resize: vertical; }
487
+ .detail-saved { font-size: 0.8rem; color: #2a8; }
488
+ button.trigger-renovate { font: inherit; font-size: 0.8rem; padding: 0.15rem 0.6rem; margin-left: 0.5rem; border: 1px solid #888; border-radius: 6px; background: transparent; color: inherit; cursor: pointer; }
450
489
  `;
451
490
  function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = null, now = /* @__PURE__ */ new Date()) {
452
491
  const name = escapeHtml(site.name);
@@ -524,6 +563,45 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
524
563
  }
525
564
  });
526
565
  });
566
+ // Trigger-renovate button: async on-demand dispatch (mirrors the cockpit).
567
+ document.querySelectorAll("button.trigger-renovate").forEach((b) => {
568
+ b.addEventListener("click", async () => {
569
+ b.disabled = true;
570
+ b.textContent = "Dispatching\u2026";
571
+ try {
572
+ const res = await fetch(b.dataset.triggerUrl, { method: "POST" });
573
+ b.textContent = res.ok ? "Dispatched \u2713" : "Failed";
574
+ if (!res.ok) b.disabled = false;
575
+ } catch {
576
+ b.textContent = "Failed";
577
+ b.disabled = false;
578
+ }
579
+ });
580
+ });
581
+ // Site-details editor: save on change (selects) / blur (inputs+textareas, only
582
+ // when the value actually changed). The per-field span shows \u2713 / \u2717.
583
+ function saveDetail(el) {
584
+ const span = document.querySelector('.detail-saved[data-for="' + el.dataset.detailField + '"]');
585
+ fetch(el.dataset.detailsUrl, {
586
+ method: "POST",
587
+ headers: { "content-type": "application/json" },
588
+ body: JSON.stringify({ field: el.dataset.detailField, value: el.value }),
589
+ })
590
+ .then((r) => {
591
+ if (span) span.textContent = r.ok ? " \u2713" : " \u2717";
592
+ })
593
+ .catch(() => {
594
+ if (span) span.textContent = " \u2717";
595
+ });
596
+ }
597
+ document.querySelectorAll("select[data-detail-field]").forEach((s) => {
598
+ s.addEventListener("change", () => saveDetail(s));
599
+ });
600
+ document.querySelectorAll("input[data-detail-field], textarea[data-detail-field]").forEach((i) => {
601
+ i.addEventListener("blur", () => {
602
+ if (i.value !== i.defaultValue) saveDetail(i);
603
+ });
604
+ });
527
605
  ${SUBMISSION_STATUS_SCRIPT}
528
606
  // Checklist gate: ticking a box POSTs the one field; the response { complete }
529
607
  // decides whether THIS report's Approve button is enabled. Scoped per report by
@@ -790,10 +868,15 @@ function signalsAttr(c) {
790
868
  for (const sig of c.watchSignals) kinds.add(sig);
791
869
  return [...kinds].join(" ");
792
870
  }
871
+ function triggerRenovateBtn(c) {
872
+ if (!c.site.gitRepo?.trim()) return "";
873
+ const url = `/api/sites/${escapeHtml(siteSlug(c.site.name))}/trigger-renovate`;
874
+ return `<button class="trigger-renovate" data-trigger-url="${url}">Trigger Renovate</button>`;
875
+ }
793
876
  function cockpitCard(c) {
794
877
  const base = card(c.site);
795
878
  const pill = `<span class="pill ${c.tier}">${PILL_LABEL[c.tier]}</span>`;
796
- const extra = `${pill}${chips(c)}${submBadge(c)}`;
879
+ const extra = `${pill}${chips(c)}${submBadge(c)}${triggerRenovateBtn(c)}`;
797
880
  const opening = `<article class="card" data-signals="${signalsAttr(c)}">`;
798
881
  return base.replace('<article class="card">', () => opening).replace("</article>", () => `${extra}</article>`);
799
882
  }
@@ -826,6 +909,16 @@ var FILTER_SCRIPT = `<script>
826
909
  catch(e){ b.textContent = 'Failed'; b.disabled = false; }
827
910
  });
828
911
  });
912
+ // trigger-renovate buttons: fire the on-demand dispatch (async, fire-and-forget).
913
+ document.querySelectorAll('button.trigger-renovate').forEach(function(b){
914
+ b.addEventListener('click', async function(){
915
+ b.disabled = true; b.textContent = 'Dispatching\u2026';
916
+ try { var res = await fetch(b.dataset.triggerUrl, { method: 'POST' });
917
+ b.textContent = res.ok ? 'Dispatched \u2713' : 'Failed';
918
+ if (!res.ok) b.disabled = false; }
919
+ catch(e){ b.textContent = 'Failed'; b.disabled = false; }
920
+ });
921
+ });
829
922
  })();
830
923
  </script>`;
831
924
  function renderCockpitHtml(model) {