Spec and plan the card harness - #56
Merged
Merged
Conversation
The earlier spec scoped this work from two wrong readings. It called the recipe local because 95 of its 148 lines differ — but og-recipe.mjs is machinery and a card list sharing a file, and diffed apart the machinery is ~62 lines differing by 5 to 9, almost all comment prose. It also called this the smallest saving of the four; on the corrected measurement it is the largest cluster left in the family. Both errors came from the duplication sweep, which has now been wrong by construction three times. The numbers here were taken after it got tests. The stronger argument is not the line count. export-og.mjs has drifted into three variants and each carries a capability the other two lost — reduced motion and hash rendering in companygraph, a recipe guard and per-card scale in guestgraph, neither in blust.ch. The recipe tests drifted the same way: 29, 31 and 31 tests, none a superset. That is the shape of the defect that nearly shipped in the suite runner round, so the union rule is written into this spec rather than left to judgement. No card's bytes change. The union makes capabilities available; a recipe has to ask. og:check stays a fixed point on all three sites, which is what makes the change reviewable at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
robertblust
force-pushed
the
card-harness-spec
branch
from
September 2, 2026 07:26
5253ae5 to
6f560f7
Compare
Self-review against the spec found three disagreements worth fixing before anyone implements from it. cardFor is not a union item. Companygraph and guestgraph both export it, which is what made it look like one — but it is defined once in each and called nowhere, in either repository. The union rule protects capabilities a site would lose; an export nothing imports is not one. It is deleted rather than moved, and the decision is recorded so the next reader does not rediscover it as an omission. Criterion 1 was unachievable as written. Dropping `from` from guestgraph's cards moves every guestgraph og.sha, because the recipe hashes every key of a card — the pictures are byte-identical, the hashes are not. The criterion now names that as the one exception and holds the line everywhere else: a changed og.png is a defect on any site, guestgraph included. SHARED_TEST_COUNT was placed inside the function that must not contain it, and its test counted occurrences of `test(` in the source — which a comment defeats. It counts registrations now. Three tests in this family have already been defeated exactly that way. Eight tasks: four package modules, a release, three site adoptions. Every task that adds a gate proves it red by mutation first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
robertblust
force-pushed
the
card-harness-spec
branch
from
September 2, 2026 07:30
df7ed68 to
1b6ceec
Compare
The whole-branch review caught two defects, both in this spec rather than in the code it describes. Critical: all three sites run og:check and test:og BEFORE npm ci, which is what made "imports nothing outside node's standard library" worth asserting. Adoption makes both steps import the package — which is not on disk until npm ci has run. Every push would have failed with ERR_MODULE_NOT_FOUND, and Task 6's own verification would not have caught it, because node_modules exists on a developer's machine. The steps move after npm ci and before the playwright install; the property actually worth keeping is that og:check needs no browser, and that one survives. Major: the adoption snippet showed a FRAME carrying deviceScaleFactor. It matched none of the three sites — blust.ch and companygraph carry clipY and no deviceScaleFactor, guestgraph the reverse. The recipe hashes every key of a card, so following the snippet moves every og.sha; measured at 8 of 8 on blust.ch. The instruction is now to copy each site's FRAME verbatim and retype nothing. Two smaller corrections to the same snippet: the specifier has no .mjs, since the exports map has no suffixed entry, and it must bind through recipeFor rather than `export * from`, which leaves root unbound so state() throws. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The design we agreed, written up as a spec, plus the implementation plan. Supersedes the tier 4 paragraph of the shared design system spec, which was wrong in two directions.
What the earlier spec got wrong
It called the recipe local because "95 of its 148 lines differ for real reasons" — but
og-recipe.mjsis machinery and a card list sharing a file. Diffed apart:It also called tier 4 "the smallest saving of the four". On the corrected measurement it is the largest cluster left in the family. Both errors came from the duplication sweep, wrong by construction three times now; these numbers were taken after it got tests.
The line count is the weaker argument
export-og.mjshas drifted into three variants, each carrying a capability the other two lost:hash— render a named viewdeviceScaleFactorThe recipe tests drifted the same way — 29, 30 and 30 tests, and no one of them a superset of another. There is no copy here to consolidate onto. This is the shape of the defect that nearly shipped in the suite runner round, so "the package takes the union, never the intersection" is written in as a rule rather than left to judgement.
The design
No fences. A visitor never downloads any of these files, so the governing rule makes every piece an import. First tier where the answer is imports throughout.
Four modules under
cards/—recipe.mjs,check.mjs,export.mjs,recipe-tests.mjs— each taking the site's recipe module as input. Each site keepsog-recipe.mjs(its cards, frame, hide rules andREPO_ROOT) and three thin callers.REPO_ROOTstays site-side deliberately: that is theSITE_ROOTdefect from the runner round, avoided by construction rather than by remembering.No card's picture changes. The union makes
settle: "reduced-motion"available everywhere but it applies only where a recipe asks, and blust.ch's does not.og:checkis a fixed point across the whole change.Self-review found three things
Fixed before this went out rather than discovered mid-implementation:
cardForis not a union item. Two sites export it, which is what made it look like one — it is defined once in each and called nowhere in either. Deleted rather than moved; the union rule protects capabilities a site would lose, and an export nothing imports is not one.fromfrom guestgraph's cards moves every guestgraphog.sha, because the recipe hashes every key of a card. Pictures identical, hashes not. Named as the one exception; the line holds everywhere else.SHARED_TEST_COUNTwas inside the function that must not contain it, and its test counted occurrences oftest(in the source — which a comment defeats, exactly how three tests in this family were defeated before. It counts registrations now.The plan
Eight tasks: four package modules, a release, three site adoptions. Every task that adds a gate proves it red by mutation first. The union recipe suite ends at 32 tests on every site, up from 29, 30 and 30.
🤖 Generated with Claude Code