refactor(web): extract EmptyState — one shared empty-state line for all routes and sections #284

Merged
dominik.polakovics merged 1 commit from afk/279 into main 2026-08-04 20:10:23 +02:00

Split out of umbrella #276 (frontend component reuse), following the SectionCard extraction in #275.

What

EmptyState (web/src/components/EmptyState.tsx) now owns the empty-state markup, and all 16 production <p class="empty">…</p> call sites render through it:

  • list routes — Tokens, Credentials, History (2), Repos, RepoIssues, RepoCRs
  • RunChat's four transcript states (ready / locating / gone / no messages)
  • NewRun's zero-repos state
  • settings sections — Imports, Secrets, Schedules, Notifications
export default function EmptyState(props: { children?: JSX.Element }) {
  return <p class="empty">{props.children}</p>;
}

The content stays a JSX slot rather than a text: string prop, because the call sites are not all static strings: History interpolates its outcome filter, RepoCRs passes a computed emptyText(), and Repos/RepoIssues/NewRun embed an <A> link mid-sentence. Children pass through untouched, so computed and reactive messages keep working exactly as they did inline — the colocated test pins both the JSX-element case and the signal-tracking case for that reason.

Props surface is deliberately just children — no class, no icon slot, no conditionals — matching the "small props surface, plain markup out" shape of SectionCard.

No visual change

Same element, same empty class, same text, character for character. web/src/styles/banners.css is untouched. Prettier re-wrapped two of the messages across lines (Credentials, Secrets); JSX collapses that to the same rendered string.

After the migration, grep -rn 'class=\"empty\"' web/src finds only EmptyState.tsx itself plus one fixture in SectionCard.test.tsx — test fixtures may keep raw markup per the issue.

Verification

Run in web/, all green:

  • npx vitest run — 70 files / 1002 tests passing, including the 6 new EmptyState tests. The pre-existing tests that assert on the .empty selector (RunChat, RepoIssues) and on the empty text (Imports, Schedules, Secrets) pass unchanged, which is the evidence the markup did not move.
  • npx tsc --noEmit, npx eslint ., npx prettier --check . — clean
  • npm run build — production build succeeds

Closes #279

🤖 Generated with Claude Code

Split out of umbrella #276 (frontend component reuse), following the `SectionCard` extraction in #275. ## What `EmptyState` (`web/src/components/EmptyState.tsx`) now owns the empty-state markup, and all 16 production `<p class="empty">…</p>` call sites render through it: - list routes — Tokens, Credentials, History (2), Repos, RepoIssues, RepoCRs - RunChat's four transcript states (ready / locating / gone / no messages) - NewRun's zero-repos state - settings sections — Imports, Secrets, Schedules, Notifications ```tsx export default function EmptyState(props: { children?: JSX.Element }) { return <p class="empty">{props.children}</p>; } ``` The content stays a JSX slot rather than a `text: string` prop, because the call sites are not all static strings: History interpolates its outcome filter, RepoCRs passes a computed `emptyText()`, and Repos/RepoIssues/NewRun embed an `<A>` link mid-sentence. Children pass through untouched, so computed and reactive messages keep working exactly as they did inline — the colocated test pins both the JSX-element case and the signal-tracking case for that reason. Props surface is deliberately just `children` — no `class`, no icon slot, no conditionals — matching the "small props surface, plain markup out" shape of `SectionCard`. ## No visual change Same element, same `empty` class, same text, character for character. `web/src/styles/banners.css` is untouched. Prettier re-wrapped two of the messages across lines (Credentials, Secrets); JSX collapses that to the same rendered string. After the migration, `grep -rn 'class=\"empty\"' web/src` finds only `EmptyState.tsx` itself plus one fixture in `SectionCard.test.tsx` — test fixtures may keep raw markup per the issue. ## Verification Run in `web/`, all green: - `npx vitest run` — 70 files / 1002 tests passing, including the 6 new `EmptyState` tests. The pre-existing tests that assert on the `.empty` selector (RunChat, RepoIssues) and on the empty text (Imports, Schedules, Secrets) pass unchanged, which is the evidence the markup did not move. - `npx tsc --noEmit`, `npx eslint .`, `npx prettier --check .` — clean - `npm run build` — production build succeeds Closes #279 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor(web): extract EmptyState, migrate all 16 call sites
All checks were successful
ci / native (pull_request) Successful in 15m35s
2cd5eb1e66
The "nothing here yet" line was hand-rolled as `<p class="empty">…</p>` at 16
production call sites — the list routes (Tokens, Credentials, History, Repos,
RepoIssues, RepoCRs), RunChat's four transcript states, NewRun, and the repo-
and user-settings sections (Imports, Secrets, Schedules, Notifications). A
spacing or visual fix to the pattern meant touching every one of them.

EmptyState now owns that markup, shaped like SectionCard (#275): a tiny props
surface, plain markup out, a colocated test in the same style. The message
stays a JSX slot rather than a `text: string` prop because the call sites are
not all static — History interpolates its outcome filter, RepoCRs passes a
computed `emptyText()`, and Repos/RepoIssues/NewRun embed an `<A>` link
mid-sentence — so children pass through untouched and stay reactive.

No visual change: same element, same `empty` class, same text. `.empty` in
banners.css is untouched and, in production code, is now applied only here;
the one remaining hand-rolled instance is a fixture in SectionCard.test.tsx.

Closes #279

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

[autoland] verdict: pass
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
Cloonar/coding-lab!284
No description provided.