diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 8888fe144..bc06cdf7a 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -5,21 +5,39 @@ import { DEFAULT_SUITES, matchesSuiteTriggers } from './test-suites.mjs'; const eventName = process.env.GITHUB_EVENT_NAME || ''; const localNoChanges = !eventName && !process.env.CI_CHANGED_FILES; -const changedFiles = localNoChanges ? [] : getChangedFiles(); +// The nightly schedule exists for exactly one thing: the full live-e2e +// matrix. A schedule event has no diff base, so the change-detection path +// degenerates to "everything changed"; without this guard that would flip on +// every file-triggered opt-in suite, including the ones that bill LLM APIs +// (skill-behavior, accept-cleanup, deepseek), every single night. +const isSchedule = eventName === 'schedule'; +const changedFiles = localNoChanges || isSchedule ? [] : getChangedFiles(); const forceDeterministic = localNoChanges || eventName === 'push' || eventName === 'workflow_dispatch'; const forceOptIn = eventName === 'workflow_dispatch'; -const plan = { - core: true, - detector: forceDeterministic || matchesSuiteTriggers('detector', changedFiles), - live: forceDeterministic || matchesSuiteTriggers('live', changedFiles), - framework: forceDeterministic || matchesSuiteTriggers('framework', changedFiles), - cli_remote_e2e: forceOptIn, - live_e2e: forceOptIn || matchesSuiteTriggers('live-e2e', changedFiles), - live_e2e_accept_cleanup: forceOptIn || matchesSuiteTriggers('live-e2e-accept-cleanup', changedFiles), - skill_behavior: forceOptIn || matchesSuiteTriggers('skill-behavior', changedFiles), - live_svelte_adapter_deepseek: forceOptIn || matchesSuiteTriggers('live-svelte-adapter-deepseek', changedFiles), -}; +const plan = isSchedule + ? { + core: true, + detector: true, + live: true, + framework: true, + cli_remote_e2e: false, + live_e2e: true, + live_e2e_accept_cleanup: false, + skill_behavior: false, + live_svelte_adapter_deepseek: false, + } + : { + core: true, + detector: forceDeterministic || matchesSuiteTriggers('detector', changedFiles), + live: forceDeterministic || matchesSuiteTriggers('live', changedFiles), + framework: forceDeterministic || matchesSuiteTriggers('framework', changedFiles), + cli_remote_e2e: forceOptIn, + live_e2e: forceOptIn || matchesSuiteTriggers('live-e2e', changedFiles), + live_e2e_accept_cleanup: forceOptIn || matchesSuiteTriggers('live-e2e-accept-cleanup', changedFiles), + skill_behavior: forceOptIn || matchesSuiteTriggers('skill-behavior', changedFiles), + live_svelte_adapter_deepseek: forceOptIn || matchesSuiteTriggers('live-svelte-adapter-deepseek', changedFiles), + }; writeGithubOutputs(plan); printSummary(plan, changedFiles); diff --git a/skill/scripts/live-browser.js b/skill/scripts/live-browser.js index bbee23bc3..988041d74 100644 --- a/skill/scripts/live-browser.js +++ b/skill/scripts/live-browser.js @@ -5970,7 +5970,16 @@ }); dismiss.textContent = '×'; dismiss.setAttribute('aria-label', 'Dismiss'); - dismiss.addEventListener('click', (e) => { e.stopPropagation(); clearMountErrorCard(); }); + dismiss.addEventListener('click', (e) => { + e.stopPropagation(); + clearMountErrorCard(); + // The card was the only recovery affordance while the bar is hidden; + // dismissing it must hand the user back a usable surface. PICKING + // reactivates the global mark and the picker. The saved session and + // server truth survive, so a later republish (SSE `done`) still + // resurrects the comparison through the normal handlers. + if (state === 'GENERATING') setLiveState('PICKING'); + }); head.appendChild(dismiss); card.appendChild(head); diff --git a/tests/ci-test-plan.test.mjs b/tests/ci-test-plan.test.mjs index 8c2d0bbac..d8b33fe72 100644 --- a/tests/ci-test-plan.test.mjs +++ b/tests/ci-test-plan.test.mjs @@ -95,6 +95,17 @@ describe('ci-test-plan', () => { assert.match(workflow, /live-e2e-accept-cleanup:/); assert.match(workflow, /live-svelte-adapter-deepseek:/); }); + it('schedule events run only the deterministic suites plus the full live-e2e matrix', () => { + const outputs = runPlan({ GITHUB_EVENT_NAME: 'schedule' }); + assert.equal(outputs.live_e2e, 'true'); + assert.equal(outputs.live_e2e_accept_cleanup, 'false'); + assert.equal(outputs.skill_behavior, 'false'); + assert.equal(outputs.live_svelte_adapter_deepseek, 'false'); + assert.equal(outputs.cli_remote_e2e, 'false'); + assert.equal(outputs.core, 'true'); + assert.equal(outputs.live, 'true'); + }); + }); function runPlan(env) {