bake: a :scope child compound merges into the element anchor

`:scope > .card` describes the wrapper's only child, the accepted element
itself, so the lasting rule is the anchor with what the compound adds
(a class the anchor lacks, an attribute, a state), never bare `.card`,
which after the append would style every card on the page. A type in the
compound must be the anchor's own; an id anchor takes any. Pinned in the
rewrite and accept tests; contract updated.

Written with AI assistance (Claude).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Abdul Wahab
2026-09-15 05:45:49 +05:00
committed by Abdul Wahab
co-authored by Claude Fable 5
parent 695d1bd515
commit 4b3a0e932d
3 changed files with 92 additions and 14 deletions
+1 -1
View File
@@ -1686,7 +1686,7 @@ Order in `live-accept.mjs`: receipt check → find `impeccable-variants-start <i
- JSX: everything above wrapped in `<indent><div data-impeccable-carbonize="ID" style={{ display: "contents" }}>``</div>` with body indented 2 more, `<style …>{\`` / `\`}</style>`, `{/* … */}` comments, `style={{ display: 'contents' }}` on the variant div.
Result `{handled:true, file: rel, carbonize:boolean, todo?:'REQUIRED before next poll: carbonize cleanup in <file>. See reference/live.md "Required after accept".', bakeSkipped?}`. Discard: replace range with deindented original → `{handled:true, file, carbonize:false}`.
**Mechanical bake** (`bake.rs`): only on `--bake` (never by default and never on `--no-bake`; the generate lane's accept carbonizes exactly like plain live's, so the agent integrates the accepted variant per live.md), a knob-free HTML/JSX accept is made permanent instead of leaving the carbonize block. Refused (falls back to the carbonize block, with `bakeSkipped:<reason>`) when: `--param-values` is non-empty; the accepted variant carries `data-impeccable-*` or `data-p-*` inside it; the preview CSS uses `var(--p-*)`, `data-p-*`, or `data-impeccable-params`; the variant's root is a component (`<PricingGrid>`, `<Card.Root>`: what it renders is unknown, and its `className` or `id` prop may never reach that element) or has neither an id nor a static class (`className={expr}`); a `:scope` cannot be rewritten (sibling combinators, `:scope` not at the front, nested `@scope`); the accepted variant declares no rule; or no destination stylesheet exists. The rewrite: the accepted `@scope ([data-impeccable-variant="N"])` block is flattened and every selector re-anchored on the root tag's selector (`#id`, else `tag.class.class`): `:scope > .x``.x`, `:scope .x``<anchor> .x`, `:scope:hover > .x``<anchor>:hover > .x`, bare `:scope``<anchor>`; Astro's `[data-impeccable-variant="N"] > .x` prefix the same way; nested `@media`/`@supports` inside the block keep their prelude; top-level `@keyframes`/`@font-face` are kept, other variants' blocks dropped. Destination: for `.jsx`/`.tsx` the `.css` file under the app root (skipping node_modules/.git/.impeccable/dist/build/coverage/framework caches, depth ≤ 6, `.min.css` and generated or git-ignored files excluded) with the most rules naming the anchor's id or classes, else the only `.css` file; for other files the page's own last `<style>` block when it has one, else the same search. The rules are **appended** under `/* impeccable generate <id>: accepted variant N */` (existing rules are never rewritten; a same-selector rule later in the cascade overrides declaration by declaration). The source is verified clean (`verifyAcceptedSource`) before anything is written; the stylesheet is written first, then the source with the variant unwrapped at the wrapper's indentation. Result `{handled:true, file, carbonize:false, baked:true, variant:'N', css:{file: rel|null (null = the page's own <style>), rules, anchor}, verify:{clean, findings}}`; the poll's completion for it is `complete`, so the session ends without `live-complete`. After accept with `--page-url`, buffered manual-edit ops whose original/new text appears as an exact text segment in the replaced original block are dropped from `pending-manual-edits.json`.
**Mechanical bake** (`bake.rs`): only on `--bake` (never by default and never on `--no-bake`; the generate lane's accept carbonizes exactly like plain live's, so the agent integrates the accepted variant per live.md), a knob-free HTML/JSX accept is made permanent instead of leaving the carbonize block. Refused (falls back to the carbonize block, with `bakeSkipped:<reason>`) when: `--param-values` is non-empty; the accepted variant carries `data-impeccable-*` or `data-p-*` inside it; the preview CSS uses `var(--p-*)`, `data-p-*`, or `data-impeccable-params`; the variant's root is a component (`<PricingGrid>`, `<Card.Root>`: what it renders is unknown, and its `className` or `id` prop may never reach that element) or has neither an id nor a static class (`className={expr}`); a `:scope` cannot be rewritten (sibling combinators, `:scope` not at the front, nested `@scope`); the accepted variant declares no rule; or no destination stylesheet exists. The rewrite: the accepted `@scope ([data-impeccable-variant="N"])` block is flattened and every selector re-anchored on the root tag's selector (`#id`, else `tag.class.class`): `:scope > .x``<anchor>` merged with `.x` (the wrapper's only child is the element itself, so the child compound rides on the anchor, a class the anchor already has once, a type only when it is the anchor's own; `#id` anchors take any type), `:scope .x``<anchor> .x`, `:scope:hover > .x``<anchor>.x:hover`, bare `:scope``<anchor>`; Astro's `[data-impeccable-variant="N"] > .x` prefix the same way; nested `@media`/`@supports` inside the block keep their prelude; top-level `@keyframes`/`@font-face` are kept, other variants' blocks dropped. Destination: for `.jsx`/`.tsx` the `.css` file under the app root (skipping node_modules/.git/.impeccable/dist/build/coverage/framework caches, depth ≤ 6, `.min.css` and generated or git-ignored files excluded) with the most rules naming the anchor's id or classes, else the only `.css` file; for other files the page's own last `<style>` block when it has one, else the same search. The rules are **appended** under `/* impeccable generate <id>: accepted variant N */` (existing rules are never rewritten; a same-selector rule later in the cascade overrides declaration by declaration). The source is verified clean (`verifyAcceptedSource`) before anything is written; the stylesheet is written first, then the source with the variant unwrapped at the wrapper's indentation. Result `{handled:true, file, carbonize:false, baked:true, variant:'N', css:{file: rel|null (null = the page's own <style>), rules, anchor}, verify:{clean, findings}}`; the poll's completion for it is `complete`, so the session ends without `live-complete`. After accept with `--page-url`, buffered manual-edit ops whose original/new text appears as an exact text segment in the replaced original block are dropped from `pending-manual-edits.json`.
Receipt: on any `handled!==false` result write `accept-receipts/<id>.json` = `{id, operation:'accept'|'discard', variantId:'N'|null, result, completedAt}` (tmp+rename). Re-run with same op/variant → prior `result` + `{handled:true, alreadyApplied:true}`; different → `{handled:false, mode:'error', error:'accept_receipt_conflict', priorOperation, priorVariantId}`.