mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-21 10:36:27 +03:00
bake: the anchor must have matched one element on the page
The lasting rules a bake appends apply to every element the anchor matches, so a `tag.class` anchor shared by siblings (three cards from one JSX element, say) restyled all of them, not the element the user picked. The overlay now journals, with the generate event, the anchor it would bake on (`element.anchor`: the id, else the tag with its classes) and how many elements matched it when Go fired (`element.anchorMatches`). The planner bakes only when the source anchor is that same selector and the count is one; otherwise it leaves the carbonize block with the count in `bakeSkipped`, and the agent integrates the variant by hand. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
committed by
Abdul Wahab
co-authored by
Claude Fable 5.1
parent
0166cbb870
commit
71a3341289
@@ -1524,7 +1524,7 @@ Body JSON `{token, type, …}`. Order of checks: JSON parse → token → `type=
|
||||
VISUAL_ACTIONS (order): `impeccable, bolder, quieter, distill, polish, typeset, colorize, layout, adapt, animate, delight, overdrive` (labels Freeform, Bolder, …).
|
||||
|
||||
Browser payloads (exact fields):
|
||||
- `generate` (replace): `{type:'generate', id, action, freeformPrompt?, count, pageUrl: location.pathname, element: extractContext(el), comments?:[{x,y,text}], strokes?:[{points:[[x,y],…]}], clientSentAt, screenshotPath?}`. `extractContext` = `{tagName(lower), id|null, classes:[…], textContent (≤500), outerHTML (sanitized ≤10000), computedStyles:{'font-family','font-size','font-weight','line-height','color','background','background-color','padding','margin','display','position','gap','border-radius','box-shadow'}, cssCustomProperties:{--x:v}, parentContext:'<tag id="" class="">'|null, boundingRect:{width,height}}`. Without annotations the event is POSTed before screenshot capture; with annotations the PNG is first `POST /annotation`ed and `screenshotPath` (abs path returned) is added.
|
||||
- `generate` (replace): `{type:'generate', id, action, freeformPrompt?, count, pageUrl: location.pathname, element: extractContext(el), comments?:[{x,y,text}], strokes?:[{points:[[x,y],…]}], clientSentAt, screenshotPath?}`. `extractContext` = `{tagName(lower), id|null, classes:[…], anchor ('#id' when the id is a CSS identifier, else 'tag.class…' over the classes that are, else null: the selector a mechanical bake would anchor lasting rules on), anchorMatches (how many elements matched `anchor` when the event was built; null without one), textContent (≤500), outerHTML (sanitized ≤10000), computedStyles:{'font-family','font-size','font-weight','line-height','color','background','background-color','padding','margin','display','position','gap','border-radius','box-shadow'}, cssCustomProperties:{--x:v}, parentContext:'<tag id="" class="">'|null, boundingRect:{width,height}}`. Without annotations the event is POSTed before screenshot capture; with annotations the PNG is first `POST /annotation`ed and `screenshotPath` (abs path returned) is added.
|
||||
- `generate` (insert): `{type:'generate', mode:'insert', id, count, pageUrl, insert:{position, anchor: extractContext(anchor)}, placeholder:{width,height}, freeformPrompt?, comments?, strokes?, clientSentAt}`.
|
||||
- `checkpoint`: `{type:'checkpoint', id, revision (monotone per browser, persisted in localStorage), revisionDomain:'browser', owner:<8-hex browser owner id>, phase: state.toLowerCase(), reason, pageUrl, expectedVariants, arrivedVariants, visibleVariant, sourceFile?, previewFile?, previewMode?, paramValues:{…}}`. Steer checkpoints: `{type:'checkpoint', id, revision, revisionDomain:'browser', owner, phase:'steer', reason, pageUrl, …extra}`. Reasons: `generate_started, variants_progress, variants_ready, browser_resumed, browser_resumed_deferred_wrapper, browser_resumed_svelte_component, param_changed, variant_anchor_missing, component_preview_anchor_missing, steer_input_focused, steer_submitted, steer_send_failed, steer_done, steer_error`. Only `variants_progress|variants_ready` count as publication progress.
|
||||
- `accept`: `{type:'accept', id, variantId: String(n), pageUrl, clientSentAt, paramValues?}`.
|
||||
@@ -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` → `<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`.
|
||||
**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}`); the anchor cannot be shown to match the accepted element alone (the journaled generate event's `element.anchor` must be the same selector, the id or the tag and class set, and its `element.anchorMatches` must be 1: `<anchor> matches N elements on the page; a lasting rule on it would restyle them all`, a different anchor, and a missing descriptor or count each name themselves); 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}`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user