Style settings for radius, spacing, border, page background and type scale (#56) - #57
Conversation
Spark is tokenised for colour and typeface and hardcoded for geometry:
there is no setting and no custom property for radius, section rhythm,
content gap, border colour, page background, container width or a type
scale. Add the geometry half of the token layer without changing what a
store on default settings renders.
Settings: nine new entries in the Style group (Layout: body_bg_color,
border_color, radius_control, radius_card, section_padding, content_gap,
container_max_width; Type Scale: heading_scale, body_size), seeded in
settings_data.json so the parity gate stays green. Selects, not free text,
so the storefront stays on a sane scale.
Bridge: layouts/base.html extends the existing :root block with the same
{% if settings.x %} pattern used for fonts and colours. The "default"
option of section_padding, content_gap, heading_scale and body_size emits
nothing, so a defaulted store's :root is what it was plus lines that equal
the CSS fallbacks. Bridge properties are noun-first (--control-radius,
--card-radius, --section-padding-y, --content-gap, --heading-scale,
--body-size, --container-max, --border-color, --body-bg-color); the
@theme keys that read them are namespace-first, mirroring the existing
--color-primary: var(--primary-color) convention, because a theme key
named like the property it reads is a self-reference.
Theme: @theme registers the tokens with today's values as fallbacks
(section-y 3rem and its 4/3 md step, band-y for the promo rhythm,
content gap 1.5rem with sm/md/lg ratios, h1/h2/h3/display sizes carrying
Tailwind's own line-height ratios and multiplied by --heading-scale).
Component rules consume the bridge properties directly: body background
and font-size, .btn radius, .btn-secondary and .product-card-bordered
border colour, .container max-width, .product-card radius (with overflow
hidden so a rounded card clips its image), .product-grid and
.category-grid gaps. Every replacement is value-identical at defaults;
a fixture render of the homepage before and after this change has zero
computed-style differences and zero differing pixels at 1280 and 375.
assets/main.css: 48219 -> 48460 bytes, twice-compile identical.
Utilities for the new keys are emitted once markup uses them, which the
section partials do in the next change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Swap the design-decision literals in the live branches of the six section partials for the token utilities registered in the previous change. Only values a tokenised design library carries move: section rhythm (py-12 md:py-16 -> py-section-y md:py-section-y-md; the promo band's py-16 md:py-20 -> py-band-y md:py-band-y-md), the featured product's column gap (gap-8 md:gap-12 -> gap-content-md md:gap-content-lg), the heading steps (hero h1 text-3xl md:text-5xl -> text-display md:text-display-md; promo h2 and the large section heading text-3xl md:text-4xl -> text-h1 md:text-h1-md; default text-2xl -> text-h2; small text-xl -> text-h3; featured product title -> text-h2) and card radius (rounded-card on the featured image, its no-image placeholder, and the category tiles; product cards already carry it through .product-card). Structural spacing stays literal: the hero's overlay inset and copy widths, heading and CTA margins, and every empty-state placeholder (the dashed-border sections and their skeleton bars), which communicate missing configuration and are not part of the merchant-facing design language. Each utility resolves to the value it replaced when no Style setting is set, line-height included (text-h2 carries Tailwind's calc(2 / 1.5), the display step's md variant carries 1). Fixture render before and after this change at default settings: 0 computed-style differences and 0 differing pixels at 1280 and 375, for both the populated homepage and a fresh store rendering only settings_data.json. With section_padding=roomy, heading_scale=large, radius_card=12px and the other settings flipped, the sections respond (section padding 64px -> 106.667px at md, hero h1 48px -> 54px, cards and tiles rounded). assets/main.css: 48460 -> 50298 bytes; the text-5xl and text-4xl utilities lost their last consumer and were pruned. Twice-compile identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A refusal-coverage pass on the previous two changes found that changing a token fallback (say --spacing-section-y from 3rem to 3.5rem) and rebuilding leaves the whole suite green, so the "identical at defaults" contract had no test behind it. tests/test_style_tokens.py now pins the 24 @theme declarations, rejects a theme key that reads a property of its own name (a self-reference compiles cleanly and disconnects the bridge at computed-value time), checks the component rules read the bridge with the old literal as fallback, checks the base layout emits nothing for a default option, checks the live section branches carry the token utilities while their placeholder branches keep py-12 md:py-16, and pins the nine setting ids with their option lists and defaults for the external token manifests that will target them. Mutation-verified: each of the three planted defects fails its test. Source-only, no compile. Documentation: DESIGN.md's Spacing, Borders & Radius and Typography chapters now state today's values as defaults that Theme Settings > Style adjusts, keep the sharp-corner default, and describe which rules consume each token; the section-spec authoring contract says new sections consume the token utilities and that placeholder chrome stays literal; the settings catalog and README list the nine settings; CHANGELOG gains an Unreleased entry. The help text for radius_control and border_color now names exactly what consumes them (.btn; .btn-secondary and bordered product cards) rather than form controls and all dividers, which are still literal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Overview
The incremental diff (vs Files Reviewed (2 files changed in incremental diff)
Previous Review Summary (commit edd6285)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit edd6285)Status: 4 Issues Found | Recommendation: Address before merge Overview
The PR successfully introduces nine merchant-controlled Style settings (radius, spacing, border, page background and type scale) wired through a Issue Details (click to expand)SUGGESTION
Files Reviewed (18 files)
Reviewed by minimax-m3 · Input: 23.4K · Output: 1.6K · Cached: 146.6K |
Review on #57 noted --color-page had no consumer: the body rule reads --body-bg-color directly and no template uses bg-page. Tailwind only emits a theme variable once a utility uses it, so the key never reached assets/main.css (unchanged at 50298 bytes); it was dead config, and the pinning test drops it too. --color-border stays: the section-spec authoring contract names border-border for new sections, and it costs nothing until one uses it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…so Figma variables become theme settings (#58) * next-theme-figma 0.7.0 / next-theme-dev 1.14.0: tokens.json manifest so Figma variables become theme settings Implements #56. next-theme-figma 0.7.0 - tokens.json (schema next-theme-figma/tokens/v1, source figma-variables) is the eleventh handoff file, named in figma-handoff.json.manifests.tokens. Required by strict validation in implementation-handoff mode, a warning elsewhere, legacy v0 exempt. new-package writes the skeleton and pointer. - validate-package checks source and schema, unique ids, typed value parsing (hex and functional colours by grammar, dimensions with units), target shape (theme-setting needs setting_id, css-custom-property needs css_var), and for Spark packages that a value landing on a Spark select setting is one of its options (setting_value carries the option). PASS line appends the tokens counts by target kind and the canonical/alias/unknown name counts. - Negative control: the same figma_name carrying two values, across entries or between the two Figma extraction sources, is a hard error in strict and non-strict mode reported as designer-input-needed with both values. The validator never picks, averages, or drops a value. - references/tokens-manifest.md documents the schema, the canonical Figma variable namespace with aliases, the pinned Spark Style setting ids (NextCommerceCo/spark#57), value rules, and the two-source extraction. figma-contract.md carries the namespace; handoff-manifest.md and the designer checklist are updated. Fixtures gain a tokens manifest. next-theme-dev 1.14.0 - Entry contract: tokens.json is reading-order row 9. "Design Tokens From tokens.json" routes by target.kind: theme-setting -> schema field + seed + :root line using {% if settings.x %}--var: {{ settings.x }};{% endif %} (never settings.* as a filter argument); existing Spark ids get the seed only; css-custom-property -> literal :root line; one-off -> section CSS; unmapped -> handback. Handback lines: Tokens mapped to settings / Tokens as custom properties / Unmapped tokens. - Step 3 takes its inputs from the manifest in implementation-handoff mode; the Change Colors or Fonts recipe no longer passes settings.* through a filter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tokens manifest: canonical functional colours in conflict comparison, setting_value only on theme-setting, :root rule read in one place Review follow-ups on the draft PR: - normalizeTokenValue canonicalises functional colours (rgba->rgb, comma or space separation, percentage alpha) so CSS-equivalent spellings from the two Figma sources never raise a false designer-input-needed conflict; a genuinely different component still does. - target.setting_value is now an error on any target kind other than theme-setting, so a misrouted token is visible instead of silently ignored. - next-theme-dev: the existing-Spark-id exception sits in the same bullet as the :root DTL literal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tokens manifest: an explicit alpha of 1 canonicalises to no alpha rgb(255,255,255) and rgba(255,255,255,1) are the same opaque colour and must not raise a designer-input-needed conflict; a real alpha difference still does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Promote the Unreleased entry to 1.4.0 and bump the three version markers (manifest.json, README.md, CLAUDE.md). No CSS or template change since #57; assets/main.css is unchanged and the drift gate confirms it. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Closes #56.
What this does
Spark was tokenised for colour and typeface and hardcoded for geometry. This adds the geometry half: nine settings in the Style group (Layout:
body_bg_color,border_color,radius_control,radius_card,section_padding,content_gap,container_max_width; Type Scale:heading_scale,body_size), bridged through the existing:rootblock inlayouts/base.html, registered in Tailwind's@theme, and consumed by the six shipped homepage sections. A merchant can now change radius, section rhythm, content gap, border colour, page background, container width, heading scale and body size from Theme Settings with no source edit, and a store on default settings renders exactly as it did before.How it fits together
layouts/base.htmlemits--control-radius,--card-radius,--section-padding-y,--content-gap,--heading-scale,--body-size,--container-max,--border-color,--body-bg-colorfrom the settings;@themeregisters--radius-control: var(--control-radius, 4px)and friends. This mirrors the existing--color-primary: var(--primary-color)convention and avoids the self-reference the issue's sketch would have produced (--radius-control: var(--radius-control)is a cycle).section_padding,content_gap,heading_scaleandbody_sizeon their default value add no line to:root, so a defaulted store's:rootis what it was plus lines that equal the CSS fallbacks.py-section-y md:py-section-y-md(3rem / 4rem),py-band-y md:py-band-y-mdfor the promo band (4rem / 5rem),text-display md:text-display-md(1.875rem / 3rem) for the hero,text-h1 md:text-h1-md(1.875rem / 2.25rem).heading_scaleis a multiplier so both breakpoints scale together;section_paddingsets the base value and the md and band steps derive by ratio.--text-*key carries Tailwind's own ratio (--text-h2--line-height: calc(2 / 1.5)), so swappingtext-2xlfortext-h2changes neither size nor leading.assets/main.cssis rebuilt and committed (48219 -> 50298 bytes); the twice-compile is byte-identical.Proof: no visual change at defaults, sections respond when flipped
Fixture render of
templates/index.html(Django DTL with stubbed platform tags,settings_data.jsonseeded, headless Chromium, full page stitched at 1x):settings_data.jsononly (empty states)Flipped, selected: section padding 64px -> 106.667px at md, promo band 80px -> 133.33px, hero h1 48px -> 54px, section h2 24px -> 27px with line-height 32px -> 36px, button radius 4px -> 12px, cards and category tiles rounded, container 1280px -> 1120px, page background and border colour changed.
Gates
make verify-theme(61 tests +tests/test_style_tokens.py),make css-drift,scripts/check-settings-parity.py(128 data values / 150 schema settings),scripts/check-templates.pyall pass.tests/test_tailwind_source_guard.pyunchanged. No@sourcechange, no new dependency, no setting renamed, no store touched.Independent review (OpenAI Codex, gpt-5.6-sol)
Each slice was built by one Codex seat and verified by a separate Codex seat with no shared context; the director (Claude Fable 5.1) planned, integrated and adjudicated.
VERDICT: FAIL/1. Required section-padding, content-gap, and typography consumers were not migrated, leaving two settings inert and one only partially implemented./2. Required public settings/design documentation was not updated.body_sizenow emits nothing so a shopper's browser font preference still applies.VERDICT: PASS/1. Non-blocking note: .challenge-tmp/ was present at final capture, is excluded by the prescribed hash command, and was not created or modified by this review.A - PROVEN-REFUSED,B - PROVEN-REFUSED,C - PROVEN-REFUSED,D - NOT-REFUSED,E - NOT-REFUSED,F - PROVEN-REFUSED,G - NOT-REFUSED,H - NOT-REFUSED/VERDICT: FAIL/1. scripts/check-templates.py does not refuse the required case E input: a settings.* value used as a filter argument.clamp(); byte drift is refused. E is a real gap but pre-existing and contested (see follow-ups), so no guard was added here. H (no test pins the fallbacks) was closed bytests/test_style_tokens.py. D and G are documented failure modes, now partly covered by the same test.VERDICT: FAIL/1. python3 -m unittest tests.test_style_tokens -v cannot import the test module./2. DESIGN.md:131 and DESIGN.md:407 incorrectly claim form controls consume radius_control./3. DESIGN.md:135 and DESIGN.md:406 incorrectly claim all borders and layout dividers consume border_color.testsshadows the repo dir;make testand CI use discovery and pass). 2 and 3 were real: docs and two help texts overstated what consumes the tokens. Repaired to name.btn,.btn-secondaryand bordered product cards; form inputs and other dividers are stated as still literal.VERDICT: PASS/1. Non-blocking note: raw variable greps include their @theme aliases; the narrower component-consumer claims are correct./2. No blocking findings.Follow-ups (not in this PR)
scripts/check-templates.pydoes not refusesettings.*used as a filter argument (refusal-coverage case E). Thenext-theme-devgotcha (skills#53 A2) says that shape 500s every route, butpartials/side_cart.html:37ships|default:settings.gift_product.pkon live stores, so the claim and the guard need a decision before a rule is added.py-section-y-lgcompiles to no rule) and the drift gate cannot see it because the bytes do not change (case D); a self-referencing theme key compiles cleanly and disconnects the bridge at computed-value time (case G).tests/test_style_tokens.pynow pins the shipped keys and rejects self-reference, but a generic guard for unknown token utilities in markup does not exist.mb-8,mt-8,mb-4) and the hero supporting copy (text-base md:text-lg) stay literal; they are rhythm rather than gap and can joincontent_gaplater if a design library needs them.--radius-*-control; skills#56'stokens.jsontargets the nine setting ids pinned bytests/test_style_tokens.py.🤖 Generated with Claude Code