refactor(admin): split the backups section

The section becomes a composer over backups/: the restore dialog with its
preview, the refusal, skip and schedule notices, the restore jobs and
their request, the upload flow, the run and download actions, and the
two row layouts sharing one action pair. The upload total is a pure
function and tested as one. The read-failure guard's 250-character
window had stopped reaching the restore preview's own failure lines; the
preview now sits in a smaller component, the guard sees it, and it is
listed as a deliberate exception with its reason.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 13:20:48 +02:00
1 parent c9917d2de0
commit 5eafed8dd5
19 files changed
+1533 -1348

No files matched your search

@@ -1,7 +1,7 @@
{
"$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 113,
"total": 130546,
"count": 112,
"total": 129084,
"entries": {
"app/api/dashboard/summary/route.ts": 1032,
"app/api/documents/inbound/route.ts": 811,
@@ -12,7 +12,6 @@
"app/api/medications/intake/bulk/route.ts": 1044,
"app/api/workouts/batch/route.ts": 958,
"app/medications/page-client.tsx": 839,
"components/admin/backups-section.tsx": 1462,
"components/charts/mood-chart.tsx": 1166,
"components/cycle/log-day-sheet.tsx": 1639,
"components/insights/coach-panel/chat-bubble.tsx": 888,
@@ -1,7 +1,7 @@
{
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 401,
"total": 137456,
"count": 399,
"total": 136612,
"entries": {
"app/achievements/page.tsx :: AchievementsPage": 206,
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
@@ -80,8 +80,6 @@
"components/admin/admin-shell.tsx :: AdminShell": 254,
"components/admin/api-token-overview-section.tsx :: ApiTokenOverviewSection": 212,
"components/admin/app-log-preview-section.tsx :: AppLogPreviewSection": 341,
"components/admin/backups-section.tsx :: BackupsSection": 618,
"components/admin/backups-section.tsx :: RestoreRowDialog": 226,
"components/admin/central-codex-section.tsx :: CentralCodexSection": 241,
"components/admin/encryption-section.tsx :: EncryptionSection": 303,
"components/admin/general-settings-section.tsx :: GeneralSettingsSection": 220,
@@ -1,7 +1,7 @@
{
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
"count": 2946,
"total": 10225,
"count": 2945,
"total": 10221,
"entries": {
"__tests__/account-payload-consumer-guard.test.ts": 2,
"__tests__/acting-account-boundary-guard.test.ts": 7,
@@ -714,7 +714,6 @@
"components/admin/api-token-overview-section.tsx": 5,
"components/admin/app-log-preview-section.tsx": 1,
"components/admin/assistant-section.tsx": 1,
"components/admin/backups-section.tsx": 4,
"components/admin/coach-feedback-section.tsx": 1,
"components/admin/danger-zone-section.tsx": 1,
"components/admin/general-settings-section.tsx": 1,
@@ -84,7 +84,7 @@ const INVENTORY: Readonly<Record<string, Poll>> = {
shape: "until-done",
cadence: "2 s until the job reports a terminal status",
},
"components/admin/backups-section.tsx": {
"components/admin/backups/use-backup-restores.ts": {
sites: 1,
shape: "until-done",
cadence: "2 s until no restore is active",
@@ -107,6 +107,18 @@ function framedRowsInCards(source: string): number[] {
return lines;
}
/**
* Read failures that draw their own markup on purpose, each with its reason.
* Keyed by file, and held live by the case below: an entry whose file no
* longer hand-rolls a branch fails rather than lingering as a blind spot.
*/
const HAND_ROLLED_BY_DESIGN = new Map<string, string>([
[
"components/admin/backups/restore-dialog.tsx",
"the restore preview never blocks the restore: a failed preview states its own absence in the dialog, and names the missing key when that is why",
],
]);
const files = walk(SRC).map((file) => ({
file: relative(SRC, file),
source: readFileSync(file, "utf8"),
@@ -143,12 +155,24 @@ describe("read failures render through a primitive", () => {
it("never hand-rolls one beside a read", () => {
const offenders = files.flatMap(({ file, source }) =>
readErrorBranches(source)
.filter((b) => b.handRolled)
.map((b) => `${file}:${b.line}`),
HAND_ROLLED_BY_DESIGN.has(file)
? []
: readErrorBranches(source)
.filter((b) => b.handRolled)
.map((b) => `${file}:${b.line}`),
);
expect(offenders).toEqual([]);
});
it("keeps every deliberate exception live", () => {
const stale = [...HAND_ROLLED_BY_DESIGN.keys()].filter((name) => {
const entry = files.find(({ file }) => file === name);
return (
!entry || !readErrorBranches(entry.source).some((b) => b.handRolled)
);
});
expect(stale).toEqual([]);
});
});
describe("an error row inside a card draws no frame of its own", () => {
@@ -319,7 +319,7 @@ function findGatedWebCalls(): GatedCall[] {
const PRECHECKED = new Set<string>([
// `upload.mutationFn` asks first, then calls `uploadOnce`, so a large file
// is not sent once only to be refused.
"components/admin/backups-section.tsx -> POST /api/admin/backups/upload",
"components/admin/backups/use-backup-upload.ts -> POST /api/admin/backups/upload",
]);
describe("fresh-proof gates on the web", () => {
@@ -97,7 +97,10 @@ const PINNED_AFFORDANCES: Record<
"src/components/admin/assistant-section.tsx": { "toast.success": 1 },
// v1.43 — the AI usage bounds: a settings save, not a write with counts.
"src/components/admin/ai-usage-section.tsx": { "toast.success": 1 },
"src/components/admin/backups-section.tsx": { "toast.success": 4 },
// The run, the download, the upload and the finished restore, one toast each.
"src/components/admin/backups/use-backup-actions.ts": { "toast.success": 2 },
"src/components/admin/backups/use-backup-restores.ts": { "toast.success": 1 },
"src/components/admin/backups/use-backup-upload.ts": { "toast.success": 1 },
"src/components/admin/central-codex-section.tsx": { "toast.success": 2 },
"src/components/admin/danger-zone-section.tsx": { "text-success": 1 },
"src/components/admin/encryption-section.tsx": {