feat(roots): iOS/Swift token export + thoughtstream brand (River Mist)#44
Merged
Conversation
Author the first real Canopy brand (vs the sunset demo) as DTCG token files, mirroring examples/sunset exactly: primitive ramps (slate primary, current secondary, tide accent, mist neutral, plus cool-tuned status ramps), light and dark semantic mappings over Canopy's role names, brand.config.json, README. River Mist is a calm slate-teal water palette. Anchors: primary slate.600 #42666f (light) / slate.300 #8fb6bb (dark); neutral mist.50 #f3f6f6 (bg light), mist.900 #1e3238 (text light), mist.950 #0e1618 (bg dark). It builds AA-clean through buildBrand() - all 39 roles mapped, every role/state pair meeting WCAG AA in light and dark, each dark override distinct from its light value. The surface primitive is named `canvas` (not `surface`) to avoid a DTCG group/token path collision with the `color.surface` semantic role. Adds spec 0032. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LV2MquNjUZebAS8S8z4mnR
Add a code-generated Swift emitter so a native SwiftUI app consumes Canopy
tokens with no re-authoring - the Swift target the architecture always
anticipated ("just another Style Dictionary platform - no token rewrite").
swift.mjs / buildSwift() (exported at @rogueoak/roots/swift, bin roots-swift)
generates one Tokens.swift for a brand from the same config roots-brand reads:
- Colours: two throwaway Style Dictionary instances resolve the brand's light
and dark semantics to concrete hex (outputReferences:false), emitted as one
Color(light:dark:) per role that adapts to the color scheme via a generated
dynamic-UIColor initializer.
- Non-colours: a third instance resolves Canopy's OWN spacing / radius / type
scale (shared across brands) to CGFloat points + Font helpers. rem converts
to points at 16pt/rem; a raw px pill (radius.full) is kept as-is.
Output is three caseless enums (CanopyColor / CanopySpacing / CanopyRadius /
CanopyFont - the idiomatic Swift namespace with no instances) with a
do-not-edit generated header. Generated on demand (not part of the web build),
landing at dist/<brand>/Tokens.swift. Verified: swiftc -typecheck passes.
Tests assert the thoughtstream brand builds AA-clean and the emitter's
structure (header, a role's light+dark hex, spacing/radius/font constants,
CLI). Adds the tokens/ dir to the package files (swift.mjs reads them at
runtime), documents the export in the README, and updates the overview docs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LV2MquNjUZebAS8S8z4mnR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds two things to
@rogueoak/roots, per spec 0032:1. thoughtstream brand (River Mist)
The first REAL Canopy brand (vs the sunset demo), authored as DTCG token files at
packages/roots/examples/thoughtstream/, mirroringexamples/sunset/structure and role set.River Mist is a calm slate-teal water palette:
slate-600 #42666flight /slate-300 #8fb6bbdarkmist-50 #f3f6f6(bg light),mist-900 #1e3238(text light),mist-950 #0e1618(bg dark)It maps all 39 Canopy semantic roles in light + dark and builds AA-clean through
buildBrand(): every role/state pair meets WCAG AA in both themes, each dark override differs fromits light value. (The surface primitive is named
canvasto dodge a DTCG group/token path collisionwith the
color.surfacerole.)2. iOS / Swift token export
swift.mjs/buildSwift()(exported at@rogueoak/roots/swift, binroots-swift) generates oneTokens.swiftfor a brand from the same configroots-brandreads - the native Swift target thearchitecture always anticipated ("just another Style Dictionary platform - no token rewrite"). It
reuses the core transforms rather than re-resolving tokens by hand:
concrete hex (
outputReferences: false), emitted as oneColor(light:dark:)per role that adaptsto the color scheme via a generated dynamic-
UIColorinitializer.across brands) to
CGFloatpoints +Fonthelpers. rem -> points at 16pt/rem; a raw px pill(
radius.full) is kept as-is.Output is three caseless enums (
CanopyColor/CanopySpacing/CanopyRadius/CanopyFont- theidiomatic Swift namespace with no instances) with a "Do not edit - generated by Style Dictionary"
header. Generated on demand (not part of the web
pnpm build), landing atdist/<brand>/Tokens.swift.Regenerate:
npx roots-swift examples/thoughtstream/brand.config.jsonVerification
node cli.mjs examples/thoughtstream/brand.config.jsonbuilds AA-clean (39/39 roles mapped).swiftc -typecheckpasses on the generatedTokens.swift(real, compilable SwiftUI).primarylight+dark hex, spacing/radius/font constants, CLI).
pnpm test+pnpm lintgreen in roots, andthe full monorepo
pnpm build/test/lintare green.docs/overview/(features, architecture) updated to match.🤖 Generated with Claude Code