Skip to content

feat: tell the pack which ground the card is painted on (#107) - #108

Merged
lex00 merged 1 commit into
mainfrom
feat/107-icon-ground
Aug 10, 2026
Merged

feat: tell the pack which ground the card is painted on (#107)#108
lex00 merged 1 commit into
mainfrom
feat/107-icon-ground

Conversation

@lex00

@lex00 lex00 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #107.

A pack's colored glyph is spliced onto a card whose fill pinhole itself
resolved, and iconFor(kind) was handed nothing about that fill. behold's Helm
mark (behold#246/#255) could only fix its 1.01:1 contrast by baking a plate that
works on every ground — chosen blind. Every pack shipping brand artwork would
re-solve it the same way.

The contract

iconFor gains an optional second argument:

iconFor(kind: string, ctx?: IconContext): string | GlyphSpec | undefined;

ctx.ground is the literal fill attribute value the painter is about to write
on the shape directly under the glyph, in pinhole's usual
var(--pin-<token>, <baked>) form — var(--pin-warnFill, #2A1417) for a
_status: "warn" card in the dark theme.

That form was picked over the alternatives because both halves carry weight: the
token name says which ground pinhole chose (i.e. the status), and the baked hex
says what it actually looks like in the theme being rendered, which is what a
pack needs to measure its own ink against. The token name alone leaves a pack
unable to do contrast math (it doesn't know the theme); the hex alone hides
which status was chosen and lies about live --pin-* overrides. The var
expression is also verifiably truthful — it's the exact string that lands in the
rect, and the tests assert the two by parsing the rendered SVG.

ground is undefined where pinhole has no honest single answer: the
containment views paint boxes and badges at fill-opacity/group opacity below
1, so the glyph reads against a composite over the page background. A pack
seeing undefined is exactly where it was before this change.

Threading

resolveGlyph(node, { override?, ground? }) passes it through; an override still
short-circuits the pack, so no ground is offered when the icon was overridden. A
new exported statusGround(status, theme) in paint/svg.ts is the single place
the value is derived, used by the card painter and by the flow / stacked /
small-multiples / morph views, so it can't drift from what those painters write
into the rect.

Compatibility

Additive. The parameter is optional, so iconFor: (kind) => … still satisfies
PresentationPack (a test asserts it as a typed literal) and resolves the same
glyph whether or not a ground is supplied. The bundled gitlab and aws packs are
untouched. behold is untouched — this only makes the contract capable of the
conditional plate.

Gates

npm run tsc, npm test (195 → 206), npm run build all green.

🤖 Generated with Claude Code

A `colored` pack glyph is spliced onto a card whose fill pinhole picked, but
`iconFor(kind)` was handed nothing about it — so behold's Helm mark had to bake
a plate that survives every ground, chosen blind (behold#246/#255). pinhole
picked the ground; pinhole should say what it picked.

`iconFor` takes an optional second argument, an `IconContext` whose `ground` is
the literal `fill` the painter is about to put on the shape under the glyph, in
pinhole's `var(--pin-<token>, <baked>)` form — the token names the status
pinhole chose, the baked hex is what that ground looks like in the theme being
rendered, which is what a pack needs for contrast math. A pack can now vary its
mark, or plate it, per actual ground.

`resolveGlyph` threads it from an optional `opts.ground`. The card painter,
plus the flow / stacked / small-multiples / morph views, name their fill through
a new `statusGround` so the value can't drift from what lands in the rect. The
containment views pass nothing on purpose: they paint at partial opacity, so
their glyphs read against a composite the fill alone doesn't state, and a pack
seeing `undefined` is where it was before this change.

Additive: the parameter is optional, existing single-argument packs type-check
and resolve identically. behold is unchanged — this only makes the contract
capable of the conditional plate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lex00
lex00 merged commit 96bde98 into main Aug 10, 2026
1 check passed
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.

colored pack glyphs are painted onto a fill pinhole chose — pass the resolved card token to iconFor

1 participant