Skip to content

Delay Jam and align cache, CSP, and robots - #196

Merged
Programcode merged 5 commits into
mainfrom
cursor/jam-csp-cache-robots-9b26
Oct 22, 2026
Merged

Programcode merged 5 commits into
mainfrom
cursor/jam-csp-cache-robots-9b26

Conversation

@Programcode

@Programcode Programcode commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Jam stays on for every visitor. It now loads after the first interaction, or eight seconds after load, and its capture iframe is removed on pagehide so that frame is not left in the document when the page is frozen. HTML uses a revalidating cache, CSP names the two Jam hosts the parent page actually contacts, and the app shell's robots meta matches the private-route noindex header.

Nothing here deploys Supabase. vercel.json is frontend hosting only, and this PR is not merged. The Access-Control-Allow-Origin delete runs on Vercel and was not confirmed on a live deployment.

Local npm run verify

GitHub Actions did not run (Actions minutes are exhausted). This is a local pass, not a green CI run.

Command: npm run verify (docs, lint, typecheck, unit tests, collaboration tests, Deno edge tests, build, service-worker test, desktop Chromium @smoke).

Exit code: 0

docs:check                          ok
lint (eslint, knip, repo rules)     ok
typecheck                           ok
vitest                              62 files, 433 tests passed
test:collaboration                  5 files, 30 tests passed
test:edge (deno 2.9.6)              399 passed, 0 failed (5s)
build + prerender + check-site      ok (48 HTML files; /expocit/sources prerendered)
test:sw                             upgrade, offline, and prefetch checks true
test:e2e:smoke                      43 passed (28.2s)
EXIT:0

The process set commit.gpgsign=false only for that run. The agent signing helper made src/test/scripts/backend-status.test.ts's beforeEach exceed the 10s hook timeout. That test file is unchanged, and with signing off it passes (7/7). Deno 2.9.6, the version in .github/workflows/deploy.yml, was installed for the edge tests because it was not on PATH.

Also ran e2e/loading-performance.spec.ts on desktop Chromium: 4 passed. That spec is not in the smoke set.

Lighthouse mobile (/ only)

Same Lighthouse 13.5.0 flags: --only-categories=performance --form-factor=mobile --screenEmulation.mobile --chrome-flags="--headless=new --no-sandbox".

These are not the same origin. Before is the production CDN. After is this branch's dist/ on 127.0.0.1. Do not read the lower local score as a regression of the CDN page.

Production before https://debate.pi-stack.org/ (2026-10-22T20:27:56Z) Local after http://127.0.0.1:4173/ (2026-10-22T20:52:14Z)
Performance 0.60 0.56
FCP 6.2 s (6221 ms) 9.9 s (9902 ms)
LCP 8.3 s (8345 ms) 9.9 s (9902 ms)
TBT 100 ms (97 ms) 50 ms (46 ms)
CLS 0 0
Speed index 6.2 s 9.9 s
TTI 8.6 s (8569 ms) 10.2 s (10176 ms)
Server response 20 ms (16 ms) 0 ms

The proof that Jam is off the first paint is the loading spec: the landing heading is visible with zero js.jam.dev requests, and a pointerdown is what starts the recorder.

Headers and CSP (local server applying vercel.json)

A static server served dist/ with the header rules, the rewrites (filesystem wins), and a simulated platform Access-Control-Allow-Origin: * that the new route then deletes.

  • /, /index.html, /app-shell.html, /theme-init.js: Cache-Control: public, max-age=0, must-revalidate (no no-store)
  • /sw.js: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0
  • /favicon.png, /hero-visual.png: public, max-age=2592000
  • /assets/…: public, max-age=31536000, immutable
  • /app, /login, /share/abc: X-Robots-Tag: noindex, nofollow
  • / and /expocit/sources: no X-Robots-Tag. Built HTML robots are index, follow, … on those pages and noindex, nofollow on app-shell.html and 404.html
  • HTML responses have no Access-Control-Allow-Origin. sw.js, images, /theme-init.js, /assets/, and /robots.txt still have *
  • /embed/debate-timer keeps its own CSP (frame-ancestors *, no Jam) and has no X-Frame-Options

Chromium (Playwright) on /: the heading rendered, Jam was absent until pointerdown, then js.jam.dev and recorder.jam.dev/capture loaded. The only securitypolicyviolation on the parent document was script-src / eval from the app bundle. That is Zod's new Function("") capability check (try/catch, then it runs without the JIT). unsafe-eval was not added. No parent-page block of js.jam.dev or recorder.jam.dev. Console errors were the local 404s for /_vercel/insights/script.js and /_vercel/speed-insights/script.js (this server does not host those routes). 'self' already allows them; production serves them from this origin. Firefox was not installed here, so it was not checked.

Issue by issue

SC-01

scheduleJamRecorder waits for load, then arms pointerdown / keydown and an 8s timer. The first of those calls requestIdleCallback (2s timeout) or setTimeout(0) and then loads recorder.js and capture.js. Signed-in userId metadata in src/lib/jam-metadata.ts is unchanged. A non-identifying id is not a small change, so it stays.

Verified by src/test/jam-loader.test.ts and the loading e2e spec. Open: once the scripts run, the capture iframe's worker poll continues. A consent banner was not added (follow-up).

SC-02

script-src, frame-src, connect-src, and img-src name https://js.jam.dev and https://recorder.jam.dev instead of https://*.jam.dev. Parent scripts are only on js.jam.dev. The capture document's own hosts (api.jam.dev, Datadog, and the rest) are requested by the iframe, so widening our connect-src would not help them. index.html does not set a CSP.

SC-11

unsafe-eval was not added. The parent bundles' Function("return this") is a globalThis fallback and is not the call we saw. The one parent eval report is Zod's caught probe, above. Datadog eval and dd_cookie_test live in the cross-origin capture bundle; third-party cookie blocking is the browser, not our CSP. Firefox marking supabase.co as a bounce tracker is unchanged and not fixable here. Vercel Web Analytics and Speed Insights are same-origin /_vercel/insights and /_vercel/speed-insights with no DSN, so 'self' covers script and connect. External va.vercel-scripts.com / vitals.vercel-insights.com hosts were not added.

PF-07

/, /index.html, /app-shell.html, and /theme-init.js are public, max-age=0, must-revalidate. /sw.js stays no-store. Hashed /assets/ stay immutable. The five unhashed public images cache for 30 days. /app already received Vercel's default revalidating cache and has no explicit no-store rule.

PF-10

The 8s / interaction delay keeps Jam off the initial network. After it loads, polling cannot be stopped; tests must not wait for networkidle. scripts/test-service-worker-offline.mjs now uses waitUntil: 'load'. The service-worker test passed.

SE-02

index.html robots meta is noindex, nofollow, so /app, /login, and /share match X-Robots-Tag even before JS. Prerender writes the public index, follow, … directive onto every canonical page and noindex, nofollow onto the 404. /expocit/sources is prerendered, in the sitemap (no lastmod, because it has no JSON-LD date), and calls usePageHead. The filesystem wins over the existing rewrite, which stays as a fallback. theme-init.js keeps private routes noindex, nofollow and sets the public directive if the shell is what served /expocit/sources.

SC-10

A routes transform with continue: true deletes Access-Control-Allow-Origin on HTML documents and leaves it on assets, /_vercel, and static extensions. Confirmed on the local server that simulates the platform * header. It takes effect on the next Vercel deploy; this PR does not deploy.

The staging origin https://brifeyypjauyusmkptkx.supabase.co stays in connect-src. One vercel.json is deployed to production and to debatelab-staging, and an unpinned production preview can still select staging from Settings. docs/operations.md says why.

Back/forward cache

pagehide (capture) removes iframe[data-jam-capture], iframe[data-jam-recorder], and iframe[src*="jam.dev"]. pageshow puts those same nodes back when event.persisted is true. The loader never registers unload or beforeunload. Unit test covers detach and restore.

Open: Chrome still cannot see inside the cross-origin frame while it is attached, which is why the other branch's bfcache report named recorder.jam.dev/capture. Detach happens on pagehide, before the freeze. Jam may not reconnect its postMessage port after the node is removed and put back; reloading the scripts would hit Jam's already-initialized guard, so this PR does not do that. The recorder is also absent until interaction or 8s, so a quick back navigation often has no Jam frame at all.

Left open

  • Signed-in user id is still sent to Jam.
  • No consent banner.
  • No unsafe-eval.
  • Firefox was not available in this environment.
  • ACAO removal and the new cache headers are unproven on a live Vercel response until this is deployed. Do not merge from here.
Open in Web Open in Cursor 

Update before merge (Cody, 22 Oct)

  • Merged main (Re-enable buttons that leave the site after Back (AUTH-15, AUTH-16) #194) into the branch (172d560). No force-push.
  • Codex review (T3 thread mcp:3be7c35b): no blockers. Its should-fix was that putting the Jam iframe back on pageshow reloads it, and Jam's already-initialized parent script rejects the new handshake. Fixed in c506b2a: pagehide still removes the frame, and pageshow no longer re-adds it. A Jam that has already loaded stays off on a bfcache-restored page until the next full load. If Jam had not started yet, its pending trigger can still start it after the restore, which is a clean first load (177269d). There's still no unload/beforeunload listener. The unit test and a new synthetic-event e2e case in e2e/loading-performance.spec.ts cover it. Codex re-reviewed: no blockers.
  • Local npm run verify on the VPS at 177269d: exit 0 (vitest 443, collaboration 30, edge 399, build/SW ok, e2e smoke 43/43). e2e/loading-performance.spec.ts on desktop-chromium: 5/5.
  • Vercel preview of 172d560: the deploy accepted routes alongside headers. HTML is public, max-age=0, must-revalidate with no ACAO. sw.js is no-store. Images are cached for 30 days. CSP is as in the diff. In Playwright on /, /login and /expocit/sources, the only CSP violation was the known Zod eval probe. Jam (js/recorder.jam.dev) and Sentry ingest loaded without being blocked.

Jam still loads for every visitor, but only after the first interaction or eight seconds, and its capture iframe is detached on pagehide so it does not keep the document out of the back/forward cache. HTML revalidates instead of using no-store, public images cache for 30 days, CSP names the two Jam hosts the loader uses, and the app shell is noindex while /expocit/sources is indexed.

Co-authored-by: Gáborjáni-Szabó Ádám <Programcode@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 12 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between c5f3ad1 and 9c72c6c.

📒 Files selected for processing: 15
  • docs/operations.md
  • e2e/loading-performance.spec.ts
  • index.html
  • public/theme-init.js
  • scripts/check-site.mjs
  • scripts/prerender.mjs
  • scripts/test-service-worker-offline.mjs
  • src/jam-loader.ts
  • src/lib/seo/pages.ts
  • src/lib/seo/use-page-head.ts
  • src/pages/SourcesPage.tsx
  • src/prerender/entry.tsx
  • src/test/jam-loader.test.ts
  • src/test/vercel-deployment-config.test.ts
  • vercel.json

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Oct 22, 2026 9:01pm UTC

Putting the capture iframe back on pageshow reloads it, and the new frame
starts a handshake that Jam's already-initialized parent script rejects.
pagehide still removes the frame so the page stays cache-eligible; the
restored page simply runs without Jam until the next full load.
Only a Jam that already loaded stays off after a restore. A trigger that
had not fired yet may still start it, which is a clean first load.
@Programcode
Programcode marked this pull request as ready for review October 22, 2026 21:16
Copilot AI balanced review requested due to automatic review settings October 22, 2026 21:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Programcode
Programcode merged commit b79f858 into main Oct 22, 2026
3 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants