ompchamber 3.3.2 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ompchamber",
3
- "version": "3.3.2",
3
+ "version": "3.3.3",
4
4
  "description": "Developer web console and diagnostic chamber for AI Oh-My-Pi — Elysia + Preact on Bun.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Wijanarko Putra Rajeb <wijanarko.rajeb@gmail.com>",
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * `/fonts.css` is derived from the SOURCE stylesheets on every request, and this
8
+ * is the gate that keeps it that way.
9
+ *
10
+ * The sheet used to be assembled from a directory the bundler wrote as a side
11
+ * effect of bundling a CSS module — so it was produced once per process and
12
+ * could never be repaired. An `rm -rf` while the server ran, or a fresh clone
13
+ * that never built, left the route answering 0 bytes for the rest of that
14
+ * process and the terminal's Nerd symbols face vanished: private-use glyphs
15
+ * became tofu boxes with no error anywhere. These assertions fail if the sheet
16
+ * ever depends on a build artifact again.
17
+ *
18
+ * The last case is the end-to-end one: every font basename the sheet names must
19
+ * resolve to a file the server actually serves. A face pointing at a basename
20
+ * no package ships is a 404 the browser swallows, which looks identical to the
21
+ * font never having been declared.
22
+ */
23
+
24
+ import { describe, expect, test } from 'bun:test';
25
+ import { existsSync } from 'node:fs';
26
+ import { join } from 'node:path';
27
+
28
+ import { fontStylesheet, serveFontFile } from '@/server/lib/assets/font-css.server';
29
+ import { packageDir } from '@/server/lib/assets/fonts.server';
30
+
31
+ const sheet = await fontStylesheet();
32
+
33
+ /** Every `url(...)` target in the sheet. */
34
+ function urls(css: string): string[] {
35
+ return [...css.matchAll(/url\(\s*(['"]?)([^'")]+)\1\s*\)/g)].map(([, , target]) => target);
36
+ }
37
+
38
+ describe('/fonts.css is assembled from source, not from a build artifact', () => {
39
+ test('it declares the bundled Nerd symbols face', () => {
40
+ expect(sheet).toContain('OMPChamber Nerd Symbols');
41
+ expect(urls(sheet)).toContain('/fonts/symbols-nerd-font-mono.woff2');
42
+ // The range is what keeps the 1.1 MB face off every machine that has a
43
+ // Nerd Font of its own, so a sheet without it is a different contract.
44
+ expect(sheet).toContain('unicode-range');
45
+ });
46
+
47
+ test('it declares the text faces the bundle cannot carry', () => {
48
+ expect(sheet).toContain('Fira Code');
49
+ expect(sheet).toContain('KaTeX_Main');
50
+ expect(urls(sheet)).toContain('/fonts/fira-code-latin-400-normal.woff2');
51
+ });
52
+
53
+ test('every url is rewritten to the served route — no relative or node_modules path survives', () => {
54
+ const targets = urls(sheet);
55
+ expect(targets.length).toBeGreaterThan(20);
56
+ for (const target of targets) {
57
+ expect(target.startsWith('/fonts/')).toBe(true);
58
+ }
59
+ });
60
+
61
+ test('every basename the sheet names resolves to a served file', async () => {
62
+ for (const target of urls(sheet)) {
63
+ const response = await serveFontFile(target.slice('/fonts/'.length));
64
+ expect(response).not.toBeNull();
65
+ expect(response?.headers.get('content-type')).toMatch(/^font\//);
66
+ }
67
+ });
68
+ });
69
+
70
+ describe('the source tree is resolved by the package, not by the dependencies', () => {
71
+ // In a hoisted install the package's own files live under
72
+ // `node_modules/ompchamber/` while its dependencies sit in the parent
73
+ // `node_modules/` — so `packageRoot()` (the first root holding
74
+ // `node_modules`) is the PARENT, and looking for `src/` beside the hoisted
75
+ // packages finds nothing. That served an empty sheet and silently dropped
76
+ // every `@font-face`; the stylesheet has to resolve through `packageDir()`.
77
+ test('the stylesheets resolve through packageDir, and it finds the source tree', () => {
78
+ // This repo is a source checkout, where `packageDir()` and `packageRoot()`
79
+ // coincide. The assertion that matters is that the source tree is found AT
80
+ // ALL — the hoisted layout above is the case that breaks, and it cannot be
81
+ // staged from inside the repo without rewriting the module's own search
82
+ // roots, so the end-to-end proof for it lives in the manual run against a
83
+ // simulated published install.
84
+ expect(existsSync(join(packageDir(), 'src'))).toBe(true);
85
+ expect(existsSync(join(packageDir(), 'src', 'shared', 'lib', 'fonts', 'nerd-symbols.css'))).toBe(true);
86
+ });
87
+ });
@@ -10,32 +10,44 @@
10
10
  * local `url()`, and a font is always a local file — so a bundled face is either
11
11
  * a base64 blob or an absolute filesystem path. `lib/bundler/css.ts` lifts each
12
12
  * block out of its stylesheet and rewrites the urls to `<FONT_ROUTE_PREFIX><basename>`;
13
- * this module serves the result.
13
+ * this module re-declares them on `/fonts.css`.
14
14
  *
15
15
  * Two jobs, one route each:
16
16
  *
17
- * - `FONT_STYLESHEET_ROUTE` (`/fonts.css`) concatenates the extracted faces.
18
- * Read per request rather than cached: it is a handful of kilobytes, and a
19
- * cache is what would keep a stale face alive across an edit to a stylesheet.
20
- * A missing directory is an empty stylesheet, not a 404 — the shell links this
21
- * unconditionally.
17
+ * - `FONT_STYLESHEET_ROUTE` (`/fonts.css`) concatenates the faces, read from the
18
+ * source stylesheets under `src/` on every request. It is a handful of
19
+ * kilobytes and four small files, and reading them is what keeps the sheet
20
+ * correct for the process's whole life — the alternative, a directory the
21
+ * bundler wrote, is only ever produced as a side effect of a CSS module being
22
+ * bundled, so it is written once per process and can never be repaired: an
23
+ * `rm -rf` while the server runs (or a fresh clone that never ran a build)
24
+ * leaves `/fonts.css` empty for the rest of that process, and the terminal's
25
+ * Nerd symbols face silently disappears — the glyphs become tofu boxes with no
26
+ * error anywhere.
27
+ *
28
+ * Only a face whose `src` names a local font is re-declared, so a stylesheet
29
+ * carrying a remote face contributes nothing (see `font-faces.ts`). The faces
30
+ * the client bundle imports all live under `src/`, which is what makes the
31
+ * source tree the honest place to read them from: the same files the bundler
32
+ * strips are the files the browser is served.
22
33
  *
23
34
  * - `FONT_ROUTE_PREFIX` (`/fonts/<basename>`) serves the file itself from the
24
35
  * installed packages. The basename is the whole key, so the lookup is an index
25
36
  * rather than a path built from the request; see `fonts.server.ts`.
26
37
  */
27
38
 
28
- import { join } from 'path';
39
+ import { dirname, join } from 'path';
29
40
  import { Glob } from 'bun';
30
41
 
31
- import { packageRoot, resolveFontFile } from '@/server/lib/assets/fonts.server';
32
-
33
- /** Directory holding one extracted face file per source stylesheet. */
34
- export const FONT_FACE_DIR = join('.ompchamber-build', 'font-faces');
42
+ import { splitFontFaces } from '@/server/lib/assets/font-faces';
43
+ import { packageDir, resolveFontFile } from '@/server/lib/assets/fonts.server';
35
44
 
36
45
  /** The route the assembled stylesheet is served on. */
37
46
  export const FONT_STYLESHEET_ROUTE = '/fonts.css';
38
47
 
48
+ /** Where the stylesheets that carry faces live, relative to the package directory. */
49
+ const STYLE_SOURCE_ROOT = 'src';
50
+
39
51
  const FONT_MIME: Record<string, string> = {
40
52
  '.woff2': 'font/woff2',
41
53
  '.woff': 'font/woff',
@@ -48,20 +60,34 @@ const STYLESHEET_HEADERS = {
48
60
  'cache-control': 'no-store',
49
61
  } as const;
50
62
 
51
- /** Every extracted face file, concatenated in a stable order. */
52
- export async function fontStylesheet(): Promise<string> {
53
- const dir = join(packageRoot(), FONT_FACE_DIR);
54
- const glob = new Glob('*.css');
63
+ /** The absolute path of every stylesheet under the package's `src/`, sorted. */
64
+ function sourceStylesheets(): string[] {
65
+ const root = join(packageDir(), STYLE_SOURCE_ROOT);
66
+ const glob = new Glob('**/*.css');
55
67
  const files: string[] = [];
56
68
  try {
57
- for await (const file of glob.scan({ cwd: dir, onlyFiles: true })) files.push(file);
69
+ for (const file of glob.scanSync({ cwd: root, onlyFiles: true })) files.push(join(root, file));
58
70
  } catch {
59
- return '';
71
+ return [];
60
72
  }
61
- // Sorted so the stylesheet is byte-stable between builds: the browser caches
62
- // on the ETag, and an unstable order would invalidate it for no change.
73
+ // Sorted so the stylesheet is byte-stable between requests: the browser
74
+ // caches on the ETag, and an unstable order would invalidate it for no change.
63
75
  files.sort();
64
- const parts = await Promise.all(files.map((file) => Bun.file(join(dir, file)).text()));
76
+ return files;
77
+ }
78
+
79
+ /** Every `@font-face` block the source stylesheets declare, concatenated. */
80
+ export async function fontStylesheet(): Promise<string> {
81
+ const parts: string[] = [];
82
+ for (const path of sourceStylesheets()) {
83
+ // A read that fails contributes nothing rather than failing the sheet: the
84
+ // shell links this unconditionally, and one unreadable file must not take
85
+ // the faces beside it down with it.
86
+ const css = await Bun.file(path).text().catch(() => '');
87
+ if (!css) continue;
88
+ const { faces } = splitFontFaces(css, dirname(path));
89
+ if (faces.length > 0) parts.push(...faces);
90
+ }
65
91
  return parts.join('\n');
66
92
  }
67
93
 
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Pulling `@font-face` blocks out of a stylesheet, and pointing their `url()`s
8
+ * at the served route.
9
+ *
10
+ * Two callers, one reason to share: the bundler's CSS loader strips faces out
11
+ * of every module it bundles (`lib/bundler/css.ts`) because Bun resolves each
12
+ * local `url()` into base64 or an absolute filesystem path, and the server
13
+ * assembles `/fonts.css` from the same stylesheets
14
+ * (`lib/assets/font-css.server.ts`). The two must agree byte for byte — a face
15
+ * stripped from the bundle has to be the one the stylesheet re-declares, or the
16
+ * font silently never loads.
17
+ *
18
+ * Only a block whose `src` names a LOCAL font moves: a face pointing at a
19
+ * remote url is valid CSS the bundle can keep. `rewriteFontUrls` replaces every
20
+ * local font url with `<FONT_ROUTE_PREFIX><basename>`, which is the only form
21
+ * that survives Bun's loader — a same-origin path is not an escape hatch, since
22
+ * `url("/fonts/x.woff2")` fails the build with "Could not resolve".
23
+ */
24
+
25
+ import { basename, isAbsolute, resolve as resolvePath } from 'path';
26
+
27
+ import { FONT_ROUTE_PREFIX } from '@/server/lib/assets/fonts.server';
28
+
29
+ /** A `url(...)` whose target is a font file. */
30
+ const URL_FUNCTION = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
31
+ const FONT_FILE = /\.(?:woff2?|ttf|otf)$/i;
32
+ /** Schemes Bun leaves alone, and the prefix this module itself emits. */
33
+ const EXTERNAL = /^(?:data:|https?:|blob:|\/\/)/i;
34
+
35
+ /** One `@font-face` block, braces included. Faces cannot nest. */
36
+ const FONT_FACE = /@font-face\s*\{[^{}]*\}/g;
37
+
38
+ /** Absolute path of a local font url, or null when it is not one. */
39
+ function localFont(specifier: string, cssDir: string): string | null {
40
+ if (EXTERNAL.test(specifier) || specifier.startsWith(FONT_ROUTE_PREFIX)) return null;
41
+ if (!FONT_FILE.test(specifier)) return null;
42
+ return isAbsolute(specifier) ? specifier : resolvePath(cssDir, specifier);
43
+ }
44
+
45
+ /** Point every font `url()` at the served path. */
46
+ export function rewriteFontUrls(css: string, cssDir: string): string {
47
+ return css.replace(URL_FUNCTION, (whole, quote: string, specifier: string) => {
48
+ const absolute = localFont(specifier, cssDir);
49
+ if (!absolute) return whole;
50
+ return `url(${quote}${FONT_ROUTE_PREFIX}${basename(absolute)}${quote})`;
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Split `@font-face` blocks out of a stylesheet.
56
+ *
57
+ * `cssDir` resolves a relative `src` url, so it must be the directory of the
58
+ * file the text came from — not the bundle's.
59
+ */
60
+ export function splitFontFaces(css: string, cssDir: string): { rest: string; faces: string[] } {
61
+ const faces: string[] = [];
62
+ const rest = css.replace(FONT_FACE, (block) => {
63
+ const hasLocal = [...block.matchAll(URL_FUNCTION)].some(([, , spec]) => localFont(spec, cssDir));
64
+ if (!hasLocal) return block;
65
+ faces.push(rewriteFontUrls(block, cssDir));
66
+ return '';
67
+ });
68
+ return { rest, faces };
69
+ }
@@ -77,12 +77,16 @@ let index: Record<string, string> | null = null;
77
77
  /**
78
78
  * The package root — the first search root that holds `node_modules`.
79
79
  *
80
- * Both ends of the build need the same answer: the plugin writes the extracted
81
- * face files here and the server reads them back, and an AOT bundle runs from
82
- * `dist/client` while a source checkout runs from the repo. Falling back to the
80
+ * The AOT bundle runs from `dist/client` while a source checkout runs from the
81
+ * repo, and both resolve their dependencies through this. Falling back to the
83
82
  * cwd keeps a checkout with no `node_modules` (a publish dry-run) working.
83
+ *
84
+ * This answers "where are the DEPENDENCIES", which is not the same question as
85
+ * `packageDir()` below: a hoisted install keeps the package's own files under
86
+ * `node_modules/ompchamber/` while its dependencies sit in the parent
87
+ * `node_modules/`, so the first root holding `node_modules` is the PARENT.
84
88
  */
85
- export function packageRoot(): string {
89
+ function packageRoot(): string {
86
90
  for (const root of searchRoots()) {
87
91
  // `existsSync` rather than `Bun.file().exists()`: the latter is async and
88
92
  // file-only, and `node_modules` is a directory.
@@ -91,6 +95,25 @@ export function packageRoot(): string {
91
95
  return process.cwd();
92
96
  }
93
97
 
98
+ /**
99
+ * The package's own directory — the first search root that holds `src/`.
100
+ *
101
+ * The source stylesheets `lib/assets/font-css.server.ts` reads for `/fonts.css`
102
+ * live there, and they must be found in every install shape. In a source
103
+ * checkout and a non-hoisted install this is the same directory
104
+ * `packageRoot()` returns; in a hoisted install it is the package under
105
+ * `node_modules/`, one level BELOW the root that holds the dependencies.
106
+ * Resolving the source tree through `packageRoot()` there would look for
107
+ * `src/` beside the hoisted packages and find nothing, which serves an empty
108
+ * stylesheet and silently drops every `@font-face`.
109
+ */
110
+ export function packageDir(): string {
111
+ for (const root of searchRoots()) {
112
+ if (existsSync(join(root, 'src'))) return root;
113
+ }
114
+ return packageRoot();
115
+ }
116
+
94
117
  async function buildIndex(): Promise<Record<string, string>> {
95
118
  const found: Record<string, string> = {};
96
119
  const glob = new Glob('*.{woff2,woff,ttf,otf}');
@@ -33,9 +33,9 @@
33
33
  import { join, relative } from 'path';
34
34
  import type { BuildOutput, BunPlugin } from 'bun';
35
35
 
36
- import { packageRoot } from '@/server/lib/assets/fonts.server';
36
+ import { packageDir } from '@/server/lib/assets/fonts.server';
37
37
 
38
- /** The shell bundle's entrypoint, relative to the package root. */
38
+ /** The shell bundle's entrypoint, relative to the package directory. */
39
39
  const SHELL_ENTRYPOINT = 'index.html';
40
40
 
41
41
  /** Bun's own config file, and the section naming the bundle's plugins. */
@@ -109,8 +109,13 @@ function formatLog(log: BundleLog, root: string): string {
109
109
  /**
110
110
  * The bundle errors behind a shell that would not render, or null when this
111
111
  * build is not the reason (a clean bundle means the failure lies elsewhere).
112
+ *
113
+ * `root` is the PACKAGE, not `packageRoot()`: the entrypoint is `index.html`
114
+ * beside the source, and in a hoisted install the first root holding
115
+ * `node_modules` is the parent, where `index.html` does not exist — so the probe
116
+ * would report no failure at all and the real build error would never be shown.
112
117
  */
113
- export async function describeShellBuildFailure(root = packageRoot()): Promise<string | null> {
118
+ export async function describeShellBuildFailure(root = packageDir()): Promise<string | null> {
114
119
  const entrypoint = join(root, SHELL_ENTRYPOINT);
115
120
  if (!(await Bun.file(entrypoint).exists())) return null;
116
121
 
@@ -30,18 +30,17 @@
30
30
  * rewrite inside the bundle can work.
31
31
  *
32
32
  * The faces therefore leave the bundle. Each block is rewritten to
33
- * `<FONT_ROUTE_PREFIX><basename>` and written to its own file under
34
- * `FONT_FACE_DIR`, keyed by the stylesheet it came from — a document Bun
35
- * never parses, which is what lets the urls survive. The server concatenates
36
- * that directory into `/fonts.css` (`lib/assets/font-css.server.ts`) and the
37
- * shell links it, so the layout rules stay bundled while the faces stay
33
+ * `<FONT_ROUTE_PREFIX><basename>` and dropped from the module, and the server
34
+ * re-declares them on `/fonts.css` from the SAME source stylesheets
35
+ * (`lib/assets/font-css.server.ts`, `lib/assets/font-faces.ts`). The shell
36
+ * links that route, so the layout rules stay bundled while the faces stay
38
37
  * cacheable files.
39
38
  *
40
- * Per-source files rather than one accumulated file because `onEnd` is NOT
41
- * invoked by `serve.static` (verified: `onStart` and `onLoad` fire, `onEnd`
42
- * never does), so there is no "build finished" moment to flush at. Writing
43
- * each source's faces as its `onLoad` returns is order-independent and works
44
- * identically under both entry points.
39
+ * The server derives the faces from source rather than from a directory this
40
+ * plugin wrote, because `onEnd` is NOT invoked by `serve.static` (verified:
41
+ * `onStart` and `onLoad` fire, `onEnd` never does) and a written artifact is
42
+ * produced exactly once per process — a `rm -rf` after boot, or a fresh clone
43
+ * that never built, would leave `/fonts.css` empty for good.
45
44
  *
46
45
  * 3. **Nested at-rules are flattened** (`flattenNestedAtRules`, registered
47
46
  * AFTER Tailwind so it sees the expanded tree). Bun's CSS minifier keeps the
@@ -53,57 +52,14 @@
53
52
  * the measurement; this one only has to run it.
54
53
  */
55
54
 
56
- import { basename, dirname, isAbsolute, join, resolve as resolvePath } from 'path';
55
+ import { dirname } from 'path';
57
56
  import postcss from 'postcss';
58
57
  import tailwind from '@tailwindcss/postcss';
59
58
  import type { BunPlugin } from 'bun';
60
59
 
61
- import { FONT_FACE_DIR } from '@/server/lib/assets/font-css.server';
62
- import { FONT_ROUTE_PREFIX, packageRoot } from '@/server/lib/assets/fonts.server';
60
+ import { splitFontFaces } from '@/server/lib/assets/font-faces';
63
61
  import { flattenNestedAtRules } from '@/server/lib/bundler/nested-at-rules';
64
62
 
65
- /** A `url(...)` whose target is a font file. */
66
- const URL_FUNCTION = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
67
- const FONT_FILE = /\.(?:woff2?|ttf|otf)$/i;
68
- /** Schemes Bun leaves alone, and the prefix this plugin itself emits. */
69
- const EXTERNAL = /^(?:data:|https?:|blob:|\/\/)/i;
70
-
71
- /** One `@font-face` block, braces included. Faces cannot nest. */
72
- const FONT_FACE = /@font-face\s*\{[^{}]*\}/g;
73
-
74
- /** Absolute path of a local font url, or null when it is not one. */
75
- function localFont(specifier: string, cssDir: string): string | null {
76
- if (EXTERNAL.test(specifier) || specifier.startsWith(FONT_ROUTE_PREFIX)) return null;
77
- if (!FONT_FILE.test(specifier)) return null;
78
- return isAbsolute(specifier) ? specifier : resolvePath(cssDir, specifier);
79
- }
80
-
81
- /** Point every font `url()` at the served path. */
82
- function rewriteFontUrls(css: string, cssDir: string): string {
83
- return css.replace(URL_FUNCTION, (whole, quote: string, specifier: string) => {
84
- const absolute = localFont(specifier, cssDir);
85
- if (!absolute) return whole;
86
- return `url(${quote}${FONT_ROUTE_PREFIX}${basename(absolute)}${quote})`;
87
- });
88
- }
89
-
90
- /**
91
- * Split `@font-face` blocks out of a stylesheet.
92
- *
93
- * Only blocks whose `src` names a local font move: a face pointing at a remote
94
- * url is valid CSS the bundle can keep.
95
- */
96
- function splitFontFaces(css: string, cssDir: string): { rest: string; faces: string[] } {
97
- const faces: string[] = [];
98
- const rest = css.replace(FONT_FACE, (block) => {
99
- const hasLocal = [...block.matchAll(URL_FUNCTION)].some(([, , spec]) => localFont(spec, cssDir));
100
- if (!hasLocal) return block;
101
- faces.push(rewriteFontUrls(block, cssDir));
102
- return '';
103
- });
104
- return { rest, faces };
105
- }
106
-
107
63
  const plugin: BunPlugin = {
108
64
  name: 'ompchamber-css',
109
65
  setup(build) {
@@ -117,12 +73,7 @@ const plugin: BunPlugin = {
117
73
  const expanded = await postcss([tailwind(), flattenNestedAtRules()]).process(source, {
118
74
  from: args.path,
119
75
  });
120
- const { rest, faces } = splitFontFaces(expanded.css, dirname(args.path));
121
- if (faces.length > 0) {
122
- const target = join(packageRoot(), FONT_FACE_DIR, `${basename(args.path)}`);
123
- const header = `/* GENERATED from ${args.path} — do not edit. */\n`;
124
- await Bun.write(target, header + faces.join('\n') + '\n');
125
- }
76
+ const { rest } = splitFontFaces(expanded.css, dirname(args.path));
126
77
  return { contents: rest, loader: 'css' };
127
78
  });
128
79
  },
@@ -135,10 +135,10 @@ export const ssrRoutes = new Elysia({ name: 'ssr' }).get('*', async ({ request }
135
135
  const themeStyle = `<style id="${THEME_STYLE_ELEMENT_ID}">${themeStyleSheet()}</style>`;
136
136
  // The font faces are a stylesheet the bundler cannot carry: Bun resolves every
137
137
  // local `url()` in CSS, so a bundled face is either base64 or an absolute
138
- // filesystem path. `lib/bundler/css.ts` lifts the blocks out and the route
139
- // below serves them, which is why the link is injected here — a
140
- // `<link href="/fonts.css">` in index.html would be resolved by the HTML
141
- // loader and fail the same way.
138
+ // filesystem path. `lib/bundler/css.ts` strips the blocks out of the bundle
139
+ // and `lib/assets/font-css.server.ts` re-declares them on the route below,
140
+ // which is why the link is injected here — a `<link href="/fonts.css">` in
141
+ // index.html would be resolved by the HTML loader and fail the same way.
142
142
  const fontLink = `<link rel="stylesheet" href="${FONT_STYLESHEET_ROUTE}">`;
143
143
 
144
144
  // Bun's dev asset URLs move to this server's prefix so the proxy above can add
@@ -2,7 +2,7 @@ import { extname, join, normalize, resolve, sep } from 'path';
2
2
 
3
3
  import { notModified } from '@/server/lib/assets/conditional.server';
4
4
  import { FONT_STYLESHEET_ROUTE, serveFontFile, serveFontStylesheet } from '@/server/lib/assets/font-css.server';
5
- import { FONT_ROUTE_PREFIX, packageRoot } from '@/server/lib/assets/fonts.server';
5
+ import { FONT_ROUTE_PREFIX, packageDir } from '@/server/lib/assets/fonts.server';
6
6
 
7
7
  /**
8
8
  * File serving for `public/` and the font routes.
@@ -21,12 +21,16 @@ import { FONT_ROUTE_PREFIX, packageRoot } from '@/server/lib/assets/fonts.server
21
21
  * catch-all, which would shadow this app's SSR route. Exposing one lookup
22
22
  * function lets the web catch-all try a file first and fall back to the shell.
23
23
  */
24
- // Resolved from the package root, not the cwd: `serve --prod` may run the built
25
- // server from anywhere, and a cwd-relative `public` would not exist — every
26
- // icon, the manifest and the service worker would 404.
27
- const PUBLIC_ROOT = resolve(packageRoot(), 'public');
24
+ // Resolved from the package's own directory, not the cwd: `serve --prod` may
25
+ // run the built server from anywhere, and a cwd-relative `public` would not
26
+ // exist — every icon, the manifest and the service worker would 404. The
27
+ // package, NOT `packageRoot()`: in a hoisted install the first root holding
28
+ // `node_modules` is the parent, where `public/` and `dist/client/` do not exist,
29
+ // so the lookup would fall through to the shell and answer an image request with
30
+ // HTML.
31
+ const PUBLIC_ROOT = resolve(packageDir(), 'public');
28
32
  /** Where `scripts/build-client.ts` writes the production bundle and its chunks. */
29
- const CLIENT_ROOT = resolve(packageRoot(), 'dist', 'client');
33
+ const CLIENT_ROOT = resolve(packageDir(), 'dist', 'client');
30
34
 
31
35
  const IMMUTABLE = 'public, max-age=31536000, immutable';
32
36
  const SHORT = 'public, max-age=3600';
@@ -141,12 +145,12 @@ export async function tryServeStatic(pathname: string, request?: Request): Promi
141
145
  const filePath = resolveWithin(CLIENT_ROOT, pathname);
142
146
  return filePath ? serveFile(filePath, ASSET_CACHE, request) : null;
143
147
  }
144
- // The extracted `@font-face` stylesheet and the files it names. Bun's CSS
148
+ // The assembled `@font-face` stylesheet and the files it names. Bun's CSS
145
149
  // loader cannot carry either — every local `url()` is resolved, so a bundled
146
150
  // face is base64 or an absolute path — which is why they are served rather
147
- // than emitted. Both live outside `public/`: the stylesheet is assembled from
148
- // the faces `lib/bundler/css.ts` wrote, and the files come from the installed
149
- // packages.
151
+ // than emitted. Both live outside `public/`: the stylesheet re-declares the
152
+ // faces `lib/bundler/css.ts` stripped, read from the source stylesheets, and
153
+ // the files come from the installed packages.
150
154
  //
151
155
  // Neither is given a conditional here: the stylesheet is `no-store` (nothing
152
156
  // is ever stored to revalidate) and a font file is `immutable` (the browser