mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-24 23:41:01 +02:00
The July 14 launch surface, previously uncommitted: - /dashboard/launch raw-WebGPU particle "memory brain" hero (RawVestigeEngine, NeuralWordmark, dendrite sign) + DOM waitlist overlay with share/referral. - Supabase waitlist client + migrations + welcome edge function; legacy waitlist archived under supabase/legacy. - vercel.json deploy config, root-redirect env wiring, base-path config, graph-only route, Playwright launch verifiers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
182 lines
6.2 KiB
JavaScript
182 lines
6.2 KiB
JavaScript
import pw from '/Users/entity002/vestige-launch-main/node_modules/.pnpm/playwright@1.58.2/node_modules/playwright/index.js';
|
|
import { mkdirSync } from 'node:fs';
|
|
|
|
const { chromium } = pw;
|
|
const URL = process.env.LAUNCH_URL || 'http://127.0.0.1:5180/dashboard/launch';
|
|
const OUT_DIR = '/tmp/vestige-launch-mobile';
|
|
mkdirSync(OUT_DIR, { recursive: true });
|
|
|
|
async function snapshot(page, label) {
|
|
const facts = await page.evaluate((snapshotLabel) => {
|
|
const engine = document.querySelector('.raw-vestige-engine');
|
|
const gpu = document.querySelector('.gpu-canvas');
|
|
const fallback = document.querySelector('.fallback-canvas');
|
|
const instantCanvas = document.querySelector('.instant-canvas');
|
|
const wordmarkBridge = document.querySelector('.wordmark-bridge');
|
|
const signSvg = document.querySelector('.sign-svg');
|
|
const input = document.querySelector('.email');
|
|
const rectFor = (el) => {
|
|
if (!el) return null;
|
|
const r = el.getBoundingClientRect();
|
|
const style = getComputedStyle(el);
|
|
return {
|
|
w: Math.round(r.width),
|
|
h: Math.round(r.height),
|
|
top: Math.round(r.top),
|
|
left: Math.round(r.left),
|
|
opacity: style.opacity,
|
|
display: style.display,
|
|
visibility: style.visibility
|
|
};
|
|
};
|
|
return {
|
|
label: snapshotLabel,
|
|
mode: engine?.getAttribute('data-mode') ?? 'missing',
|
|
engine: rectFor(engine),
|
|
gpu: rectFor(gpu),
|
|
fallback: rectFor(fallback),
|
|
instantCanvas: rectFor(instantCanvas),
|
|
wordmarkBridge: rectFor(wordmarkBridge),
|
|
signSvg: rectFor(signSvg),
|
|
signPathCount: document.querySelectorAll('.sign-svg path').length,
|
|
input: rectFor(input),
|
|
bodyText: document.body.innerText.trim().slice(0, 120),
|
|
webgpu: Boolean(navigator.gpu),
|
|
viewport: { w: window.innerWidth, h: window.innerHeight, dpr: window.devicePixelRatio }
|
|
};
|
|
}, label);
|
|
|
|
await page.screenshot({ path: `${OUT_DIR}/${label}.png`, fullPage: false });
|
|
return facts;
|
|
}
|
|
|
|
async function sampleFallbackPixels(page) {
|
|
return page.evaluate(() => {
|
|
const c = document.querySelector('.fallback-canvas');
|
|
if (!c || c.width < 2 || c.height < 2) return { ok: false, reason: 'no sized fallback canvas' };
|
|
const sample = document.createElement('canvas');
|
|
sample.width = 120;
|
|
sample.height = 180;
|
|
const ctx = sample.getContext('2d');
|
|
if (!ctx) return { ok: false, reason: 'no 2d context' };
|
|
try {
|
|
ctx.drawImage(c, 0, 0, sample.width, sample.height);
|
|
const data = ctx.getImageData(0, 0, sample.width, sample.height).data;
|
|
let lit = 0;
|
|
let max = 0;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const lum = (data[i] + data[i + 1] + data[i + 2]) / 3;
|
|
if (lum > 18) lit += 1;
|
|
max = Math.max(max, data[i], data[i + 1], data[i + 2]);
|
|
}
|
|
return {
|
|
ok: true,
|
|
width: c.width,
|
|
height: c.height,
|
|
litFraction: +(lit / (data.length / 4)).toFixed(3),
|
|
maxChannel: max
|
|
};
|
|
} catch (error) {
|
|
return { ok: false, reason: String(error).slice(0, 120) };
|
|
}
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
args: [
|
|
'--enable-unsafe-webgpu',
|
|
'--enable-features=Vulkan,WebGPU',
|
|
'--ignore-gpu-blocklist',
|
|
'--use-gl=angle'
|
|
]
|
|
});
|
|
const context = await browser.newContext({
|
|
viewport: { width: 390, height: 844 },
|
|
deviceScaleFactor: 3,
|
|
isMobile: true,
|
|
hasTouch: true
|
|
});
|
|
const page = await context.newPage();
|
|
const consoleIssues = [];
|
|
page.on('console', (message) => {
|
|
if (['error', 'warning'].includes(message.type())) {
|
|
consoleIssues.push(`${message.type()}: ${message.text()}`);
|
|
}
|
|
});
|
|
page.on('pageerror', (error) => consoleIssues.push(`pageerror: ${error.message}`));
|
|
|
|
const rounds = [];
|
|
for (let i = 0; i < 4; i += 1) {
|
|
const started = Date.now();
|
|
await page.goto(URL, { waitUntil: 'domcontentloaded' });
|
|
const domcontentloadedMs = Date.now() - started;
|
|
await page.waitForTimeout(120);
|
|
const t120 = await snapshot(page, `reload-${i}-120ms`);
|
|
await page.waitForTimeout(480);
|
|
const t600 = await snapshot(page, `reload-${i}-600ms`);
|
|
const pixels600 = await sampleFallbackPixels(page);
|
|
await page.waitForTimeout(900);
|
|
const t1500 = await snapshot(page, `reload-${i}-1500ms`);
|
|
await page.waitForTimeout(1300);
|
|
const t2800 = await snapshot(page, `reload-${i}-2800ms`);
|
|
rounds.push({ i, domcontentloadedMs, t120, t600, pixels600, t1500, t2800 });
|
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
|
}
|
|
|
|
await context.close();
|
|
await browser.close();
|
|
|
|
const failures = [];
|
|
const opacityNumber = (value) => Number.parseFloat(value ?? '0') || 0;
|
|
const bridgeVisible = (snap) =>
|
|
Boolean(
|
|
snap.instantCanvas &&
|
|
snap.instantCanvas.w >= 350 &&
|
|
snap.instantCanvas.h >= 760 &&
|
|
opacityNumber(snap.instantCanvas.opacity) >= 0.9
|
|
);
|
|
for (const round of rounds) {
|
|
for (const snap of [round.t120, round.t600, round.t1500, round.t2800]) {
|
|
if (snap.mode === 'missing') {
|
|
if (!bridgeVisible(snap)) {
|
|
failures.push(`reload ${round.i} ${snap.label}: engine missing and instant 3D bridge not visible`);
|
|
}
|
|
continue;
|
|
}
|
|
if (snap.webgpu) {
|
|
if (snap.mode === 'fallback') {
|
|
failures.push(`reload ${round.i} ${snap.label}: fallback appeared before WebGPU`);
|
|
}
|
|
if (opacityNumber(snap.fallback?.opacity) > 0.01) {
|
|
failures.push(`reload ${round.i} ${snap.label}: fallback canvas is visible on WebGPU path`);
|
|
}
|
|
if (snap.mode === 'booting') {
|
|
if (!bridgeVisible(snap)) {
|
|
failures.push(`reload ${round.i} ${snap.label}: instant 3D bridge not visible during boot`);
|
|
}
|
|
}
|
|
} else if (!snap.fallback || snap.fallback.w < 350 || snap.fallback.h < 760) {
|
|
failures.push(`reload ${round.i} ${snap.label}: fallback canvas not viewport-sized without WebGPU`);
|
|
}
|
|
if (!snap.wordmarkBridge && !snap.signSvg) {
|
|
failures.push(`reload ${round.i} ${snap.label}: no wordmark bridge/svg`);
|
|
}
|
|
if (!snap.input || snap.input.w < 250) {
|
|
failures.push(`reload ${round.i} ${snap.label}: signup input not visible`);
|
|
}
|
|
}
|
|
if (!round.t600.webgpu && (!round.pixels600.ok || round.pixels600.litFraction < 0.02)) {
|
|
failures.push(`reload ${round.i}: fallback pixels not lit at 600ms`);
|
|
}
|
|
}
|
|
|
|
console.log(JSON.stringify({ url: URL, rounds, consoleIssues, failures, screenshotDir: OUT_DIR }, null, 2));
|
|
if (failures.length) process.exit(1);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|