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
Check · Review · Land · Live done — Landed and live in production.
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
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:
DrillResultinsrc/snapshot/drill.tsgainsphases: { phase, ms }[]andbudgetMs, on green and red alike. Each phase is timed from the first timeonProgressreports 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.tsadds one nullable TEXT column of JSON,{phases, budgetMs}.src/snapshot/drills.tsvalidates 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: anywhereon.backups-drillso 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.tshas 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 aDrillResultgotphases: [], budgetMs: 0so they type-check.
What I didn't do
GATE: I leftscripts/gate.tsalone. 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 touchsrc/core/migration-pins.jsonorreleases.json.scripts/pin-migrations.tssays 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
budgetMsis the 10 min floor plus a few ms, not exactly 600000. - The "manifest" phase is kept and shown too, since
restoreSnapshotreports it.
Try it: run a drill from /settings/backups and read the line under "Last drill".
🤖 Generated with Claude Code
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
lastDrillreturn 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-timingwraps 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.
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 insrc/snapshot/drills.ts.tscand the snapshot-drill tests pass (36 pass, 0 fail).- Terminal: nothing to change. The README says "The terminal has no backups view", and
lastDrillis 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-timingspan. Its parent.backups-drillhasoverflow-wrap: anywhere, so the long text should wrap and not scroll sideways, but that is unconfirmed.
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.
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.tsnow 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:
phasesandbudgetMsare each on their own line in the test fixtures I touched (domains,drill-board-line,drill-verdict,drill-weeklyandsnapshot-drill-pagetests). - 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: anywhereis on.backups-drill, and I expect it to hold.
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.
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 --noEmitis clean. The snapshot-drill, schema, other drill and domains tests pass (123 pass, 0 fail). I did not run the whole check.What changed
DrillResultinsrc/snapshot/drill.tsgainsphases: { phase, ms }[]andbudgetMs, on green and red alike. Each phase is timed from the first timeonProgressreports it to the first report of the next phase. The last phase ends when the restore ends, or when the drill fails.20261019120000-drill-phases.tsadds one nullable TEXT column of JSON,{phases, budgetMs}.src/snapshot/drills.tsvalidates 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.overflow-wrap: anywhereon.backups-drillso it wraps on a 375 px screen. I haven't looked at it on a real narrow screen.test/snapshot-drill-phases.test.tshas 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 aDrillResultgotphases: [], budgetMs: 0so they type-check.What I didn't do
GATE: I leftscripts/gate.tsalone. This tree derives the gate from the test files, so the new test file is picked up without being named.releases.json: I didn't touchsrc/core/migration-pins.jsonorreleases.json.scripts/pin-migrations.tssays the release agent pins new migrations when it cuts a release.Things to know
budgetMsis the 10 min floor plus a few ms, not exactly 600000.restoreSnapshotreports it.Try it: run a drill from /settings/backups and read the line under "Last drill".
🤖 Generated with Claude Code
Closes #2622