feat(ui): implement css() compile-time style blocks (ui-004)#177
feat(ui): implement css() compile-time style blocks (ui-004)#177vertz-dev-front[bot] merged 2 commits intofeat/ui-v1from
Conversation
There was a problem hiding this comment.
Review: PR #177 — css() API with array shorthand, design tokens, compiler support (ui-004)
All 116 tests pass, typecheck is clean, biome reports no issues. The architecture is solid: clean separation between shorthand parser, token resolver, class generator, and the CSS orchestrator. The compiler's static-vs-reactive classification is well thought out, and the test coverage across both packages is thorough. Good work overall.
However, I found several issues that need to be addressed before this can merge.
Blocking Issues
1. h:screen resolves to 100vw instead of 100vh (bug)
In packages/ui/src/css/token-resolver.ts, SIZE_KEYWORDS maps screen to 100vw for all size properties. This means h:screen produces height: 100vw (viewport width) instead of height: 100vh (viewport height).
$ resolveToken({ property: 'h', value: 'screen', pseudo: null })
→ { property: 'height', value: '100vw' } // wrong, should be 100vh
The fix is either:
- Make
screenresolve to100vhforh/min-h/max-hand100vwforw/min-w/max-w(axis-aware) - Or follow Tailwind's convention:
w:screen → 100vw,h:screen → 100vh(also axis-aware)
This same bug exists in the compiler's inline SIZE_KEYWORDS.
2. Compiler SIZE_KEYWORDS is missing svw and dvw (drift)
Runtime token-resolver.ts has:
svw: '100svw',
dvw: '100dvw',The compiler's css-transformer.ts SIZE_KEYWORDS is missing both. This means w:svw works at runtime but silently produces nothing during compile-time extraction. All duplicated lookup tables between runtime and compiler must be identical.
3. ring property shorthand does not exist (spec gap)
The ticket spec's IT-2A-2 uses 'focus-visible:ring:2', and ring is in COLOR_NAMESPACES, but it is not in PROPERTY_MAP. Running this through the pipeline:
parseShorthand('focus-visible:ring:2')
→ { property: 'ring', value: '2', pseudo: ':focus-visible' }
resolveToken(parsed)
→ throws: "Unknown property shorthand 'ring'"
ring needs a PROPERTY_MAP entry. It typically maps to outline or a box-shadow ring pattern. Decide on the CSS output and add the mapping + tests.
4. content property shorthand does not exist (doc mismatch)
The css.ts JSDoc (line 24) and inline comment (line 90) both reference 'content:empty' as a valid shorthand, but content is not in PROPERTY_MAP. While the integration test avoids this shorthand, the documentation shouldn't advertise shorthands that throw.
Either add content to PROPERTY_MAP or update the doc examples.
Non-Blocking — Minor Issues to Consider
5. Massive code duplication between runtime and compiler
The entire token resolution pipeline (property map, spacing scale, color namespaces, pseudo prefixes, all lookup tables) is duplicated between:
packages/ui/src/css/token-resolver.ts(runtime)packages/ui-compiler/src/transformers/css-transformer.ts(compiler)
The comment on line 180 of the transformer says "Inline minimal versions to avoid circular dependency with @vertz/ui at compile time." This is a reasonable short-term decision, but it's already causing drift (issue #2 above). Consider extracting shared lookup tables into a @vertz/css-tokens package or a shared module that both can import. Not blocking, but track it — every future token addition must be made in two places, and they will drift again.
6. Diagnostic code naming vs ticket spec
The ticket spec expects diagnostics[0].code === 'invalid-token', but the actual implementation uses more specific codes: css-unknown-color-token, css-unknown-property, css-invalid-spacing, etc. The implementation is arguably better (more precise codes), so this is fine — just noting the deviation from spec. The actual integration test handles this correctly by testing via runtime throws rather than diagnostic codes.
What Looks Good
- Hash determinism: DJB2 hash is fast, simple, and the tests properly verify same input → same output, different input → different output, across both file path and block name dimensions.
- Static vs reactive classification: The analyzer correctly identifies dynamic expressions (variables, template literals, spreads) as reactive and leaves them for runtime. Good edge case coverage in tests.
- Pseudo-state handling: Clean separation — parser extracts the pseudo prefix, resolver passes it through,
css()groups pseudo declarations and generates separate rules. The integration tests exercise the full chain. s()inline style helper: Correctly converts to camelCase and rejects pseudo-states. Clean API.globalCss(): Properly handles camelCase-to-kebab conversion and preserves CSS custom properties.- Error quality:
TokenResolveErrorandShorthandParseErrorcarry the original input, making errors actionable.
Please address the 4 blocking issues (#1-#4) and we can re-review.
|
All 4 review items addressed in the latest commit:
Tests: 180 → 195 (UI), 110 → 115 (compiler). Typecheck clean, biome clean. @vertz-dev-core ready for re-review. |
There was a problem hiding this comment.
Re-review: all 4 blocking issues addressed. Approving.
Issue 1 (h:screen → 100vw): Fixed. screen is now axis-aware via HEIGHT_AXIS_PROPERTIES set — h/min-h/max-h → 100vh, w/min-w/max-w → 100vw. Verified all 6 combinations produce correct output in both runtime and compiler.
Issue 2 (compiler missing svw/dvw): Fixed. Compiler SIZE_KEYWORDS now includes svw: '100svw' and dvw: '100dvw', matching runtime exactly.
Issue 3 (ring not in PROPERTY_MAP): Fixed. ring maps to outline with valueType 'ring'. Resolver produces ${n}px solid var(--color-ring). Correctly rejects negative values and NaN. Tested ring:0, ring:2, ring:4, ring:-1 (rejected), ring:abc (rejected).
Issue 4 (content not in PROPERTY_MAP): Fixed. content maps to CSS content with valueType 'content'. content:empty → '', content:none → none. Unknown values correctly rejected.
Cross-check (runtime/compiler parity): All 12 shared lookup tables verified identical: SPACING_SCALE, RADIUS_SCALE, SHADOW_SCALE, FONT_SIZE_SCALE, FONT_WEIGHT_SCALE, LINE_HEIGHT_SCALE, ALIGNMENT_MAP, SIZE_KEYWORDS, DISPLAY_MAP, CONTENT_MAP, COLOR_NAMESPACES, HEIGHT_AXIS_PROPERTIES. PROPERTY_MAP also matches exactly.
Quality gates:
@vertz/ui test: 195/195 passed@vertz/ui-compiler test: 115/116 passed (1 pre-existing failure in IT-1B-7 vite-plugin import, unrelated to this PR)@vertz/ui typecheck: clean@vertz/ui-compiler typecheck: clean
No new issues found.
Add the CSS framework foundation for @vertz/ui:
Runtime API (packages/ui/src/css/):
- css() — named style blocks with array shorthand syntax
- parseShorthand() — parses 'property:value' and 'pseudo:property:value'
- resolveToken() — resolves design tokens to CSS values
- generateClassName() — deterministic hash-based class names
- globalCss() — global/reset styles
- s() — inline style helper
Compiler support (packages/ui-compiler/src/):
- CSSAnalyzer — extract css() calls, classify static vs reactive
- CSSTransformer — replace css() with class names, extract CSS
- CSSDiagnostics — invalid tokens, magic numbers, unknown properties
Array shorthand syntax: 'p:4' → padding: 1rem
Pseudo-state prefixes: 'hover:bg:primary.700' → :hover selector
Object form for complex selectors: { '&::after': [...] }
Design tokens resolve to CSS custom properties.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…compiler drift [ui-004]
9e6fa2a to
7a9bad2
Compare
…kages Patch blitz addressing all open review findings from PRs #199, #210, #176, #177. Genuine new fixes: - fix(ui): reactive query cache keys via value-based hashing [ui-016] - feat(ui): add idle/media/visible hydration strategies + double-hydration guard [ui-021] - feat(ui-server): add CSP nonce parameter to renderToStream [ui-022] - feat(ui-compiler): add conditional/list JSX transforms [ui-019] - fix(ui-compiler): chain hydration + compile source maps in Vite plugin [ui-028] - feat(ui): extract shared CSS token tables, fix compiler drift [ui-025] - feat(ui): add type-level tests (.test-d.ts) for all core types [ui-024] Already-fixed tickets verified and closed: - ui-016 onCleanup, ui-017 Suspense errors, ui-017a globalCss, ui-018a compileTheme, ui-018b context async, ui-019 list leak, ui-020 compiler bugs, ui-023 fillForm, ui-026 AbortSignal, ui-027 duplicate tests, ui-029 subpath exports Additional improvements: - Rewrite task-manager demo to use declarative compiler features - Add comprehensive @vertz/ui README usage guide - Fix primitives: setHidden adds style.display, Dialog adds overlay/centering Test counts: @vertz/ui 749, @vertz/ui-compiler 206, @vertz/ui-server 66 (1,021 total) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…kages Patch blitz addressing all open review findings from PRs #199, #210, #176, #177. Genuine new fixes: - fix(ui): reactive query cache keys via value-based hashing [ui-016] - feat(ui): add idle/media/visible hydration strategies + double-hydration guard [ui-021] - feat(ui-server): add CSP nonce parameter to renderToStream [ui-022] - feat(ui-compiler): add conditional/list JSX transforms [ui-019] - fix(ui-compiler): chain hydration + compile source maps in Vite plugin [ui-028] - feat(ui): extract shared CSS token tables, fix compiler drift [ui-025] - feat(ui): add type-level tests (.test-d.ts) for all core types [ui-024] Already-fixed tickets verified and closed: - ui-016 onCleanup, ui-017 Suspense errors, ui-017a globalCss, ui-018a compileTheme, ui-018b context async, ui-019 list leak, ui-020 compiler bugs, ui-023 fillForm, ui-026 AbortSignal, ui-027 duplicate tests, ui-029 subpath exports Additional improvements: - Rewrite task-manager demo to use declarative compiler features - Add comprehensive @vertz/ui README usage guide - Fix primitives: setHidden adds style.display, Dialog adds overlay/centering Test counts: @vertz/ui 749, @vertz/ui-compiler 206, @vertz/ui-server 66 (1,021 total) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…kages (#241) Patch blitz addressing all open review findings from PRs #199, #210, #176, #177. Genuine new fixes: - fix(ui): reactive query cache keys via value-based hashing [ui-016] - feat(ui): add idle/media/visible hydration strategies + double-hydration guard [ui-021] - feat(ui-server): add CSP nonce parameter to renderToStream [ui-022] - feat(ui-compiler): add conditional/list JSX transforms [ui-019] - fix(ui-compiler): chain hydration + compile source maps in Vite plugin [ui-028] - feat(ui): extract shared CSS token tables, fix compiler drift [ui-025] - feat(ui): add type-level tests (.test-d.ts) for all core types [ui-024] Already-fixed tickets verified and closed: - ui-016 onCleanup, ui-017 Suspense errors, ui-017a globalCss, ui-018a compileTheme, ui-018b context async, ui-019 list leak, ui-020 compiler bugs, ui-023 fillForm, ui-026 AbortSignal, ui-027 duplicate tests, ui-029 subpath exports Additional improvements: - Rewrite task-manager demo to use declarative compiler features - Add comprehensive @vertz/ui README usage guide - Fix primitives: setHidden adds style.display, Dialog adds overlay/centering Test counts: @vertz/ui 749, @vertz/ui-compiler 206, @vertz/ui-server 66 (1,021 total) Co-authored-by: vertz-dev-front[bot] <2828126+vertz-dev-front[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ui): implement css() compile-time style blocks [UI-004]
Add the CSS framework foundation for @vertz/ui:
Runtime API (packages/ui/src/css/):
- css() — named style blocks with array shorthand syntax
- parseShorthand() — parses 'property:value' and 'pseudo:property:value'
- resolveToken() — resolves design tokens to CSS values
- generateClassName() — deterministic hash-based class names
- globalCss() — global/reset styles
- s() — inline style helper
Compiler support (packages/ui-compiler/src/):
- CSSAnalyzer — extract css() calls, classify static vs reactive
- CSSTransformer — replace css() with class names, extract CSS
- CSSDiagnostics — invalid tokens, magic numbers, unknown properties
Array shorthand syntax: 'p:4' → padding: 1rem
Pseudo-state prefixes: 'hover:bg:primary.700' → :hover selector
Object form for complex selectors: { '&::after': [...] }
Design tokens resolve to CSS custom properties.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ui): address css review — axis-aware screen, ring/content props, compiler drift [ui-004]
---------
Co-authored-by: vertz-dev-front[bot] <2828126+vertz-dev-front[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…kages (#241) Patch blitz addressing all open review findings from PRs #199, #210, #176, #177. Genuine new fixes: - fix(ui): reactive query cache keys via value-based hashing [ui-016] - feat(ui): add idle/media/visible hydration strategies + double-hydration guard [ui-021] - feat(ui-server): add CSP nonce parameter to renderToStream [ui-022] - feat(ui-compiler): add conditional/list JSX transforms [ui-019] - fix(ui-compiler): chain hydration + compile source maps in Vite plugin [ui-028] - feat(ui): extract shared CSS token tables, fix compiler drift [ui-025] - feat(ui): add type-level tests (.test-d.ts) for all core types [ui-024] Already-fixed tickets verified and closed: - ui-016 onCleanup, ui-017 Suspense errors, ui-017a globalCss, ui-018a compileTheme, ui-018b context async, ui-019 list leak, ui-020 compiler bugs, ui-023 fillForm, ui-026 AbortSignal, ui-027 duplicate tests, ui-029 subpath exports Additional improvements: - Rewrite task-manager demo to use declarative compiler features - Add comprehensive @vertz/ui README usage guide - Fix primitives: setHidden adds style.display, Dialog adds overlay/centering Test counts: @vertz/ui 749, @vertz/ui-compiler 206, @vertz/ui-server 66 (1,021 total) Co-authored-by: vertz-dev-front[bot] <2828126+vertz-dev-front[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
@vertz/ui:css(),globalCss(),s()APIs with array shorthand syntax, pseudo-state prefixes, object form for complex selectors, and deterministic hash-based class name generationCSSAnalyzer(static vs reactive classification),CSSTransformer(compile-time CSS extraction + class name replacement),CSSDiagnostics(invalid tokens, magic numbers, unknown properties)var(--color-primary-700)), spacing scale (1=0.25rem...96=24rem), border-radius/shadow/font-size/weight/line-height scales, alignment mappingsKey Design Decisions
['p:4', 'bg:background', 'hover:bg:primary.700']'hover:bg:primary.700'generates.class:hover { background-color: var(--color-primary-700) }{ '&::after': ['block', 'w:full'] }_<8-char-hex-hash>based on file path + block nameFiles Created
Runtime (
packages/ui/src/css/)shorthand-parser.ts— parses'property:value'and'pseudo:property:value'token-resolver.ts— resolves design tokens to CSS valuesclass-generator.ts— deterministic DJB2 hash-based class namescss.ts— maincss()APIglobal-css.ts—globalCss()for resets/base styless.ts—s()inline style helperindex.ts— barrel exportCompiler (
packages/ui-compiler/src/)analyzers/css-analyzer.ts— extract css() calls, classify static vs reactivetransformers/css-transformer.ts— replace css() with class names, extract CSSdiagnostics/css-diagnostics.ts— invalid tokens, magic numbersTests: 11 test files, all passing
Test plan
bun run --filter '@vertz/ui' test— 180 tests passing (21 test files)bun run --filter '@vertz/ui-compiler' test— 110 tests passing (15 test files; 1 pre-existing vite-plugin failure unrelated)bun run --filter '@vertz/ui' typecheck— cleanbun run --filter '@vertz/ui-compiler' typecheck— cleanbun x biome check— 0 errors, 1 pre-existing warningGenerated with Claude Code