You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 504ea07
Browse filesBrowse the repository at this point in the historyBrowse files
Load only the active locale and open the app offline (T11: PF-01, PF-02, PF-04, PF-05, PF-06, PF-09) (#215)
* Cache every public icon file for 30 days
C3 (#199) added icon-1024.png and the 192/512/maskable manifest icons,
which still fell back to revalidating on every request. The test now
reads public/ so a new icon size can't miss the rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Load only the active locale and keep app-only code off public pages
- Spanish and Hungarian strings are their own chunks, fetched when chosen;
English stays in the entry as the default and fallback. The first render
waits for the chosen language, and a later choice wins over one still
loading.
- AppLayout (header, navigation, debate dialogs) loads only under /app.
mountApp starts the page chunk and the layout while the entry starts,
instead of when React first reaches them.
- The landing reveal is a CSS transition, so / no longer loads
framer-motion's 118 KB chunk.
- The session check and layout load show the route's skeleton instead of a
full-screen spinner.
- check-build-boundaries fails if the es/hu catalogs or AppLayout re-enter
the entry or the landing, login, home, generate or attack graphs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Open the main app pages offline and keep each page's own HTML
The build writes a precache list into sw.js from the Vite manifest: the
shell's scripts and styles, the signed-in layout, Home, Library, History,
Timer and Settings, the es/hu strings and Inter's Latin fonts. A missing
manifest key fails the build.
Offline, app routes get the precached app-shell.html (the paths
vercel.json rewrites) and a public page gets only its own HTML, stored
under its own path. Before, every navigation was stored as /index.html,
so any route could come back as the last page visited. /auth/callback,
Supabase paths and /_vercel/ stay network-only. Caches carry the build's
id and the next build's worker deletes them.
vite preview applies the vercel.json rewrites, so the offline test loads
what production serves. The test's async waitForFunction calls passed at
once without checking (Playwright treats the returned Promise as truthy);
they now poll.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hold Supabase reads while offline instead of retrying them
Offline, supabase-js retried every failed read three more times, so
opening Settings sent about 70 doomed requests in 30 seconds (user_roles
four times, user_settings 18). REST, Functions and Storage reads now wait
for the 'online' event and go out once; an abort still ends them. Writes
fail at once as before, and Auth is left alone because auth-js holds its
session lock during a user lookup.
TanStack Query also starts with the browser's real online state, so a page
opened offline pauses its queries instead of running them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Don't reload for a missing chunk while offline
Choosing a language whose strings were never cached, offline, failed its
import and triggered the stale-deploy reload, which without a service
worker replaces the page with the browser's offline error. Offline the
chunk is unreachable, not stale, so the error stands and the app keeps
the current language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Treat /expocit/sources as a public page offline, and always leave the startup shell
/expocit/sources is prerendered and Vercel serves that file before the
rewrite, so offline it now gets its own stored HTML instead of the shell
(whose page chunk is not precached). vite preview serves prerendered
pages before the rewrites too. The non-prerendered first render now also
runs if waiting for the language rejects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Disable Upgrade Now while offline and say why
Checkout needs the network. Offline the button stays disabled with a short
notice under it (one new string, pricingModal.offlineUpgrade, in all three
catalogs) and re-enables on the 'online' event. Done here now that T4
(#207) has merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Load every language before the flowchart opens
T9 (#206) recognises flowchart starter text saved in any language by
comparing it with each catalog. With Spanish and Hungarian loaded only on
demand, a chart saved in Hungarian and opened in English kept its
Hungarian starter text. The flowchart route's loader now fetches every
catalog before the page renders. Only that page (and a hover prefetch of
it) pays for them, and they are precached for offline.
The flowchart tests now load through the route loader; without this
change four of them fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review round 1: stop retries offline instead of holding reads
- Offline Supabase reads are no longer held until 'online'. A held read kept
the token of whoever started it and could replay after sign-out, and a
browser that wrongly reports offline would never send it. Every request
is now sent; one that fails while the browser is offline ends as an
abort, which postgrest-js does not retry. TanStack Query is no longer
seeded from navigator.onLine; failed queries refetch on 'online' instead.
- A language chosen offline before its strings were cached is applied when
the connection returns.
- The first render waits at most 3 s for the language's strings.
- The service worker's build id also covers the unhashed public files it
serves cache-first (theme-init.js, icons), and an offline public page
prefers the copy saved on the last visit over the one stored at install.
- The landing page's code is precached with its HTML, so / opened offline
is not replaced by the error page.
- test:sw now visits /auth/callback before checking it is never cached,
and checks the precached files sit in the install-time shell cache.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review round 2: load layout and page together after sign-in
- After a client-side navigation into /app (sign-in, the callback), the
page chunk now starts with the layout chunk instead of after it.
- The offline no-retry rule also matches reads through the dev:preview
staging proxy path.
- hero-visual.jpg and placeholder.svg also feed the worker's build id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/architecture.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -166,7 +166,9 @@ CI and staging must not upload authenticated browser captures. Logging must not
166
166
167
167
## PWA and offline behavior
168
168
169
-
The service worker and manifest provide installable web assets and an offline app shell. It precaches only the navigation shell and static identity assets; route chunks enter the runtime cache when a user visits them. On first registration, the client also reports same-origin `/assets/` resources that loaded before the worker took control so the first visited route works offline without a forced reload. Updates wait for the normal service-worker lifecycle instead of reloading an active page. Offline support does not make unvisited routes, authenticated Supabase, or AI operations available without a network.
169
+
The service worker ([`public/sw.js`](../public/sw.js)) and manifest provide installable web assets and an offline app. At install it precaches the landing page, `app-shell.html`, the static identity assets, and a list the build writes into the worker (`serviceWorkerPrecache` in [`vite.config.ts`](../vite.config.ts)): the shell's scripts and styles, the signed-in layout, the Home, Library, History, Timer and Settings pages, the Spanish and Hungarian strings, and Inter's Latin fonts. After one online visit those pages open offline and the language can be switched there. Other route chunks enter the runtime cache when a user visits them or hovers their link; on first registration the client also reports same-origin `/assets/` resources that loaded before the worker took control. Offline, app routes (`/app`, `/login`, `/share`, the paths `vercel.json` rewrites to the shell) get the precached shell, and a public page gets only its own HTML, stored under its own path when visited. `/auth/callback`, Supabase paths and `/_vercel/` never touch a cache. Each build's caches carry the build's id and the next build's worker deletes them on activation. Updates wait for the normal service-worker lifecycle instead of reloading an active page. Offline support does not make other unvisited routes, authenticated Supabase, or AI operations available without a network.
170
+
171
+
Only English strings ship in the entry. [`src/i18n.ts`](../src/i18n.ts) fetches the Spanish or Hungarian catalogs when that language is chosen, and the first render waits for them.
Copy file name to clipboardExpand all lines: docs/operations.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,7 +46,7 @@ Vercel builds and serves the frontend from the Git repository. A push to `main`
46
46
-`trailingSlash: false`, which 308-redirects `/app/settings/` to `/app/settings` before routing, since rewrite and redirect sources match exactly;
47
47
- 301 redirects from the retired brand pages (`/debatelab`, `/debatelab-by-programcode`, and the rest) to `/`;
48
48
- 301 redirects from retired guide slugs to the guide that replaced them, such as `/debate-weighing-guide` to `/how-to-weigh-in-debate`. The `trailingSlash` 308 runs first, so `/debate-weighing-guide/` reaches the same page in two permanent hops and needs no rule of its own;
49
-
- revalidating cache headers (`public, max-age=0, must-revalidate`, without `no-store`, so a document can enter the back/forward cache) for `/`, `index.html`, `app-shell.html`, and `theme-init.js`. `sw.js` stays `no-store` so a new service worker is not stuck in the HTTP cache. Hashed `/assets/` are immutable. The unhashed images in `public/` (`favicon.ico`, `favicon.png`, `favicon-72.png`, `hero-visual.jpg`, `placeholder.svg`) are cached for 30 days. Jam's capture iframe is removed on `pagehide` (never via `unload`) so that cross-origin frame cannot veto the cache;
49
+
- revalidating cache headers (`public, max-age=0, must-revalidate`, without `no-store`, so a document can enter the back/forward cache) for `/`, `index.html`, `app-shell.html`, and `theme-init.js`. `sw.js` stays `no-store` so a new service worker is not stuck in the HTTP cache. Hashed `/assets/` are immutable. The unhashed images in `public/` (the favicons, `apple-touch-icon.png`, the `icon-*.png` manifest icons, `hero-visual.jpg`, `placeholder.svg`) are cached for 30 days; the test reads `public/`, so a new icon file must be added to that rule. Jam's capture iframe is removed on `pagehide` (never via `unload`) so that cross-origin frame cannot veto the cache;
50
50
-`noindex` headers for `/app`, `/login`, `/share` and `/embed/debate-timer`. The app shell's robots meta is `noindex, nofollow`, the same policy. Public pages, including the prerendered `/expocit/sources`, are `index, follow`. A route deletes the platform `Access-Control-Allow-Origin: *` header on HTML responses and leaves it on static assets;
51
51
- Content Security Policy and the other response security headers, including the `Permissions-Policy` that allows the microphone for the app's own origin only. Jam is limited to `https://js.jam.dev` and `https://recorder.jam.dev`; `script-src` does not include `unsafe-eval`. Web Analytics and Speed Insights stay on this origin (`/_vercel/insights` and `/_vercel/speed-insights`), which `'self'` already allows. Every path except `/embed/debate-timer` refuses to be framed (`X-Frame-Options: DENY` and `frame-ancestors 'none'`). `/embed/debate-timer` allows any site to frame it and gets its own CSP, which permits only same-origin scripts and requests. The two rules use complementary sources (`/((?!embed/debate-timer$).*)` and `/embed/debate-timer`) so no path gets both, a new embed needs its path added to both, and `src/test/vercel-deployment-config.test.ts` fails if two matching rules ever set the same header.
Copy file name to clipboardExpand all lines: docs/testing.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -102,15 +102,15 @@ This sends one tiny typed choice to OpenRouter's Decisions endpoint using the pi
102
102
npm run build
103
103
```
104
104
105
-
This proves the Vite production bundle compiles, prerenders the public pages, checks the built site's links, fragments, JSON-LD and basic accessibility with `scripts/check-site.mjs`, and then walks the emitted Vite manifest to reject feature-only PDF, DOCX, chart, ReactFlow, export, and Admin modules from the static `index.html` dependency graph. It does not prove authentication, database behavior, provider access, or production deployment.
105
+
This proves the Vite production bundle compiles, prerenders the public pages, checks the built site's links, fragments, JSON-LD and basic accessibility with `scripts/check-site.mjs`, and then walks the emitted Vite manifest to reject feature-only PDF, DOCX, chart, ReactFlow, export, and Admin modules, the Spanish and Hungarian catalogs, and the signed-in layout from the static `index.html` dependency graph. It does not prove authentication, database behavior, provider access, or production deployment.
106
106
107
107
## Production service-worker smoke
108
108
109
109
```bash
110
110
npm run test:sw
111
111
```
112
112
113
-
Run this after `npm run build`. It serves `dist/` through Vite preview and proves that a v2 worker cache upgrades to v3 and claims the page, a first-visited route and its display logo reload offline, an unvisited route shows recovery UI, an intent-prefetched route opens offline, and private API paths do not enter Cache Storage. It uses a stub session and blocks external requests, so it is not authenticated backend or deployed-host proof.
113
+
Run this after `npm run build`. It serves `dist/` through Vite preview (which applies the `vercel.json` rewrites, so app routes get `app-shell.html` as in production) and proves that a v2 worker cache upgrades to this build's caches and claims the page; that after one visit to `/app`, Home, Library, History, Settings and Timer open offline as fresh navigations, the timer runs, the offline indicator shows, and switching to Spanish works; that a visited public page answers offline only with itself and an unvisited one gets the offline page; that an unvisited route shows recovery UI and an intent-prefetched one opens; and that private API paths and `/auth/callback` do not enter Cache Storage. It uses a stub session and blocks external requests, so it is not authenticated backend or deployed-host proof.
@@ -78,19 +107,43 @@ async function cacheFirst(request) {
78
107
}
79
108
}
80
109
81
-
asyncfunctionnavigationFallback(request){
110
+
functionofflinePage(){
111
+
returnnewResponse('<!doctype html><title>Offline</title><main style="font-family:system-ui;padding:2rem"><h1>Offline</h1><p>DebateLab is unavailable until your connection returns.</p></main>',{
newResponse('<!doctype html><title>Offline</title><main style="font-family:system-ui;padding:2rem"><h1>Offline</h1><p>DebateLab is unavailable until your connection returns.</p></main>',{
0 commit comments