Skip to content

feat(visuals): bind badge, button and action-button recipes through relationships (2b/6) - #1577

Merged
GarthDB merged 10 commits into
next/visual-recipesfrom
slice/2b-relationship-bindings
Oct 31, 2026
Merged

GarthDB merged 10 commits into
next/visual-recipesfrom
slice/2b-relationship-bindings

Conversation

@GarthDB

@GarthDB GarthDB commented Oct 31, 2026

Copy link
Copy Markdown
Member

Squash merge is fine; the next slice is rebased onto the result.

Part 2b of 6 of the visual recipes work from draft #1569, targeting the long-lived integration branch next/visual-recipes (not main). Based on #1576 (slice 2a).

Description

Recipes now take their values from the component's relationships instead of naming tokens directly. This slice moves badge, button and action-button over, and adds what the move needs.

  • Option-scoped relationships: a relationship's scope.options can pin component options (an array means any of). For a relationship selector the renderer keeps the records whose options match the request and prefers the one that pins the most; a record with no options is the default. The JS runtime applies the same step. The schema, SPEC-053 and SPEC-066 follow.
  • States: scope.options.state counts as a state of the token it binds, so a plain palette token can be bound for one state. The final semantics: a pinned scope.options.state replaces the bound token's own name.state, and focus and key-focus both read as keyboard-focus. sdk/core/tests/relationship_states.rs pins this (the_relationship_state_picks_the_hover_token, compiled_candidates_carry_the_relationship_state, a_pinned_state_replaces_the_tokens_own_state).
  • Recipes: badge, button and action-button bind color, padding, gap, stroke width, corner radius, font size, focus indicator colors, progress colors, the hold-icon inset and the press perspective through relationships.
  • Focus indicator naming: the focus-ring anatomy part, gap token term and recipe parts are now focus-indicator. The token keeps legacyKey focus-ring-gap, so legacy output is unchanged.
  • Snapshot test: sdk/core/tests/recipe_snapshots.rs renders every valid option combination of every recipe (53,544) across sizes, states, color schemes and scales, and hashes the SVG and diagnostics per option set into fixtures/recipe-snapshots.json. A SceneCache makes matrix renders about 70x faster.
  • Duplicate repoints: 24 relationship records changed $ref from the desktop token to the same-named mobile token (the fix fix(design-data): point the second duplicate relationship at the mobile token #1572 made for 17 other components): badge 8, button 8, action-button 8. Counted by script, comparing relationships/{badge,button,action-button}.json before (the 2a tree) and after for records with the same scope, context and options; all 24 changes are desktop to mobile with the same token name, and none is any other kind of change. Records the generator added, removed or re-scoped have no counterpart to compare and are not counted.

Not in this slice: site UI polish and WASM parity tests (slice 3), anatomy standard (4), platform-aware rendering (5).

Recipe Token selectors Relationship selectors Recipe lines
badge 145 → 0 9 → 17 1,896 → 287
button 86 → 0 13 → 32 1,313 → 395
action-button 35 → 4 23 → 28 659 → 390

The four action-button selectors that stay are values no relationship covers yet (SPEC-067 keeps reporting them).

How to review this

The relationship JSON cannot be reviewed line by line. It adds about 5,650 lines across relationships/{badge,button,action-button}.json (roughly 260 new records, most of them pinned by scope.options). Review the mechanism and the evidence instead.

How the records were made: they are generated, not hand-written, by throwaway scripts that are not committed. The method:

  1. Render the old recipe through the WASM engine over every option combination, producing an oracle table of which token each option set and state picks for each property.
  2. Find the smallest option boxes that cover each distinct token result, simulating the engine's selection rule: the record pinning the most options wins, ties are flagged, and boxes are padded to the full option domain so specificity is strictly ordered.
  3. Emit relationship records with scope.options (and state where needed), reusing existing contexts or adding role-named ones (Label, Progress, Progress track, Hold icon).
  4. Replace the recipe's token selectors with relationship selectors.
  5. Verify by rendering every combination before and after.

Correctness rests on the equivalence evidence and the snapshot test below, not on the generator.

Evidence that the move changes no output:

  1. Snapshot test. recipe_snapshots.rs and its fixture are in this PR, so a reviewer can regenerate and compare: UPDATE_RECIPE_SNAPSHOTS=1 cargo test -p design-data-core --test recipe_snapshots.
  2. Before and after on the same renderer. I rendered the new renderer against the old data (the 2a tree) and against the new data, and compared the hashes in the snapshot fixture. Badge (375 option sets), checkbox (32) and switch (16) are identical. Button and action-button differ in every set because every set sweeps the focus state, and the focus part's data-part label changed from focus-ring to focus-indicator (the rename above).
  3. Direct SVG comparison. I also rendered 324 button option sets (all four interaction states, three color schemes, three sizes, the static colors, pending, disabled, label hidden, variants and styles) and 192 action-button sets with design-data render, old data against new. After mapping data-part="focus-ring" to focus-indicator, all are identical, and the sets that differ before that mapping are exactly the focus-state ones (81 button, 48 action-button). This is a sample, not the full sweep; the full sweep is what the snapshot test covers.

Review guide, in order:

  1. spec/component-visuals.md, component-visual.schema.json and SPEC-053/066: how scope.options is specified.
  2. sdk/core/src/visual.rs (selection step) and the matching JS runtime change: the preference rule.
  3. sdk/core/tests/relationship_states.rs, recipe_snapshots.rs and the recipe_lint_baseline changes.
  4. The three recipes in visuals/: shorter, with relationship selectors (git diff shows the token selectors removed).
  5. Skim relationships/*.json; spot-check a few records with design-data render <component> --option ....

Related Issue

Epic spectrum-design-data-p6ga (component visual recipes); slicing tracked in p6ga.9; split from #1569. Part 2b of 6, after #1576. Decision recorded in the bead notes: visual recipes bind values through relationships by default.

Motivation and Context

A recipe that names tokens duplicates what the relationships already say and drifts from them. Binding through relationships keeps one source of truth, and shrinks the recipes (badge by about 85%), which also makes the recipe lints (SPEC-067) pass for the migrated bindings.

How the slice was made: the 10 commits of #1569 that follow slice 2a, cherry-picked in order and then rebased onto the merged 2a. Commits already on main (5f51c2b4, the 103 repoints; 5c38e349, SPEC-072) were skipped, so the repoint and SPEC-072 changeset text those commits touched is unchanged here. There were no conflicts.

How Has This Been Tested?

On the slice head, in a clean worktree, rebased onto the merged 2a:

  • cargo fmt --check and cargo clippy --workspace --all-targets -- -D warnings
  • moon run sdk:test sdk-wasm:test design-data:validate design-data:validate-dataset tokens:verifyLegacyOutput --force (uncached): all 11 tasks pass; legacy output unchanged
  • recipe_snapshots and recipe_lint_baseline pass with the committed fixtures
  • node tools/changeset-linter/src/cli.js check --fail-on-warnings: 7 valid
  • node .github/scripts/check-ci-coverage.mjs: 0 uncovered
  • pnpm ai:check valid; git diff pnpm-lock.yaml empty
  • The before/after comparisons described above

Screenshots (if appropriate):

N/A

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Changeset: checkbox-relationship-context-prefix is extended to cover this slice. It accumulates on the integration branch and is not released until the final merge to main.

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

test added 10 commits October 31, 2026 15:01
A relationship's scope.options can now pin component options (arrays mean any of). For a
relationship selector the renderer keeps the records whose options match the request and
prefers the one that pins the most; a record with no options is the default. The JS
runtime applies the same step. The schema, SPEC-053 and SPEC-066 follow.

Badge moves over: its recipe shrinks from 1896 to 289 lines and its token selectors from
145 to 0, with all 9000 render combinations byte-identical.

Also adds a render snapshot test for every visual recipe (one hash per option set,
53,544 combinations) and a SceneCache that makes matrix renders about 70x faster.
…o relationships

Padding, gap, stroke width, corner radius and font size now bind through relationships
(7 selectors each); all 39,936 render combinations are byte-identical. 16 relationship
records that pointed twice at the desktop token now point at the mobile token, so the
mobile renders resolve. The recipe lint baseline drops to match.
…ts token

A relationship that binds a plain palette token (no state in its name) can now say which
state it applies to with scope.options.state, so a recipe can bind a stateful color through
a relationship. The compiled candidates carry the merged states, so the JS runtime needs no
change. No existing recipe output changes.
…on-scoped relationships

Fill, stroke, icon, label and hold-icon colours move from token selectors in the recipes
to relationship records pinned by scope.options (and a state where needed). A pinned
relationship state now replaces the token's own state, and 'focus' reads as keyboard-focus.
Rendered output is unchanged across every option combination.
…or everywhere

Design settled on focus-indicator (it names the intent, not the shape). The focus-ring gap
token, anatomy term and the button and action-button anatomy and recipe parts now use it;
the token keeps legacyKey focus-ring-gap so legacy output is unchanged. focus ring stays
as the name of the shared ring style class in the terminology.
…h relationships

The recipe used the deprecated transparent-white/black-1000 tokens. The Static white and
Static black focus indicator contexts hold tokens with the same values, so the rendered
stroke is unchanged; only the token label in the scene changes.
…ugh relationships

New Progress and Progress track contexts reference the same gray and accent tokens the
recipes selected before, so renders are unchanged.
@changeset-bot

changeset-bot Bot commented Oct 31, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8f5c985

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 14 packages
Name Type
@adobe/spectrum-design-data Patch
@adobe/design-data-spec Minor
@adobe/design-data-wasm Minor
@adobe/spectrum-component-api-schemas Patch
@adobe/design-system-registry Patch
@adobe/design-data-agent-mcp Patch
@adobe/design-data-mcp Patch
markdown-generator Patch
@adobe/token-corpus-migrate Patch
@adobe/token-naming-audit Patch
@adobe/design-data Patch
s2-tokens-viewer Patch
@adobe/spectrum-design-data-mcp Patch
component-options-editor Patch

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Run report for 8f5c9850

Total time: 7m 8s | Comparison time: 14m 51s | Estimated savings: 7m 42s (51.9% faster)

Action Time Status Info
⬛️ SetupProto(0.57.4) 0ms Skipped
🟩 SyncWorkspace 9.4ms Passed
🟩 SyncProject(sdk) 0.4ms Passed
🟩 SyncProject(design-data-spec) 1ms Passed
🟩 SyncProject(sdk-wasm) 1.1ms Passed
🟩 SyncProject(design-data) 0.8ms Passed
🟩 SyncProject(design-data-glue) 0.7ms Passed
🟩 SyncProject(component-schemas) 0.9ms Passed
🟩 SyncProject(design-data-mcp) 1ms Passed
⬛️ SetupToolchain(node:20.17.0) 151.7ms Skipped
⬛️ SetupToolchain(rust:1.88.0) 257.5ms Skipped
⬛️ SetupToolchain(pnpm:10.17.1) 117.9ms Skipped
⬛️ SetupToolchain(javascript) 0ms Skipped
🟩 SetupEnvironment(javascript) 0.7ms Passed
⬛️ SetupEnvironment(javascript, sdk) 0.8ms Skipped
⬛️ InstallDependencies(javascript) 0.1ms Skipped
🟩 SetupEnvironment(rust, sdk) 142.7ms Passed
🟩 RunTask(design-data:validate-registry) 232.7ms Passed
⬛️ InstallDependencies(rust, sdk) 3.1ms Skipped
🟩 RunTask(design-data:validate-visuals) 378.1ms Passed
And 19 more...
Expanded report
Action Time Status Info
🟩 RunTask(sdk:fmt-check) 1.6s Passed
🟩 RunTask(component-schemas:test) 3.1s Passed
🟩 InstallDependencies(javascript, sdk) 4.4s Passed
🟩 RunTask(sdk:codegen-check) 66.8ms Passed
🟩 RunTask(sdk:lint) 52.3s Passed
🟩 RunTask(sdk:test-doc) 1m 44s Passed
🟩 RunTask(sdk:build) 1m 58s Passed
🟩 RunTask(design-data:legacy-output) 852.5ms Passed
🟩 RunTask(design-data:roundtrip-verify) 1.4s Passed
🟩 RunTask(design-data:validate-dataset) 23.2s Passed
🟩 RunTask(design-data:validate) 37s Passed
🟩 RunTask(markdown-generator:generate) 2.4s Passed
🟩 RunTask(sdk-wasm:cache-build) 2m 32s Passed SLOW
🟩 RunTask(sdk:test) 5m 4s Passed SLOW
🟩 RunTask(sdk-wasm:build) 2m 35s Passed SLOW
🟩 RunTask(design-data-mcp:stage) 1.4s Passed
🟩 RunTask(design-data-mcp-eval:test) 16.5s Passed
🟩 RunTask(sdk-wasm:test) 1m 6s Passed
🟩 RunTask(sdk-wasm:ci) 11.6ms Passed
Changed files
.changeset/checkbox-relationship-context-prefix.md
packages/design-data-spec/conformance/valid/SPEC-025/dataset.json
packages/design-data-spec/conformance/valid/SPEC-049/dataset.json
packages/design-data-spec/schemas/relationship.schema.json
packages/design-data-spec/spec/component-visuals.md
packages/design-data-spec/spec/relationship-format.md
packages/design-data/components/action-button.json
packages/design-data/components/button.json
packages/design-data/registry/anatomy-terms.json
packages/design-data/registry/token-terminology.json
packages/design-data/relationships/action-button.json
packages/design-data/relationships/badge.json
packages/design-data/relationships/button.json
packages/design-data/tokens/layout.tokens.json
packages/design-data/visuals/action-button.json
packages/design-data/visuals/badge.json
packages/design-data/visuals/button.json
sdk/core/src/registry_data.rs
sdk/core/src/validate/rules/spec025.rs
sdk/core/src/validate/rules/spec049.rs
sdk/core/src/validate/rules/spec053.rs
sdk/core/src/validate/rules/spec066.rs
sdk/core/src/validate/rules/visual_util.rs
sdk/core/src/visual.rs
sdk/core/tests/fixtures/recipe-lint-baseline.json
sdk/core/tests/fixtures/recipe-snapshots.json
sdk/core/tests/recipe_snapshots.rs
sdk/core/tests/relationship_states.rs
sdk/plugins/site/assets/visual-runtime.js
sdk/plugins/site/src/lib.rs
sdk/wasm/test/visual-runtime-parity.test.js

@github-actions

Copy link
Copy Markdown
Contributor

🧩 Component Schema Changes Report

Component Schemas Changed (0 added, 0 deleted, 2 updated)

Original Branch: next/visual-recipes

✅ No Breaking Changes

This PR contains only non-breaking changes to component schemas.

🔄 Non-Breaking Updates

action-button

button


This diff was generated automatically and contains only backward-compatible changes.


This comment was automatically generated by the component schema diff tool. 🤖

@github-actions

Copy link
Copy Markdown
Contributor

🎨 Token Changes Report

Tokens Changed (0)

Original Branch: next/visual-recipes


This comment was automatically generated by the token diff tool. 🤖

@GarthDB
GarthDB merged commit 4110465 into next/visual-recipes Oct 31, 2026
9 checks passed
@GarthDB
GarthDB deleted the slice/2b-relationship-bindings branch October 31, 2026 22:10
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