Skip to content

Scope by the path relative to the build's working directory - #56

Merged
backspace merged 2 commits into
mainfrom
package-relative-scope-prefix
Oct 9, 2026
Merged

backspace merged 2 commits into
mainfrom
package-relative-scope-prefix

Conversation

@backspace

@backspace backspace commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Background and Goal

The first hash in a data-scopedcss-* attribute was the md5 of the component file's absolute path. Two builds of the same source at different paths, such as two CI runners or two developers' checkouts, therefore gave different attributes. In an app that prerenders HTML in one build and serves it with another build's CSS, the scoped rules then match nothing, and components render unstyled.

This identifies a file on disk by its path relative to the build's working directory (process.cwd()). Two checkouts that build from the same directory inside the checkout then give the same attribute. A filename that is not a file on disk (for example a synthetic module path a transpiler passes) is used as given, so those callers get the same prefix as before.

Key decisions and non-obvious mechanics

  • The build's working directory is part of the identity. Builds that must agree have to run from the same directory inside the checkout. A build of the same source from another directory gives different prefixes.
  • Both paths are resolved through symlinks. process.cwd() is always a real path. A filename reached through a symlink would otherwise give a relative path up to the root and back down, which holds the absolute path again.
  • This changes every prefix a build emits for files on disk. That is a visible change for any consumer that stores rendered HTML, which must be re-rendered once. Labelled enhancement.
  • Unit tests are new for this package. glimmer-scoped-css/tests/unique-identifier.test.cjs runs with node:test against the built dist, and the root pnpm test runs it before the test-app. With the old behaviour restored, the two working-directory tests fail.
  • Verified in Boxel first. The same change is a pnpm patch in Scope glimmer-scoped-css styles by package-relative path boxel#6594. Its CI builds the host at two runner paths, and both builds give the same 259 CSS file names and the same 456 scope prefixes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RhwZUafCP38ur35n87R7BW

@backspace backspace added the enhancement New feature or request label Oct 7, 2026
@backspace

Copy link
Copy Markdown
Contributor Author

[Claude Code 🤖] The red Tests and Floating Dependencies checks both come from one test-app test, Acceptance | scoped css: an addon can use scoped styles (7 of 8 pass). That test also fails on main. The last CI run on main (2025-10-21) is red, its logs have expired, and a local run of main (with test-addon-to-import built) fails the same test with the same 7/8. The new node:test unit tests pass in both jobs (5/5).

@backspace

Copy link
Copy Markdown
Contributor Author

[Claude Code 🤖] Root cause of the red an addon can use scoped styles test (also red on main): the scoped style is applied. Current Chrome reports the computed text-decoration shorthand without its default style keyword, as underline rgb(0, 0, 0), and the test compares against underline solid rgb(0, 0, 0). Checked on a bare page in Chrome 150 (CI runs 154): an element with text-decoration: underline solid rgb(0, 0, 0) reports the shorthand as underline rgb(0, 0, 0), while the longhands report underline / solid / rgb(0, 0, 0). In the built test-app the addon paragraph has the matching data-scopedcss attribute and computes underline rgb(0, 0, 0). A fix that does not depend on the browser's shorthand serialization is to assert textDecorationLine: 'underline' (and textDecorationColor) instead of the shorthand. Not part of this PR.

…bute everywhere

The scope prefix was the md5 of the absolute file path, so builds of the same
source at different paths gave different data-scopedcss attributes. HTML
prerendered by one build then did not match the CSS of another. A file inside
a package is now identified by its package name and its path inside the
package. A filename that is not a file on disk is used as given, so callers
that pass synthetic paths get the same prefix as before.

Adds node:test unit tests for uniqueIdentifier, run by `pnpm test`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhwZUafCP38ur35n87R7BW
@backspace
backspace force-pushed the package-relative-scope-prefix branch from fdba626 to d3b8312 Compare October 8, 2026 08:34
The first hash of the scope attribute is the md5 of the file's path
relative to process.cwd(), with both paths resolved through symlinks,
in place of the nearest named package and the path inside it. A
filename that does not exist on disk is still used as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhwZUafCP38ur35n87R7BW
@backspace backspace changed the title Scope by package-relative path so the same source gets the same attribute everywhere Scope by the path relative to the build's working directory Oct 8, 2026
@backspace
backspace merged commit e45fe0a into main Oct 9, 2026
1 of 3 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant