diff --git a/README.md b/README.md index f412336..48b70e0 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ Legacy `chats` migration is handled during startup. New code only reads the sess ## Frontend Structure -The browser UI remains framework-free and uses native ES modules. Shared behavior lives under `public/app/`: +The browser UI remains framework-free and uses native ES modules. Shared behavior lives under `public/js/`: - `api.js`: JSON and streaming request helpers - `constants.js`: theme and onboarding metadata @@ -222,7 +222,7 @@ The browser UI remains framework-free and uses native ES modules. Shared behavio - `statusNotice.js`: shared status and error presentation - `dom.js`: cached chat-page DOM references -`public/style.css` is a self-contained design system rebuilt around semantic tokens, shared components, and deliberate responsive breakpoints. It contains the six visual identities and their dark variants without legacy imports or precedence layers. `public/legal.css` contains only the legal-page layout. +`public/css/style.css` is a self-contained design system rebuilt around semantic tokens, shared components, and deliberate responsive breakpoints. It contains the six visual identities and their dark variants without legacy imports or precedence layers. `public/css/legal.css` contains only the legal-page layout. Pure chat message, retry, memory, summary, and persona-state formatting lives in `src/services/chatMessageHelpers.js`; `chatService.js` remains responsible for orchestration and persistence. @@ -280,6 +280,6 @@ Run `node --test --test-isolation=none` and `npm run lint` to check the feature Protected pages redirect to sign-in with a local return destination. Public discovery only returns published persona summaries; using a persona requires an account. -All page routes render the same navigation from `src/pageLayout.js` into the `` placeholder. `public/shell.css` owns the shared header, page spacing, and mobile navigation; `public/app/workspaceNavigation.js` owns sign-out. Page-specific headers contain only local titles and actions. The chat sidebar is reserved for conversation history. Public and home content share `public/pages.css` and `public/pages.js`. Page routes run before static assets and authenticated API routers; rendered pages use private, non-cacheable responses. +All page routes render the same navigation from `src/pageLayout.js` into the `` placeholder. `public/css/shell.css` owns the shared header, page spacing, and mobile navigation; `public/js/workspaceNavigation.js` owns sign-out. Page-specific headers contain only local titles and actions. The chat sidebar is reserved for conversation history. Public and home content share `public/css/pages.css` and `public/js/pages.js`. Page routes run before static assets and authenticated API routers; rendered pages use private, non-cacheable responses. For environments that block child-process test workers, run `node --test --test-isolation=none`. diff --git a/public/account.html b/public/account.html index 2385c2c..0abd76d 100644 --- a/public/account.html +++ b/public/account.html @@ -5,7 +5,7 @@ Sign in · Elsewhere - + @@ -80,9 +80,9 @@ Privacy Terms - - \ No newline at end of file + diff --git a/public/chat.html b/public/chat.html index 9c81075..a66b820 100644 --- a/public/chat.html +++ b/public/chat.html @@ -3,7 +3,7 @@ - + Elsewhere @@ -464,4 +464,4 @@ - \ No newline at end of file + diff --git a/public/home.html b/public/home.html index 3186d40..23e3adc 100644 --- a/public/home.html +++ b/public/home.html @@ -8,9 +8,9 @@ - - - + + + @@ -59,4 +59,4 @@ - \ No newline at end of file + diff --git a/test/app.test.js b/test/app.test.js index b4152ef..62241f4 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -1087,3 +1087,31 @@ test("frontend scripts and styles revalidate across deployments", async () => { } } finally { await server.close(); } }); + +test("every served page references existing local assets", async () => { + const server = await startTestServer(); + try { + const assetPattern = /(?:src|href)="([^"#]+\.(?:css|js|svg)(?:\?[^"#]*)?)"/g; + const assertPageAssets = async (route, headers = {}) => { + const response = await fetch(server.baseUrl + route, {headers}); + assert.equal(response.status, 200, `${route} must render`); + const html = await response.text(); + for (const [, reference] of html.matchAll(assetPattern)) { + if (/^https?:\/\//.test(reference)) continue; + const assetUrl = new URL(reference, server.baseUrl + route); + const assetResponse = await fetch(assetUrl, {headers}); + assert.equal(assetResponse.status, 200, `${route} references missing asset ${assetUrl.pathname}`); + } + }; + + for (const route of ["/", "/login", "/register", "/market", "/privacy.html", "/tos.html"]) { + await assertPageAssets(route); + } + + const jar = new CookieJar(); + await request(server.baseUrl, "/register", {method: "POST", body: {username: "assetchecker", password: "password123"}, jar}); + for (const route of ["/app", "/app/chats/new", "/app/personas", "/app/settings", "/market"]) { + await assertPageAssets(route, jar.headers()); + } + } finally { await server.close(); } +});