refactor(web): extract ListRowCard — one component for the entity list-row card #288
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!288
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/282"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of umbrella #276, follow-up to #275 (SectionCard) and #279 (EmptyState).
What
The entity list-row card —
<article class="card">wrapping a.card-headof a.card-titlespan, whatever chips belong beside it, a.spacer, and the row's action buttons, over an optional<p class="muted card-sub">metadata line — was hand-rolled at five production sites.web/src/components/ListRowCard.tsxnow owns that markup, with a colocated test inSectionCard's style.Migrated (the row markup for these entities is now produced only by the component):
routes/Tokens.tsxTokenCardroutes/repo-settings/sections/Imports.tsxImportRowroutes/Credentials.tsxCredentialCardroutes/settings/sections/Notifications.tsxroutes/repo-settings/sections/Schedules.tsxScheduleRowDesign notes
badgesrenders before it, so Credentials' kind and "in use" chips keep hugging the title;actionsrenders after it, so buttons stay at the trailing edge. A single combined slot would have to pick a side, and picking "after" would slide Credentials' chips to the right edge — a visual change.titleis a JSX slot, because Notifications decorates its device label with aShow-gated<span class="muted"> · this device</span>inside the.card-titlespan.subis gated on!== undefined, not truthiness. Every caller that passes one rendered the line unconditionally before; Schedules'cadenceSummaryfalls back toexpr.trim(), so a truthiness gate would silently drop that row's metadata line for a blank cadence.'delete' | 'reenable' | nullso it can say which of its two buttons is working, Credentials a sharedrun()helper that also drives an inlineErrorBannerand a view/rename/replace mode. Folding them together would either change behaviour or need one knob per call site. The shared thing here is the markup, not the wiring.No visual change
Same elements, class strings, text, confirm prompts, busy labels and error propagation at all five sites. The one node added anywhere is a trailing
<span class="spacer">in Credentials' head, which is inert there:.spacerisflex: 1—flex: 1 1 0%— so it claims only space that was already empty in that left-packed row and cannot force a wrap atflex-basis: 0..card-title/.card-sub/.card-headinstyles/cards.cssare untouched, and per-entity modifiers still attach (class="token-card"on Tokens).Rows deliberately left hand-rolled
Every remaining
<div class="card-head">in production code, with why it is not this shape:components/ProviderAuthCard.tsx— a<section class="card auth-status-card">, not an<article>list row; its head is aSwitchover login state plus a non-destructive Refresh, and its body is a multi-branchSwitchof forms and confirms. Provider-auth family, not an entity row.routes/Repos.tsx— the head's trailing items are three<A class="card-link">navigations (Issues / CRs / Settings) alongside a conditional "Stop all". That is the card-link row family under #276, not title + action.routes/History.tsx— the head ends in an outcome chip and has no action button; the body is a multi-block run summary (chip row, failure note, escalation block, trailingcard-link).routes/RepoIssues.tsx— the card is the<A>(built withclassList, not a literalclass), so there is no<article>to hand over.routes/RepoCRs.tsx— the.card-headis nested inside an<A>rather than being a direct child of the card.routes/repo-settings/sections/Secrets.tsx— the closest miss. It stacks twocard-sublines (aShow-gated description above a fixed "Updated …"), which the single metadata slot cannot order without reintroducing them aschildren, and its title needscard-title mono— a title-class knob no other row wants. Worth revisiting if a second row ever wants either.Verification
web/has nonode_modulesand this worktree has no Node toolchain, sovitest/tsc/eslint/prettiercould not be run locally — the CInativejob is the gate. Review was done by reading: every migrated row was checked element-by-element against its pre-migration JSX for order, class strings and text, and the existing suites for these routes (Tokens.test.tsx,Imports.test.tsx,Notifications.test.tsx,Schedules.test.tsx,settings/index.test.tsx,repo-settings/index.test.tsx,e2e/smoke.spec.ts) assert on rendered text and button labels rather than on.card/.card-headstructure, with the one exception ofImports.test.tsx's.card-titleprobe, whichListRowCardrenders unconditionally.Closes #282
[autoland] verdict: pass
Validated against the validation core.
Signal:
ci / native (pull_request)— success, run 392 (10m11s), the required check. It is the run that includes the formatting fix and the first run to get pastprettier --checkintonpm test,tsc --noEmit/vite build,go build -tags ui,go test -tags ui ./...and golangci-lint. (ci-nixis path-gated to nix/dependency changes and correctly did not run — this diff is SPA-only.)Fixed inline (trivial): run 391 failed solely on
prettier --checkflaggingweb/src/components/ListRowCard.test.tsx. Reproduced locally with the pinned toolchain (Node 24.18.0, reponpm ci) and appliedprettier --writeverbatim in7067dc0— prettier joins JSX text onto the line of the element that follows it (Pixel 8<span class="muted"> · this device</span>). No semantic change: JSX discards newline-bearing whitespace either way, sotitle.textContent === 'Pixel 8 · this device'still holds.Conventions: Conventional Commits title,
Closes #282present, diff scope confined to the component, its colocated test and the five migrated routes — no drive-by. No conflict withmain.Correctness: all five migrations read element-for-element identical to their pre-migration JSX — order, class strings, text, confirm prompts, busy labels, error propagation.
token-cardstill attaches viaclass.subgated on!== undefinedis the right call: Schedules'cadenceSummarycan return''and a truthiness gate would drop that row's metadata line. Every acceptance criterion on #282 is met, including the audit of rows left hand-rolled.