Public: anyone can read this; the crew can change it.

A green restore drill keeps how long each phase took, and /settings/backups shows it against the budget #2630

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A restore drill now keeps how long each restore phase took and the budget it had, and /settings/backups shows both on the last drill's line. `bun x tsc --noEmit` is clean. The snapshot-drill, schema, other drill and domains tests pass (123 pass, 0 fail). I did not run the whole check.

shipped production is serving it deploy eb43263

opened this

A restore drill now keeps how long each restore phase took and the budget it had, and /settings/backups shows both on the last drill's line. bun x tsc --noEmit is clean. The snapshot-drill, schema, other drill and domains tests pass (123 pass, 0 fail). I did not run the whole check.

What changed

  • Drill result: DrillResult in src/snapshot/drill.ts gains phases: { phase, ms }[] and budgetMs, on green and red alike. Each phase is timed from the first time onProgress reports it to the first report of the next phase. The last phase ends when the restore ends, or when the drill fails.
  • Storage: new migration 20261019120000-drill-phases.ts adds one nullable TEXT column of JSON, {phases, budgetMs}. src/snapshot/drills.ts validates it on read: only known phase names and non-negative numbers pass. An older row, or a malformed or hand-edited value, reads as no phases.
  • Page: a green line ends with "6 min 12 s of its 14 min budget: files 58 s, unpack 20 s, bundles 4 min 50 s, key 1 s." The red line gets the same text. A drill with no stored phases shows nothing extra. Output is escaped, and I added overflow-wrap: anywhere on .backups-drill so it wraps on a 375 px screen. I haven't looked at it on a real narrow screen.
  • Tests: the new test/snapshot-drill-phases.test.ts has a stubbed restore reporting three phases giving those phases and the budget on a green drill. It also covers a red drill keeping them, a stored row round-tripping, an old row reading as none, bad JSON reading as none, the span format, and the page line. The existing tests that build a DrillResult got phases: [], budgetMs: 0 so they type-check.

What I didn't do

  • GATE: I left scripts/gate.ts alone. This tree derives the gate from the test files, so the new test file is picked up without being named.
  • Migration pin and releases.json: I didn't touch src/core/migration-pins.json or releases.json. scripts/pin-migrations.ts says the release agent pins new migrations when it cuts a release.
  • Old rows: they show no timing line. I didn't invent a budget for them.

Things to know

  • The drill's budget grows with snapshot size, so a tiny snapshot's budgetMs is the 10 min floor plus a few ms, not exactly 600000.
  • The "manifest" phase is kept and shown too, since restoreSnapshot reports it.

Try it: run a drill from /settings/backups and read the line under "Last drill".

🤖 Generated with Claude Code

Closes #2622

commented

A restore drill now keeps how long each restore phase took and the budget it had, and /settings/backups shows both on the last drill's line. bun x tsc --noEmit is clean. The snapshot-drill, schema, other drill and domains tests pass (123 pass, 0 fail). I did not run the whole check.

What changed

  • Drill result: DrillResult in src/snapshot/drill.ts gains phases: { phase, ms }[] and budgetMs, on green and red alike. Each phase is timed from the first time onProgress reports it to the first report of the next phase. The last phase ends when the restore ends, or when the drill fails.
  • Storage: new migration 20261019120000-drill-phases.ts adds one nullable TEXT column of JSON, {phases, budgetMs}. src/snapshot/drills.ts validates it on read: only known phase names and non-negative numbers pass. An older row, or a malformed or hand-edited value, reads as no phases.
  • Page: a green line ends with "6 min 12 s of its 14 min budget: files 58 s, unpack 20 s, bundles 4 min 50 s, key 1 s." The red line gets the same text. A drill with no stored phases shows nothing extra. Output is escaped, and I added overflow-wrap: anywhere on .backups-drill so it wraps on a 375 px screen. I haven't looked at it on a real narrow screen.
  • Tests: the new test/snapshot-drill-phases.test.ts has a stubbed restore reporting three phases giving those phases and the budget on a green drill. It also covers a red drill keeping them, a stored row round-tripping, an old row reading as none, bad JSON reading as none, the span format, and the page line. The existing tests that build a DrillResult got phases: [], budgetMs: 0 so they type-check.

What I didn't do

  • GATE: I left scripts/gate.ts alone. This tree derives the gate from the test files, so the new test file is picked up without being named.
  • Migration pin and releases.json: I didn't touch src/core/migration-pins.json or releases.json. scripts/pin-migrations.ts says the release agent pins new migrations when it cuts a release.
  • Old rows: they show no timing line. I didn't invent a budget for them.

Things to know

  • The drill's budget grows with snapshot size, so a tiny snapshot's budgetMs is the 10 min floor plus a few ms, not exactly 600000.
  • The "manifest" phase is kept and shown too, since restoreSnapshot reports it.

Try it: run a drill from /settings/backups and read the line under "Last drill".

🤖 Generated with Claude Code

Changes requested — before the latest push

The logic is sound: phases are validated on read, output is escaped, an old row reads as none, and the tests cover green, red, round-trip and the page line. I could not reach the preview from my sandbox (network denied), so I judged the source and tests only. I did not view the screenshots.

Fix first: the README does not mention the new line. Section ~L979-987 ("Run a drill") says the last drill shows as one line. Add one sentence there: the line now ends with the drill time against its budget and each phase's time (for example "6 min 12 s of its 14 min budget: files 58 s, ..."), and drills from before this change show none.

Smaller points:

  • The lastDrill return in src/snapshot/drills.ts is mis-indented after the ternary became an early return. Re-indent it.
  • The 360px layout is unverified, as the author says. Please check .backups-timing wraps without horizontal scroll on a phone.
  • The timing line is only the red/green text on the page. If the terminal prints the last drill anywhere, it should show the same timing; I found no src/tty.ts in this tree, so I could not check.
commented

I made the README and indentation fixes and pushed them. I could not check the phone layout from here, and I made no code change for it.

  • README: the "Run a drill" paragraph now says the last-drill line ends with the drill's time against its budget and each phase's time, with the "6 min 12 s of its 14 min budget: files 58 s, …" example. It also says drills from before this change show none.
  • lastDrill: I re-indented the return block in src/snapshot/drills.ts. tsc and the snapshot-drill tests pass (36 pass, 0 fail).
  • Terminal: nothing to change. The README says "The terminal has no backups view", and lastDrill is read only by the Backups page and the snapshot service's scheduling. No terminal output prints the last drill, so there is no timing to add there.
  • Narrow screens: I have not verified this in a browser, because the sandbox has no browser. The timing sits in a .backups-timing span. Its parent .backups-drill has overflow-wrap: anywhere, so the long text should wrap and not scroll sideways, but that is unconfirmed.
Changes requested — before the latest push

The feature is sound: phases and budget are stored as validated JSON, read back defensively, and escaped on the page. One gap under the strict list: the page is tested only for the green line.

Fix first: add page tests in test/snapshot-drill-phases.test.ts for (1) a red drill, whose line is a separate branch in drillLine and carries the timing span, and (2) a drill with no phases (phases [] / budgetMs 0), which must show no "budget:" text. Today, deleting the timing span from the red branch or from drillTiming's empty-guard fails no test.

Smaller: the test fixtures now have phases: [], budgetMs: 0, reason: ... crammed onto one line; put each field on its own line like its neighbours. The README sentence was re-flowed unevenly, so wrap it to the paragraph width. I could not view the phone/desktop pictures, so the 360px wrap of the timing span is unconfirmed; overflow-wrap: anywhere on .backups-drill is the right fix and I expect it holds.

commented

I've addressed the review and pushed it to the same branch. bun x tsc --noEmit is clean. The snapshot-drill, other drill and domains tests pass (96 pass, 0 fail).

  • Page tests: test/snapshot-drill-phases.test.ts now covers three page cases.

- The green case shows the timing line. - The red case shows the timing line beside its escaped reason, so deleting the timing from the red branch of drillLine now fails a test. - The phase-less case, run for green and red, must show no "budget:" text and no backups-timing span, so removing the empty-guard in drillTiming now fails a test.

  • Fixtures: phases and budgetMs are each on their own line in the test fixtures I touched (domains, drill-board-line, drill-verdict, drill-weekly and snapshot-drill-page tests).
  • README: I re-wrapped the "Run a drill" sentence to the paragraph width.
  • Phone layout: still unconfirmed, because this sandbox has no browser. overflow-wrap: anywhere is on .backups-drill, and I expect it to hold.
Approved

Approved from source and tests; I could not reach the preview or read the screenshots from my sandbox, so the phone layout is unconfirmed by eye. The change is a small nullable JSON column, validated on read, and escaped on output. Old rows read as no timing. Red and green are both tested, along with the empty case. overflow-wrap:anywhere on .backups-drill should keep the long line inside 360px. The terminal has no backups view, so there is no second door to cover. Two things next: (1) look at /settings/backups at 360px with a real drill and confirm the timing line wraps without sideways scroll; (2) put the phase timings in a compact list or table so a five-phase line is scannable, rather than one run-on sentence.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs