refactor(web): one shared head row per kind — extract SectionHead, fold the rest into SectionCard #286
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!286
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/280"
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?
Closes #280
Unifies the two competing head-row patterns onto shared components, so the row's markup and spacing live in one place per kind.
What changed
<div class="section-head">hand-rolled in 12 route files<SectionHead title=… action=… />SectionCard(#275) + 4 card sections with a bare<h2>SectionCardeverywhereh2reset.section-head h2" commentSectionHead(+ colocated test inSectionCard.test.tsx's style) — heading, always-present.spacer, optional action/controls slot.titleis aJSX.Elementrather thanSectionCard'sstring, because the detail routes put an inline.issue-numberspan inside the heading. The #275 spacing contract extends here: the component owns the gap below the row, call sites add nothing..section-headdropsjustify-content: space-between— the spacer right-aligns the action now, so the row's markup, and therefore its height, is identical with and without one. The test pins that:['H2','SPAN']vs['H2','SPAN','BUTTON'].SectionCard— IssueDetail's Comments, CRDetail's Diff, RepoIssues' ready queue, RepoLabels' new-label card..ready-list/.comment-listdrop the top margins the component makes redundant.Out of scope and untouched, as the issue specifies:
.card-titlelist rows (#282) and the.card.form-cardshells (#281).Visual verification
Measured in headless chromium against HEAD's stylesheet and HEAD's markup —
before.css/after.cssare script-built by concatenating every file inbase.css's import order from each revision — at 1280×900 and 390×844, with the real Source Serif 4 face loaded.Every page head: 0.00px delta on row height, row edges, heading position, action position, and the gap below the row. All eight specimens, both viewports.
Two deltas are real and intended:
space-betweenonly ever looked right with two children, and History is the one head with three, so its filters were spread across the row. #280 names History's controls as the action slot's use case, and every other multi-control head in the app already right-clusters via.head-actions.h2's 0.35rem margin collapsing with the body's top margin — +6.41px (ready queue, diff), +2.41px (comments). That is the #275 contract working as designed (the gap is stated, not inherited from font metrics) and it is the value the other 21 section cards already use.Follow-up filed
#285 — the measurement showed
.section-headhas noflex-wrap(unlike.card-head), so History's twomin-width: 180pxselects already overflowed a phone viewport by 28.63px; the spacer's extra 0.65rem gap widens that to 39.02px. Filed rather than fixed here: wrapping visibly changes History's phone layout, which #280 puts out of scope.Checks
npm run lint,npm run format:check,npm test(70 files / 1001 tests),npm run build(tsc --noEmit+ vite) — all green locally.[autoland] verdict: pass
PASS — validated against the validation core.
Signal relied on:
ci / native (pull_request)— success in 8m25s. Its "SPA — lint, format, test, build" step (npm run lint,format:check,test,build) runs unconditionally on every PR and covers exactly this web-only diff; the path-gatedci-nixgate is not implicated (no nix/dependency changes). No local re-run needed.Conventions: Conventional Commits title;
Closes #280present and well-formed on branchafk/280.Diff scope: matches #280's brief. All five acceptance criteria met — SectionHead exists with a colocated test (5 cases), no hand-rolled
.section-headremains in routes/components, four card sections migrated to SectionCard, the always-present.spacerkeeps row height independent of the action (pinned by the['H2','SPAN']vs['H2','SPAN','BUTTON']assertion). #281/#282 families correctly left untouched, and the flex-wrap finding was filed as #285 rather than scope-crept in.CSS relocation verified: moving the
h2reset into cards.css is safe —.section-head h2moves later inbase.cssimport order (shell 11 → cards 12) with no competing rule;.settings-back-head h2moves earlier (settings-ia 34 → cards 12) and nothing between 13 and 34 matches it at equal-or-higher specificity..spaceris global (layout.css:9)..card.ready-queuestill matches SectionCard'sclass=\"card ready-queue\"output.Conflict: none. Base advanced to
83ad232(#284, EmptyState);git merge-treeis clean, and the two sides are disjoint region-by-region across the six shared route files — #284 touched only<p class=\"empty\">bodies inside<Match>, this PR only head rows. Merged import blocks are correctly ordered.The two visual deltas the body documents (History's filters right-clustering, +12px head gap on the four migrated cards) are the issue's stated intent, measured and disclosed — not defects.
Merge conflict with
mainresolved — base had advanced todc62f8c(#289, the Banner refactor). Merge commit7ac62f3(Merge branch 'main' into afk/280), pushed to the head branch; no rebase, no force-push.Three files conflicted:
web/src/routes/Repos.tsx,web/src/routes/RepoLabels.tsx,web/src/routes/Tokens.tsx— one hunk each, all the same shape:<div class="section-head">with<SectionHead …/><ErrorBanner …/>to<Banner …/>on the line directly belowThe two edits are orthogonal — different elements, adjacent lines, neither touching the other's semantics — so the resolution is the union of both sides, taken verbatim:
Deterministic and behaviour-preserving; no semantic choice was made, nothing was dropped from either side. Import blocks auto-merged correctly (
BannerinErrorBanner's slot,SectionHeadadded). Verified after resolution: zero remainingErrorBannerreferences, zero hand-rolledclass="section-head"outsideSectionHead.tsxitself, andSectionHeadimported in all three files.Re-verified: the resolution commit is one no gate had seen, so the earlier green no longer covered it. CI re-ran on
7ac62f3—ci / native (pull_request)success in 8m8s (run 395).Merge conflict with
mainresolved — base had advanced todc62f8c(#289, the Banner refactor). Merge commit7ac62f3(Merge branch 'main' into afk/280), pushed to the head branch; no rebase, no force-push.Three files conflicted:
web/src/routes/Repos.tsx,web/src/routes/RepoLabels.tsx,web/src/routes/Tokens.tsx— one hunk each, all the same shape:<div class="section-head">with<SectionHead …/><ErrorBanner …/>to<Banner …/>on the line directly belowThe two edits are orthogonal — different elements, adjacent lines, neither touching the other's semantics — so the resolution is the union of both sides, taken verbatim:
Deterministic and behaviour-preserving; no semantic choice was made, nothing was dropped from either side. Import blocks auto-merged correctly (
BannerinErrorBanner's slot,SectionHeadadded). Verified after resolution: zero remainingErrorBannerreferences, zero hand-rolledclass="section-head"outsideSectionHead.tsxitself, andSectionHeadimported in all three files.Re-verified: the resolution commit is one no gate had seen, so the earlier green no longer covered it. CI re-ran on
7ac62f3—ci / native (pull_request)success in 8m8s (run 395).Merge conflict with
mainresolved — base had advanced todc62f8c(#289, the Banner refactor). Merge commit7ac62f3(Merge branch 'main' into afk/280), pushed to the head branch; no rebase, no force-push.Three files conflicted:
web/src/routes/Repos.tsx,web/src/routes/RepoLabels.tsx,web/src/routes/Tokens.tsx— one hunk each, all the same shape:<div class="section-head">with<SectionHead …/><ErrorBanner …/>to<Banner …/>on the line directly belowThe two edits are orthogonal — different elements, adjacent lines, neither touching the other's semantics — so the resolution is the union of both sides, taken verbatim:
Deterministic and behaviour-preserving; no semantic choice was made, nothing was dropped from either side. Import blocks auto-merged correctly (
BannerinErrorBanner's slot,SectionHeadadded). Verified after resolution: zero remainingErrorBannerreferences, zero hand-rolledclass="section-head"outsideSectionHead.tsxitself, andSectionHeadimported in all three files.Re-verified: the resolution commit is one no gate had seen, so the earlier green no longer covered it. CI re-ran on
7ac62f3—ci / native (pull_request)success in 8m8s (run 395).Merge conflict with
mainresolved — base had advanced todc62f8c(#289, the Banner refactor). Merge commit7ac62f3(Merge branch 'main' into afk/280), pushed to the head branch; no rebase, no force-push.Three files conflicted:
web/src/routes/Repos.tsx,web/src/routes/RepoLabels.tsx,web/src/routes/Tokens.tsx— one hunk each, all the same shape:<div class="section-head">with<SectionHead …/><ErrorBanner …/>to<Banner …/>on the line directly belowThe two edits are orthogonal — different elements, adjacent lines, neither touching the other's semantics — so the resolution is the union of both sides, taken verbatim:
Deterministic and behaviour-preserving; no semantic choice was made, nothing was dropped from either side. Import blocks auto-merged correctly (
BannerinErrorBanner's slot,SectionHeadadded). Verified after resolution: zero remainingErrorBannerreferences, zero hand-rolledclass="section-head"outsideSectionHead.tsxitself, andSectionHeadimported in all three files.Re-verified: the resolution commit is one no gate had seen, so the earlier green no longer covered it. CI re-ran on
7ac62f3—ci / native (pull_request)success in 8m8s (run 395).