Skip to content

Show the time of each stage in the table view - #42

Merged
anurag merged 1 commit into
mainfrom
ag/stage-times
Sep 24, 2026
Merged

anurag merged 1 commit into
mainfrom
ag/stage-times

Conversation

@anurag

@anurag anurag commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The stage table of the table view at /table shows how long each stage took, and a timer counts the seconds of the stage that runs now. The classic view at / does not change.

  • Time column: a new column next to the name of each stage. A stage that stopped shows its time. The row of the stage that runs now shows a timer that counts each second. A stage that the run goes into again, as Building after a failed verification, shows the sum of its times. A stage that did not start has no time.
  • Phone layout: the time is on the line of the name of the stage, at the right.

API and schema

  • New column stage_history (jsonb, default []): one item for each time that the run goes into a stage, with the stage and the time. setRunStage() adds the item in the same update that changes the stage.
  • GET /v1/apps/:runId and GET /ui/apps now also give stageHistory, with stage, startedAt, and finishedAt for each item. A stage stops when the next stage starts, and the last stage stops when the run stops. finishedAt is null for the stage that runs now. Postgres writes each time in the JSON with the offset of its session, and the API gives it in UTC, as the other times of a run.
  • scripts/doctor.ts checks the new column.

Code

  • public/table.js adds up the times of each stage at each poll. Between polls, a timer adds the time since the stage that runs now started. The timer runs four times each second, because a timer that runs once each second can skip a second when it runs late. It writes a cell only when the text of the cell changes.
  • The stage table keeps aria-live="off", so a screen reader does not read the timer each second.
  • formatDuration() now always shows the seconds, also for one hour or more (1h 2m 13s). This also changes the "Time to generate" column of the sites table for such runs.
  • AGENTS.md, docs/README.md, and docs/FAQ.md tell about the new column and the new field.

Deploy note

Only the pre-deploy command of the gateway applies app/schema.sql, and the workflows service builds faster. So for a short time after the merge, a run can start on the new setRunStage() before the column exists. Such a run fails at once at its first stage, with column "stage_history" does not exist. Submit the prompt again after the deploy. #40 had the same gap for finished_at. Runs from before this change have no stage times.

Test plan

  • npm run check: Biome, tsc, and 390 Vitest tests pass. New tests cover the start and stop times in the run response, the stage that runs now, and the Time cell of each row of the stage table.
  • The migration, on a throwaway Postgres with the schema of main and an old row: it runs two times with no error, and the old row gets an empty history.
  • setRunStage(), finishRun(), getRun(), and listRunsByUser() against that Postgres, with the session time zone set to America/Los_Angeles. Postgres writes -07:00, and the API gives UTC.
  • The table view with seeded runs (running, failed, and deployed with a second build round) at 1280, 900, and 375 pixels wide. The timer counts each second. When the run went from Building to Verifying, the next poll stopped Building at the right time. When the run failed, the timer stopped.
  • After the deploy: a real run shows the times of its stages.

🤖 Generated with Claude Code

The stage table of the table view has a new Time column, next to the
name of each stage. A stage that stopped shows how long it took. The
stage that runs now shows a timer that counts its seconds. A stage
that the run goes into again, as building after a failed verification,
shows the sum of its times. On a phone, the time is on the line of the
name of the stage. The classic view does not change.

A new stage_history column records when the run goes into each stage:
each time that setRunStage() changes the stage, it adds one item with
the stage and the time. GET /v1/apps/:runId and GET /ui/apps now also
give stageHistory: each stage that the run went into, in order, with
the time that it started and the time that it stopped. A stage stops
when the next stage starts, and the last stage stops when the run
stops. The stage that runs now has no stop time.

table.js adds up the times of each stage from the last poll. Between
polls, a timer adds the time since the stage that runs now started.
The timer runs four times each second, because a timer that runs once
each second can skip a second when it runs late. A time of one hour or
more now also shows its seconds, in the sites table too.

Runs from before this change have no stage times. Only the pre-deploy
command of the gateway applies the schema, so a run that starts on the
new version of the workflows service before that migration is done
fails at its first stage. Submit the prompt again after the deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anurag
anurag enabled auto-merge (squash) September 24, 2026 15:47
@anurag
anurag merged commit 9231867 into main Sep 24, 2026
3 checks passed
@anurag
anurag deleted the ag/stage-times branch September 24, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant