M4: House style on-brand by construction (design tokens + skill consumes house style) - #309
Conversation
…ntion 9)
Expands the outline-only M4 into full phase blocks, informed by an Explore of
tinkerdown + lvt/components (2026-07-24). Operator decisions: spine-only, no
enforcement lint.
Re-scoped all three parts of the plan's filing ("component vocabulary +
enforceable house style, upstream lvt/components + tinkerdown"):
- TINKERDOWN-ONLY (like M3): tinkerdown consumes 1 of lvt/components' ~29
components (datatable) and reimplements the rest natively. New components would
be tinkerdown-local; upstream is opt-in for reuse, not a dependency.
- SPINE-ONLY: the verified in-grain gap is that style_guide is a
declared-but-unconsumed config field and the design-token layer already exists
but is duplicated + partial (4 consumed tokens never defined; fallbacks
re-hardcoded per use). Component enrichment deferred (operator decision).
- NO ENFORCEMENT LINT: a "flag free-form HTML" lint fights tinkerdown's
semantic-HTML-first / PicoCSS-classless model. The vision (guaranteed on-brand,
no generic LLM aesthetic) is delivered by tokens-BY-CONSTRUCTION, not rejection
— the mechanism was mis-specified, not the intent.
Two tinkerdown phases:
1. Design-token system — single-source the implicit :root token set (server.go),
define the 4 undefined tokens, drop the per-use re-hardcoded fallbacks, drive
from a tokens config. Semantic HTML + PicoCSS then renders on-brand by
construction.
2. Wire the house style into generation — the skill consumes the manifest's
style_guide + a token summary (closing the unconsumed-field gap); style block
stays optional (default tokens skin the page).
Roadmap, gap-row-4, deliverables, house-style-gap section, and headers updated in
step; M5 remains outline-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M9pJSPmG6i1D8s6rpEV4h
A project can set its on-brand page-shell palette once via `styling.tokens`
in tinkerdown.yaml — snake_case keys (accent, card_bg, …) mapped to the CSS
custom properties they drive in every page's :root, overriding the built-in
default through the same mechanism primary_color already uses for --accent.
Generated UIs are on-brand by construction, not by careful prompting.
- StylingConfig.Tokens + KnownStyleTokens (14 page-shell tokens) +
ValidateStyleTokens() wired into Load — an unknown key fails loudly at
config load (names the key, lists the known set); absent, built-in defaults
apply (backward-compatible).
- buildStylingOverrideCSS emits a sorted, CSS-injection-sanitized
:root { --token: value } block, after the primary_color block so an
explicit tokens.accent wins by source order (the two :root blocks must stay
separate — precedence is load-bearing).
- Unit: emission + sanitization (server); round-trip, fail-loud typo,
backward-compat, map↔validator drift (config).
- E2E (four-channel): getComputedStyle proves the override reaches pixels
through the real cascade (#ff00ff), not just emitted <style> text; an
unconfigured site keeps the default (#007bff = DefaultConfig.PrimaryColor).
Scope (settled at M4 kickoff, advisor-confirmed): page-shell palette IN; the
generated client-chrome CSS (search/tabs re-hardcoded fallbacks + the four
undefined tokens) is OUT — internal chrome, never author-facing house style,
and hand-editing a generated/minified artifact is the #295 provenance trap.
Token overrides are single-value across both themes, like primary_color.
Plan Phase-1 block rewritten from the pre-kickoff full scope to the shipped
spine + Learn recorded (drift fixed in this commit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M9pJSPmG6i1D8s6rpEV4h
The /tinkerdown generation skill now reads a project's house style and authors against it, closing a declared-but-unconsumed gap: generation.style_guide (a markdown file of house tone/layout/do-don't) was a config field nothing ever read. - SKILL.md: step 1 also reads styling (theme + tokens) + generation.style_guide and cats the style-guide file; step 2 carries the on-brand lever — write semantic HTML, never hardcode colours, follow the style-guide. Style block stays optional (absent → on-brand defaults). - reference.md: new § House style — the rule + a 14-token table (snake_case key → CSS property → what it skins). Fixed the § Tailwind CSS example, which demonstrated hardcoded colours (bg-blue-500/text-gray-800/text-white) — a worked counter-example to the new rule, in the same LLM context; now layout/spacing only, colour from tokens. - Tests: TestStyleTokensDocumented (forward drift-guard — KnownStyleTokens ⊆ reference.md, so a new token can't go untaught; forward-only by design, since scraping token-like words from prose would false-match), TestSkillWiresHouseStyle (structural), TestPIIManifestHouseStyleConsumable (integration — LoadFromDir exposes Generation.StyleGuide, which resolves, + Styling.Tokens; proves the field is consumable, not that a Go path consumes it). - The PII reference app ships a style-guide.md + styling.tokens (real token keys only) demonstrating a governed, on-brand console. No Go runtime code: the skill cats the style-guide, so a missing path fails loudly at generation, needing no load-time validator. House style is advisory (guidance + Phase 1 tokens), never enforced — the no-lint milestone shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M9pJSPmG6i1D8s6rpEV4h
ReviewNicely scoped and well-documented change — the tokens + skill-consumption split is clean, and the test coverage (unit, config-load, structural drift-guard, and a real four-channel E2E through Code quality / correctness
Security
Test coverage
Scope / process
Overall: solid, low-risk, well-tested addition. No blocking issues. |
Addresses the claude-review note on PR #309: ValidateStyleTokens gates token keys (fail-loud at load) but not values — an unsafe value round-trips into config and is dropped at render (sanitizeCSSValue), the same as primary_color/ font. TestLoad_StylingTokensUnsafeValueAccepted pins that behavior so it can't silently change. Load-time value validation is deliberately not added: primary_color and font have the identical silent-drop behavior, so validating only token values would introduce a new inconsistency. A load-time sanity check across all style values is a separate, out-of-scope consistency pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M9pJSPmG6i1D8s6rpEV4h
|
Thanks for the thorough review. Addressed the test-coverage note in e647f14:
On the other notes, all non-blocking and agreed:
|
Review: M4 — House style on-brand by constructionReviewed the full diff (config.go, styling.go, tests, SKILL.md/reference.md, PII example). Overall this is well-scoped, well-tested, and the docs/plan/CHANGELOG stay in lockstep with the code — good discipline throughout. Code quality / correctness
Minor / non-blocking observations
Test coverageStrong across every layer:
SecurityNo new attack surface: token values flow through the same CLAUDE.md complianceNo Note on verificationI wasn't able to run 🤖 Generated with Claude Code |
…d keys Addresses the claude-review note on PR #309: iterating the tokens map and returning on the first unknown key named a different key run-to-run when more than one was wrong. Collect the unknown keys, sort, and report the first — deterministic error message. TestValidateStyleTokens_MultipleUnknownDeterministic locks it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M9pJSPmG6i1D8s6rpEV4h
|
Addressed the determinism note in the latest commit: |
ReviewI read through the full diff (
Went back through the rest of the diff looking for anything new the earlier rounds didn't cover: Correctness
Security
Test coverage
CLAUDE.md compliance
Docs/scope
No blocking issues. This is ready to merge as far as I can tell — nice job closing out the two review rounds with targeted, well-justified fixes rather than over-correcting. 🤖 Generated with Claude Code |
M4 — House style on-brand by construction
Generated UIs are now on-brand by construction (design tokens apply automatically) and by guidance (the generation skill authors against the house style) — delivered by tokens + guidance, no enforcement lint (the re-scoped milestone shape: tinkerdown-only, spine-only, no lint).
Phase 1 — Declarative design tokens (
styling.tokens)A project sets its on-brand page-shell palette once:
StylingConfig.Tokens+KnownStyleTokens(14 page-shell tokens) +ValidateStyleTokens()wired intoLoad— an unknown key fails loudly at config load (names the key, lists the known set); absent → built-in defaults (backward-compatible).buildStylingOverrideCSSemits a sorted, CSS-injection-sanitized:root { --token: value }block, after theprimary_colorblock so an explicittokens.accentwins by source order.make buildregenerates the committed client bundle from stale node_modules, breaking checkbox e2e tests #295 provenance trap.Phase 2 — The skill consumes the house style
Closes a declared-but-unconsumed gap:
generation.style_guidewas a config field nothing read.SKILL.mdreadsstyling+generation.style_guideand carries the on-brand lever — write semantic HTML, never hardcode colours, follow the style-guide.reference.mdgains a § House style (rule + 14-token table); its § Tailwind CSS example, which demonstrated hardcoded colours, is fixed to layout/spacing only.KnownStyleTokens⊆reference.md, so a new token can't go untaught) + structural + integration tests.style-guide.md+styling.tokens, demonstrating a governed, on-brand console.Verification
go test ., all 54 files incl. every!ciE2E) with./tinkerdownpre-built.TestStylingTokensComputedStyle) proves a token override reaches computed style through the real cascade; unconfigured site keeps the default./simplifyon both phases; two advisor catches fixed in-commit (Phase 1's four-channel E2E gap; Phase 2's self-contradicting Tailwind counter-example).Notes
primary_color); per-theme palettes are a future affordance.docs/plans/2026-07-09-ephemeral-ui-reframe.md) updated per-phase with Learn; Phase 1's stale pre-kickoff block rewritten to shipped scope.🤖 Generated with Claude Code
https://claude.ai/code/session_018M9pJSPmG6i1D8s6rpEV4h