back up on publish, which is when this site actually changes

A nightly backup of a site that only changes when somebody publishes spends most
of its life copying a database identical to yesterday's, and misses the one
moment worth capturing. So "On publish" joins the schedules, and goes first.

Stored in the same column as a cron expression because it answers the same
question — WHEN — and a second column would mean every read had to decide which
of the two was authoritative.

It creates no cron job and no catch-up tick. A missed run is a run whose TIME
passed while the machine was down; an event has no time, so there is nothing to
have missed, and a catch-up here would take a backup every fifteen minutes of a
site nobody had touched.

`backupAfterPublish()` does nothing unless this is the configured trigger, so
the publish path can call it unconditionally and never has to know what the
backup settings say. It swallows failures for the same reason the timer does: a
backup that could not be taken must not present as a publish that failed, when
the thing the person asked for worked.

Nothing calls it yet — there is no publish in this plugin, because the sections
that would do the publishing are not built.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 13:19:22 +01:00
co-authored by Claude Opus 5
parent 89c9dd6f63
commit 9ff25256c1
2 changed files with 65 additions and 11 deletions
+26 -9
View File
@@ -42,8 +42,18 @@ type Settings = {
recent: Backup[];
};
/** Every schedule anybody has actually wanted, and their expressions. */
/** The sentinel the API stores for "not on a clock". Kept in step with `ON_PUBLISH` in api/backups.ts. */
const ON_PUBLISH = 'on-publish';
/**
* When to back up.
*
* "On publish" is first because it is the right answer for a site that only changes when somebody
* changes it: a nightly backup spends most of its life copying a database identical to yesterday's,
* and misses the one moment worth capturing.
*/
const CHOICES = [
{ label: 'On publish', cron: ON_PUBLISH },
{ label: 'Hourly', cron: '0 * * * *' },
// Midnight, 6am, noon and 6pm — anchored to the clock rather than to whenever this was saved.
{ label: 'Every 6 hours', cron: '0 */6 * * *' },
@@ -266,14 +276,21 @@ export const BackupsRow = ({ prodName, devName }: { prodName: string; devName: s
</Button>
))}
</div>
{/* The expression stays visible and editable. The buttons write into it; they do not hide it. */}
<Input
value={cron}
onChange={(event) => setCron(event.target.value)}
className="w-full font-mono text-xs"
autoComplete="off"
spellCheck={false}
/>
{/* The expression stays visible and editable for a SCHEDULE. "On publish" is not one, so the
box would be a field with nothing to put in it. */}
{cron === ON_PUBLISH ? (
<p className="text-xs text-muted-foreground/70">
A backup is taken every time the site is published, and at no other time.
</p>
) : (
<Input
value={cron}
onChange={(event) => setCron(event.target.value)}
className="w-full font-mono text-xs"
autoComplete="off"
spellCheck={false}
/>
)}
</div>
<div className="space-y-1">