Skip to content

test(web): guard every upstream name the fork stylesheets borrow - #108

Merged
NoahHendrickson merged 3 commits into
customfrom
t3code/fork-upstream-names-guard
Sep 2, 2026
Merged

NoahHendrickson merged 3 commits into
customfrom
t3code/fork-upstream-names-guard

Conversation

@NoahHendrickson

Copy link
Copy Markdown
Owner

Problem

Every fork stylesheet reaches into upstream by name: a CSS variable it reads with var(), a data-slot or data attribute it selects, a utility class it targets. None of those is a typed contract, so an upstream sync can rename one and nothing fails to compile. The fork rule simply stops matching, or worse, an unresolved var() invalidates the whole declaration around it at computed-value time.

That is exactly how the timeline cutoff broke in #107: upstream pingdotgg#8799 renamed --topbar-scroll-fade-height, the fork's mask rule kept reading the old name, mask-size went invalid, and the transcript painted straight through the composer. The guard for that customization stayed green because it pinned the fork's own text rather than the name upstream exports.

Fix

A new guard, forkUpstreamNames.test.ts, that resolves every name the fork stylesheets borrow against the web app's own source:

  • CSS variables referenced with var() must be declared in some stylesheet, as a Tailwind arbitrary property in a className, or stamped from TypeScript. Tailwind's default theme is included for --color-*.
  • Data attributes and slot values in selectors must be stamped by some component, including bare JSX booleans.
  • Class selectors must appear in a className or an @utility, with Tailwind escaping undone.

Tests and comments are excluded from the corpus, so a stale name that survives only in an assertion or a comment cannot count as a declaration. A short allowlist covers names Base UI stamps at runtime (positioner variables, the tooltip-trigger attribute), each with a note saying who sets it.

It asserts existence only, never meaning. A token whose value changed semantics still needs the guard that owns that customization.

Its first run also found .chat-composer-glass in the Cool Darker glass block: upstream pingdotgg#8734 dropped that class for arbitrary utilities on ComposerSurface, and the composer's filters are already cleared by the composer-box rules, so the dead arm is removed and the vibrancy manifest entry no longer lists it.

Manifest entry fork-upstream-names added, tier 1, watching index.css.

Verification

  • Swapped the old fade name back into theme.custom.css and ran the guard: it fails naming --topbar-scroll-fade-height (theme.custom.css). Restored, it passes.
  • vp test run src/__fork_guards__ → 46 files, 385 tests passing.
  • vp run typecheck in apps/web clean.

Stacked on #107: the fade fix there is what makes this guard pass on the current stylesheet. Merge #107 first, or this PR's guard will report the same rename against custom, which is the correct answer.

No runtime code changes beyond removing one dead selector arm. Web only.

Claude Fable 5.1 via Claude Code.

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.4 KiB 13.4 KiB −10 B (−0.1%) 15.1 KiB ✅
Codex Thread snapshot wire 6.9 KiB 6.9 KiB −4 B (−0.1%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.6 KiB 6.5 KiB −6 B (−0.1%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 10 10 0 (0.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.3 KiB −171 B (−1.2%) 15.1 KiB ✅
Claude Thread snapshot wire 6.9 KiB 6.9 KiB −3 B (−0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.5 KiB 6.4 KiB −168 B (−2.5%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.8 KiB 56.4 KiB −1.5 KiB (−2.5%) 66.4 KiB ✅
Claude Live turn messages 10 9 −1 (−10.0%) 21 ✅

Baseline: 11b1b69 · PR result: e325cd5 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST CHANGES. The new guard recreates the failure mode it claims to catch, for every data attribute and slot the fork stylesheets select.

sourceCorpus walks all of apps/web/src for ts/tsx/css/html, excluding only tests and __fork_guards__/. That walk includes theme.custom.css, theme.custom.palettes.css, and custom/**/*.css — the same files collect() harvests references from. The attribute check then asks whether the attribute name appears as a token in that blob, and whether the value appears as a quoted literal. Both strings already live in the selector that produced the reference.

The attach-button rules in this PR are the next silent miss. Today ChatComposer.tsx still stamps data-chat-composer-actions="right", so the test is green for two reasons at once. After an upstream rename, the stylesheet still contains [data-chat-composer-actions="right"], sourceCorpus still matches, and CI stays green. Same class of bug as --topbar-scroll-fade-height in #107.

Split the corpora by role, not by ownership:

  1. References — already forkCss. Keep.
  2. Declarations (variables) — all CSS, including fork sheets. A borrowed var(--old) with no name: declaration still fails. That path actually works; it is why the fade rename is a real catch.
  3. Stamps (attributes, slots, classes) — ts/tsx/html only. Drop css from the sourceCorpus filter. Do not denylist FORK_STYLESHEETS out of a mixed blob. CSS selectors are not stamps.

Variable and class checks are fine (a leading . does not satisfy the class token regex; that is how .chat-composer-glass was found). The CSS presentation changes belong in these files and the sibling guards pin their outcomes. One structural fix, then this is the right guard.

Open in Web View Automation 

Sent by Cursor Automation: Thermo nuke 4.6

Comment on lines +90 to +99
const sourceCorpus = (() => {
const files = walk(webSrc).filter(
(file) =>
/\.(?:tsx?|css|html)$/u.test(file) &&
!/\.test\.[cm]?tsx?$/u.test(file) &&
!file.includes(`${NodePath.sep}__fork_guards__${NodePath.sep}`),
);
files.push(NodePath.join(webRoot, "index.html"));
return files.map((file) => stripComments(NodeFS.readFileSync(file, "utf8"))).join("\n");
})();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a structural miss, not a coverage nit. css in this filter puts theme.custom.css, theme.custom.palettes.css, and custom/**/*.css into sourceCorpus. Those are the files collect() reads references from.

The attribute check later is a token / "value" search against that blob. [data-chat-composer-actions="right"] in this PR's own stylesheet satisfies both, so an upstream rename that removes the stamp from ChatComposer.tsx leaves CI green. That is the #107 failure mode this guard exists to catch.

Do not denylist the fork sheets. CSS selectors are not stamps. Drop css from this filter and keep declarationCorpus for var() declarations (including fork-owned --fork-*). Stamps are ts/tsx/html.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac6aec9. sourceCorpus (stamps) is now .ts/.tsx plus index.html only; declarationCorpus keeps every stylesheet, fork sheets included, for var() targets. Probed both ways: renaming the composer-host slot value in ComposerSurface fails the guard with [data-slot="composer-host"] (theme.custom.css), and renaming the fork-timeline-cutoff class in MessagesTimeline fails it with .fork-timeline-cutoff (theme.custom.css). Also rebased onto the current #107 head, which now stamps the attach button by name.

@NoahHendrickson NoahHendrickson left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issue: sourceCorpus includes CSS, then the data-attribute test searches sourceCorpus for each referenced attribute name and quoted value. The selector being validated therefore supplies both strings itself. For example, after removing data-chat-composer-actions="right" from ChatComposer, theme.custom.css still contains [data-chat-composer-actions="right"], so the guard remains green—the same self-validating failure mode this PR is intended to eliminate. Split the corpora by role: keep CSS in declarationCorpus for variables, but build the attribute/slot stamp corpus from TS/TSX/HTML only.

xxxxxxxxxxxxx and others added 2 commits September 2, 2026 12:54
Every fork stylesheet reaches into upstream by name — a CSS variable read
with var(), a data attribute or data-slot value it selects, a class it
targets — and none of those is a typed contract. An upstream rename
compiles clean and simply un-matches the fork rule; an unresolved var()
is worse, since it invalidates the whole declaration around it at
computed-value time. That is how the timeline cutoff vanished when pingdotgg#8799
renamed --topbar-scroll-fade-height while the guard for it stayed green
by pinning the fork's own text.

forkUpstreamNames.test.ts resolves each referenced name against the web
source (tests and comments excluded, so a stale name quoted in a comment
cannot count as a declaration), Tailwind's default theme, and a short
allowlist of names Base UI stamps at runtime. Verified it fails on the
pingdotgg#8799 rename with the old name swapped back in.

Its first run also found .chat-composer-glass in the Cool Darker glass
block: upstream pingdotgg#8734 dropped that class for arbitrary utilities on
ComposerSurface, and the composer's filters are already cleared by the
composer-box rules, so the dead arm is removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review on #108: the stamp corpus walked CSS too, so every data attribute
and slot value the fork stylesheets select was satisfied by the selector
that produced the reference — the self-validating failure mode the guard
exists to catch.

Declarations (var() targets) still come from every stylesheet, fork
sheets included, since a borrowed variable with no declaration anywhere
is the real catch. Stamps come from .ts/.tsx and index.html only.
Probed by renaming the composer-host slot value in ComposerSurface and
the fork-timeline-cutoff class in MessagesTimeline: both now fail the
guard naming the stale selector.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@NoahHendrickson
NoahHendrickson force-pushed the t3code/fork-upstream-names-guard branch from 5a09bc4 to ac6aec9 Compare September 2, 2026 16:55
@NoahHendrickson

Copy link
Copy Markdown
Owner Author

Addressed the blocking issue in ac6aec9: the corpora are split by role. Stamps (attributes, slots, classes) are resolved against TS/TSX/HTML only, so a selector can never satisfy its own check; variable declarations still come from every stylesheet. Verified the guard now fails when a slot value or class stamp is renamed in source. Rebased onto #107's latest head.

@github-actions github-actions Bot added size:M and removed size:L labels Sep 2, 2026
@NoahHendrickson
NoahHendrickson merged commit 1767a8c into custom Sep 2, 2026
19 checks passed
@NoahHendrickson
NoahHendrickson deleted the t3code/fork-upstream-names-guard branch September 2, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants