feat(storybook): stories for the operator action surfaces #438

Merged
gmackie merged 1 commit from feat/storybook-operator-actions into main 2026-08-25 05:56:38 +00:00
Owner

Why

Four operator surfaces had no coverage — the controls that actually change production:

Surface Lines
Manual deploy 428
Fleet rolling update 495
Node provisioning 387
Per-stage secrets 432

Their in-flight and failed states matter far more than their idle ones, and those are exactly the states you can't reach without deploying something for real.

24 stories

  • Deploy — settled, in flight (the elapsed counter ticks live), failed, first-ever, no flake ref, request rejected
  • Rolling update — idle selection, batch in progress (2 done / 1 deploying / 1 failed / 1 pending), complete, stopped on failures, start rejected
  • Provisioning — every step of the machine plus all four failure modes (Hetzner, tunnel, Tailscale, agent timeout)
  • Stage secrets — populated (including never-rotated and 400-day-stale keys), empty, add rejected, slow backend

Two things found by looking, not by typechecking

The hub frame shape was wrong. ProvisioningView matches on type === "provisioning_step" and reads a nested stepUpdate; the flatter shape I first wrote was silently ignored. The "live" stories sat on their initial step and looked static rather than broken.

That's the third time a guessed stream payload has slipped past typecheck in this project — after traffic.weight vs traffic.shifted, and basis-points vs percent. Stream frame shapes have to be read off the consumer, and the only way to catch a wrong one is to verify the animation actually happens.

The live provisioning story can't show a clean run. The socket effect lists currentStep in its dependencies:

}, [nodeId, hubWsUrl, currentStep]);

so every step change tears the WebSocket down and opens a new one — eight sockets for one provision. Against the real hub that's merely wasteful (the server re-pushes state on connect); against a scripted replay each reconnect restarts the script, so the view sawtooths through the early steps.

Rather than ship a story that implies a smooth provision, that one is now described for what it actually demonstrates, and the per-step stories carry the real coverage. The reconnect-per-step behaviour is worth a look on its own.

Verified

404/404 stories render clean in a browser. Typecheck, lint and build all pass.

Stacks cleanly with #436 (different files).

## Why Four operator surfaces had no coverage — the controls that actually change production: | Surface | Lines | |---|---| | Manual deploy | 428 | | Fleet rolling update | 495 | | Node provisioning | 387 | | Per-stage secrets | 432 | Their in-flight and failed states matter far more than their idle ones, and those are exactly the states you can't reach without deploying something for real. ## 24 stories - **Deploy** — settled, in flight (the elapsed counter ticks live), failed, first-ever, no flake ref, request rejected - **Rolling update** — idle selection, batch in progress (2 done / 1 deploying / 1 failed / 1 pending), complete, stopped on failures, start rejected - **Provisioning** — every step of the machine plus all four failure modes (Hetzner, tunnel, Tailscale, agent timeout) - **Stage secrets** — populated (including never-rotated and 400-day-stale keys), empty, add rejected, slow backend ## Two things found by looking, not by typechecking **The hub frame shape was wrong.** `ProvisioningView` matches on `type === "provisioning_step"` and reads a nested `stepUpdate`; the flatter shape I first wrote was silently ignored. The "live" stories sat on their initial step and looked *static* rather than broken. That's the third time a guessed stream payload has slipped past typecheck in this project — after `traffic.weight` vs `traffic.shifted`, and basis-points vs percent. **Stream frame shapes have to be read off the consumer**, and the only way to catch a wrong one is to verify the animation actually happens. **The live provisioning story can't show a clean run.** The socket effect lists `currentStep` in its dependencies: ```ts }, [nodeId, hubWsUrl, currentStep]); ``` so every step change tears the WebSocket down and opens a new one — **eight sockets for one provision**. Against the real hub that's merely wasteful (the server re-pushes state on connect); against a scripted replay each reconnect restarts the script, so the view sawtooths through the early steps. Rather than ship a story that implies a smooth provision, that one is now described for what it actually demonstrates, and the per-step stories carry the real coverage. The reconnect-per-step behaviour is worth a look on its own. ## Verified **404/404 stories render clean** in a browser. Typecheck, lint and build all pass. Stacks cleanly with #436 (different files).
feat(storybook): stories for the operator action surfaces
All checks were successful
CI / gitleaks (pull_request) Successful in 6s
CI / storybook (pull_request) Successful in 1m33s
forgegraph/ci CI passed
CI / ci (pull_request) Successful in 9m45s
69fcf0635d
Manual deploy, fleet rolling update, node provisioning and per-stage secrets —
the controls that change production. Their in-flight and failed states matter
far more than their idle ones, and those are the states you cannot reach
without actually deploying something.

24 stories:

  - deploy            settled, in flight (the elapsed counter ticks live),
                      failed, first-ever, no flake ref, request rejected
  - rolling update    idle selection, batch in progress, complete, stopped on
                      failures, start rejected
  - provisioning      every step of the machine plus all four failure modes
                      (Hetzner, tunnel, Tailscale, agent timeout)
  - stage secrets     populated, empty, add rejected, slow backend

Two things found by checking the rendered result rather than the types.

The hub frame shape was wrong. ProvisioningView matches on
type === "provisioning_step" and reads a nested stepUpdate; the flatter shape I
first wrote was silently ignored, so the "live" stories sat on their initial
step and looked static rather than broken. Third time a guessed stream payload
has slipped past typecheck — these shapes have to be read off the consumer.

And the live provisioning story cannot show a clean run at all: the socket
effect lists currentStep in its dependencies, so every step change tears the
WebSocket down and opens a new one — eight sockets for one provision. Against
the real hub that is merely wasteful, because the server re-pushes state on
connect; against a scripted replay each reconnect restarts the script. Rather
than ship a story that implies a smooth provision, that one is now described
for what it demonstrates, and the per-step stories carry the real coverage.

Verified: 404/404 stories render clean in a browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gmackie deleted branch feat/storybook-operator-actions 2026-08-25 05:56:38 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gmackie/ForgeGraph!438
No description provided.