fix(web): cover new archived RunStatus in Files empty-state taxonomy

origin/main introduced a new `archived` lifecycle status as a terminal
state reached by explicit user action on a previously terminal run.
deriveEmptyKind didn't know about it — archived runs with files would
have rendered "The diff for this run is no longer available. If you
expect files here, please report it.", which is wrong; the diff was
captured normally, the run was just archived later.

Adds `archived` to the terminal-success branch so archived runs show
the correct empty-state copy (R4b or R4c2) based on total_changed,
same as a succeeded run.

The regression-guard test is also tightened: it now iterates over
`RunStatus` from @qltysh/fabro-api-client rather than a hand-
maintained list, so any future addition to the OpenAPI spec fails
this test until the decision table grows a branch. This exact class
of silent-regression is what made me miss archived in the first
place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-19 19:41:03 -04:00
parent 1f6eb5b336
commit 3435c02dca
No known key found for this signature in database
4 changed files with 17 additions and 27 deletions

View file

@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test";
import TestRenderer from "react-test-renderer";
import { RunStatus } from "@qltysh/fabro-api-client";
import {
deriveEmptyKind,
emptyStateCopy,
@ -59,7 +60,7 @@ describe("deriveEmptyKind", () => {
// Terminal-success + teardown states → R4(b) or R4(c2) depending on
// whether files were ever changed
test.each(["succeeded", "removing"])(
test.each(["succeeded", "removing", "archived"])(
"%s with changes but no data is R4(c2) 'diff_lost'",
(status) => {
expect(
@ -72,7 +73,7 @@ describe("deriveEmptyKind", () => {
},
);
test.each(["succeeded", "removing"])(
test.each(["succeeded", "removing", "archived"])(
"%s with no changes is R4(b)",
(status) => {
expect(
@ -105,24 +106,13 @@ describe("deriveEmptyKind", () => {
).toBe("unknown");
});
test("every documented RunStatus gets a non-unknown empty kind when applicable", () => {
// Regression guard: if a new RunStatus appears in
// `apps/fabro-web/app/data/runs.ts` without a matching branch here,
// the decision table silently returns "unknown" ("not available right
// now") — misleading copy for a cancelled or paused run.
const knownRunStatuses = [
"submitted",
"queued",
"starting",
"running",
"blocked",
"paused",
"removing",
"succeeded",
"failed",
"dead",
];
for (const status of knownRunStatuses) {
test("every documented RunStatus gets a non-unknown empty kind", () => {
// Regression guard sourced from the generated API client enum so any
// new RunStatus added to the OpenAPI spec fails this test until the
// decision table grows a branch. Without this guard, unhandled
// statuses silently render as "unknown" ("not available right now") —
// misleading copy for e.g. a paused or archived run.
for (const status of Object.values(RunStatus)) {
const result = deriveEmptyKind({
runStatus: status,
totalChanged: 0,

View file

@ -49,7 +49,7 @@ export function emptyStateCopy(kind: EmptyKind): string {
///
/// The full RunStatus enum (per fabro-types/src/status.rs) is:
/// submitted, queued, starting, running, blocked, paused, removing,
/// succeeded, failed, dead
/// succeeded, failed, dead, archived
/// partial_success is a stage status, not a run status.
export function deriveEmptyKind(args: {
runStatus: string | undefined;
@ -81,10 +81,10 @@ export function deriveEmptyKind(args: {
return degraded ? "unknown" : "failed_before_checkpoint";
}
// Terminal-success + teardown states: the run ran to completion or is
// shutting down. Distinguish "succeeded with no changes" (R4b) from
// "succeeded but diff lost" (R4c2) via total_changed.
if (s === "succeeded" || s === "removing") {
// Terminal-success, teardown, and archive states: the run ran to
// completion (possibly long ago). Distinguish "had no changes" (R4b)
// from "diff captured then lost" (R4c2) via total_changed.
if (s === "succeeded" || s === "removing" || s === "archived") {
if (degraded) {
// We have a patch; the component renders PatchDiff instead of an
// empty state. Shouldn't reach here in practice.

View file

@ -61,7 +61,7 @@
<script type="module" src="/assets/chunk-sadshphz.js"></script>
<script type="module" src="/assets/chunk-pmthkscp.js"></script>
<script type="module" src="/assets/chunk-v61ks9f7.js"></script>
<script type="module" src="/assets/entry-fqcfxfyb.js"></script>
<script type="module" src="/assets/entry-pmqz8d3n.js"></script>
<script type="module" src="/assets/chunk-n1k68xa8.js"></script>
<script type="module" src="/assets/chunk-rsph5pvm.js"></script>
<script type="module" src="/assets/chunk-9t57pdty.js"></script>