Skip to content

feat(site): tighten base infra step grid and card typography - #27797

Merged
tracyjohnsonux merged 9 commits into
mainfrom
tj/template-builder-base-infra-grid
Aug 4, 2026
Merged

feat(site): tighten base infra step grid and card typography#27797
tracyjohnsonux merged 9 commits into
mainfrom
tj/template-builder-base-infra-grid

Conversation

@tracyjohnsonux

@tracyjohnsonux tracyjohnsonux commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Updates the base infrastructure step, base parameters step, and module screens in the template builder for typography consistency.

Base infra step

  • Grid gains a xl:grid-cols-4 tier while keeping lg:grid-cols-3, so no width band gets looser than before.
  • Card title: text-sm font-bold.
  • Card description and View details link: text-xs font-normal.

Base parameters step

  • Body copy in the README markdown unified to text-xs.
  • List item text and ::marker inherit text-content-secondary so bullets match paragraph color.
  • Inline <code> inherits body size instead of hardcoded text-sm.
  • ConfigurationField description slots (SelectField, RadioField, SwitchField, SwitchGroupField) now use text-xs. The TextField path already rendered text-xs via FormField and is unchanged.
  • Headings unchanged.

Module screens (for consistency)

  • ModuleSelectStep: same additive xl:grid-cols-4 tier; empty-state copy moves to text-xs.
  • ModuleCard: matches TemplateCard (title text-sm font-bold, description and link text-xs font-normal).
  • ModuleConfiguration: 'No configuration required' notice moves to text-xs.
  • ModuleSettingsStep: sensitive-variable notice moves to text-xs.
  • TemplateCustomizationsStep BaseTemplateCard heading matches card title style (text-sm font-bold).

This PR was generated on behalf of @tracyjohnsonux by the Coder agent.

@tracyjohnsonux

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-08-03 18:31 UTC by @tracyjohnsonux
Spend: $80.74 / $100.00

Review history
  • R1 (2026-08-03), 1 Nit, 2 Note, 2 P2, 3 P3, COMMENT. Review
  • R2 (2026-08-03), 1 Nit, 2 Note, 2 P2, 3 P3, COMMENT. Review
  • R3 (2026-08-03): 17 reviewers, 8 Nit, 3 Note, 6 P2, 16 P3, 2 P4, COMMENT. Review

deep-review v0.9.0 | Round 3 | 0e1a9a9..8c6c85f

Last posted: Round 3, 35 findings (6 P2, 16 P3, 2 P4, 8 Nit, 3 Note), COMMENT. Review

Finding inventory

Finding inventory: PR 27797

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (6930107) BaseTemplateParametersStep.tsx:120 Description resize done via id-suffix descendant selector; only affects the switch description at ConfigurationField.tsx:279 R1 Netero Yes
CRF-2 P2 Author fixed (6930107) TemplateCard.tsx:64 Sibling ModuleCard keeps old title/description scale, so the same wizard shows two card typographies R1 Netero Yes
CRF-3 P3 Author fixed (6930107); incomplete, see CRF-11 BaseInfraSelectStep.tsx:45 4-up at lg yields ~148px cards because the step is not full width R1 Netero Yes
CRF-4 P3 Author fixed (6930107) TemplateCard.tsx:74 Description drops to 12px while the "View details" link in the same block stays text-sm R1 Netero Yes
CRF-5 P3 Author fixed (8c6c85f) BaseInfraSelectStep.tsx:45 Neither changed step has a story, so a visual-only change ships with no visual coverage R1 Netero Yes
CRF-6 Nit Author fixed (6930107) BaseTemplateParametersStep.tsx:141 [&_li]:marker:text-content-secondary redundant next to [&_li]:text-content-secondary R1 Netero Yes
CRF-7 Note Open TemplateCard.tsx:64 (LEFT) Removed text-md was never a real utility; 9 other files still use the dead class R1 Netero Yes
CRF-8 Note Open BaseTemplateParametersStep.tsx:143 [&_code]:text-[length:inherit] also matches fenced-code code, no effect today R1 Netero Yes
CRF-9 Note Dropped by orchestrator (verification record, not a defect) BaseTemplateParametersStep.tsx:137 text-xs font-normal pairing beats the config's baked-in weight by source order R1 Netero No
CRF-10 Note Dropped by orchestrator (subsumed by CRF-1) BaseTemplateParametersStep.tsx:120 Fragment-to-div swap is layout-neutral R1 Netero No
CRF-11 P2 Open BaseInfraSelectStep.tsx:45 CRF-3's fix deleted the lg:grid-cols-3 tier, so 1024-1279px drops from 3-up to 2-up and diverges from ModuleSelectStep R3 Netero P2, Mafu-san P2, Kite P2, Meruem P2, Nami P2, Pariston P2, Hisoka P3, Zoro P3, Razor P3, Leorio P3 Yes
CRF-12 P2 Open BaseInfraSelectStep.stories.tsx:69 Loading story settles into ErrorAlert; baseline captures a transport-dependent 502/404 and races the fetch R3 Komugi P2, Bisky P2, Mafuuu P3, Chopper P3, Nami P3, Meruem P3, Razor P3, Netero P3 Yes
CRF-13 P2 Open BaseTemplateParametersStep.stories.tsx:34 Story fixture has no bool variable, so none of the four changed ConfigurationField slots render in any story R3 Bisky P2, Mafu-san P2, Nami P2, Chopper P3, Kite P3, Meruem P3, Razor P3 Yes
CRF-14 P2 Open ConfigurationField.tsx:138 The four description slots are hand-copies of FormField's block; the fix converged copies instead of removing them R3 Robin P2, Zoro P3 Yes
CRF-15 P3 Open ConfigurationField.tsx:129 Required asterisk is text-sm in the four slots and text-xs in FormField, so one form shows two asterisk sizes R3 Robin P3, Nami Nit Yes
CRF-16 P3 Open ConfigurationField.tsx:279 Only SwitchField keeps font-normal, so the now-uniform 12px descriptions render at weight 400 and 500 R3 Gon P3, Mafuuu P3, Hisoka Nit Yes
CRF-17 P3 Open BaseTemplateParametersStep.tsx:137 Root text-xs shrinks fenced code blocks from 14px to 12px; three shipped bases embed copy-paste blocks R3 Kite P3, Mafu-san P3, Razor P3 Yes
CRF-18 P3 Open BaseTemplateParametersStep.tsx:143 [&_code]:text-[length:inherit] sets font-size only, leaving 24px leading on inline code; the root fix belongs in Markdown.tsx:90 R3 Hisoka P3, Meruem P3, Zoro P3 Yes
CRF-19 P3 Open ModuleConfiguration.tsx:70 Secondary copy left at text-sm beside the now-12px descriptions, in ModuleConfiguration and ModuleSettingsStep R3 Hisoka P3, Pariston Nit Yes
CRF-20 P3 Open TemplateCard.tsx:81 Raw text-xs overrides only the font size of Link's lg variant, leaving an 18px icon beside 12px text R3 Mafuuu P3 Yes
CRF-21 P3 Open ModuleCard.tsx:65 TemplateCard/ModuleCard are one component twice; a third card keeps font-medium, so CRF-2's class is not closed R3 Robin P3, Zoro P3, Meruem P3 Yes
CRF-22 P3 Open BaseInfraSelectStep.stories.tsx:41 Story has no width wrapper and is not layout: "fullscreen", so the pixel viewport matrix does not apply and the grid tier is captured only by harness coincidence R3 Bisky P3 Yes
CRF-23 P3 Open BaseInfraSelectStep.stories.tsx:18 Fixture seeds 4 bases where production ships 9, the one count that makes 4-up look like a clean row R3 Pariston P3 Yes
CRF-24 P3 Open BaseInfraSelectStep.stories.tsx:85 getAllByText(...).length > 0 cannot fail and two fixtures share a description, so a card can lose its description and pass R3 Gon P3, Leorio P3, Chopper P3, Bisky Nit, Hisoka Nit, Mafu-san Nit, Meruem Nit, Nami Nit, Zoro Nit, Komugi Note, Razor Note Yes
CRF-25 P3 Open BaseInfraSelectStep.stories.tsx:78 Reverting all five production files leaves 6/6 story tests green, so the assertions constrain nothing this PR changed R3 Netero, Zoro P3, Leorio P3 Yes
CRF-26 P3 Open ConfigurationField.tsx:138 PR description names five slots under a count of four and credits an untouched TextField change R3 Mafu-san P2, Leorio P3, Pariston Nit, Chopper Nit, Gon Note, Hisoka Note, Kite Note, Meruem Note, Razor Note, Netero Note Yes
CRF-27 P4 Open BaseInfraSelectStep.tsx:36 No empty state for zero bases; BaseTemplateParametersStep discards error and isLoading (FE5) R3 Mafuuu P4, Nami P4, Mafu-san Note Yes
CRF-28 P4 Open BaseInfraSelectStep.tsx:18 detailsUrl defined four times in this directory, two verbatim pairs R3 Robin P4 Yes
CRF-29 Nit Open BaseInfraSelectStep.stories.tsx:7 makeBase drops the Partial<T> half of the makeModule precedent, so no fixture can set an icon and the Windows base carries os: "linux" R3 Kite Nit, Gon Nit, Meruem Nit, Zoro Nit, Nami Nit, Robin Note Yes
CRF-30 Nit Open BaseInfraSelectStep.stories.tsx:51 Query key retyped as a string literal instead of templateBuilderBases().queryKey (FE7) R3 Mafuuu Nit, Nami Nit, Robin Nit Yes
CRF-31 Nit Open BaseTemplateParametersStep.stories.tsx:28 Third local TemplateBuilderBase shape in the directory; FE9 asks for a shared Mock* constant R3 Mafuuu Nit, Nami Nit, Robin Nit, Zoro Nit Yes
CRF-32 Nit Open BaseInfraSelectStep.stories.tsx:75 Comment restates the three assertions below it (FE4) R3 Gon P2, Mafu-san Nit Yes
CRF-33 Nit Open BaseTemplateParametersStep.stories.tsx:70 Half the comment restates the assertions; only the trailing clause carries information R3 Gon P2 Yes
CRF-34 Nit Open BaseInfraSelectStep.stories.tsx:63 Selected asserts nothing, and no story anywhere exercises card selection or the Enter/Space handler R3 Kite Nit Yes
CRF-35 Nit Open ConfigurationField.tsx:279 ml-[44px] is an undocumented derived offset on a line this PR modifies R3 Gon Nit Yes
CRF-36 P3 Open BaseTemplateParametersStep.stories.tsx:6 Prerequisites fixture paraphrases the real docker README and drops the fenced block, the one construct with an undisclosed size change R3 Mafu-san P2, Kite P3, Meruem P3, Razor Nit Yes
CRF-37 Note Open BaseTemplateParametersStep.tsx:137 text-xs also drops paragraph leading from 24px to 16px, a 1.33 ratio on 12px prose R3 Hisoka Note Yes
CRF-38 P3 Dropped by orchestrator (premise disproved: pixel runs in CI) BaseInfraSelectStep.stories.tsx:72 Claim that no visual-snapshot service exists in the repo R3 Zoro No

Drop gate, CRF-38: keep-argument is that if no snapshot service existed, the PR description's "visual scale is covered" would be false outright and CRF-5's closure would be void, which would be a P2. Dropped because the premise is disproved: site/pixel.jsonc configures @coder/pixel-storybook and .github/workflows/ci.yaml:1080,1088 builds Storybook and runs pnpm pixel-storybook. Zoro grepped for chromatic only. The surviving half of the concern, that the assertions themselves constrain nothing, is posted as CRF-25.

Severity adjustments made during cross-check, each with the keep-argument written first:

  • CRF-26 (Mafu-san P2 to P3). Keep-argument for P2: the PR description is the durable record and it asserts an audit of a path that was never touched, so a reader must diff-check the sentence before trusting any of it. Adjusted to P3 because the cost is bounded at one diff check by one reader and no code is wrong; ten reviewers ranged Note to P2 with the mode at Note. P3 sits above the mode and below the outlier.
  • CRF-36 (Mafu-san P2 to P3). Keep-argument for P2: the snapshot baseline shows a document no user is ever served, so the coverage added for CRF-5 is coverage of synthetic input. Adjusted to P3 because half of Mafu-san's evidence is wrong: [&_a]:font-normal is a context line in the diff, not an addition, so the missing links exercise nothing this PR changed. The surviving half, the missing fenced block, is the subject of CRF-17.
  • CRF-32 and CRF-33 (Gon P2 to Nit). Keep-argument for P2: site/AGENTS.md FE4 is a project rule, both comments are new in this PR, and a comment that narrates its own assertions decays into a false promise, which is exactly what CRF-24 documents happening here. Adjusted to Nit because the consequence is reader noise on two lines, Mafu-san rated the same comment a Nit under the same rule, and CRF-24 already carries the false-promise consequence at P3.
  • CRF-22 (Nami P2 to P3, using Bisky's mechanism). Keep-argument for P2: the story added to close CRF-5 photographs geometry the product never renders, so the coverage is nominal. Adjusted to P3 because Nami's, Chopper's and Razor's shared premise, that pixel captures these stories at the configured 1900px desktop viewport, is disproved by @coder/pixel-storybook/build/crawler/storybook.js:137-155: the viewport axis applies only when parameters.layout === "fullscreen", and neither story sets it, so both are shot autofit at the browser context default. Bisky reached this independently and rated it P3. The geometry mismatch is real at any width; the magnitude is smaller than P2's evidence claimed.

Kite's praise on the getAllByText handling contradicts eleven reviewers who flagged the same lines. Not carried: getAllByText throws on zero matches, so .length > 0 after it is tautological, which is the specific mechanism Kite did not engage with. Posted as CRF-24.

Contested and acknowledged

CRF-5 (P3, BaseInfraSelectStep.tsx:45) - No story for either changed step

  • Finding: Neither BaseInfraSelectStep nor BaseTemplateParametersStep has a Storybook story, so a purely visual change ships with no visual regression coverage. AGENTS.md requires stories for component and page testing; ModuleSelectStep.stories.tsx is the precedent for mocking the query these steps depend on.
  • Author defense (R2, PRRC_kwDOGkVX1s7c7sAF): "Ack. Adding stories for both steps is out of scope for this typography pass; tracking as a follow-up." No ticket linked. DEVEX-724 is the PR's own Linear linkback, not a follow-up for story coverage.
  • Status: Resolved in R3 by adding both stories in 8c6c85f9f, not by deferral. The entry is kept as the record of the R2 deferral attempt. R3 panel findings CRF-12, CRF-13, CRF-22, CRF-23, CRF-24, CRF-25 and CRF-36 are about what those stories cover; they do not reopen CRF-5.

Round log

Round 1

Netero-only first pass. 2 P2, 3 P3, 1 Nit, 4 Notes raised; 2 Notes dropped. Panel gated pending P2 resolution. Reviewed against 0e1a9a9..41ea0f3.

Round 2

BLOCKED by churn guard. CRF-1 to CRF-4 and CRF-6 have targeted fixes in 6930107. CRF-5 deferred with no ticket, which blocks. CRF-7 and CRF-8 are Notes that requested no change. No reviewers spawned, no panel. Reviewed against 41ea0f3..6930107.

Round 3

Churn guard PROCEED (CRF-5 addressed in 8c6c85f). Netero found a P2, but rounds 1 and 2 both ended without panel review, which exceeds the skill's cap of two consecutive no-panel rounds, so the Netero gate was not applied a third time and the P2 travelled with the panel. Panel of 17: Bisky, Chopper, Ging-React, Ging-TS, Gon, Hisoka, Kite, Komugi, Leorio, Mafu-san, Mafuuu, Meruem, Nami, Pariston, Robin, Zoro, plus Razor as wildcard. Ging-TS and Ging-React reported no findings. New: 4 P2, 15 P3, 2 P4, 7 Nit, 1 Note; 1 P3 dropped. Reviewed against 6930107..8c6c85f (full diff 0e1a9a9..8c6c85f).

Orchestrator error recorded: in round 2 I accepted CRF-3 as fixed after verifying lg:grid-cols-4 became xl:grid-cols-4, without checking what the base SHA had at that line. The base had lg:grid-cols-3. Ten reviewers found it in round 3 (CRF-11). The lesson is to diff the fixed line against the base SHA, not against the previous round's head.

Law analysis

Not run. Effective additions 182, below the 1000 threshold.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First-pass review only. These are mechanical findings from a single reviewer; the full review panel has not looked at this PR yet and will review once the P2s are resolved.

The changes are small and self-consistent within the three touched files, tsc --noEmit and biome check are clean, and the Tailwind classes were verified against a probe build rather than assumed (notably that text-xs/text-sm bake in fontWeight: 500, so the explicit font-bold/font-normal do win by source order). The two P2s are both about reach: one change is narrower than it looks, the other is wider than the PR applied it.

2 P2, 3 P3, 1 Nit, 2 Notes.

On the PR description question, swapping to named text-sm-bold/text-xs-body utilities is a separate decision from these findings; the mapping to text-sm font-bold / text-xs font-normal is correct for the current config.

From the reviewer, on the class this PR removed: "So the old h3 had no font-size class at all and rendered at the inherited size; this PR is the first time that heading gets an explicit size."

🤖 This review was automatically generated with Coder Agents.

Comment thread site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx Outdated
Comment thread site/src/pages/TemplateBuilder/TemplateCard.tsx
Comment thread site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx Outdated
Comment thread site/src/pages/TemplateBuilder/TemplateCard.tsx
Comment thread site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx Outdated
Comment thread site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx Outdated
</div>

<div>
<h3 id={nameId} className="text-md font-semibold text-content-primary">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-7] The removed text-md was never a real utility. (Netero)

The probe build emitted .text-sm, .text-xs, .font-bold, .font-normal, and both arbitrary variants, but no .text-md. tailwind.config.js:26-31 extends fontSize with 2xs, xs, sm, base, 3xl only, and Tailwind's default scale has no md key. Nine other files still use the dead class, including ModuleCard.tsx:65, DynamicParameter.tsx:400, and four DeploymentSettingsPage charts.

No action needed in this PR. Worth knowing because it changes what this line does: the title was previously unsized and inherited, so text-sm is a new explicit size, not a step down from 16px. The nine remaining uses are a separate cleanup.

🤖

"[&_p]:mb-3 [&_p]:text-content-secondary",
"[&_li]:text-content-secondary [&_li]:marker:text-content-secondary",
"[&_a]:font-normal",
"[&_code]:text-[length:inherit]",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-8] [&_code]:text-[length:inherit] also matches fenced-code code elements, with no visual effect today. (Netero)

The probe build confirms it compiles to .[&_code]:text-[length:inherit] code { font-size: inherit }, one specificity step above the inline-code text-sm at site/src/components/Markdown/Markdown.tsx:89, so the inline-code override works as intended. Prism blocks (Markdown.tsx:77-86) render code without a font-size class, so forcing inherit there changes nothing today, but the selector is broader than the stated intent.

No action needed. Flagged so that if a font size is ever added to Prism code blocks, this selector is where it gets overridden.

🤖

@linear-code

linear-code Bot commented Aug 3, 2026

Copy link
Copy Markdown

DEVEX-724

@tracyjohnsonux
tracyjohnsonux requested a review from chrifro August 3, 2026 18:15
- ConfigurationField: use text-xs for field descriptions consistently
  (fixes source of body-copy size mismatch instead of scoped override)
- ModuleCard: apply same typography updates as TemplateCard
- TemplateCard: shrink View details link to text-xs to match description
- BaseInfraSelectStep: gate 4-up grid at xl instead of lg
- BaseTemplateParametersStep: drop scoped wrapper and redundant marker: variant
@tracyjohnsonux

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further review is blocked on one unaddressed finding.

Five of the six actionable findings have targeted fixes in 6930107d3, and each one was fixed at the source rather than papered over: the wrapper selector is gone and the four ConfigurationField descriptions are now text-xs (CRF-1), ModuleCard picked up the same title, description, and link scale as TemplateCard (CRF-2, CRF-4), 4-up moved from lg to xl (CRF-3), and the redundant marker: variant is gone (CRF-6). I verified each class string in the diff.

Unaddressed:

  • CRF-5 (P3, no Storybook story for either changed step). The reply says it is out of scope and "tracking as a follow-up," but no ticket is linked. DEVEX-724 is this PR's own Linear linkback, not a follow-up for story coverage. This needs a human decision: link a ticket that covers story coverage for BaseInfraSelectStep and BaseTemplateParametersStep, or state explicitly that the gap is accepted. An acknowledgement without either leaves nothing behind once this PR merges, and a visual-only change is exactly the class of change a story is the only thing that catches.

CRF-7 and CRF-8 are Notes that asked for no change; no response needed.

One thing to fix while you are here: the PR description still says "Field description size scoped to this step via child selector; shared ConfigurationField is unchanged." 6930107d3 does the opposite, and correctly so, but that also means the change now reaches every ConfigurationField consumer, including the module settings step. Worth saying in the description, since it widens what a reviewer should look at.

The review panel has still not looked at this PR. Once CRF-5 has a ticket or an explicit human decision, the next round runs the full panel against the current head.

🤖 This review was automatically generated with Coder Agents.

@tracyjohnsonux

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First panel round. Seventeen reviewers on the current head; two (Ging-TS, Ging-React) found nothing in their domains and said so.

The typography half of this PR holds up under measurement. Reviewers probed the running components rather than reading class names, and the intended scale is what renders: card title 14px/700, description 12px/400, View details 12px/400, list items 12px with markers matching by inheritance, inline code following body size. CRF-1's fix went to the source instead of a descendant selector, CRF-2's sibling was brought along, and CRF-5 was closed by writing the stories rather than by promising a ticket. Pariston went looking for a case against the typography work and reported he could not build one.

The findings cluster in two places. The grid fix removed a tier nobody asked it to remove, and the stories added to cover this change do not render most of what changed.

My own error, since ten reviewers found it and I did not: in round 2 I verified CRF-3's fix by checking that lg:grid-cols-4 became xl:grid-cols-4 and called it fixed. I never checked what the base SHA had on that line. It had lg:grid-cols-3, so the fix deleted the 3-up tier and 1024-1279px now renders 2-up, looser than before this PR started. That is CRF-11, and it is the one finding I would fix before anything else.

One premise needs correcting for anyone reading the review threads: four reviewers assumed the pixel snapshots capture these stories at the configured 1900px desktop viewport. They do not. @coder/pixel-storybook/build/crawler/storybook.js:137-155 applies the viewport axis only to stories with parameters.layout: "fullscreen", and neither new story sets it, so both are shot autofit at the browser context default. The xl:grid-cols-4 tier makes it into the baseline only because that default sits on the xl boundary, which is coincidence rather than coverage. A separate claim that this repo has no snapshot service at all is wrong and was dropped; pixel.jsonc plus ci.yaml:1080,1088 run it.

4 P2, 15 P3, 2 P4, 7 Nit, 1 Note.

Two process notes. The commit 6930107d3 fix(site): address review feedback carries five distinct behavior changes and names none of them; whoever bisects a typography regression in this directory lands on it and has to read the diff. And the PR description has drifted from the diff in three places (CRF-11, CRF-26, and the "visual scale is covered" claim in CRF-25), which matters because it is the text that survives the squash.

CRF-7 is unchanged: the dead text-md class is still on nine files, though none of them are in TemplateBuilder any more. CRF-8's conclusion was independently re-verified by measurement and holds.

From Bisky, on the Loading story: "a story that cannot express its own name should not exist."


site/src/pages/TemplateBuilder/ConfigurationField.tsx:129

P3 [CRF-15] The required asterisk is text-sm in these four slots and text-xs in FormField, so one form shows two asterisk sizes. (Robin P3, Nami Nit)

FormField.tsx:40 renders the asterisk as text-xs font-bold text-content-destructive. ConfigurationField.tsx:129, :179, :264, and :304 render it as text-sm font-bold text-content-destructive. A TextField goes through FormField (:100-107); a SelectField, RadioField, SwitchField, or SwitchGroupField does not. A required text field and a required dropdown sitting in the same ConfigurationFieldContainer grid (:340) therefore show two asterisk sizes.

Not introduced here, but this PR walked all four of these blocks to unify description size and left the marker beside them behind, so after the change descriptions agree and the asterisks do not. Four lines, same edit as the descriptions.

🤖

site/src/pages/TemplateBuilder/ModuleConfiguration.tsx:70

P3 [CRF-19] Secondary copy sitting beside the descriptions this PR shrank is still text-sm, so one panel now shows two sizes of secondary text. (Hisoka P3, Pariston Nit)

ModuleConfiguration.tsx:70 renders "No configuration required." at text-sm text-content-secondary, in the same <section>, four pixels of margin away from descriptions that are now text-xs. Same root cause, one more instance: ModuleSettingsStep.tsx:165 renders the sensitive-variable notice at text-sm text-content-secondary directly under a ModuleConfiguration.

The PR deliberately reaches into the module settings step through the shared component, so these two are the remaining instances of the class it is fixing. Hisoka also notes the coverage asymmetry: ModuleConfiguration.stories.tsx exists so its mismatch lands in a snapshot, while ModuleSettingsStep has no story and its instance will not.

🤖

site/src/pages/TemplateBuilder/ConfigurationField.tsx:104

P3 [CRF-26] The PR description lists five names under "all four ConfigurationField description slots" and credits this PR with a TextField change it does not make. (Mafu-san P2, Leorio P3, and eight more at Nit or Note)

Ten of seventeen reviewers stopped on this sentence, which is itself the cost.

A reviewer reading that sentence believes the text-input description path was audited and changed here. It was not touched, so the sentence has to be checked against the diff before it can be trusted, which is the cost this finding is about.

Leorio's replacement:

All four description slots that hardcoded text-sm (SelectField, RadioField, SwitchField, SwitchGroupField) now use text-xs. The TextField path already rendered text-xs through FormField and is unchanged.

While you are in that paragraph, the claim that the change "reaches every ConfigurationField consumer" also overstates what a user sees. Four reviewers grepped for it: no production code constructs select, radio or switch-group fields, so in shipped UI the change resolves to switch descriptions in the base-parameters and module-settings steps. Editing all four for internal consistency is right; the description just reads wider than the diff is.

🤖

site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx:36

P4 [CRF-27] Neither of this step's non-happy paths has handling, and the sibling step discards error and isLoading entirely. (Mafuuu P4, Nami P4, Mafu-san Note)

const bases = data?.bases ?? [] feeds the grid directly, so a successful response with zero bases produces an empty role="radiogroup" and a Continue button that stays disabled forever with nothing on screen explaining why.

const { data } = useQuery(...) discards error and isLoading. On failure base is undefined, so line 127 falls back to the string "Base Template", line 128 to an empty description, variables to [], and prerequisites to "". The user reaches step 2 of a wizard, sees an empty card with a placeholder name, and is told nothing.

site/AGENTS.md FE5 requires loading, error and empty handling. Both predate this PR and neither is in its diff. Recorded rather than assumed as a follow-up, because this PR is the one that adds story files next to both gaps and covers neither.

🤖

site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx:18

P4 [CRF-28] detailsUrl is defined four times in this directory, as two verbatim pairs. (Robin P4)

BaseInfraSelectStep.tsx:19          `https://registry.coder.com/templates/${baseId}`
BaseTemplateParametersStep.tsx:28   `https://registry.coder.com/templates/${baseId}`   (verbatim duplicate)
ModuleSelectStep.tsx:46             `https://registry.coder.com/modules/${moduleId}`
ModuleSettingsStep.tsx:75           `https://registry.coder.com/modules/${moduleId}`   (verbatim duplicate)

Untouched by this PR and outside its scope; recorded because both files in the first pair are in the diff. One helper taking a "templates" | "modules" kind retires all four.

🤖

🤖 This review was automatically generated with Coder Agents.

</TemplateBuilderSubtitle>

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 [CRF-11] CRF-3's fix replaced the lg tier instead of supplementing it, so 1024-1279px drops from 3-up to 2-up and the step now disagrees with the next step in the same wizard. (Netero P2, Mafu-san P2, Kite P2, Meruem P2, Nami P2, Pariston P2, Hisoka P3, Zoro P3, Razor P3, Leorio P3)

Base was grid-cols-1 sm:grid-cols-2 lg:grid-cols-3. Head is grid-cols-1 sm:grid-cols-2 xl:grid-cols-4. Nothing matches between sm (640px) and xl (1280px).

One pixel of viewport swings a card from 440px to 212px. 1024-1279 covers the common laptop widths, and the PR's stated goal is to tighten this grid, so the range it loosens most is the one users see most.

Viewport Columns Card width
1024 (pre-PR) 3 203px
1024 (HEAD) 2 312px
1279 (HEAD) 2 440px
1280 (HEAD) 4 212px

Pariston counted what the step actually renders: coderd/templatebuilder/bases.go:240 enumerates nine base directories and all nine resolve to examples, so ceil(9/3) and ceil(9/4) are both 3. The new 4-up tier removes zero rows at any width, while the lg band goes from 3 rows to 5.

ModuleSelectStep.tsx:247 still uses the exact string this line abandoned, grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4, and both grids render into the same container in the same wizard. Between 1024px and 1279px the base step shows 2 columns and the module step shows 3.

That is CRF-2's divergence class applied to layout one round after CRF-2 was fixed for typography. Kite states the tradeoff honestly: restoring the tier puts 203px cards back at 1024px, which is 9px below the 212px this PR already accepts at xl, so it is the status quo rather than a new regression. Fix is one token, sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4, and decide whether ModuleSelectStep gets the matching xl tier. Meruem's structural version, one exported CARD_GRID_CLASSES consumed by both grids, is what stops the two from drifting again.

This is on me as much as on the fix: I verified CRF-3's closure against the previous round's head instead of the base SHA and missed it.

🤖

},
};

export const Loading: Story = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 [CRF-12] The story named Loading settles into an error alert, and what it captures depends on the surrounding server and on response latency. (Komugi P2, Bisky P2, Mafuuu P3, Chopper P3, Nami P3, Meruem P3, Razor P3, Netero P3)

Six reviewers verified this with throwaway probe stories rather than inferring it. Komugi sampled the canvas twice:

  • t0: <div role="status" data-testid="loader"> (the Loader).
  • t1: <div role="alert"> with Request failed with status code 502.

parameters: { queries: [] } only removes the seed that meta installs. It does not stop the query. [...] The probe also proves the override direction: meta seeded { bases: [] } (which renders an empty grid, not a loader), and the story still fetched.

The value is unpinned in three directions: 502 under pnpm test:storybook from the storybook-test-setup middleware, 404 under the static build pixel serves, and whatever CODER_HOST returns under pnpm storybook. Bisky adds that the captured pixels contain an unmasked Axios stack trace and status code, so the baseline differs between harnesses and any change produces a diff nobody can attribute.

The repo already has the fix, and Komugi found it: EditOAuth2AppPageView.stories.tsx:70-77 pins the pending state with beforeEach and a never-resolving mock.

export const Loading: Story = {
	parameters: { queries: [] },
	beforeEach: () => {
		spyOn(API, "getTemplateBuilderBases").mockReturnValue(new Promise(() => {}));
	},
};

ModuleSelectStep.stories.tsx:80 has the identical defect and is where this was copied from. Fixing only the new file leaves the pattern in place for the next person to copy.

🤖

icon: "",
os: "linux",
prerequisites,
variables: [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 [CRF-13] The story added to cover this step renders none of the four ConfigurationField description slots the PR changed. (Bisky P2, Mafu-san P2, Nami P2, Chopper P3, Kite P3, Meruem P3, Razor P3)

The base fixture has exactly one variable, type: "string". variableToField (BaseTemplateParametersStep.tsx:56) maps everything non-bool to type: "text", and TextField (ConfigurationField.tsx:92) delegates its description to FormField. FormField.tsx:47 renders text-xs, and git show 0e1a9a9f0:site/src/components/FormField/FormField.tsx shows text-xs there before this PR too. So the one description this story renders was already the target size and is untouched by the diff.

Seven reviewers independently grepped every story that builds a ConfigurationFieldDefinition. TemplateConfiguration.stories.tsx and ModuleConfiguration.stories.tsx between them render select, radio, switch and switch-group, and not one passes a field-level description, so all four changed slots render nowhere in the repo.

The switch path is live, not theoretical. Meruem traced it to shipped data:

coderd/templatebuilder/bases/kubernetes/base.json defines use_kubeconfig with "type": "bool" and a description, so selecting the Kubernetes base renders line 279.

Two cheap edits close it: add a bool variable with a non-empty description to this fixture, and add description to one radio, one switch-group and one select field in ModuleConfiguration.stories.tsx, whose Default story already renders all three in one snapshot.

🤖

</Label>
{description && (
<div id={descriptionId} className="text-sm text-content-secondary">
<div id={descriptionId} className="text-xs text-content-secondary">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 [CRF-14] The description slot this PR resizes in four places already exists as one component, FormField, which was already text-xs. (Robin P2, Zoro P3)

FormField.tsx:46-50 renders exactly this block [...] It already wires descriptionId into aria-describedby (FormField.tsx:25-30, :59), and it was already at text-xs before this PR. That is why TextField needed no edit [...] So the change converged the copies on the value FormField already held, not on FormField itself. The copies remain, and they are not decorative: they have already drifted.

Re-raise against CRF-1 with new evidence: the fix moved the resize to the right layer and landed it as four duplicates of a block the codebase owns in one place. ConfigurationField.tsx:138, :188 and :313 are now byte-identical to each other and to FormField's; :279 is the same block plus ml-[44px] font-normal. The measurable cost of the duplication is CRF-15 and CRF-16, both of which are drift between these five copies.

SwitchRow at ConfigurationField.tsx:224-242 is this file's own precedent for extracting a shared sub-component. A FieldDescription sibling, importable by FormField too, collapses five slots to one. I am not asking for a refactor as a condition of this PR, but the next scale or color change to field descriptions is a five-site edit with the same odds of missing one, and this PR is the second lockstep edit in three rounds.

🤖

<div
id={descriptionId}
className="ml-[44px] text-sm font-normal text-content-secondary"
className="ml-[44px] text-xs font-normal text-content-secondary"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [CRF-16] Only SwitchField keeps font-normal, so the now-uniform 12px descriptions render at two weights. (Gon P3, Mafuuu P3, Hisoka Nit)

tailwind.config.js:28 bakes fontWeight: "500" into text-xs, so text-xs text-content-secondary at lines 138, 188, and 313 renders at weight 500, while SwitchField at line 279 renders at 400 because it keeps font-normal.

Mafuuu names why this PR is where it becomes visible:

Before the PR the switch description differed in both size and weight, so the weight gap was invisible. Now that sizes match, weight is the only remaining difference and it shows up side by side: in the base-parameters step a string variable renders a FormField description at 500 and a bool variable renders this one at 400 in the same column.

Gon adds that the card descriptions this PR also resized use font-normal (TemplateCard.tsx:74, ModuleCard.tsx:74), so the weight question is already answered elsewhere in the same diff. Pick one weight for description text and apply it to all four slots. This is a different claim from CRF-9, which was about one pairing being correct by source order, not about the four slots disagreeing with each other.

🤖

},
};

// Verifies that every base is rendered as a radio option with the expected

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-32] The comment restates the three assertions directly below it. (Gon P2, Mafu-san Nit)

findAllByRole("radio"), toHaveLength(bases.length), and the getByText loop already say "every base renders as a radio with its title and description." The comment adds no invariant, no reason, no trap. Delete it.

site/AGENTS.md FE4 forbids comments that restate assertions. Gon rated this a finding; I am posting it as a Nit because the standalone consequence is reader noise, and the part that has real cost, the comment promising description coverage that does not exist, is CRF-24. Worth contrasting with the precedent this file copied: ModuleSelectStep.stories.tsx:86 explains the fixture arithmetic instead of narrating its own calls.

🤖


export const Default: Story = {};

// Verifies that the field description and README body copy are rendered so

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-33] Half this comment restates the assertions; the trailing clause is the part worth keeping. (Gon P2)

"the field description and README body copy are rendered" is what findByText(/Container image for workspaces/) and the two findByRole("heading") calls show. The part worth keeping is why presence assertions cover a typography change.

Gon's rewrite: // The step's typography rules only render when body copy and headings exist. Leorio independently called this comment's framing the honest one of the two, so it is the sentence to keep, just shorter.

🤖


export const Default: Story = {};

export const Selected: Story = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-34] Selected sets state and asserts nothing, and no story anywhere exercises selection or the keyboard handler. (Kite)

Selected sets selectedBaseId: "docker" and stops. Nothing asserts aria-checked="true" on the docker card, so the story would pass unchanged if TemplateCard's selected prop stopped being wired through. onSelectBase is fn() in args and no story clicks a card, and TemplateCard's Enter/Space handler (TemplateCard.tsx:41-46) is never invoked.

Class rather than instance: TemplateCard.stories.tsx has six stories and zero play functions, so that keyboard handler has no coverage in the repo at all. A play that tabs to a card, presses Enter, and asserts onSelectBase was called with the right base closes both, and AGENTS.md asks for exactly that.

🤖

<div
id={descriptionId}
className="ml-[44px] text-sm font-normal text-content-secondary"
className="ml-[44px] text-xs font-normal text-content-secondary"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-35] ml-[44px] is a derived offset with nothing recording the derivation, on a line this PR modifies. (Gon)

44px is Switch's default width (w-9, 36px, Switch.tsx:21) plus the SwitchRow gap (gap-2, 8px, line 232), which aligns the description with the switch label. Nothing states that, so a change to either value silently breaks the alignment.

One line of comment above the div naming the two values it is derived from.

🤖

<MemoizedMarkdown
className={cn(
"text-sm font-normal",
"text-xs font-normal",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-37] text-xs also drops paragraph leading from 24px to 16px, a 1.33 ratio on 12px prose. (Hisoka)

The utility changes two things at once, and the README is the one place in this PR with real multi-line prose. 1.33 is tight for body copy; the rest of the class list is explicit about spacing ([&_p]:mb-3, [&_h3]:mt-2 [&_h3]:mb-1), so if the leading is deliberate it would be worth writing it down as leading-normal or similar rather than inheriting whatever the size utility happens to bake.

No change requested. Flagged because it is the same shape as CRF-17 and CRF-18: a size utility here sets three properties, and only one of them was the intent. The pixel baseline will show whether it reads well.

🤖

…odule screens

- Restore the lg:grid-cols-3 tier on base infra and module select grids so
  1024-1279px keeps its previous density; xl:grid-cols-4 is now additive.
- Match module screens to the base parameter typography scale:
  ModuleConfiguration 'No configuration required' notice, ModuleSettingsStep
  sensitive-variable notice, and ModuleSelectStep empty-state copy all move
  to text-xs.
- Bring BaseTemplateCard heading in TemplateCustomizationsStep in line with
  TemplateCard/ModuleCard (text-sm font-bold).
- Remove the two new step stories; step-level story coverage will be handled
  as a separate follow-up.
@tracyjohnsonux

Copy link
Copy Markdown
Contributor Author

Scope reset after discussion with the review app. This PR is a small design pass; anything beyond that is being tracked separately with the engineering team.

Applied:

  • CRF-11 — restored the lg:grid-cols-3 tier so 1024-1279px is not looser than base. xl:grid-cols-4 is additive.
  • Module screens consistency — same grid pattern on ModuleSelectStep; ModuleConfiguration, ModuleSettingsStep, and TemplateCustomizationsStep.BaseTemplateCard typography aligned with the base scale.

Reverted:

  • The two new step stories (BaseInfraSelectStep.stories.tsx, BaseTemplateParametersStep.stories.tsx) are removed. Step-level story coverage is a pre-existing gap and will be handled separately (CRF-5, CRF-12, CRF-13, CRF-22, CRF-23, CRF-24, CRF-25, CRF-33, CRF-34).

Deferred as follow-ups (out of scope for this design pass):

  • CRF-14 — extracting a shared FieldDescription component for the four duplicated slots in ConfigurationField.
  • CRF-15 — required-asterisk size drift between FormField and ConfigurationField.
  • CRF-16SwitchField description weight (400 vs 500) drift.
  • CRF-21TemplateCard/ModuleCard duplication.
  • CRF-26 — PR description clarifications (addressed in the updated description).
  • CRF-27 — loading and error handling on BaseInfraSelectStep and BaseTemplateParametersStep.
  • CRF-28 — deduplicating detailsUrl across four call sites.
  • CRF-7text-md cleanup across the codebase.
  • Remaining nits (CRF-29 through CRF-37).

On behalf of @tracyjohnsonux; the deferred items are being coordinated with the engineering team.

@jeremyruppel jeremyruppel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried this out in my workspace and it looks great. imo no additional stories needed as this is mostly styling

@tracyjohnsonux
tracyjohnsonux enabled auto-merge (squash) August 4, 2026 16:24
@tracyjohnsonux
tracyjohnsonux merged commit 5f3b875 into main Aug 4, 2026
26 checks passed
@tracyjohnsonux
tracyjohnsonux deleted the tj/template-builder-base-infra-grid branch August 4, 2026 16:29
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants