fix(lint,skills): make the reflow-prop fix faithful, not a lossy scale swap

The previous fixHint and skill guidance told agents to "settle via scale" for
text-reflow props — but uniform scale resizes the glyphs, it does not change
the gaps between them, so swapping a letterSpacing tween for scale lints clean
while silently animating a different thing.

Make the guidance faithful per property:
- fontSize -> scale (same visual, sub-pixel smooth).
- letterSpacing / wordSpacing -> split the text into per-character elements and
  animate each glyph's x (the spread); uniform scale is NOT equivalent. Or hold
  the value statically if it's a minor flourish.

Add a "preserve the intent" principle to gsap-transforms-and-perf.md: a fix must
reproduce the same start/end state and trajectory and be verified against the
ORIGINAL render, not just pass lint — lint-clean-and-smooth is not the bar,
faithful-and-smooth is.

Redo the vignette demo title with the faithful per-glyph x spread (15.12px =
(0.32em-0.18em)*108px, centered about 8 chars). Render-verified: settled
endpoint matches the original at 46.5 dB, settle is smooth (24/24 unique frames).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-06-29 10:59:49 -07:00
co-authored by Claude Opus 4.8
parent 082280e8de
commit 6e21072f44
4 changed files with 52 additions and 11 deletions
+18 -2
View File
@@ -1234,9 +1234,25 @@ export const gsapRules: LintRule<LintContext>[] = [
);
}
if (reflowProps.length > 0) {
// Faithful fix differs by property: fontSize maps to scale (same visual), but
// letterSpacing/wordSpacing do NOT — uniform scale resizes glyphs, it does not
// change the gaps between them. The smooth equivalent of a spacing tween is a
// per-glyph split with an x transform per character.
const sizing = reflowProps.filter((p) => p === "fontSize");
const spacing = reflowProps.filter((p) => p !== "fontSize");
const parts: string[] = [];
if (sizing.length > 0) {
parts.push(`replace ${sizing.join("/")} with scale (same visual, no reflow)`);
}
if (spacing.length > 0) {
parts.push(
`for ${spacing.join("/")}, split the text into per-character elements and animate ` +
"each glyph's x (the spread) — uniform scale is NOT equivalent — or hold the final value statically",
);
}
fixes.push(
`do not animate ${reflowProps.join("/")} (they reflow text and snap glyph positions) ` +
"settle via scale, or set the final value statically",
`do not animate ${reflowProps.join("/")} (they reflow text and snap glyph positions): ` +
parts.join("; "),
);
}
if (usesRoundProps) fixes.push("remove roundProps");