Skip to content

Glass swatch library release (WYSIWYG exclusions + metadata flags) - #143

Draft
dompm wants to merge 42 commits into
mainfrom
library-release
Draft

Glass swatch library release (WYSIWYG exclusions + metadata flags)#143
dompm wants to merge 42 commits into
mainfrom
library-release

Conversation

@dompm

@dompm dompm commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Draft release PR for the consolidated glass swatch library. Do not merge — for CTO review.

Release checklist

  • Striker exclusion (2026-07-16) — 146 unfired strikers filtered out (WYSIWYG); pre-struck exception verified to match zero products (Bullseye's trailing code is a grade F/P/B/C, no "S").
  • kiln_transformative + kept-but-flagged metadata (2026-07-17) — 4 Bullseye Alchemy rows excluded as kiln_transformative; reactive:true (15 rows) + iridized:true (162 shipped rows) metadata added. Quarantine 150 total; library 1332 → 1182.
  • Scale audit (2026-07-17 — scale-audit, task Improve crop handle usability: wider hit areas + corner handles #18) — 423 Bullseye products audited. 183 known-wrong ~10in macro/detail stamps nulled, 10 whole-sheet heights made aspect-consistent, 198 needs_repick flags; iridescent transmission-shot + split-backdrop-seam rules folded in (14 reflection-side picks, 91 transmission re-picks). Absolute sample size is a documented, retunable assumption. Pick-preference implemented + gated pending image hosting. See section below.
  • CTO flag list + VLM judge pass — apply the CTO audit-UI flag export + judge pass.
  • Image hosting — resolve catalog-image hosting/CDN (VITE_SWATCH_CDN_URL); this is also what unblocks activating the scale re-pick (SCALE_AWARE_REPICK) and clearing the 198 needs_repick rows.
  • Final review — CTO sign-off before merge.

Canonical checklist lives at docs/library-picker-rebuild/RELEASE_CHECKLIST.md.

What this branch ships (vs main)

The whole library stack — main has none of it: the registry data (now 1182 rows), the picker/VLM build pipeline, the WYSIWYG exclusion policy, the scale audit, and the frontend Glass Library dialog.

Policy layer (striker exclusions + metadata)

Two WYSIWYG exclusion categories (a cold-process artist never performs the kiln step that develops the named color):

Category Count Rule
unfired_striker 146 striker:true and not pre-struck
kiln_transformative 4 whole-word Alchemy / Silver-to-X
Total quarantined 150

Per-row reasons + breakdowns in striker_quarantine.md / .json. Two kept-but-flagged metadata booleans: reactive:true (15 rows), iridized:true (162 shipped rows).

Scale audit layer (commit 65d3feb)

Bullseye galleries share one photo set across cart sizes; the builder stamped the cart-size width (10in for -1010) onto whichever gallery image was picked, but ~45% of picks are zoomed detail/macro crops covering a few inches — so those textures render 2-3x too coarse at physical scale (verified on 000100-0051-F, reproduced: SIFT _01_03 scale 0.31, 305 inliers).

  • Whole-sheet photo is a fixed studio sample (true aspect 1.327). Lead-requested checks: rotation ruled out (minAreaRect rectified aspect == bbox, tilt ~0° across 361 — not a tilted 17x20); reeded-rib anchor consistent (5/6 converge at ~82 ribs/sheet, one roller) but not closable (no published rib pitch; the 2x2in Color Sample photos are a generic stock _02 + sub-2in macro _01). So the absolute long side is not pixel-recoverable — SAMPLE_LONG_IN=10.0in is a documented, globally-retunable assumption; every relative correction is independent of it. ← CTO calibration decision.
  • Field semantics: real_world_{w,h}_in = the swatch IMAGE's footprint (inches/px the app needs), NOT the cart sale size — the exact conflation that caused the bug.
  • The detail→whole-sheet bridge does not hold (SIFT/NCC/FFT all fail), so macros are fixed by re-pick, not point-estimate.
  • Registry (Bullseye rows only; every other row byte-identical): 183 known-wrong ~10in stamps → null, 10 whole-sheet heights made aspect-consistent, 198 needs_repick:true.
  • Iridized (keys off iridized): 14 reflection-side picks + 91 transmission re-picks flagged (backlit preview needs the transmission/white side; never crop across a split backdrop seam).
  • Deliverables: docs/library-picker-rebuild/scale_audit.json (per-product method+confidence sidecar), scale_report.md, scale_boards/ (~35 examples incl. failures), scripts/scale_audit.py (+ scale_audit_lib.py, idempotent).
  • Frontend GlassLibraryDialog.tsx made null-safe (fallback width + "scale TBD"). Gallery image cache is gitignored (no large binaries; boards downscaled to ~1.3MB total).
  • Pick preference preferred_gallery_image() in build_swatch_library.py encodes the measured-better rule, gated SCALE_AWARE_REPICK=False until image hosting lands (re-picking regenerates gitignored catalog_images crops — the exact gap that forced the library: exclude unfired strikers (pre-struck exception) + striker schema #141 revert).

Known limitations (honest)

  • Absolute scale is an assumption, not a measurement.
  • Near-white/clear-on-white glass defeats segmentation: 35 no-wholesheet + 15 unmeasurable (~12%, low confidence, dims mostly kept + flagged).
  • Frontend typecheck not run in this worktree (no node_modules); the change is a minimal, null-safe type widening + fallback.

Checklist footnotes

  • Activating the scale re-pick (SCALE_AWARE_REPICK) + clearing the 198 needs_repick rows is part of image hosting (re-picking regenerates gitignored crops).
  • SAMPLE_LONG_IN=10.0in is an assumption; retune the single constant at final review if a physical reference lands.
  • Frontend typecheck for the null-safe GlassLibraryDialog.tsx change is deferred to final review (no node_modules in the audit worktree).

🤖 Generated with Claude Code

dompm and others added 30 commits May 24, 2026 09:55
PR #75 renamed the SheetPanel prop to `onUpdatePieceTransform` in App.tsx
but SheetPanel still destructures `onTransformChange`, so the per-piece
wrapper closes over an undefined callback. Konva fires the drag events,
the wrapper resolves (it's a real arrow fn), then crashes trying to call
undefined → "i is not a function" repeats on every `_drag` and
`_endDragAfter`. Side effects: no live preview while moving, dragged
pieces only land on the next click, occasional stale selection.

Rename the App-side prop back to `onTransformChange` so it matches what
SheetPanel actually destructures. No SheetPanel changes needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…classifier correctly handle Blackberry Cream
…duct link and front-lit indicators, and commit registry to Git
…he library build

Replaces the images[0] positional rule in scripts/build_swatch_library.py with an
argmax over every gallery candidate, scored by the vendored research/delighting-035
picker (scripts/swatch_picker.py + its report-019 dependency scripts/audit_flagger.py,
provenance headers in each). Addresses glass-library-integration-review.md Addendum 2
(SGE image picking structurally broken) and Blocker-1 patch #2 (below-floor products
get Quarantined instead of shipping a bad image).

- Candidates are scored on 320px CDN thumbnails (cached, idempotent); full images are
  re-downloaded only when the final pick differs from what is already on disk.
- Anti-churn stability rule: a shipped image is only replaced when the picker's margin
  over it exceeds 0.15 (and never protected when it no longer clears the 0.45 floor).
  Rationale documented in apply_stability_rule(); suppressed 223 of 410 argmax
  disagreements in the full rebuild while allowing 187 genuine fixes through.
- Failed re-fetches now revert image_url to the previously-shipped URL so a network
  outage cannot wedge an item into a registry/disk mismatch.
- Legacy quarantine-list, -v2 preference, formula dedup, crop/calibration logic kept.
- docs/library-picker-rebuild/: before/after diff report + 20-change contact sheet
  from the full rerun (registry/images themselves are gitignored runtime data in
  frontend/public/assets/, regenerated by running this script).

Both maintainer SGE validation cases verified fixed in the final registry
(youghiogheny-yuf5100gr -> gallery position 4, youghiogheny-y700hs -> position 3);
reactive/Alchemy recovered set ships the -v2 images, unrecoverable set excluded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-white false quarantines

Decision 3 (glass-library-integration-review.md Addendum): replace the blanket
is_reactive_cloud drop rule with REACTIVE_CLOUD_CROP_OVERRIDE -- the two
000009-0030/-0050 SKUs are re-included using the adjudicated clean crop
(x:[650,1200], y:[0,1200] of the 1200x1200 -v2 image), which excludes the
reaction-demo tile corner insert entirely. Adjudication recorded in research
trunk's refetch_manifest.json (recovered entries with human_verification_caveat).

Decision 4 (036 report): WHITE_ON_WHITE_OVERRIDE restores the two products the
picker floor wrongly quarantined (Opaque White 000013-0030, silver-gray Cascade
002249-CA37) via an explicit per-SKU list instead of lowering the floor globally.

Decision 5: registry entries now carry a 'lighting' field ('front-lit'/'back-lit')
derived from the report-015/019 per-manufacturer priors, alongside the existing
front_lit bool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ership overrides

Replaces the stale pre-rebuild registry (1333 entries, committed in 06ac88e)
with the current picker-corrected one from the 036 rebuild (1265 entries;
docs/library-picker-rebuild/report.md), plus the four ownership-cleanup entries:

- bullseye-0000090030f1010 / -0000090050f1010: Reactive Cloud KEEP+crop
  (Decision 3) -- point at the -cropped.jpg derived from the -v2 image,
  crop_box [650, 0, 1200, 1200], real-world width scaled accordingly.
- bullseye-0000130030f1010 / -002249ca37f1010: white-on-white false-positive
  restores (Decision 4).
- every entry now carries the 'lighting' field (Decision 5).

The 548MB catalog_images/ stay untracked; run
python3 scripts/build_swatch_library.py to fetch them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-setup docs

Decision 2 (review Blocker 2): with the registry committed but the 548MB
catalog_images/ untracked, a clean checkout must degrade loudly, not silently:

- registry fetch failure now renders an explicit setup message (run
  scripts/build_swatch_library.py) instead of the generic no-matches empty state
- per-thumbnail onError fallback shows an 'image not fetched' note instead of a
  broken image when catalog images 404
- README gains a 'Glass swatch library data' section documenting the two-part
  data layout and the fetch/refresh workflow
- docs/library-picker-rebuild/OWNERSHIP.md records ownership and the decision
  log for the integration-review cleanup

Decision 5: the front-lit warning badge now keys off the registry's new
'lighting' field (front_lit kept as fallback).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…product

The Phase B dedup loop skips picker-Quarantined rows before the
REACTIVE_CLOUD_CROP_OVERRIDE is consulted, so a future run in which the picker
floors both of the product's (borderline-by-construction) hybrid photos would
silently drop the adjudicated KEEP+crop entries. Route the reactive-cloud ids
through apply_manual_overrides too, restoring status Downloaded with the -v2
source URL; Phase B/C then apply the crop as usual.

Verified with a unit-level harness: quarantine->Downloaded conversion for both
override lists, pass-through for other ids, and the crop fast-path returning
the 550x1200 cropped files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erified border-scrubbed crops (#124)

* feat: prefer Bullseye whole-sheet/grip photos over flat renders, with hardware crop

Diagnosis: Bullseye galleries lead with a full-bleed solid-color macro render
(_01) that scores perfectly on the picker's audit/coverage components (no
background, edge-to-edge 'sheet'), while the real whole-sheet photo -- white
studio ground, sometimes clamp grips on the right edge, a product label and a
sharpie batch number -- is penalized as advisory product_on_white (audit 0.35)
and capped on coverage by its white border. The flat render therefore beat the
real sheet photo by 0.04-0.28 base score wherever both existed.

Fix (swatch_picker component (f), Bullseye-only, no ML):
- grip/whole-sheet detector on downloaded pixels: near-white ground fraction,
  dominant-sheet blob, dark clamp blobs touching the right edge
- relative rank adjustment: grip +0.35 / whole-sheet +0.15 / flat chip -0.30,
  the penalty only when a sibling candidate is itself a whole-sheet photo
- crop box (fraction coords) excludes the clamps, label column (fixed 80%%-of-
  sheet-width right cut) and the sharpie strip (12%% top inset); applied in the
  build's existing crop_box pipeline at full resolution
- churn gate: a shipped Bullseye pick may only change when it is the diagnosed
  flat-chip -> detected whole-sheet/grip upgrade; everything else keeps the
  current image and is listed in the report (6 kept this run: 2 white-on-white
  sheets, 2 photo-vs-photo taste swaps, 2 pale striker tints whose grip photo
  the detector cannot yet crop safely)
- --only-manufacturer scoping: re-runs one manufacturer's collection, carries
  every other manufacturer's registry rows through verbatim
- legacy-quarantine -v2 rows are carried through when the recovery file is
  absent in the checkout instead of silently dropping shipped products

Result: 273 Bullseye picks upgraded (242 whole-sheet, 31 grip), all 273 with
hardware/label/sharpie cropped out; 0 rows removed; non-Bullseye rows
byte-identical. Review board (old|new for every change):
docs/library-picker-rebuild/bullseye_grip_review.jpg

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: full-resolution border scrub for Bullseye grip crops (no background survives)

Lead review of the first board found a systematic artifact: most crops kept a
thin near-white sliver along the bottom edge (plus occasional corner wedges).
Root cause is two-fold: (1) the sheet bbox's bottom rows are a bright specular
reflection band with a DARK cut-edge line below it, so an outermost-row check
stops at the dark line and traps the white band inside the crop; (2) the band's
depth varies 17-38px per photo and, at the picker's 320px thumb scale, JPEG
downsampling blends it below the white threshold entirely -- a thumb-scale-only
scrub passed 47 crops that were still dirty at full resolution.

The scrub (swatch_picker._scrub_crop_box) is redesigned and now runs twice:
- cheap thumb-scale pass in bullseye_features() as the churn-gate prefilter
- AUTHORITATIVE full-resolution pass in the build's Phase C on the actual
  downloaded pixels, immediately before cropping
Mechanics: scan a deep window (15% of the crop) in from each edge, cut past the
deepest near-white-contaminated row/col, apply a safety inset on all four sides
(err toward trimming sheet, never keeping background), then SELF-VERIFY the
final crop's border bands are clean. A scrub that loses >20% of the crop area
or fails verification returns failure -- the product keeps its currently-shipped
pick and is listed as uncertain instead of shipping a bad crop.

Result vs the branch base: 232 Bullseye picks upgraded (all flat-chip ->
whole-sheet/grip, every crop re-verified at full res: zero border rows/cols
above the 5% near-white QA threshold across all 232). 48 products are now held
as uncertain (was 6): pale/white opalescents, white-mix streaky lines and
bright iridescents whose near-white content cannot be safely rectangle-cropped
-- listed in docs/library-picker-rebuild/report.md. 0 rows removed; all
non-Bullseye rows byte-identical. Review board regenerated:
docs/library-picker-rebuild/bullseye_grip_review.jpg

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rdering bug (#128)

PR #124's crop phase only ran on picks that changed that run, so products
whose already-shipped pick was itself an uncropped grip/whole-sheet photo
never got the border scrub. Swept all 217 Bullseye rows shipped with
cropped:false by classifying pixels (not URL/filename heuristics) through
the existing grip/whole-sheet detector, applying the same authoritative
full-res scrub + hard-reject discipline as #124.

Found and fixed a real ordering bug in swatch_picker._scrub_crop_box: the
left/right contamination check used the pre-row-trim row range, so a
contamination band confined to the top/bottom (e.g. a specular reflection
strip near the cut edge) got double-counted as contaminating every column,
forcing a false symmetric left+right trim that blew the 20% area-loss
budget on otherwise-croppable grip photos (confirmed on the four products
named in the task: Butterscotch/Marigold Yellow/Plum Striker/Gold Purple
Opalescent). The final self-verification band is unchanged and still
catches genuine residual contamination regardless of ordering.

Also wrote a bespoke black-corner scrub (new contamination class: a black
mount/frame wedge in the top-left AND bottom-left corners, invisible to the
white-background detector since white opalescent glass reads as its own
background) for Dense White Opalescent Thin-rolled, and resolved the two
other user-named cases by fetching full product galleries: Clear
Transparent 3mm has no flat alternative to its shipped side-view (kept,
flagged for a second source); Light Mineral Green's washed gradient look is
the true appearance of the finish across all 3 candidates, and the picker's
raw preference for a tighter macro crop would repeat the flat-chip
anti-pattern #124 demotes, so the shipped pick was kept.

Result: 8 of 217 newly cropped, 19 hard-rejected to the uncertain list
(kept uncropped, mostly false-positive whole-sheet detections on pale
glass), 188 correctly left alone. Bullseye: 358 cropped / 209 uncropped of
567.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dompm and others added 9 commits July 13, 2026 15:21
…+ raw results)

scripts/vlm_pick_judge.py: re-scrapes each sampled product's full live
gallery (throttled ~1 req/s/host), builds a numbered contact sheet, and asks
one 'claude -p' subprocess per (product, model) to forced-choice pick the
straight-on flat swatch photo (strict parse, one retry). 41-product sample =
4 user-named Oceanside failures + a low/high pick_score mix across all 4
manufacturers. Judged with both sonnet and haiku. Registry read-only.

scripts/vlm_judge_report.py: renders the review board + pilot report from
the results (committed next).

docs/library-picker-rebuild/vlm_judge_pilot_results.json: raw per-product
results (heuristic index, both judges' picks, latency, cost). The data/
working dir (thumbs, contact sheets, caches) stays gitignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deliverable A: docs/library-picker-rebuild/vlm_judge_pilot_board.jpg --
41 rows, heuristic pick | sonnet pick | haiku pick per product, named
failures highlighted.

Deliverable B: docs/library-picker-rebuild/vlm_judge_pilot.md -- named-
failure verdicts (3 fixed outright, 1 improved-to-quarantine, 1 already
fine on the current gallery), agreement stats (sonnet-haiku 73%),
NONE-rates (sonnet 2%, haiku 12% incl. a verified false NONE), measured
cost/latency, corpus projection ($68 sonnet / $21 haiku for 1,269
products), recommendation: one-time sonnet judge-everything backfill --
confidence gating is not viable because all named failures had HIGH
heuristic scores (1.0-1.5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cro fix

Sonnet forced-choice judge over the 5 flagged products (5 calls, $0.26,
zero parse failures), applied through the registry per the
sweep_uncropped_bullseye.py precedent:

- bullseye-0002430030f1010 (Translucent White 3mm): CHANGED #4 -> #1.
  Shipped pick was the product-label macro (_04.jpg, heuristic score
  0.9044). Winner is the flat white sheet on a BLACK ground -- added a
  self-verifying dark-edge trim (generalizing report.md's bespoke
  dark-corner scrub, as it requested), crop [46,38,1192,1162], 89% kept.
  Row carries pick_score=null + vlm_judge provenance marker.
- bullseye-0011010000f1010 (Clear single-rolled): NONE as predicted --
  single-image gallery (side view); kept, confirmed second-source case.
- bullseye-0012470031f1010 (Mineral Green irid): CONFIRMED shipped _02
  over the macro _01, independently validating the sweep's reasoning.
- bullseye-0001130050f1010 / bullseye-0002430050f1010 (white glass):
  both CONFIRMED; the judge did not fall for the 000243-0050 gallery's
  own label macro.

WARNING documented in vlm_judge_targeted.md: the current picker still
argmaxes the label macro 0.907 vs 0.603 (margin 0.30 > stability 0.15),
so an unguarded full rebuild will churn this row back -- needs a
vlm_judge-marker guard in build_swatch_library.py (not modified here;
concurrent-edit freeze).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build_swatch_library.py's rebuild had no knowledge of the vlm_judge
provenance marker vlm_judge_targeted.py writes onto a registry row: the
stability rule only protects a previously-shipped image when its OLD score
clears PICKER_FLOOR, but a judge-picked row always has pick_score=None and
typically isn't even in this run's freshly-scraped candidate_scores -- so a
plain rebuild silently reverted judge overrides (e.g. Translucent White)
straight back to the heuristic's confidently-wrong pick.

Guard now checks the existing row for a vlm_judge marker before the
picker-quarantine skip, the stability rule, and the Bullseye churn gate, and
carries the row through byte-for-byte when present -- same posture as the
existing carried_v2_rows pattern. Verified with a monkeypatched rebuild
simulation: the Translucent White judge row survives a rebuild where the
heuristic argmax tries to re-pick the label-macro image, while a
non-judge-marked control row is still processed by the normal machinery.
Adds a corpus-wide, structured-signal-based striker classifier to
build_swatch_library.py rather than relying on literal "Striker" name matches
(which miss real cases -- Translucent White and Salmon Pink both ship pale/
unfired but neither has "Striker" in its name):

- Bullseye: membership in the vendor's own `strikers` Shopify collection,
  reduced to base color codes (independent of thickness/format variant). This
  is the same signal that drives the "striker" page badge/tooltip on a
  Bullseye product page -- a per-product metafield not exposed on the regular
  /products.json feed (confirmed empirically: Translucent White/Salmon Pink's
  body_html says nothing about striking, but their live page embeds
  "key":"striker"/"value":"true"). 140 registry rows match (26 explicitly
  named "Striker" + 114 hidden, including both known cases).
- Wissmach: a 'Striker' tag or a "color matures upon firing" family of phrases
  in body_html -- Wissmach puts this disclaimer directly on the product page.
  6 registry rows match (3 named + 3 hidden: Gold Tone Opal, String of Pearls
  Opal Reactive, Oyster Pearl Opal Reactive).
- Oceanside and Youghiogheny: investigated (tags, body_html keyword scan, and
  a live-page probe for a Bullseye-style metafield badge) with no striker
  signal found. Youghiogheny's "High Strike" line was specifically checked
  against Youghiogheny's own glossary and confirmed to be a different thing --
  a hand-rolled, non-fusible mottled art-glass product line, already fully
  colored as shipped, not a kiln-refire color. Deliberately not flagged.

`striker` (bool) and `strikes_to` (cleaned color-name string, or null) are now
always present on every registry row. Verified with a monkeypatched rebuild
covering all 4 manufacturers plus known striker/non-striker controls.
…rows

Adds scripts/vlm_judge_striker.py: a sonnet judge pass over every registry
row flagged `striker: true`, asking specifically for the PALE/unfired
candidate (what actually ships) instead of the vivid/fired color a naive
photo-quality heuristic would prefer. Also backfills striker/strikes_to onto
the currently-committed registry (a full corpus rebuild will regenerate these
fields going forward via the classifier landed in 939df9c).

Audited so far: 34 of 146 confirmed striker rows (the 29 literal "Striker"-
named products across Bullseye/Wissmach, plus the 5 hand-confirmed hidden
cases: Translucent White x2, Salmon Pink x2, Wissmach Orange-Red Opal Striker
Luminescent). 16 picks switched from a fired/vivid or outright wrong photo to
the pale/unfired sheet; 11 confirmed already-correct; 6 have no valid
pale candidate in their own gallery (left as-is, flagged); 1 manual
cross-thickness override applied (Salmon Pink 2mm's own gallery's only
non-vivid photo was a different, unrelated blue glass color entirely --
reused the 3mm sibling's genuine pale photo of the same base color instead).

The remaining ~112 hidden (non-"Striker"-named) Bullseye striker rows are not
yet individually audited -- follow-up work, tracked separately.
…ception)

CTO pivot 2026-07-16 (supersedes "switch the striker photo to its pale unfired
sheet"): unfired striker glass is now filtered OUT of the shipped library
entirely. WYSIWYG -- striker colors only develop once fired to ~1200F+, and the
~99% of users who work cold-process (foil/came) never reach that temp, so a
striker sheet keeps its dull unfired color forever; showing it (fired OR pale)
misleads them. Strikers are premium-priced (gold-derived pinks/rubies/purples).

- build_swatch_library.py: should_exclude_striker()/is_prestruck()/
  partition_striker_exclusions() run as the last registry-assembly step (catches
  scraped + all carry-through row sources). --exclude-strikers-in-place applies
  just this filter to an existing registry with no network scrape (used here).
  Schema fields striker/strikes_to kept -- they now DRIVE the exclusion.
- Registry: 1332 -> 1186 rows; 146 unfired strikers quarantined (Bullseye 140,
  Wissmach 6). Every kept row byte-identical to before; documented quarantine
  list in docs/library-picker-rebuild/striker_quarantine.{md,json}, not a
  silent drop.
- Pre-struck exception keeps ZERO products: the brief's "S-suffix" rule does
  NOT exist. Bullseye's coding system defines the trailing letter as a GRADE
  code (F/P/B/C, no S, no pre-struck code); all strikers ship in F grade and no
  row carries "Struck" wording. Detector kept as a defensive forward guard on
  whole-word struck/pre-struck/pre-fired (never "striker").
- Identification 100% complete; Bullseye flags cross-checked against the live
  strikers collection: 0 missed, 0 spurious. Oceanside 0, Youghiogheny 0,
  Wissmach 6 -- cold-glass mainstays striker-sparse as expected.
- vlm_judge_striker.py marked SUPERSEDED (its photo switches are moot; excluded
  rows do not ship). Reverted the uncommitted moot photo-switch changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	frontend/src/App.css
#	frontend/src/App.tsx
…flags

Extends the WYSIWYG library policy (CTO, 2026-07-17) on top of the 2026-07-16
striker exclusion.

Exclusion -- second category `kiln_transformative`:
- Bullseye Alchemy line (4 rows: 001015-0031/0051 Silver-to-Gold, 001016-0031/
  0051 Silver-to-Bronze). Ships silver-toned; develops gold/bronze only when
  kiln-fused, which cold-process (foil/came) users never do -- same WYSIWYG
  failure as strikers, distinct mechanism. Matched by whole-word Alchemy /
  Silver-to-X (is_kiln_transformative), kept as its own reason category so the
  quarantine taxonomy stays clean.
- Quarantine: 146 -> 150 (146 unfired_striker + 4 kiln_transformative). Shipped
  library 1186 -> 1182. striker_quarantine.{md,json} now carry a per-row
  `category` and per-category/per-manufacturer breakdowns.

Kept-but-flagged metadata (sparse booleans, WYSIWYG-safe rows stay shipped):
- reactive:true (15 rows) -- Reactive Cloud/Ice/Red Reactive + Wissmach/Yough
  reactives. Reaction needs in-contact kiln fusing; stable in cold work.
- iridized:true (162 shipped rows; 166 registry-wide, 4 are excluded Alchemy) --
  whole-word irid/iridescent/iridized; the rainbow is real angle-dependent
  thin-film reflection. Marks picker special-casing + future spectral-veil
  targets. Whole-word rule rejects "Viridian" (substring), so 166 not 167.

Implementation: classify_exclusion()/apply_metadata_flags() in
build_swatch_library.py; full-rebuild path applies flags then partitions;
--apply-library-policy-in-place (alias --exclude-strikers-in-place) lands it on
the existing registry with no network scrape, merging into the existing
quarantine. Byte-identity verified: exactly 173 shipped rows change and every
change is a pure reactive/iridized flag addition -- no other field touched, 4
Alchemy removed.

Also adds docs/library-picker-rebuild/RELEASE_CHECKLIST.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploying vitrai with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8d27a96
Status: ✅  Deploy successful!
Preview URL: https://811e79ea.vitrai.pages.dev
Branch Preview URL: https://library-release.vitrai.pages.dev

View logs

dompm and others added 3 commits July 17, 2026 08:54
… stamps

Bullseye galleries share one photo set across cart sizes; the builder stamped the
cart-size width (10in for -1010) onto whichever gallery image was picked, but ~45%
of picks are zoomed detail/macro crops covering a few inches -- so those textures
render 2-3x too coarse at physical scale (verified on 000100-0051-F).

scripts/scale_audit.py (+ scale_audit_lib.py) downloads each gallery (throttled,
local-only, gitignored), detects the whole-sheet studio shot, measures it, and
classifies the current pick. The whole-sheet photo is a FIXED studio sample (aspect
1.326, p10-p90 1.318-1.331 across 361 products); its absolute long side is not
pixel-recoverable, so SAMPLE_LONG_IN=10.0in is a documented, globally-retunable
assumption -- every relative correction is independent of it. The detail->whole-sheet
bridge does not hold (SIFT/NCC/FFT all fail), so macro footprints are corrected by
re-pick, not point-estimate.

Registry (Bullseye rows only; all other rows byte-identical):
- 183 known-wrong ~10in stamps on detail/macro picks -> real_world_{w,h}_in = null
- 10 whole-sheet heights made aspect-consistent (square 10x10 stamp fix)
- 198 needs_repick:true flags (per-product targets in the sidecar)

Iridized (keys off iridized flag): 14 reflection-side picks + 91 transmission
re-picks flagged (backlit preview needs the white/transmission side; never crop
across a split backdrop seam).

- docs/library-picker-rebuild/scale_audit.json  per-product sidecar (method+confidence)
- docs/library-picker-rebuild/scale_report.md   findings, calibration, limitations
- docs/library-picker-rebuild/scale_boards/     ~35 examples incl. failures
- GlassLibraryDialog.tsx: null-safe scale (fallback + "scale TBD")
- build_swatch_library.py: preferred_gallery_image() pick preference, gated
  (SCALE_AWARE_REPICK) pending the image-hosting re-pick rebuild

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mantics

Lead-requested calibration checks, both negative for a cleaner absolute anchor:
- Rotation ruled out: minAreaRect rectified aspect 1.327 == axis-aligned bbox,
  median tilt ~0deg across 361 sheets -- the sample truly has ~1.327 aspect, not a
  17x20 (1.176) sheet at a studio tilt.
- Reeded-rib physical anchor: 5/6 reeded whole-sheets converge on ~80-83 ribs across
  the full sheet (one physical roller), but ribs->inches is not closable -- Bullseye
  publishes no rib pitch and the 2x2in Color Sample photos are unusable (generic
  multi-sample stock _02, sub-2in full-bleed macro _01). SAMPLE_LONG_IN=10.0in stays
  a documented assumption.

Also: state registry field semantics explicitly (real_world_{w,h}_in = the IMAGE
footprint, not the cart sale size -- the exact conflation that caused the bug) in
scale_report.md and the sidecar meta; add checklist footnotes (re-pick/image-hosting
coupling, calibration retune, frontend typecheck deferred to final review). No
registry data change (audit is idempotent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on item

Lead-requested closeout notes (no data change; audit idempotent):
- Candidate sample identity (UNVERIFIED): confirmed 1.327 aspect ~= 4:3 within
  rolled-edge slop -> possibly a metric 40x30cm studio blank (15.75x11.8in). If so
  SAMPLE_LONG_IN ~= 15.75 (1.57x rescale of every Bullseye scale). Not switching the
  constant on speculation -- recorded with the implied reeded rib pitch under each
  hypothesis (10in -> ~3.1mm, 15.75in -> ~4.9mm) so one physical measurement
  adjudicates instantly. In scale_report.md + sidecar meta.sample_size_candidate.
- Release checklist: added deferred "CTO tape-measure calibration" item (rib pitch of
  a physical reeded sheet, or a studio-sample size -> closes SAMPLE_LONG_IN).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant