Skip to content

feat: boundary-revision batch 9 — token surface + 11-widget catalog#20

Merged
simonsangla merged 2 commits intomainfrom
feat/boundary-revision-batch9
Apr 16, 2026
Merged

feat: boundary-revision batch 9 — token surface + 11-widget catalog#20
simonsangla merged 2 commits intomainfrom
feat/boundary-revision-batch9

Conversation

@simonsangla
Copy link
Copy Markdown
Owner

Summary

Implements the boundary-revision delta from context/plans/build-site-boundary-revision.md (29 tasks T-100..T-129, 5 tiers). Adds two new cavekits (product-boundary, widgets), extends cavekit-schema with shadow + radius groups + 5 new color slots, and grows the widget catalog from 8 to 11 IDs (badge, pricing-card, testimonial). Single coherent batch; all 220 tests pass.

Cavekits added/extended

  • NEW cavekit-product-boundary.md (R1–R4): in-scope surface, hard-wall denials, frozen 11-widget catalog, cross-cutting purity invariants
  • NEW cavekit-widgets.md (R1–R6): backfills shipped widget-builder + previews; extends to 11 IDs + kpi-tile metric variant
  • EDITED cavekit-schema.md: R1 expanded (4 → 9 slots); R8 shadow group; R9 radius group; R4/R5/R7 updated to compose them
  • EDITED cavekit-overview.md: 7 domains, 53 requirements, full cross-ref map + dependency graph

Implementation deltas

Schema: 9-slot color group, shadow group (4 named CSS strings), radius group (5 numeric pixel slots with sm<=md<=lg<=xl ascending rule). DEFAULT_THEME values from portfolio/DESIGN.md §2/§5/§6. Persistence patches legacy records read-side (no version bump).

Exports: all 12 export functions emit the new tokens.

  • CSS: --color-muted etc., --shadow-* (4), --radius-* (5)
  • SCSS: $color-*, $shadow-*, $radius-*
  • Tailwind: theme.extend.colors (5 new), boxShadow, borderRadius
  • Style Dictionary: shadow group (type: 'boxShadow'), radius group (type: 'dimension')
  • JSON / TS: surface naturally via the schema-extended object

Back-compat preserved: omitting widgets argument yields prior output exactly.

Widget UI: WidgetPreview gains 4 new arms (badge, pricing-card, testimonial, kpi-tile metric variant). kpi-tile takes optional variant?: 'tile' | 'metric'. Metric variant uses top-hairline + serif numeric + uppercase tracked label per portfolio/DESIGN.md §4.10/§6. WidgetSelector auto-grew to 11 toggles (no API change).

Editor: 5 new color pickers, Shadows section (4 textareas), Radii section (5 number inputs). New store actions updateShadows + updateRadii (validated, undoable, partial).

Wiring: themeToStyleVars emits all 18 new CSS vars so previews + chrome inherit them automatically.

Files

  • 14 source files modified
  • 4 new test files added (shadows.test.ts, radii.test.ts, backCompat.test.ts, WidgetPreview.test.tsx)
  • 5 existing test files updated to spread DEFAULT_THEME in fixtures
  • 4 cavekit files written/edited
  • 1 build site written (build-site-boundary-revision.md)
  • 1 handoff updated (AGENT_HANDOFF.md Batch 9 section prepended)

Validation

  • lint: 0 errors
  • typecheck: pass
  • test: 220/220 (157 prior + 63 new)
  • build: pass (vite ~108ms; 17.47kB CSS / 292kB JS)

Browser verification

With Dark preset:

  • 9 color rows in editor (Primary..On Invert)
  • Shadows section rendered with 4 textareas
  • Radii section rendered with 5 number inputs
  • 11 themed preview cards including the 3 new IDs (badge, pricing-card, testimonial)
  • JSON export shows the 5 new color slots in colors, plus widgets array reflecting selection
  • Switching presets re-themes all 11 previews within the same render cycle
  • Legacy persisted records (4-color, 8-widget) load successfully with patched defaults

Boundary R3 frozen catalog

badge, button, card, empty-state, input, kpi-tile, modal, navbar, pricing-card, table, testimonial

Adding/removing/renaming any ID requires a dedicated boundary-revision PR.

Test plan

  • npm run lint — clean
  • npm run typecheck — clean
  • npm run test — 220/220
  • npm run build — succeeds
  • Browser: editor renders all 9 color slots + Shadows + Radii sections
  • Browser: 11 themed preview cards present
  • Browser: JSON export includes new tokens
  • Browser: legacy persisted theme loads with patched defaults

🤖 Generated with Claude Code

…ion build site

- New cavekit-product-boundary.md: locks scope (R1 in-scope surface, R2 hard-wall
  out-of-scope, R3 frozen 11-widget catalog, R4 cross-cutting purity invariants)
- New cavekit-widgets.md: backfills shipped widget-builder + previews and extends
  catalog from 8 to 11 (badge, pricing-card, testimonial added alphabetically),
  plus kpi-tile metric-variant requirement
- Updated cavekit-schema.md: R1 grows from 4 to 9 color slots; new R8 shadow
  group; new R9 radius group; R4/R5/R7 updated to compose them; out-of-scope
  loses border-radius and shadow exclusions
- Updated cavekit-overview.md: 7 domains, 53 requirements, new cross-refs
- New context/plans/build-site-boundary-revision.md: 29 tasks (T-100..T-129) in
  5 tiers, 78/78 NEW criteria covered, full Mermaid graph
Implements the boundary-revision delta T-100..T-129 from
context/plans/build-site-boundary-revision.md.

Schema (Tier 0+1):
- ColorTokenSchema: 4 → 9 slots (added muted, hairline, inkSoft,
  surfaceInvert, onInvert)
- ShadowTokenSchema: 4 named CSS box-shadow strings (primary, secondary,
  card, float)
- RadiusTokenSchema: 5 numeric pixel values (pill, sm, md, lg, xl) with
  sm<=md<=lg<=xl ascending rule
- ThemeConfigSchema, ThemeVariantPair: compose new groups + share-equality
  validation across light/dark variants
- DEFAULT_THEME: 9 colors + 4 shadows + 5 radii (values from
  portfolio/DESIGN.md §2/§5/§6)
- WIDGET_IDS: 8 → 11 (badge, pricing-card, testimonial inserted
  alphabetically), labels + selection schema + default selection updated
- Persistence: legacy 4-color records load with patched defaults; legacy
  8-widget selections load with 3 new keys patched to false (read
  back-compat — no schema-version bump required)

Exports (Tier 2): every export function emits the new tokens
- CSS / SCSS / Tailwind / Style Dictionary all gain color/shadow/radius
  emission rules; JSON / TS surface naturally via the schema-extended
  object; back-compat preserved (omitting widgets argument yields prior
  output exactly)

Widget UI (Tier 2):
- WidgetPreview: 4 new arms (badge, pricing-card, testimonial, kpi-tile
  metric variant); kpi-tile gains optional `variant?: 'tile' | 'metric'`
  prop; metric uses top-hairline + serif numeric + uppercase tracked label
  per portfolio/DESIGN.md §4.10/§6
- WidgetSelector: auto-grew to 11 toggles (no signature change)

Editor (Tier 3):
- Store: updateShadows + updateRadii actions (validated, undoable, partial)
- ThemeEditor: 5 new color pickers, Shadows section (4 monospaced
  textareas), Radii section (5 number inputs); per-field error surface
  follows existing pattern

Wiring (Tier 4):
- themeToStyleVars emits all --color-* (9), --shadow-* (4), --radius-* (5)
- App threads new store actions through to ThemeEditor

Tests:
- 220/220 pass (157 existing fixed + 63 new)
- New: tests/schema/shadows.test.ts (positive, missing slots, invalid
  values, validator surface)
- New: tests/schema/radii.test.ts (positive, missing slots, invalid
  values, ascending-order rule, validator surface)
- New: tests/persistence/backCompat.test.ts (legacy 4-color theme load
  patches; legacy 8-widget selection load patches; round-trip preserves
  all groups)
- New: tests/components/WidgetPreview.test.tsx (every catalog ID renders
  unique marker; kpi-tile metric variant produces distinct DOM)
- Existing fixtures updated to spread DEFAULT_THEME so they remain
  schema-valid as the surface grew
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
theme-forge Ready Ready Preview, Comment Apr 16, 2026 4:28pm

@simonsangla simonsangla merged commit 124015d into main Apr 16, 2026
3 checks passed
@simonsangla simonsangla deleted the feat/boundary-revision-batch9 branch April 16, 2026 16:29
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Batch 9, establishing a Product Boundary to lock application scope and expanding the theme schema with shadow and radius token groups. The color palette grows to nine slots, and the widget catalog expands to eleven IDs with new renderings for badges, pricing cards, and testimonials. Export and persistence layers are updated to support these tokens and ensure backward compatibility for legacy records. Feedback recommends refactoring repetitive CSS variable generation and export logic into generic helper functions and implementing a more robust migration strategy for legacy data patching.

Comment on lines +61 to +74
function patchLegacyTheme(raw: unknown): unknown {
if (typeof raw !== 'object' || raw === null) return raw
const r = raw as Record<string, unknown>
const colors = (typeof r.colors === 'object' && r.colors !== null)
? { ...DEFAULT_THEME.colors, ...(r.colors as Record<string, unknown>) }
: { ...DEFAULT_THEME.colors }
const shadows = (typeof r.shadows === 'object' && r.shadows !== null)
? { ...DEFAULT_THEME.shadows, ...(r.shadows as Record<string, unknown>) }
: { ...DEFAULT_THEME.shadows }
const radii = (typeof r.radii === 'object' && r.radii !== null)
? { ...DEFAULT_THEME.radii, ...(r.radii as Record<string, unknown>) }
: { ...DEFAULT_THEME.radii }
return { ...r, colors, shadows, radii }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patchLegacyTheme function performs manual object spreading for every theme group. This is prone to errors if the schema evolves further. Consider using a more robust approach like a deep merge utility or a schema-aware migration function to ensure all nested properties are correctly handled.

Comment on lines +22 to +30
for (const k of Object.keys(config.colors) as Array<keyof typeof config.colors>) {
out[`--color-${kebab(String(k))}`] = config.colors[k]
}
for (const k of Object.keys(config.shadows) as Array<keyof typeof config.shadows>) {
out[`--shadow-${k}`] = config.shadows[k]
}
for (const k of Object.keys(config.radii) as Array<keyof typeof config.radii>) {
out[`--radius-${k}`] = `${config.radii[k]}px`
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The manual iteration over keys and construction of CSS variable names is repetitive. This could be refactored into a single helper function that takes a prefix and a record to generate the key-value pairs, reducing code duplication.

Comment thread src/export/exportTheme.ts
Comment on lines +56 to +72
function colorVarLines(colors: ThemeConfig['colors']): string[] {
return (Object.keys(colors) as Array<keyof typeof colors>).map(
(k) => ` --color-${kebab(String(k))}: ${colors[k]};`,
)
}

function shadowVarLines(shadows: ThemeConfig['shadows']): string[] {
return (Object.keys(shadows) as Array<keyof typeof shadows>).map(
(k) => ` --shadow-${k}: ${shadows[k]};`,
)
}

function radiusVarLines(radii: ThemeConfig['radii']): string[] {
return (Object.keys(radii) as Array<keyof typeof radii>).map(
(k) => ` --radius-${k}: ${radii[k]}px;`,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The functions colorVarLines, shadowVarLines, and radiusVarLines share identical logic for mapping object keys to CSS variable strings. These can be unified into a single generic function to improve maintainability.

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