Skip to content

feat: build-time component-style attribution (RFC 0001 — Task A)#49

Merged
Narrator merged 3 commits into
mainfrom
rfc-0001-styles-buildtime
Jun 7, 2026
Merged

feat: build-time component-style attribution (RFC 0001 — Task A)#49
Narrator merged 3 commits into
mainfrom
rfc-0001-styles-buildtime

Conversation

@Narrator
Copy link
Copy Markdown
Member

@Narrator Narrator commented Jun 7, 2026

Summary

Lands the build-time half of RFC 0001: Component-Style Capture (sprint 2734, Task A).

  • @domscribe/core: adds optional StyleSource on ManifestEntry (className + parsed utility tokens + CSS-in-JS source-block location) and optional ComponentStyles on RuntimeContextSchema (≤32-property computed allowlist + resolved CSS custom properties). Bumps ANNOTATION_SCHEMA_VERSION from 1 → 2 with a purely-additive v1 → v2 migration step + tests for forward-compat with v1-pinned clients.
  • @domscribe/transform: parser-agnostic style extractor wired into the existing JSX AST visit. Handles className literals, no-interpolation template literals, clsx / cn / classNames / tw / twMerge / cva calls, conditional/logical/array/object expressions, member-expression helpers, and bare-identifier spread. Scans top-level styled.foo / styled(X) / emotion css declarations to link JSX tags to their source blocks (block text capped at 4 KB, no theme resolution). Behind a new captureStyles feature flag (default off in v0.x per RFC reversibility plan); plumbed through Vite, webpack, and turbopack plugins.
  • @domscribe/relay: manifest.query returns styleSource automatically (schema-driven); resolve extracts and forwards it explicitly. Tool descriptions updated so agents know they can request static styling context before reaching for runtime data.

The runtime half (StyleCapturer populating componentStyles, MCP query.bySource extension, test-fixtures falsifier harness) is Task B (#49) and depends on the schema additions in this PR.

Test plan

  • nx run domscribe-core:test — 122 tests pass (incl. 3 new migration cases).
  • nx run domscribe-transform:test — 405 tests pass (incl. 33 new corpus cases under test/className-corpus/ + 7 new injector integration cases for captureStyles: true).
  • nx run domscribe-relay:test — 306 tests pass (incl. new resolve.tool forward-the-styleSource case; updated file-annotation-storage specs to reference ANNOTATION_SCHEMA_VERSION instead of hard-coding 1).
  • nx run-many -t test --exclude domscribe-test-fixtures — all 10 projects pass.
  • nx run-many -t lint -p domscribe-core,domscribe-transform,domscribe-relay — clean.
  • nx run-many -t build --exclude domscribe-test-fixtures — all 12 projects build.
  • Falsifier coverage — measuring the ≥70% one-shot styling-completion rate requires the test-fixtures harness shipping in Task B (feat: build-time component-style attribution (RFC 0001 — Task A) #49); the build-time side cannot self-certify it.

Out of scope

Explicitly deferred per DOP / PM plan: Svelte adapter, post-completion visual-diff loop, props/state hardening, RCP-as-public-protocol revival, vendor directory, per-adapter (React/Vue/Next/Nuxt) styling work, transform-time Tailwind/theme resolution.

Schema-bump risk

ANNOTATION_SCHEMA_VERSION 1 → 2 is additive only. V1-pinned consumers reading v2 annotations see the v1 fields untouched (the new componentStyles slot is absent). On-disk v1 annotations migrate forward through FileAnnotationStorage.read / listByStatus (existing migrate-on-read path) without re-validation. Asserted by annotation-migrations.spec.ts and file-annotation-storage.spec.ts.

🤖 Generated with Claude Code

Domscribe Staff SWE (bot) and others added 3 commits June 7, 2026 06:34
…N_SCHEMA_VERSION to 2

Introduces optional `styleSource` on `ManifestEntry` (build-time className
tokens + CSS-in-JS source-block location) and optional `componentStyles`
on `RuntimeContextSchema` (≤32-property computed allowlist + resolved CSS
custom properties), per RFC 0001. Lands the schema half so transform,
runtime, and relay packages can build against it without circular work.

The v1 → v2 migration is purely additive — fields are optional, so v1
payloads pass through untouched. A no-op `migrationSteps[1]` is registered
so `migrateAnnotation` can stamp persisted v1 annotations to v2 without
throwing on the version walk. Forward-compat guarantee for v1-pinned
clients reading v2 annotations is asserted in the migration spec.

Also adds `COMPONENT_STYLES_ALLOWLIST` as the authoritative computed-style
property list so the runtime, the relay, and downstream tooling agree on
the capture contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a parser-agnostic style extractor that piggybacks on the existing
JSX AST visit:

- `extractClassNameFromJSX` resolves className tokens through string
  literals, no-interpolation template literals, conditional/logical
  expressions, array/object expressions, and clsx/cn/tw/classNames/twMerge/
  cva helpers (incl. member-expression helpers like `utils.cn`).
  Unknown helpers and bare identifiers yield an empty token set rather
  than guessing — the agent can read source if static reasoning fails.
- `collectCssInJsDeclarations` scans top-level `const X = styled.foo\`\``,
  `styled(Y)\`\``, and emotion `css\`\`` declarations and records source
  location + verbatim block text (4 KB cap). Library is inferred from the
  imported module specifier; no theme or config resolution at transform
  time (per RFC 0001).
- The injector calls both on the same AST pass when `captureStyles` is on,
  resolves the JSX tag name to its binding via `resolveTagToBindingName`,
  and attaches an optional `styleSource` to each manifest entry.

`captureStyles` defaults off in v0.x per RFC 0001 reversibility plan and
is plumbed through `InjectorOptions`, `InjectorRegistry`, Vite, webpack,
and turbopack plugins.

Tests:
- 33-case real-world corpus under `test/className-corpus/` exercising
  every pattern the RFC review flagged (literals, helpers, templates,
  conditionals, spread, CSS-in-JS).
- End-to-end injector spec runs the real Babel parser with
  `captureStyles: true` to verify className tokens and styled-component
  source-block linkage land on manifest entries.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… tools

`ManifestEntrySchema` now carries an optional `styleSource`, so
`manifest.query` returns it automatically via the existing schema-driven
output. `resolve` extracts individual fields and previously dropped
unknown ones — extended to forward `styleSource` alongside `file`/
`start`/`end`/`componentName`/`tagName`.

Tool descriptions are updated so agents know they can request styling
context via the static `styleSource` (build-time className tokens + CSS-
in-JS source-block location) before reaching for the runtime
`query.bySource` path. Description text deliberately conditions on
"when build-time style capture is enabled" — the field is absent when
the transform was run with the default `captureStyles: false`.

Storage-layer specs are updated to reference `ANNOTATION_SCHEMA_VERSION`
rather than hard-coding `1`, so the v2 bump in @domscribe/core doesn't
silently regress the on-read migration assertions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Narrator Narrator marked this pull request as ready for review June 7, 2026 13:36
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jun 7, 2026

View your CI Pipeline Execution ↗ for commit 777e497

Command Status Duration Result
nx run domscribe-test-fixtures:integration--web... ✅ Succeeded 1m 38s View ↗
nx run domscribe-test-fixtures:integration--web... ✅ Succeeded 1m 41s View ↗
nx run domscribe-test-fixtures:integration--web... ✅ Succeeded 1m 20s View ↗
nx run domscribe-test-fixtures:integration--web... ✅ Succeeded 1m 16s View ↗
nx run domscribe-test-fixtures:install-fixture-... ✅ Succeeded 47s View ↗
nx run domscribe-test-fixtures:install-fixture-... ✅ Succeeded 46s View ↗
nx run domscribe-test-fixtures:install-fixture-... ✅ Succeeded 20s View ↗
nx run domscribe-test-fixtures:install-fixture-... ✅ Succeeded 17s View ↗
Additional runs (18) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-07 13:41:52 UTC

@Narrator Narrator merged commit 27fc379 into main Jun 7, 2026
24 checks passed
@Narrator Narrator deleted the rfc-0001-styles-buildtime branch June 7, 2026 13:44
Narrator pushed a commit that referenced this pull request Jun 7, 2026
Task A (#49) landed on main while this PR was open and added the same
optional componentStyles field to RuntimeContextSchema plus the
COMPONENT_STYLES_ALLOWLIST constant and a v1→v2 schema version bump in
@domscribe/core. Resolved annotation.ts by taking main's version
(Task A's additions are a functional superset — same shape for
ComponentStylesSchema and componentStyles, plus the version-history
note, the allowlist constant, and ComponentStylesAllowlist type).

All other changes auto-merged cleanly: this PR's StyleCapturer, relay
componentStyles surface, and falsifier harness sit on top of Task A's
build-time style-source attribution.

Note for follow-up: there are now two allowlists living in two places —
COMPONENT_STYLES_ALLOWLIST in @domscribe/core (32 entries from Task A)
and STYLE_CAPTURE_ALLOWLIST in @domscribe/runtime (31 entries from
this PR). They overlap but are not identical. The schema field is
record<string,string> so neither is enforced; this is a documentation/
contract divergence, not a runtime defect. PE/PM should pick one
canonical list in a follow-up.

Post-merge verification (Nx targets on the merge commit):
  - domscribe-core:test     → pass (all suites green, 100% coverage on annotation.ts)
  - domscribe-runtime:test  → pass (548 tests)
  - domscribe-relay:test    → pass
  - domscribe-transform:test → pass
  - typecheck across all 4 → clean
  - lint across all 4      → clean

Co-Authored-By: Claude Opus 4.7 <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.

1 participant