# Full tagging run report

All 1,069 real components across the six reachable registries tagged
through `classifier.dev`. Output: `data/tags/<registry>.json`, one file
per registry, committed.

## Totals

- **Components tagged: 1,069** (204 react-bits, 79 magicui, 282
  aceternity, 51 kokonutui, 420 animate-ui, 33 motion-primitives).
- **Decisions spent on this run: 6,414** (1,069 x 6 dimensions exactly,
  confirmed from checkpoint `decisionsSpent` totals, no waste).
- **Cumulative decisions spent today** (both pilot runs plus this run):
  900 + 900 + 6,414 = **8,214**, which is **41.1% of the 20,000/day free
  quota**. Roughly 11,800 decisions of headroom remain in the same day.
- **Wall-clock time for the API calls themselves: about 2 minutes 43
  seconds** (first `taggedAt` to last `taggedAt` across all six output
  files). Total calendar time for the run was longer because it
  self-throttled once (see "Self-throttle" below) and a real data issue
  was found and fixed mid-run (see "Bug found" below); neither of those
  is API processing time.

## Self-throttle worked, then needed one fix

The first invocation of `pnpm tag` correctly self-throttled: aceternity
stopped at 150/282 and animate-ui at 300/420 when the locally-tracked
`RateLimit-Remaining` dropped below the next chunk's decision cost,
exactly as designed, and both were checkpointed cleanly.

Resuming immediately after, though, kept refusing to send anything, for
every registry, including ones that had never been throttled. Cause: the
throttle check trusted the `RateLimit-Remaining` value from the *previous
process run* indefinitely, with no expiry. Since classifier.dev's header
tracks a per-minute window (confirmed in PILOT_REPORT.md), that stale
number was wrong within a minute of being recorded, but nothing ever
re-checked it against a real request. Fixed by only trusting a
remembered value for 5 seconds; a resumed run now always attempts a real
request and lets the actual 429/`Retry-After` response (already handled
correctly) govern backoff, rather than guessing from old data. Full
account in the code comment at `src/tagger.ts`'s `RATE_LIMIT_TRUST_WINDOW_MS`.

## Bug found: a real name collision in aceternity's own registry.json

The run initially produced 1,068 tagged components, one short of 1,069.
aceternity's `registry.json` contains two different entries both named
`background-lines`: one `registry:ui` primitive (no description) and one
`registry:block` demo (described "Background Lines"). Under this
project's `registry/name` identity key the two collided; the first one
tagged silently made the second look "already done" to a later resumed
run. This is aceternity's own data, not something Ingest introduced, and
`npx shadcn add background-lines` against that registry would hit the
same ambiguity. Fixed by folding `compositionLevel` into the identity
key, which is enough to tell this one primitive/block pair apart without
touching the other 1,067 components. Cost to recover the dropped entry:
6 decisions, one retag, not a re-run. Full account in DECISIONS.md #16.

## Confidence by dimension, per registry

Share of choice-dimension answers (`category`, `motion`, `visual_density`,
`interaction_model`) with confidence under 0.6:

| registry | components | under-0.6 rate |
|---|---|---|
| kokonutui | 51 | 18.1% |
| magicui | 79 | 18.4% |
| react-bits | 204 | 21.7% |
| motion-primitives | 33 | 22.7% |
| animate-ui | 420 | 23.6% |
| **aceternity** | 282 | **28.7%** |
| **all** | **1,069** | **23.9%** |

## aceternity is the outlier, and it is not primarily an enrichment artifact

aceternity's rate (28.7%) is materially worse than every other registry
(18-24%). Splitting it by whether the component was source-enriched
(the 112 `registry:ui` primitives, thin index description, tagged from
fetched source per DECISIONS.md #11) or not (the 170 `registry:block`
demos, tagged from their existing short index description):

- Primitives (source-enriched): 112 components, 26.6% under 0.6.
- Blocks (short description only, no enrichment, HTTP 401 on the
  per-item source, see DECISIONS.md #12): 170 components, **30.1%** under
  0.6.

The blocks, which were never touched by source enrichment, are actually
slightly worse than the primitives that were. This rules out "the
enriched source is noisy" as the explanation. What lines up instead:
`visual_density` alone is 43.3% under 0.6 for aceternity, well above its
36.0% catalog-wide pilot rate, and `category` is 29.1% for aceternity
against roughly 18.7% catalog-wide in the pilot. aceternity's catalog
(hover cards, hero sections, elaborate layered effects, page-length
blocks) is plausibly just more visually varied and harder to place in a
single category than react-bits' single-purpose effects or
motion-primitives' small curated primitives, an inherent property of that
registry's content, not a pipeline defect. This is consistent with, not
contradicting, the pilot's finding that `visual_density` is the weak
dimension: aceternity is where that weakness shows up most, because its
components are the ones where "how busy does this feel" is a genuinely
harder call.

## What this means going in to Match/Resolve/Surface

No further tagging changes are planned before those stages are built. The
confidence-weighted matcher (DECISIONS.md #15) is the intended mitigation
for both the general `visual_density` weakness and aceternity's elevated
rate specifically; a low-confidence aceternity tag will now contribute
little to a match rather than needing this run to be re-done.

## Registry expansion run, 2026-09-21: shadcnblocks, shadcn-dashboard,
assistant-ui, bundui

Everything above this section describes the original six-registry,
1,069-component run only. This section documents the second tagging run,
against the four product-UI registries added after per-registry filter
verification (REGISTRY_EXPANSION_STEP1_2.md). Three other candidates
(shadcn-ui-blocks, plate, react-aria) were evaluated and excluded for
specific filter gaps, tracked in ROADMAP.md.

### Totals

- **Components tagged this run: 5,050** (4,171 shadcnblocks, 508
  shadcn-dashboard, 154 assistant-ui, 217 bundui).
- **Decisions spent this run: 30,300** (5,050 x 6 dimensions exactly,
  confirmed from checkpoint `decisionsSpent` totals).
- **Combined catalog after this run: 6,119 components** (1,069 original +
  5,050 new), **36,714 cumulative tagging decisions** (6,414 original +
  30,300 new).

### Run spanned two IPs, by design

The free tier's 20,000/day quota could not cover 30,300 decisions in one
day on one IP. The run was deliberately split:

- **Part 1** (assistant-ui, bundui, shadcn-dashboard, all complete;
  shadcnblocks partial, 2,400/4,171): 19,674 decisions, stopped cleanly
  on a real `ClassifyQuotaExhaustedError` after 5 bounded retries (not an
  infinite retry loop), checkpointed to disk, no data lost.
- **Part 2**, resumed from the exact checkpoint after a network/IP
  switch (shadcnblocks completed, 4,171/4,171): 10,626 decisions, no
  quota rejection this time, only the ordinary per-minute throttle
  pausing and auto-resuming as designed.
- **Total classifier.dev usage that day, every call**: 19,694 on the
  first IP (19,674 tagging + 20 from the category-coverage sample used
  to check the `category` dimension's label coverage before tagging,
  98.5% of that IP's daily quota) plus 10,626 on the second IP (53.1% of
  its daily quota).

`src/runtime/classify.ts` gained a distinct `ClassifyQuotaExhaustedError`
and a capped 429 backoff (`MAX_429_SLEEP_MS`, 30 seconds) during this run,
so a genuine daily-quota rejection fails fast and cleanly instead of
risking a multi-hour hang on an unverified `Retry-After` value.
`src/pipeline/tag.ts` catches that error specifically and prints the exact
resume point (components already tagged, decisions spent) instead of a
stack trace.

### Confidence by dimension, per registry

Same methodology as the table above (share of `category`/`motion`/
`visual_density`/`interaction_model` answers under 0.6 confidence):

| registry | components | under-0.6 rate |
|---|---|---|
| bundui | 217 | 21.4% |
| shadcn-dashboard | 508 | 22.8% |
| shadcnblocks | 4,171 | 26.6% |
| **assistant-ui** | 154 | **37.0%** |
| **new 4 combined** | **5,050** | **26.3%** |
| **original 6** | **1,069** | **23.9%** (unchanged, confirms no regression) |
| **all 10 combined** | **6,119** | **25.9%** |

assistant-ui is now the single worst-confidence registry in the whole
catalog, worse than aceternity (28.7%) on every one of the four
dimensions individually, not just on average:

| dimension | original 6 combined | aceternity | assistant-ui |
|---|---|---|---|
| category | 18.7% | 29.1% | 35.7% |
| motion | 27.3% | 23.0% | 29.9% |
| visual_density | 32.7% | 43.3% | 52.6% |
| interaction_model | 16.8% | 19.5% | 29.9% |

Likely cause, same shape as the aceternity finding above: assistant-ui's
content (agent and chat UI: tool timelines, trace waterfalls, reasoning
panels) sits further from the schema's original motion/marketing anchors
than any other registry's content. This is documented behavior, not a
bug: confidence-weighted matching (DECISIONS.md #15) already discounts
these tags automatically, so assistant-ui-heavy briefs are more likely to
land on `shortlist` or `no_match` than a confident pick, even when a
relevant component exists and ranks near the top by text similarity. See
README.md's limitations section for the user-facing version of this note.
