From f49ca0a5b298fec8cd26887051fd0580a7b8d10d Mon Sep 17 00:00:00 2001 From: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:20:15 +0200 Subject: [PATCH 01/43] fix(invite): report a rewritten landing as found, not as 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured against the deploy, which is what #30 left open and #32 promised to settle with one curl: curl -sI https://realunit.app/invite/AB12CD -> HTTP/2 404 The body is correct — the landing shell with the rewritten meta tags — but the status is not. _routes.json sends /invite/ to the Function, and the asset lookup behind context.next() resolves the code-less shell through the _redirects 200-rewrite while keeping the not-found status of the path that was asked for. The middleware copied that status through. Browsers render a 404 body, so a person following the link sees the right page. Share crawlers do not: they drop a 404 before reading the tags the middleware just wrote, which is the entire reason the rewrite exists. Every invite and promo link shared today therefore previews as nothing. The promotion is guarded on the landing marker, so a broken deploy that serves the site's own 404 page on these paths keeps saying 404 instead of looking healthy, and the reason phrase is dropped rather than left reading "Not Found". functions/_middleware.js was outside coverage while it decided the status every crawler sees, which is how this shipped unnoticed. It is measured now, at 100%, with the entry point's own test file. npm run check: exit 0, 173 tests. Verified by mutation: reverting the promotion turns three tests red, and removing the marker guard turns the two that protect a genuine 404 red. The other half of the same open question is now answered too: _headers do apply to Function-served requests. CSP, X-Frame-Options and Referrer-Policy are all present on https://realunit.app/invite/AB12CD. --- functions/_middleware.js | 12 +++- functions/lib/itunes-banner.js | 28 ++++++++ test/itunes-banner-function.test.mjs | 23 ++++++ test/middleware.test.mjs | 100 +++++++++++++++++++++++++++ vitest.config.mjs | 11 ++- 5 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 test/middleware.test.mjs diff --git a/functions/_middleware.js b/functions/_middleware.js index b1d59d2..68e7ca0 100644 --- a/functions/_middleware.js +++ b/functions/_middleware.js @@ -8,7 +8,11 @@ * /js/invite-banner.js and invite.js run. * public/ stays generic; this is not a site-wide renderer. */ -import { injectLandingFromRequestUrl, shouldRewriteItunesBanner } from './lib/itunes-banner.js'; +import { + injectLandingFromRequestUrl, + landingStatus, + shouldRewriteItunesBanner, +} from './lib/itunes-banner.js'; export async function onRequest(context) { const url = new URL(context.request.url); @@ -24,9 +28,11 @@ export async function onRequest(context) { const injected = injectLandingFromRequestUrl(html, context.request.url); const headers = new Headers(response.headers); headers.delete('content-length'); + const status = landingStatus(response.status, injected); return new Response(injected, { - status: response.status, - statusText: response.statusText, + status, + // A promoted status must not keep "Not Found" as its reason phrase. + statusText: status === response.status ? response.statusText : '', headers, }); } diff --git a/functions/lib/itunes-banner.js b/functions/lib/itunes-banner.js index 6711830..dbc0fe7 100644 --- a/functions/lib/itunes-banner.js +++ b/functions/lib/itunes-banner.js @@ -424,6 +424,34 @@ export function parseLangFromUrl(urlLike) { } } +/** + * The landing shell carries this id in both /invite and /promo; the site's + * 404 page does not. It is the marker for "the bytes we are holding really are + * a landing page". + */ +const LANDING_MARKER = 'id="state-loading"'; + +/** + * Report a rewritten landing as found. + * + * _routes.json sends /invite/ to the Function, and the asset lookup + * behind context.next() resolves the code-less shell through the _redirects + * 200-rewrite while keeping the not-found status of the path that was asked + * for. Measured on the deploy: the body is the landing, the status is 404. + * Browsers render it anyway, but share crawlers drop a 404 before they read + * the tags this module just wrote — which is the whole reason the rewrite + * exists. + * + * Only a body that really is the landing shell is promoted. If a broken deploy + * ever serves the site's 404 page on these paths, it has to keep saying 404 + * instead of looking healthy. + */ +export function landingStatus(status, html) { + if (status !== 404) return status; + if (typeof html !== 'string' || !html.includes(LANDING_MARKER)) return status; + return 200; +} + /** Crawlers snapshot og:title / twitter:title from the HTML bytes. Names wait for lookup JS. */ export function shareTitle(kind, code, lang) { // Keep the kind guard first: this function is exported, and a missing kind diff --git a/test/itunes-banner-function.test.mjs b/test/itunes-banner-function.test.mjs index 6350db0..280354c 100644 --- a/test/itunes-banner-function.test.mjs +++ b/test/itunes-banner-function.test.mjs @@ -16,6 +16,7 @@ import { shareTitle, injectShareDescriptionHtml, shareDescription, + landingStatus, parseLangFromUrl, injectShareLocaleHtml, injectSiteNameHtml, @@ -633,6 +634,28 @@ describe('an English locale without a code keeps English copy', () => { }); }); +describe('landingStatus', () => { + const shell = '
'; + + test('promotes a not-found landing to found', () => { + expect(landingStatus(404, shell)).toBe(200); + }); + + test('leaves every other status alone', () => { + expect(landingStatus(200, shell)).toBe(200); + expect(landingStatus(500, shell)).toBe(500); + expect(landingStatus(302, shell)).toBe(302); + }); + + test('refuses to promote a body that is not a landing', () => { + // The site's own 404 page must keep saying 404 rather than look healthy. + expect(landingStatus(404, 'Seite nicht gefunden — RealUnit')).toBe(404); + expect(landingStatus(404, '')).toBe(404); + expect(landingStatus(404, null)).toBe(404); + expect(landingStatus(404, undefined)).toBe(404); + }); +}); + describe('the browser mirror and the function module say the same thing', () => { // public/js/lib/invite-core.js carries a second copy of shareTitle and // shareDescription. Nothing in production calls its HTML injectors, so a diff --git a/test/middleware.test.mjs b/test/middleware.test.mjs new file mode 100644 index 0000000..c77cc58 --- /dev/null +++ b/test/middleware.test.mjs @@ -0,0 +1,100 @@ +import { describe, expect, test } from 'vitest'; +import { onRequest } from '../functions/_middleware.js'; + +// The landing shell as Pages hands it to the middleware: the marker the status +// promotion keys on, plus the tags the rewrite fills in. +const SHELL = + 'RealUnit — Einladung' + + '' + + '' + + '
'; + +const NOT_FOUND_PAGE = + 'Seite nicht gefunden — RealUnit'; + +function context({ + url, + method = 'GET', + status = 404, + body = SHELL, + type = 'text/html; charset=utf-8', +}) { + const headers = new Headers({ 'content-type': type, 'content-length': String(body.length) }); + const next = () => + Promise.resolve( + new Response(body, { status, statusText: status === 404 ? 'Not Found' : 'OK', headers }), + ); + return { request: { url, method }, next }; +} + +describe('the landing middleware', () => { + test('reports a rewritten landing as found instead of not found', async () => { + // Measured on the deploy: Pages resolves /invite/ to the shell through + // the _redirects rewrite but keeps the not-found status of the asked path. + // Share crawlers drop a 404 before they read the tags written just above. + const res = await onRequest(context({ url: 'https://realunit.app/invite/AB12CD' })); + expect(res.status).toBe(200); + const html = await res.text(); + expect(html).toContain('RealUnit — Einladung AB12CD'); + expect(res.headers.get('content-length')).toBeNull(); + expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8'); + }); + + test('a promo landing is promoted the same way', async () => { + const res = await onRequest(context({ url: 'https://realunit.app/promo/EVT1' })); + expect(res.status).toBe(200); + expect(await res.text()).toContain('RealUnit — Promo-Code EVT1'); + }); + + test('a real 404 page on a landing path keeps saying 404', async () => { + // A broken deploy has to stay visibly broken rather than look healthy. + const res = await onRequest( + context({ url: 'https://realunit.app/invite/AB12CD', body: NOT_FOUND_PAGE }), + ); + expect(res.status).toBe(404); + }); + + test('a landing that was already found keeps its status', async () => { + const res = await onRequest(context({ url: 'https://realunit.app/invite/', status: 200 })); + expect(res.status).toBe(200); + expect(res.statusText).toBe('OK'); + }); + + test('a path the rewrite does not own is passed through untouched', async () => { + const ctx = context({ url: 'https://realunit.app/', status: 200 }); + const res = await onRequest(ctx); + expect(await res.text()).toBe(SHELL); + // Passed through, so the header the rewrite would have dropped is still there. + expect(res.headers.get('content-length')).toBe(String(SHELL.length)); + }); + + test('a non-GET request is passed through untouched', async () => { + const res = await onRequest( + context({ url: 'https://realunit.app/invite/AB12CD', method: 'HEAD' }), + ); + expect(res.status).toBe(404); + expect(res.headers.get('content-length')).toBe(String(SHELL.length)); + }); + + test('a response that is not HTML is passed through untouched', async () => { + const res = await onRequest( + context({ url: 'https://realunit.app/invite/AB12CD', type: 'application/json', body: '{}' }), + ); + expect(res.status).toBe(404); + expect(await res.text()).toBe('{}'); + }); + + test('a response with no content-type is passed through untouched', async () => { + // Constructing a Response from a string sets content-type on its own, so + // the header is removed again to reach the missing-header path. + const ctx = context({ url: 'https://realunit.app/invite/AB12CD' }); + ctx.next = () => { + const res = new Response(SHELL, { status: 404 }); + res.headers.delete('content-type'); + return Promise.resolve(res); + }; + const res = await onRequest(ctx); + expect(res.headers.get('content-type')).toBeNull(); + expect(res.status).toBe(404); + }); +}); diff --git a/vitest.config.mjs b/vitest.config.mjs index 2369248..7134afc 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -9,7 +9,7 @@ export default defineConfig({ // Only the extracted, side-effect-free browser logic is unit-tested to // 100%. The DOM/network glue in public/*.js is covered by the Playwright // functional suite instead (see CONTRIBUTING.md). - include: ['public/js/lib/**/*.js', 'functions/lib/**/*.js'], + include: ['public/js/lib/**/*.js', 'functions/lib/**/*.js', 'functions/_middleware.js'], // Report every matched file even if no test imports it, so a new, untested // public/js/lib/*.js drops coverage below 100% instead of silently passing. all: true, @@ -34,6 +34,15 @@ export default defineConfig({ branches: 87, statements: 98, }, + // The Function entry point itself. It was unmeasured while it decided + // the status every share crawler sees, which is how a landing could + // answer 404 in production without a single test noticing. + 'functions/_middleware.js': { + lines: 100, + functions: 100, + branches: 100, + statements: 100, + }, }, }, }, From d9f3796bf2939d64c4faf87b69a27dbd301ba3f1 Mon Sep 17 00:00:00 2001 From: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:27:20 +0200 Subject: [PATCH 02/43] docs(invite): describe the status promotion where the rules are written The conformity lane found the documentation and the config describing a narrower change than the one that landed, which is the same drift #30 had to correct in this file. - CONTRIBUTING said public/js/lib was the only code with a coverage gate and the gate table named only that path; functions/lib and the Function entry point carry one too. - The ground rule described the middleware as a pure byte rewrite with "nothing else transformed", which no longer holds now that it also decides the status a crawler sees. - The vitest comment still claimed the measured surface was side-effect-free browser logic only. - The file header of the middleware did not mention the promotion at all. Two lanes independently found the same test gap: the promoted responses asserted their status but not their reason phrase, so reverting the ternary would have stayed green at 100% branch coverage. Both promotion tests now assert the empty phrase, and the untouched-404 test asserts it keeps "Not Found". npm run check: exit 0, 173 tests, functions/_middleware.js at 100%. Verified by mutation: keeping the reason phrase on promotion turns the two promotion tests red. --- CONTRIBUTING.md | 30 ++++++++++++++++++------------ functions/_middleware.js | 4 ++++ test/middleware.test.mjs | 5 +++++ vitest.config.mjs | 9 ++++++--- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2719b1..4174679 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,8 +9,13 @@ This repo is the **realunit.app** website — public, static. See the `public/` ships verbatim to Cloudflare Pages — what you commit is what gets served. The one exception is the invite/promo HTML: `functions/_middleware.js` rewrites those bytes on the way out so crawlers see the code in - `apple-itunes-app`, `og:*` and the App Links before any script runs. Nothing - else is transformed, and there is no server-side rendering. The dev dependencies exist **only** for the quality gates below + `apple-itunes-app`, `og:*` and the App Links before any script runs. It also + reports a rewritten landing as `200`: Pages resolves `/invite/` to the + code-less shell through the `_redirects` rewrite but keeps the not-found + status of the path that was asked for, and a crawler drops a `404` before it + reads the tags. The promotion is guarded on the landing marker, so the site's + own 404 page keeps saying 404. Nothing else is transformed, and there is no + server-side rendering. The dev dependencies exist **only** for the quality gates below (formatting, HTML validation, unit tests, screenshots); nothing compiles or bundles the site. - **Invite/promo HTML rewrite is banner, canonical, and store handoff.** Safari, @@ -45,8 +50,9 @@ This repo is the **realunit.app** website — public, static. See the adding any other host to that allowlist needs a reason in the PR. - Inline `style="…"` attributes and `