diff --git a/.fork/customizations.yaml b/.fork/customizations.yaml index dc18cca4625c..4aac3eb704ab 100644 --- a/.fork/customizations.yaml +++ b/.fork/customizations.yaml @@ -1715,9 +1715,60 @@ in favor of their own tsconfig island; the engine is bundled to an injectable IIFE string by the fork:design-mode-engine Vite plugin as virtual:fork-design-mode-engine. + + Two accuracy rules govern what a change request is allowed to assert, both + added after a request cost more time than doing the edit by hand, and both + since corrected for how the underlying APIs actually behave. + + First, a css bullet may only say "change `px-2.5` -> `px-1`" once + cssOrigin.ts has PROVED that class is the lever, by removing it and + re-measuring while the element shows its original cascade. Removal alone + cannot decide a TIE — if something else declares the same value, removing + the class moves nothing — so a tie gets a second probe: the utility's own + declared value is applied inline and re-measured, which resolves + `calc(var(--spacing) * N)` in the element's context. A declared value that + computes to something OTHER than the measured one means the utility + provably lost (the motivating `px-2.5`-is-10px-but-measured-8px case is + decided, not hedged); only a genuine same-value tie is reported as + ambiguous and names BOTH. Culprit naming excludes exactly ONE selector — + the probed utility's own single-class rule, a tautology — and nothing + broader: a plain-CSS project's `.composer-chip` is the likeliest culprit + there even though the element carries the class, and a competing utility on + the element is a finding. When the class provably is not the lever the + bullet names the winning rule and its file (Vite's data-vite-dev-id is what + still carries the authored filename). Ranking is layer-aware: unlayered + beats layered above + specificity, because Tailwind v4 utilities are in `@layer utilities` and an + unlayered fork rule of trivial specificity beats them. Two CSSOM traps are + load-bearing here: since CSS Nesting a CSSStyleRule HAS a (truthy, empty) + `cssRules`, so style rules must be handled before any grouping check or + every declaration is skipped; and `matchMedia` never throws, so only + CSSMediaRule may be condition-gated — `@supports`/`@container` must be + descended into unconditionally. + + Second, a source location is only forwarded when react-grab reports the + reporting frame as `isSymbolicated === true`. Not `!== false`: react-grab + 0.1.44 / bippy 0.5.41 return a frame UNTOUCHED when symbolication fails, so + there is no `false` to test for and `!== false` passed every failure. The + frame is paired by position first, since the context's filePath is + normalized while the frame's fileName is a raw served URL. Rejection costs + the LINE, not the file: `data-t3-source-file` keeps the authored path and + `data-t3-component` the fiber's component name, rendered as "Rendered by + `` in path (line not resolvable)" and only when no location resolved. + Hint results are TTL-evicted from the preload cache like nulls — they are + not successes, and caching them would pin a transient failure for the + element's lifetime. + + Relatedly, the NO_PREVIEW guardrail must not promise upstream's automatic + verification while this fork has not vendored client/verifier.ts; a guard + test pins that wording. tier: 4 files: - apps/web/src/custom/designMode/protocol.ts + # Outside engine/ on purpose: like protocol.ts it crosses the TS-island fence + # (the engine tsconfig includes it by path) so the web project can type-check + # and unit-test it, which files under engine/ cannot be. + - apps/web/src/custom/designMode/cssOrigin.ts - apps/web/src/custom/designMode/designModeStore.ts - apps/web/src/custom/designMode/designModeBridge.ts - apps/web/src/custom/designMode/designChangeDraftStore.ts @@ -1819,3 +1870,4 @@ - apps/web/package.json verify: - apps/web/src/__fork_guards__/forkDesignMode.test.ts + - apps/web/src/__fork_guards__/forkDesignModeCssOrigin.test.ts diff --git a/apps/desktop/src/preview/DesignSourceResolver.test.ts b/apps/desktop/src/preview/DesignSourceResolver.test.ts index cc7946d7c9a4..4679874be47a 100644 --- a/apps/desktop/src/preview/DesignSourceResolver.test.ts +++ b/apps/desktop/src/preview/DesignSourceResolver.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vite-plus/test"; -import { normalizeResolvedSource } from "./DesignSourceResult.ts"; +import { describeResolvedSource, normalizeResolvedSource } from "./DesignSourceResult.ts"; const VALID = { filePath: "/src/components/Button.tsx", @@ -21,13 +21,27 @@ describe("normalizeResolvedSource", () => { expect(normalizeResolvedSource({ ...VALID, columnNumber: 0 })?.column).toBe(1); }); - it("carries only the source location — extra react-grab context never crosses", () => { + it("carries the source location and component name — nothing else crosses", () => { const result = normalizeResolvedSource({ ...VALID, componentName: "SubmitButton", selector: "button.primary", + snippet: "