Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Semver applies from 1.0.0. A breaking change to a documented API needs a major
In an app whose default locale is not `en`, every tab read `⟦dev.panel.mail.title⟧`. The
framework catalog is registered under `en` only, and the shell translated through the app's
ambient locale.
- **render:** `setStylesheetRoot` resolves a relative root, so `loadApp('.')` from `/app` classifies
sheets correctly. Found in review of #528.

## 22.2.1 - 2026-09-25

Expand Down
9 changes: 9 additions & 0 deletions packages/render/src/module-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ describe('an app served from /app', () => {
expect(site).not.toContain('color:blue');
});

// `loadApp('.')` from `/app` names a RELATIVE root, and every path the plugin hands the loader is
// absolute: compared as written, the root matched nothing and the `/app/` segment won again.
test('a relative root is resolved against the working directory before it is compared', () => {
setStylesheetRoot('app');
const sheet = join(process.cwd(), 'app', 'apps/web/site/page.module.scss');
loadStylesheet(sheet, '.hero{color:red}');
expect(registeredStylesheets()[0]?.surface).toBe('site');
});

// The order `x build` meets: a sheet can register before `loadApp` names the root, and a
// classification frozen at registration would keep the answer the wrong root gave.
test('naming the root reclassifies what registered before it, and moves the revision', () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/render/src/module-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
* `bun test` all load a component the same way and there is no separate "bundled" behaviour.
*/

// why: Bun ships no path API, and a relative app root has to be resolved against the working
// directory before it can be compared with the absolute paths the Bun plugin hands this loader.
import { resolve } from 'node:path';
import { renderThrowable } from '@ultimat3/core';
import { compileStylesheet, isGlobalStylesheet, stripCharset } from './css-modules';
import { PrerenderFailedError } from './errors';
Expand Down Expand Up @@ -115,10 +118,12 @@ const surfaceOfSheet = (path: string): Surface | null =>
* Every sheet already registered is classified again, because a sheet can load before the root is
* named — and a classification frozen then would keep the answer the wrong root gave. The revision
* moves only when an answer does, so naming the same root twice mints no new stylesheet URL.
* `undefined` returns to the working directory.
* A relative root is resolved against the working directory. `undefined` returns to the working
* directory.
*/
export function setStylesheetRoot(root: string | undefined): void {
stylesheetRoot = root;
// Resolved: `loadApp('.')` is a legal call, and `.` is a prefix of no absolute path.
stylesheetRoot = root === undefined ? undefined : resolve(root);
for (const [path, sheet] of stylesheets) {
const surface = surfaceOfSheet(path);
if (surface === sheet.surface) continue;
Expand Down
Loading