Lighthouse production results and launch summary (Maestro 2c8f8887) (#204)
CI / ci (push) Successful in 11m9s

Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #204.
This commit is contained in:
2026-10-11 20:39:35 -04:00
committed by approver_agent
parent 8b0e2f25a9
commit 90c2d79354
11 changed files with 44863 additions and 1 deletions
+54
View File
@@ -0,0 +1,54 @@
# Launch evidence
Evidence that the production PWA meets the launch gates. The raw reports and
the check script are committed under [lighthouse/](lighthouse/).
## Run conditions
| | |
| --- | --- |
| Target | Production, <https://mealquest.cubecraftlabs.com> |
| Build | `mealquest:743a155` |
| Session | Signed in (household session cookie, supplied at run time) |
| Date | 2026-10-11 |
| Tool | Lighthouse 13.5.0, mobile form factor |
The session cookie value is redacted from the committed reports
(`configSettings.extraHeaders`); nothing else in them is changed.
## Lighthouse
Reports: [lighthouse/2026-10-11/](lighthouse/2026-10-11/) — one
`.report.json` and one `.report.html` per page.
| Page | Route | Performance | Accessibility | Best practices |
| --- | --- | --- | --- | --- |
| Home | `/` | 71 | 100 | 100 |
| Plan | `/plan` | 72 | 100 | 100 |
| Discover | `/discover` | 66 | 100 | 100 |
| Dinner detail | `/dinner/<id>` | 70 | 100 | 100 |
**Gate: accessibility ≥ 95 on every page — met** (100 on all four).
Performance is recorded for reference but is not gated.
## Installability
Lighthouse 10 and later removed the PWA category, so installability was checked
directly with a Playwright probe:
[installability-check.mjs](lighthouse/2026-10-11/installability-check.mjs).
It loads each of the four pages above in a signed-in Chrome session with
Playwright's Pixel 5 mobile emulation (viewport, touch, `isMobile` and mobile
user agent; the cookie comes from the `CK` environment variable, never the
file) and gates every page on all of:
- **Manifest** — the linked manifest is fetched with HTTP 200 and has
`name` "MealQuest", `display` "standalone", a `start_url`, and icons at
192x192, 512x512 and one with `maskable` purpose.
- **Service worker** — `navigator.serviceWorker.ready` resolves within 10
seconds (expiry is a failure), and a registration exists whose active worker
is `activated`.
It prints a PASS or FAIL line per condition per page and exits 1 if any
condition fails on any page.
**Result: PASS on all four pages.**
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
import { chromium, devices } from 'playwright';
const ck = process.env.CK;
if (!ck) { console.error('CK (session cookie) is not set'); process.exit(2); }
const origin = 'https://mealquest.cubecraftlabs.com';
const pages = [['home','/'],['plan','/plan'],['discover','/discover'],['dinner','/dinner/01M4PKT9QYA6C4JCF67XE7RA6R']];
const SW_READY_TIMEOUT_MS = 10000;
const browser = await chromium.launch({ executablePath: '/usr/bin/google-chrome', args: ['--no-sandbox'] });
// Mobile emulation: Pixel 5 viewport, device scale, touch, isMobile and user agent.
const ctx = await browser.newContext({ ...devices['Pixel 5'] });
await ctx.addCookies([{ name: '__Host-mealquest_session', value: ck, url: origin + '/' }]);
let fail = 0;
for (const [name, path] of pages) {
const page = await ctx.newPage();
page.on('console', m => { if (/sw|service|error/i.test(m.text())) console.log(' [console]', m.text().slice(0,120)); });
page.on('pageerror', e => console.log(' [pageerror]', String(e).slice(0,120)));
await page.goto(origin + path, { waitUntil: 'networkidle', timeout: 30000 });
const href = await page.evaluate(() => document.querySelector('link[rel="manifest"]')?.href || null);
const sw = await page.evaluate(async (ms) => {
const ready = await Promise.race([
navigator.serviceWorker.ready.then(() => 'ready', e => 'error: ' + String(e)),
new Promise(r => setTimeout(() => r('timeout after ' + ms + ' ms'), ms)),
]);
const regs = await navigator.serviceWorker.getRegistrations();
return { ready, registrations: regs.length, state: regs[0]?.active?.state || null };
}, SW_READY_TIMEOUT_MS);
const manifest = href
? await page.evaluate(async (h) => { const r = await fetch(h); return { status: r.status, body: r.ok ? await r.json() : {} }; }, href)
: { status: 'no manifest link', body: {} };
const m = manifest.body;
const icons = m.icons || [];
const hasSize = (s) => icons.some(i => (i.sizes || '').split(/\s+/).includes(s));
const checks = [
['manifest HTTP 200', manifest.status === 200, manifest.status],
['manifest name "MealQuest"', m.name === 'MealQuest', m.name],
['manifest display "standalone"', m.display === 'standalone', m.display],
['manifest start_url present', !!m.start_url, m.start_url],
['icon 192x192', hasSize('192x192'), icons.map(i => i.sizes).join(',')],
['icon 512x512', hasSize('512x512'), icons.map(i => i.sizes).join(',')],
['icon purpose maskable', icons.some(i => (i.purpose || '').split(/\s+/).includes('maskable')), icons.map(i => i.purpose || 'any').join(',')],
['service worker ready', sw.ready === 'ready', sw.ready],
['service worker registered', sw.registrations > 0, sw.registrations],
['service worker activated', sw.state === 'activated', sw.state],
];
console.log(`${name} (${path}) url=${page.url().replace(origin, '')}:`);
for (const [label, ok, actual] of checks) console.log(` ${ok ? 'PASS' : 'FAIL'} ${label} (got ${JSON.stringify(actual ?? null)})`);
const pass = checks.every(([, ok]) => ok);
if (!pass) fail++;
console.log(` => ${pass ? 'INSTALLABLE' : 'NOT INSTALLABLE'}`);
await page.close();
}
await browser.close();
console.log(fail ? `FAILED on ${fail} of ${pages.length} pages` : `PASS on all ${pages.length} pages`);
process.exit(fail ? 1 : 0);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long