Four things looked wrong after the last deploy. Two were the same bug, and it is not design drift — it is deleted CSS.
The failure
Removing a rule from a scoped stylesheet leaves its modifiers behind. The selector still exists, so nothing reads as unused, and the element renders with no base styling at all.
class
what survived
what it looked like
.btn-workspace
:hover
the Workspace link as raw browser blue, underlined
.milestone-header
.clickable, :hover
children are written for a flex ROW — .ms-name { flex: 1 }, the progress track, .ms-pct — so without the parent they stacked, and a one-line milestone became five
.milestone-group
nothing
no card around each milestone
.ds-header
its h1 descendant
no spacing of its own
.system-card
--archived, :hover .system-actions
same flex-row collapse; every child still declares flex: 1 / flex-shrink: 0
.systems-list
nothing
the systems list rendering with browser bullets and indent
.graph-embed
its :deep() descendant
the panel is a flex column whose header is flex-shrink: 0; without this the height: 100% below resolves against auto and does nothing — a comment specifying a rule that could not work
vue-tsc --noEmit is the frontend's entire check, and a dead style typechecks perfectly.
The check
scripts/check_dangling_styles.py, in the lint lane, stdlib only, knows no class name or convention (rule #115). It reports an element whose every static class has no base rule anywhere while at least one carries modifier rules.
It was wrong about four on its first run — .pane.empty and td.numare base rules for the element carrying those classes. It now records a compound's whole class set. A check with false reports is one that gets skimmed.
Seven reports remain, all wrappers that are bare on purpose, each now carrying a comment saying so — which is what makes a new entry a signal rather than noise.
Known blind spot, recorded in the script: a class with no rule anywhere is invisible, being indistinguishable from a semantic-only hook. .systems-list was found by reading the file beside a class that was half-styled.
Header overlap
The nav pill bar was position: absolute; left: 50%, so it never participated in layout — out of room it overlapped the brand and utility cluster instead of pushing them. A sixth link reached that at ~1270px, an ordinary window.
Now 1fr auto 1fr. A 1fr track has an auto minimum, so neither side can be squeezed under its content and the two stay equal to each other — which is what keeps the bar centred in the viewport rather than in the leftover space. Overflow becomes the header growing, not two things sharing pixels.
Token preview
The checkerboard was on the whole specimen stage, so every swatch sat in a frame of checks and the pattern became the loudest thing on the page. It is now a background layer under the colour: an opaque value hides it, a 15% tint shows exactly as much as it should. Text specimens lose the box entirely, and name/value/purpose are one line each with the full text on hover — they wrapped freely before, so a card was two to five lines tall depending on how long its color-mix() happened to be.
The design-token check caught --tp-fill on the commit that introduced it: set inline only, so nothing in any stylesheet declared it. Declaring it on .tp-swatch is the real fix — a token resolving to nothing now renders as bare checks rather than an invalid gradient.
Also
.btn-cta joins the shared button family. The gradient-and-glow brand moment was living in one view's scoped block, which is what made it deletable. The project header actions are now one family at one size instead of four stylings.
The shared button shape gained inline-flex + gap, so a button carrying an icon centres it without each caller rebuilding the row.
A project's goal is a textarea. It rendered as "Maintain Scribe as the reliabl" in a sidebar with room to spare.
Not fixed, and visible
The delete-confirmation modals still use a separate bespoke .modal-btn family. That is #2279's territory (DRY pass 4 lists ~12 usages) and will read as inconsistent beside the corrected header row.
Verification
CI green on a6d6550 — typecheck, lint + design-token check + dangling-style check, unit, integration, plugin hooks, image build. No migration.
Four things looked wrong after the last deploy. Two were the same bug, and it is not design drift — it is deleted CSS.
## The failure
Removing a rule from a scoped stylesheet leaves its modifiers behind. The selector still exists, so nothing reads as unused, and the element renders with **no base styling at all**.
| class | what survived | what it looked like |
|---|---|---|
| `.btn-workspace` | `:hover` | the Workspace link as **raw browser blue, underlined** |
| `.milestone-header` | `.clickable`, `:hover` | children are written for a flex ROW — `.ms-name { flex: 1 }`, the progress track, `.ms-pct` — so without the parent they **stacked**, and a one-line milestone became five |
| `.milestone-group` | nothing | no card around each milestone |
| `.ds-header` | its `h1` descendant | no spacing of its own |
| `.system-card` | `--archived`, `:hover .system-actions` | same flex-row collapse; every child still declares `flex: 1` / `flex-shrink: 0` |
| `.systems-list` | nothing | the systems list rendering with **browser bullets and indent** |
| `.graph-embed` | its `:deep()` descendant | the panel is a flex column whose header is `flex-shrink: 0`; without this the `height: 100%` below resolves against `auto` and does nothing — a comment specifying a rule that could not work |
`vue-tsc --noEmit` is the frontend's entire check, and **a dead style typechecks perfectly**.
## The check
`scripts/check_dangling_styles.py`, in the lint lane, stdlib only, knows no class name or convention (rule #115). It reports an element whose every static class has no base rule anywhere while at least one carries modifier rules.
It was wrong about four on its first run — `.pane.empty` and `td.num` **are** base rules for the element carrying those classes. It now records a compound's whole class *set*. A check with false reports is one that gets skimmed.
Seven reports remain, all wrappers that are bare on purpose, each now carrying a comment saying so — which is what makes a *new* entry a signal rather than noise.
Known blind spot, recorded in the script: a class with **no** rule anywhere is invisible, being indistinguishable from a semantic-only hook. `.systems-list` was found by reading the file beside a class that was half-styled.
## Header overlap
The nav pill bar was `position: absolute; left: 50%`, so it never participated in layout — out of room it **overlapped** the brand and utility cluster instead of pushing them. A sixth link reached that at ~1270px, an ordinary window.
Now `1fr auto 1fr`. A `1fr` track has an auto minimum, so neither side can be squeezed under its content and the two stay equal to each other — which is what keeps the bar centred in the *viewport* rather than in the leftover space. Overflow becomes the header growing, not two things sharing pixels.
## Token preview
The checkerboard was on the whole specimen stage, so every swatch sat in a frame of checks and the pattern became the loudest thing on the page. It is now a background layer **under** the colour: an opaque value hides it, a 15% tint shows exactly as much as it should. Text specimens lose the box entirely, and name/value/purpose are one line each with the full text on hover — they wrapped freely before, so a card was two to five lines tall depending on how long its `color-mix()` happened to be.
The design-token check caught `--tp-fill` on the commit that introduced it: set inline only, so nothing in any stylesheet declared it. Declaring it on `.tp-swatch` is the real fix — a token resolving to nothing now renders as bare checks rather than an invalid gradient.
## Also
- `.btn-cta` joins the shared button family. The gradient-and-glow brand moment was living in one view's scoped block, which is what made it deletable. The project header actions are now one family at one size instead of four stylings.
- The shared button shape gained `inline-flex` + `gap`, so a button carrying an icon centres it without each caller rebuilding the row.
- A project's goal is a textarea. It rendered as "Maintain Scribe as the reliabl" in a sidebar with room to spare.
## Not fixed, and visible
The delete-confirmation modals still use a separate bespoke `.modal-btn` family. That is #2279's territory (DRY pass 4 lists ~12 usages) and will read as inconsistent beside the corrected header row.
## Verification
CI green on `a6d6550` — typecheck, lint + design-token check + dangling-style check, unit, integration, plugin hooks, image build. No migration.
Operator reported four things looking wrong. Two were the same bug, and it is
not a design drift — it is deleted CSS.
Removing a rule from a scoped stylesheet leaves its modifiers behind. The
selector still exists, so nothing reads as unused, and the element renders with
no base styling at all:
.btn-workspace base gone, :hover survived — the Workspace link rendered
as raw browser blue, underlined
.milestone-header base gone, .clickable and :hover survived. Every child is
written for a flex ROW (.ms-name { flex: 1 }, the progress
track, .ms-pct), so without the parent they stacked and a
one-line milestone became five. That is the "projects
section uses space poorly" — a deletion, not a redesign.
.milestone-group no rule at all; the card around each milestone
.ds-header only its h1 descendant survived
vue-tsc cannot see any of it. A dead style typechecks perfectly.
scripts/check_dangling_styles.py finds the shape: an element whose every static
class has no base rule anywhere, while at least one carries modifier rules. It
reports 11 more. Reported and not gated, because a genuinely bare wrapper is
legitimate — the signal is the count growing. Runs in the lint lane, stdlib
only, and knows no class name or convention (rule #115).
Also from the same report:
- The header pill bar was `position: absolute; left: 50%`, so it did not
participate in layout: out of room, it OVERLAPPED the brand and the utility
cluster instead of pushing them. A sixth link reached that at ~1270px, an
ordinary window. Now `1fr auto 1fr` — a 1fr track has an auto minimum, so
neither side can be squeezed under its content and the two stay equal, which
is what keeps the bar centred in the viewport rather than in the leftover
space. Overflow becomes the header growing, not two things sharing pixels.
- The token preview put its checkerboard on the whole specimen stage, so every
swatch sat in a frame of checks and the pattern read as the loudest thing on
the page. The checks now sit UNDER the colour as a second background layer:
an opaque value hides them, a 15% tint shows exactly as much as it should.
Text-bearing specimens lose the box entirely, and name/value/purpose are one
line each with the full text on hover — they wrapped freely before, so a card
was two lines tall or five depending on how long its color-mix() happened to
be, and the grid had no rhythm.
- .btn-cta joins the shared button family: the gradient-and-glow brand moment
the system carries tokens for, which had been living in one view's scoped
block. That is what made it deletable. The header actions are now one size
and one family instead of four sizes and two.
- The shared button shape gained inline-flex + gap, so a button carrying an
icon centres it without each caller rebuilding the row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
The swatch set it inline only, and a custom property that exists nowhere in a
stylesheet is exactly what check_design_tokens reports as unresolvable — it was
right, and it caught this on the commit that introduced it.
Declaring it on .tp-swatch is the real fix rather than a silencer: a token that
resolves to nothing now renders as bare checks instead of an invalid gradient,
which is what the inline value would produce when empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
A project goal is a paragraph in practice. This one rendered as "Maintain
Scribe as the reliabl" with no way to read the rest but arrowing through it,
in a sidebar with room to spare. Description gets two more rows for the same
reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
#2444. Each needed reading rather than a batch fix, and the split was 2 real
losses, 4 false reports, 5 wrappers that are bare on purpose.
REAL:
.system-card was a flex row, and every child still says so —
.system-swatch and .system-actions are flex-shrink: 0,
.system-body and .system-form--inline are flex: 1.
align-items: flex-start is why the swatch carries
margin-top: 0.3rem: nudged onto the first line of text.
.systems-list no rule AT ALL, so the systems list rendered with browser
bullets and indent. Invisible to the check — see below.
.graph-embed the panel is a flex column whose header is flex-shrink: 0,
so this is the item that takes the remaining height. Without
it the `height: 100%` on the line below resolves against auto
and does nothing, which left the comment above it specifying
a rule that could not work.
FALSE REPORTS, and the checker was wrong rather than the code:
`.pane.empty` and `td.num` are base rules for the element that carries those
classes — the check read any compound with more than a lone class as a
modifier. It now records a compound's whole class SET and clears an element
carrying all of them, which is exact: recording the classes individually would
have cleared `.pane` everywhere on the strength of a rule that only applies
alongside `.empty`. Four reports gone, and a check with false reports is one
that gets skimmed.
BARE ON PURPOSE — .rb, .topic-group, .new-topic, .sub-list, .dash-head, and
both .detail-row rows. Each namespaces descendant rules and assumes nothing
about layout, which is the tell that separates them from a deleted base. All
seven now carry a comment saying so, so the next reader doesn't re-litigate
them and a NEW entry in the report means something actually changed.
Also recorded in the script: it cannot see a class with no rule anywhere, since
that is indistinguishable from a semantic-only hook. `.systems-list` was found
by reading the file beside a class that WAS half-styled.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Four things looked wrong after the last deploy. Two were the same bug, and it is not design drift — it is deleted CSS.
The failure
Removing a rule from a scoped stylesheet leaves its modifiers behind. The selector still exists, so nothing reads as unused, and the element renders with no base styling at all.
.btn-workspace:hover.milestone-header.clickable,:hover.ms-name { flex: 1 }, the progress track,.ms-pct— so without the parent they stacked, and a one-line milestone became five.milestone-group.ds-headerh1descendant.system-card--archived,:hover .system-actionsflex: 1/flex-shrink: 0.systems-list.graph-embed:deep()descendantflex-shrink: 0; without this theheight: 100%below resolves againstautoand does nothing — a comment specifying a rule that could not workvue-tsc --noEmitis the frontend's entire check, and a dead style typechecks perfectly.The check
scripts/check_dangling_styles.py, in the lint lane, stdlib only, knows no class name or convention (rule #115). It reports an element whose every static class has no base rule anywhere while at least one carries modifier rules.It was wrong about four on its first run —
.pane.emptyandtd.numare base rules for the element carrying those classes. It now records a compound's whole class set. A check with false reports is one that gets skimmed.Seven reports remain, all wrappers that are bare on purpose, each now carrying a comment saying so — which is what makes a new entry a signal rather than noise.
Known blind spot, recorded in the script: a class with no rule anywhere is invisible, being indistinguishable from a semantic-only hook.
.systems-listwas found by reading the file beside a class that was half-styled.Header overlap
The nav pill bar was
position: absolute; left: 50%, so it never participated in layout — out of room it overlapped the brand and utility cluster instead of pushing them. A sixth link reached that at ~1270px, an ordinary window.Now
1fr auto 1fr. A1frtrack has an auto minimum, so neither side can be squeezed under its content and the two stay equal to each other — which is what keeps the bar centred in the viewport rather than in the leftover space. Overflow becomes the header growing, not two things sharing pixels.Token preview
The checkerboard was on the whole specimen stage, so every swatch sat in a frame of checks and the pattern became the loudest thing on the page. It is now a background layer under the colour: an opaque value hides it, a 15% tint shows exactly as much as it should. Text specimens lose the box entirely, and name/value/purpose are one line each with the full text on hover — they wrapped freely before, so a card was two to five lines tall depending on how long its
color-mix()happened to be.The design-token check caught
--tp-fillon the commit that introduced it: set inline only, so nothing in any stylesheet declared it. Declaring it on.tp-swatchis the real fix — a token resolving to nothing now renders as bare checks rather than an invalid gradient.Also
.btn-ctajoins the shared button family. The gradient-and-glow brand moment was living in one view's scoped block, which is what made it deletable. The project header actions are now one family at one size instead of four stylings.inline-flex+gap, so a button carrying an icon centres it without each caller rebuilding the row.Not fixed, and visible
The delete-confirmation modals still use a separate bespoke
.modal-btnfamily. That is #2279's territory (DRY pass 4 lists ~12 usages) and will read as inconsistent beside the corrected header row.Verification
CI green on
a6d6550— typecheck, lint + design-token check + dangling-style check, unit, integration, plugin hooks, image build. No migration.Operator reported four things looking wrong. Two were the same bug, and it is not a design drift — it is deleted CSS. Removing a rule from a scoped stylesheet leaves its modifiers behind. The selector still exists, so nothing reads as unused, and the element renders with no base styling at all: .btn-workspace base gone, :hover survived — the Workspace link rendered as raw browser blue, underlined .milestone-header base gone, .clickable and :hover survived. Every child is written for a flex ROW (.ms-name { flex: 1 }, the progress track, .ms-pct), so without the parent they stacked and a one-line milestone became five. That is the "projects section uses space poorly" — a deletion, not a redesign. .milestone-group no rule at all; the card around each milestone .ds-header only its h1 descendant survived vue-tsc cannot see any of it. A dead style typechecks perfectly. scripts/check_dangling_styles.py finds the shape: an element whose every static class has no base rule anywhere, while at least one carries modifier rules. It reports 11 more. Reported and not gated, because a genuinely bare wrapper is legitimate — the signal is the count growing. Runs in the lint lane, stdlib only, and knows no class name or convention (rule #115). Also from the same report: - The header pill bar was `position: absolute; left: 50%`, so it did not participate in layout: out of room, it OVERLAPPED the brand and the utility cluster instead of pushing them. A sixth link reached that at ~1270px, an ordinary window. Now `1fr auto 1fr` — a 1fr track has an auto minimum, so neither side can be squeezed under its content and the two stay equal, which is what keeps the bar centred in the viewport rather than in the leftover space. Overflow becomes the header growing, not two things sharing pixels. - The token preview put its checkerboard on the whole specimen stage, so every swatch sat in a frame of checks and the pattern read as the loudest thing on the page. The checks now sit UNDER the colour as a second background layer: an opaque value hides them, a 15% tint shows exactly as much as it should. Text-bearing specimens lose the box entirely, and name/value/purpose are one line each with the full text on hover — they wrapped freely before, so a card was two lines tall or five depending on how long its color-mix() happened to be, and the grid had no rhythm. - .btn-cta joins the shared button family: the gradient-and-glow brand moment the system carries tokens for, which had been living in one view's scoped block. That is what made it deletable. The header actions are now one size and one family instead of four sizes and two. - The shared button shape gained inline-flex + gap, so a button carrying an icon centres it without each caller rebuilding the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs