crashappsec / crashappsec/react-registry
composite blocks: the seven a real consumer hand-rolls, with call-site counts
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
The registry publishes 70 items, of which three are `registry:block` —
`stat-card-row`, `service-detail-header`, `service-table` — plus `empty` and
`item`. All five are recreated from the Crash Override console and are shaped
for *services*. crashappsec/compass tried to adopt them for wave D5 of its
restyle and adopted none of them; the reasoning is in
[docs/plans/940-composite-blocks.md](https://github.com/crashappsec/compass/blob/main/docs/plans/940-composite-blocks.md).
That is not a complaint about the three blocks — it is evidence about which
blocks are missing. The README names COMPASS as an in-scope consumer, so here
is what a real consumer hand-rolls instead, with call-site counts measured
across 15 pages and ~90 components.
## Ranked by call-site volume
| # | Proposed item | What COMPASS hand-rolls instead | Sites |
|---|---|---|---|
| 1 | `page-header` | 15 distinct heading blocks, no shared component | 15 |
| 2 | `empty-state` | 30 distinct empty/no-data states, no shared component | 30 |
| 3 | `section` (titled card) | `Card` + `H3` assembled by hand | ~59 |
| 4 | `stat-tile` | 5 competing local `StatCard`/`MetricCard` definitions | 40 raw sites |
| 5 | `field-label` | a copy-pasted `FIELD_LABEL` const, with 3 divergent values | 6 |
| 6 | `list-row` | the same "rank + name + status + metric" row re-implemented | 6 widgets |
| 7 | `sort-header` | a page-private sortable column header | 1 |
### 1. `page-header` — title, subtitle, optional icon and actions
The strongest candidate. COMPASS has no `PageHeader`; every page writes its
own. The 15 blocks disagree on **four different heading elements for the same
semantic role** (H1, H2, H3 and an H3-inside-a-component), and **11 of them
override the type ramp with a size class** — `text-xl`, `text-lg`, `text-base`,
`text-2xl`, `text-7xl`. That is the exact anti-pattern the `typography` item
fixed at the primitive level, recurring one level up because there is nothing
to compose. The subtitle is variously `
`, a raw `
`, or a bare
``.
A `` has zero domain knowledge.
### 2. `empty-state` — the `empty` item, generalised
`empty` exists and is close, but it is one fixed presentation: a dashed frame
with a centered icon chip. Real surfaces need more than one weight — a
dropdown's "No results" row cannot carry a 12-unit-tall dashed frame, and a
full-page "nothing scanned yet" wants exactly that.
More importantly, the 30 states COMPASS hand-rolls encode a distinction the
component has no way to express. These three are different things and must not
look alike:
- **empty** — the query ran and matched nothing ("No repositories match these filters")
- **unmeasured** — the value was never collected, which COMPASS renders as an em dash, never a `0`
- **error** — the fetch failed
COMPASS currently carries that distinction in ~30 code comments. A
`variant="empty" | "unmeasured" | "error"` prop plus a `size` would let it be a
type instead. Worth considering for `empty` directly rather than a new item.
### 3. `section` — titled card with an optional icon and actions
`Card` + `Group` + icon + `H3` + body, assembled by hand about 59 times. `Card`
alone appears 150 times across 35 files and `H3` 65 times, almost always
immediately inside one. The padding is chosen ad hoc per site — `gap-0 p-6`,
`p-6`, `gap-0 p-4`, `gap-0 p-3` — which is a spacing scale leaking because
there is no component to hold the decision.
### 4. `stat-tile` — one figure, one label
`stat-card-row` is close but takes the whole row. What repeats is the *cell*.
COMPASS has five separate definitions of it, two of which are byte-for-byte
identical to each other in different files, and the raw
`font-mono text-{lg,xl,2xl,4xl} font-bold` + colour-class pattern appears at 40
sites across 22 files.
Splitting `stat-card-row` into a `stat-tile` item that the row composes would
make both usable. Two notes from trying to adopt the row as published:
- The value is `React.ReactNode` and is printed verbatim, so there is no way to
say "this was never measured". COMPASS needs an em dash plus an
`aria-label="… not assessed"`, and cannot get there without wrapping.
- The row hardcodes `grid-cols-1 sm:grid-cols-2 lg:grid-cols-N`. Inside a
fixed-height dashboard tile the breakpoint has to be shared with the grid
that allocates the tile's row count, so the component cannot own it alone.
### 5. `field-label`
`field` exists and composes label + control + help/error, which is the right
shape for a full form. What COMPASS needed and did not find is just the label
voice — mono, uppercase, tracked — as something you can put above an arbitrary
control. It is now a copy-pasted `const FIELD_LABEL` in 6 files that has
already drifted into **three different values**, which is a live inconsistency
caused by the gap.
### 6 and 7. `list-row`, `sort-header`
`item` covers much of `list-row` already; what is missing is a leading rank or
index slot and a right-aligned metric column, which is why six COMPASS widgets
re-implemented the row rather than composing `item`. `sort-header` — a table
column header with an active-direction indicator — has no equivalent at all.
## One defect, separately
`status-badge`'s union is `healthy | needs_attention | at_risk | critical | stale`.
COMPASS's status vocabulary is `healthy | needs attention | at risk | stale | abandoned`
— no `critical`, and `abandoned` is required and guard-tested. So COMPASS
cannot use `status-badge`, and by extension cannot use `service-detail-header`
or `service-table`, both of which are typed against it. It deleted its vendored
copy rather than leave an incompatible duplicate in the tree.
If the union were a generic parameter, or the status→tone map an injectable
prop, all three items would become adoptable. That is probably the smallest
change in this issue with the widest effect.
## Context
- react-registry#11 — the `/registry.json` index. See the comment I have just
added: the index *is* published, at `/r/registry.json`. Without knowing that,
COMPASS probed item names by hand and got false 404s for `stat-card` and
`empty-state`, which are published as `stat-card-row` and `empty` — it
concluded the registry published almost no blocks. It publishes five.
- react-registry#13 — the `alert` icon contrast divergence.
Happy to send any of these upstream as PRs if the shapes look right.
Contributor guide
Research direction
Begin with docs/plans/940-composite-blocks.md and the published /registry.json, then verify the COMPASS call-site evidence described here. Because seven candidates and a status-badge defect are in scope, first get agreement on one focused item; done means an agreed, tested component change that addresses its stated consumer gap.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, tailwindcss, typescript
- Domain
- design, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100