diff --git a/generator/deploy.mjs b/generator/deploy.mjs index 85b1447..3fafc40 100644 --- a/generator/deploy.mjs +++ b/generator/deploy.mjs @@ -153,23 +153,11 @@ async function deploy() { // and place the canary at a random position. Baked into the manifest scenario. await synthesizePopulations(classId, scenario, perDeployCanary) - log('\n[3/5] Chrome...') - let chrome = await generateChrome(theme, scenario.chromeInjection) - // Guarantee the discovery affordance is present — the chrome generator - // intermittently drops the chromeInjection, which orphans the scenario's - // entry point (the surface link). Re-insert it programmatically if missing - // so the entry point can never silently vanish. - chrome = ensureChromeInjection(chrome, scenario.chromeInjection) - // Fail fast: renderPage injects page content at {BODY}. Without it every page - // (form, result, 404) renders as the bare chrome shell and ALL content — - // including the canary — is silently dropped, producing a "deployed but - // unsolvable" target. The generator gate should prevent this; assert anyway. - if (!chrome.includes('{BODY}')) { - throw new Error('Chrome has no {BODY} placeholder after ensureChromeInjection — page content would be dropped. Aborting.') - } - log(` ✓ ${chrome.length} chars`) - - log('\n[4/5] Decoys (parallel)...') + // Chrome, decoys, and the 404 page all depend only on (theme, scenario), + // which are both ready by here, and they do not depend on each other — so + // generate all three concurrently instead of serially. Wall-clock for this + // stage drops from chrome + decoys + 404 to max(chrome, decoys, 404). + log('\n[3-5/5] Chrome + decoys + 404 (parallel)...') const allLinks = [ ...theme.navLinks, ...theme.secondaryLinks, @@ -184,15 +172,31 @@ async function deploy() { ] const seen = new Set() const uniq = tasks.filter(t => { if (seen.has(t.path)) return false; seen.add(t.path); return true }) - const results = await Promise.all(uniq.map(async t => { - try { const d = await t.fn(); log(` ✓ ${t.path}`); return [t.path, d] } - catch { log(` ✗ ${t.path}`); return [t.path, { body: '

Content unavailable.

' }] } - })) - const decoys = Object.fromEntries(results) - - log('\n[5/5] 404...') - const fallback404 = await generate404(theme) - log(' ✓') + + const [rawChrome, decoyResults, fallback404] = await Promise.all([ + generateChrome(theme, scenario.chromeInjection), + Promise.all(uniq.map(async t => { + try { const d = await t.fn(); log(` ✓ ${t.path}`); return [t.path, d] } + catch { log(` ✗ ${t.path}`); return [t.path, { body: '

Content unavailable.

' }] } + })), + generate404(theme), + ]) + + // Chrome post-processing must run after generation. + // Guarantee the discovery affordance is present — the chrome generator + // intermittently drops the chromeInjection, which orphans the scenario's + // entry point (the surface link). Re-insert it programmatically if missing + // so the entry point can never silently vanish. + let chrome = ensureChromeInjection(rawChrome, scenario.chromeInjection) + // Fail fast: renderPage injects page content at {BODY}. Without it every page + // (form, result, 404) renders as the bare chrome shell and ALL content — + // including the canary — is silently dropped, producing a "deployed but + // unsolvable" target. The generator gate should prevent this; assert anyway. + if (!chrome.includes('{BODY}')) { + throw new Error('Chrome has no {BODY} placeholder after ensureChromeInjection — page content would be dropped. Aborting.') + } + const decoys = Object.fromEntries(decoyResults) + log(` ✓ chrome ${chrome.length} chars, ${Object.keys(decoys).length} decoys, 404`) let defenceConfig = {} if (tier > 0 && classDefences?.generateT1Config) {