Skip to content

Style settings for radius, spacing, border, page background and type scale (#56) - #57

Merged
next-devin merged 4 commits into
mainfrom
style-tokens
Sep 7, 2026
Merged

Style settings for radius, spacing, border, page background and type scale (#56)#57
next-devin merged 4 commits into
mainfrom
style-tokens

Conversation

@next-devin

Copy link
Copy Markdown
Contributor

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 :root block in layouts/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

  • Bridge properties are noun-first, theme keys are namespace-first. layouts/base.html emits --control-radius, --card-radius, --section-padding-y, --content-gap, --heading-scale, --body-size, --container-max, --border-color, --body-bg-color from the settings; @theme registers --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).
  • The default option emits nothing. section_padding, content_gap, heading_scale and body_size on their default value add no line to :root, so a defaulted store's :root is what it was plus lines that equal the CSS fallbacks.
  • Responsive pairs are preserved as token pairs. py-section-y md:py-section-y-md (3rem / 4rem), py-band-y md:py-band-y-md for 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_scale is a multiplier so both breakpoints scale together; section_padding sets the base value and the md and band steps derive by ratio.
  • Line-heights ride along. Each --text-* key carries Tailwind's own ratio (--text-h2--line-height: calc(2 / 1.5)), so swapping text-2xl for text-h2 changes neither size nor leading.
  • Only design decisions moved. Section rhythm, grid gaps, the featured product's column gap, heading steps and card radius consume tokens. Structural spacing (hero overlay inset, heading/CTA margins) and every empty-state placeholder (dashed borders, skeleton bars) stay literal on purpose.
  • assets/main.css is 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.json seeded, headless Chromium, full page stitched at 1x):

comparison state 1280 wide 375 wide
after vs before populated homepage, all six sections live 0 computed-style diffs, 0 differing pixels 0 / 0
after vs before fresh store, settings_data.json only (empty states) 0 / 0 0 / 0
flipped vs after roomy / loose / large / 12px radii / 1120px / 18px / custom colours 191 diffs, page 4048 -> 4505px 170 diffs, page 5689 -> 6120px

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.py all pass. tests/test_tailwind_source_guard.py unchanged. No @source change, 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.

slice seat verdict line, verbatim synthesis
Phase 2: settings + bridge + @theme review 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. Every check inside the Phase 2 boundary passed (settings, bridge, theme keys, default equivalence, artifact, guard coverage, ASCII). Both FAIL items were the next two phases by plan; the packet had not stated the phase boundary. Adjudicated as deferred, not repaired. Director repair on top: the default body_size now emits nothing so a shopper's browser font preference still applies.
Phase 3: six section partials review 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. All seven checks PASS: exactly the 14 listed replacements, placeholder branches hash-identical to HEAD, every replaced utility value-identical including line-height, md steps preserved, twice-compile identical, flipped-setting arithmetic confirmed from the CSS.
Phase 3: refusal coverage of the guards challenge (reasoning xhigh) 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. The parity gate refuses a missing seed, an out-of-list select value and a wrong-typed colour; sass-compat refuses a standalone 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 by tests/test_style_tokens.py. D and G are documented failure modes, now partly covered by the same test.
Phase 3b + 4: pinning test + docs, round 1 review 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. 1 is a machine artifact (a user-site package named tests shadows the repo dir; make test and 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-secondary and bordered product cards; form inputs and other dividers are stated as still literal.
Phase 3b + 4, round 2 re-review VERDICT: PASS / 1. Non-blocking note: raw variable greps include their @theme aliases; the narrower component-consumer claims are correct. / 2. No blocking findings. Environment finding confirmed as outside the repository; both wording repairs verified against the CSS; parity still green; boundary hash matched.

Follow-ups (not in this PR)

  • scripts/check-templates.py does not refuse settings.* used as a filter argument (refusal-coverage case E). The next-theme-dev gotcha (skills#53 A2) says that shape 500s every route, but partials/side_cart.html:37 ships |default:settings.gift_product.pk on live stores, so the claim and the guard need a decision before a rule is added.
  • Tailwind emits nothing for an unknown theme key (py-section-y-lg compiles 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.py now pins the shipped keys and rejects self-reference, but a generic guard for unknown token utilities in markup does not exist.
  • Heading and CTA margins (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 join content_gap later if a design library needs them.
  • spark#30 (section wave 1) should ship on these tokens; spark#51 derived-theme follow-ups can drop the invented --radius-*-control; skills#56's tokens.json targets the nine setting ids pinned by tests/test_style_tokens.py.

🤖 Generated with Claude Code

next-devin and others added 3 commits September 7, 2026 15:58
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>
Comment thread css/input.css Outdated
Comment thread css/input.css
Comment thread css/input.css
Comment thread css/input.css
@kilo-code-bot

kilo-code-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

The incremental diff (vs edd6285) only removes the unused --color-page theme declaration and its EXPECTED_THEME_DECLARATIONS entry, resolving the previous bg-page SUGGESTION. The deletion is one line removed from css/input.css and one constant removed from tests/test_style_tokens.py — both are clean, byte-stable removals with no new code paths, no new dependencies, and no shift in behaviour. Existing inline comments on unchanged lines (--color-border, --spacing-band-y / --spacing-section-y-md, --text-h1 / --text-display) remain active in the discussion with maintainer replies and are not re-raised because the lines themselves were not changed by this incremental diff.

Files Reviewed (2 files changed in incremental diff)
  • css/input.css - 0 issues (1 line removed: --color-page declaration)
  • tests/test_style_tokens.py - 0 issues (1 constant removed: --color-page entry)
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

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 4

The PR successfully introduces nine merchant-controlled Style settings (radius, spacing, border, page background and type scale) wired through a layouts/base.html bridge into css/input.css @theme declarations, and pins every fallback with tests/test_style_tokens.py. Sizes at defaults are byte-identical to the literals they replaced, the bridge condition is well-designed (default options emit nothing), and the live/placeholder split is preserved. The findings below are low-severity but worth resolving before merge.

Issue Details (click to expand)

SUGGESTION

File Line Issue
css/input.css 37 --color-page registered but no template consumes bg-page; only the direct body { background-color } rule uses the bridge. Adds unused bytes to main.css.
css/input.css 38 --color-border registered but border-border is only referenced in docs/section-specs/README.md authoring contract; no partial in this PR uses it.
css/input.css 43 --spacing-band-y is identical to --spacing-section-y-md (line 42): calc(var(--section-padding-y, 3rem) * 4 / 3). Two tokens for one computed value.
css/input.css 53 --text-h1 and --text-display (line 57) use the identical formula calc(1.875rem * var(--heading-scale, 1)), so text-h1 and text-display render at the same mobile size at every scale; only the -md variants differ. The token names imply a hierarchy that the implementation collapses at mobile.
Files Reviewed (18 files)
  • CHANGELOG.md - 0 issues
  • DESIGN.md - 0 issues
  • README.md - 0 issues
  • assets/main.css - 0 issues
  • configs/settings_data.json - 0 issues
  • configs/settings_schema.json - 0 issues
  • css/input.css - 4 issues
  • docs/section-specs/README.md - 0 issues
  • docs/theme-settings-partials.md - 0 issues
  • layouts/base.html - 0 issues
  • partials/section_featured_categories.html - 0 issues
  • partials/section_featured_product.html - 0 issues
  • partials/section_featured_products.html - 0 issues
  • partials/section_heading.html - 0 issues
  • partials/section_hero.html - 0 issues
  • partials/section_on_sale.html - 0 issues
  • partials/section_promo_banner.html - 0 issues
  • tests/test_style_tokens.py - 0 issues

Fix these issues in Kilo Cloud


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>
@next-devin
next-devin merged commit 1ba49d0 into main Sep 7, 2026
2 checks passed
@next-devin
next-devin deleted the style-tokens branch September 7, 2026 10:19
next-devin added a commit to NextCommerceCo/skills that referenced this pull request Sep 8, 2026
…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>
next-devin added a commit that referenced this pull request Sep 8, 2026
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>
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.

Style settings for radius, spacing, border, page background and type scale

1 participant