Status: fixed
What to fix: In the deck editor card browser, rows with the not impl (and/or banned) badge have their fixed-width columns shifted relative to normal rows, and the cost column can overflow on its own. Two causes, one symptom ("columns misaligned"):
1. Badges are content-sized elements injected mid-flex-row. Rows are div.browser-card-row with display: flex; gap: 8px (static/editor.html:237-244); the only flexible column is the title (flex: 1, editor.html:246-251). .unimpl-badge (static/editor.js:535-541, CSS editor.html:314-323) and .banned-badge (editor.js:543-551, CSS editor.html:324-332) have no width and no flex-shrink: 0, so on affected rows the title absorbs ~60px less and every fixed column (type 80px / cost 28px / faction 100px) sits at a different x-offset than on unaffected rows.
2. Cost column overflows. editor.js:520 renders card.cost + '💳' inside a hard width: 28px (editor.html:258-263); the emoji plus digits frequently exceeds 28px and collides with the faction column, independent of the badges.
Fix direction (agreed): give every row identical column geometry — a fixed-width status slot that is always rendered (empty/visibility: hidden when not applicable) holding the unimpl/banned badges, with flex-shrink: 0; and make the cost column wide enough for its content (or drop/resize the emoji). Apply the same discipline to the deck-side rows (makeDeckRow, editor.js:395-452, .deck-card-row editor.html:151-165), which carry the banned badge. Check the mobile breakpoint too: static/style.css:805-874 hides type/faction but keeps badges.
Definition of done: unimplemented/banned rows pixel-align with normal rows in the browser list and deck list, desktop and mobile widths; cost column never overflows for 1–2 digit costs.
Fixed. Both rows now have one flexible column (the title) and nothing else:
.browser-card-status / .deck-card-status are always rendered and hold the
not impl / banned badges, every fixed column is flex-shrink: 0, and the
titles get min-width: 0 + ellipsis so a long card name yields instead of
squeezing type/cost/faction. The cost column went 28px → 40px with nowrap
(two digits plus the credit glyph is ~30px at 12px type).
One judgement call worth recording: the browser status column is sized for both
badges at once (104px), which is free on desktop but a third of a 360px row. At
the breakpoint it narrows to 52px and drops not impl — banned is the badge
that explains why the + button refuses — and sheetMeta now appends
"not implemented" so the fact is still one tap away. That is the same trade the
breakpoint already makes for the type and faction columns.
Follow-up (review). The first pass left one column conditional: the deck
row's influence slot was appended only when the card actually cost influence,
and it was min-width: 20px holding ●.repeat(inf × qty) — so its presence
*and* its width varied, and the −/+ buttons still moved between rows. Influence
is now a column like the others: always appended, width: 30px, empty for
in-faction cards. The pips became a count plus a single pip (3●), because
fifteen dots — three copies of a 5-influence card — is ~165px, a width no fixed
column can hold; that is the same number-plus-glyph shape the browser's cost
column uses, and the N influence tooltip is unchanged.
Guards: tests/test_static_pages.py, section "Row column geometry (ticket 48)"
— the badges are appended to the status column and never to the row, every
fixed column declares a width and flex-shrink: 0, the titles declare
min-width: 0, the cost column is at least 2.5em wide, and the breakpoint
keeps a fixed (narrower) status column. There is no browser in the suite, so
these are source-level guards on the geometry contract, not pixel assertions.