diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ad1a579..c2e158ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ on: - "v[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: -# A PR's synchronize pushes supersede each other; keep only the latest run per ref. concurrency: group: ci-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} diff --git a/frontend/e2e/affected-routes.ts b/frontend/e2e/affected-routes.ts index 5e493bd0..14f53b9c 100644 --- a/frontend/e2e/affected-routes.ts +++ b/frontend/e2e/affected-routes.ts @@ -7,15 +7,11 @@ const owns = (dir: string, file: string): boolean => file === dir || file.starts /** * Maps a set of changed repo files to the manifest routes they touch, by - * prefix-matching each change against a route's owning source dirs. This is the - * substrate for selective runs and for scoping the exploratory agent to the - * changed surface. Pure — the CLI and the vitest unit test both drive it. + * prefix-matching each change against a route's owning source dirs. * - * - no frontend change → no routes (a backend-only diff; the caller falls back - * to the full suite, and schema-compat covers the backend contract); - * - a frontend change owned by no route → all routes (shared infra: the e2e - * engine, config, src/lib, a provider used everywhere) — conservative on - * purpose, since over-running is safe and under-running hides a regression; + * - no frontend change → no routes (the caller falls back to the full suite); + * - a frontend change owned by no route → all routes (shared infra: e2e engine, + * config, src/lib, an everywhere-provider) — over-run rather than under-run; * - otherwise → the routes whose `sources` the changes fall under. * * Paths are repo-relative (`frontend/...`); `sources` are `frontend/`-relative. diff --git a/frontend/e2e/fixtures/test.ts b/frontend/e2e/fixtures/test.ts index d790efbf..c6b3be16 100644 --- a/frontend/e2e/fixtures/test.ts +++ b/frontend/e2e/fixtures/test.ts @@ -12,9 +12,8 @@ const errorsTest = base.extend<{ pageErrorLog: PageErrorLog }>({ await use(log); - // Asserted here rather than left to the spec: a spec that never destructured this - // fixture collected nothing, so opting in was the same as opting out. Console errors - // stay with `expectCleanPage` — several specs drive genuine 4xx paths that log one. + // Auto (not opt-in): a spec that never destructured this fixture would assert nothing. + // Console errors stay with `expectCleanPage` — several specs drive 4xx paths that log one. baseExpect(log.pageErrors, 'no uncaught page errors').toEqual([]); }, { auto: true }, diff --git a/frontend/e2e/mock-llm/scenario.mjs b/frontend/e2e/mock-llm/scenario.mjs index c38dbdc4..cc39a1cd 100644 --- a/frontend/e2e/mock-llm/scenario.mjs +++ b/frontend/e2e/mock-llm/scenario.mjs @@ -35,8 +35,6 @@ export const RULES = [ match: /get_number/, toolCalls: [{ args: { value: 1 }, name: 'get_number' }], }, - // The task reporter summarizes the finished task and only accepts the - // report_result tool. { label: 'task-report', match: /report_result|TASK EXECUTION EVALUATOR AND REPORTER/, @@ -51,8 +49,7 @@ export const RULES = [ }, ], }, - // The refiner re-plans between subtasks and only accepts subtask_patch; - // an empty operations list means "plan unchanged". + // An empty operations list means "plan unchanged". { label: 'subtask-refine', match: /subtask_patch/, diff --git a/frontend/e2e/mocks/cassettes/base.ts b/frontend/e2e/mocks/cassettes/base.ts index f7a26753..b6c2238b 100644 --- a/frontend/e2e/mocks/cassettes/base.ts +++ b/frontend/e2e/mocks/cassettes/base.ts @@ -18,9 +18,8 @@ const flows: ResultOf = { flows: [] }; const flowTemplates: ResultOf = { flowTemplates: [] }; -// Live entry, not dead weight: after a websocket reconnect the app re-requests -// resources on the wire with {recursive:true} (removing this red-lights the -// reconnect spec on the 501 gate). +// After a websocket reconnect the app re-requests resources on the wire with {recursive:true}; +// removing this entry red-lights the reconnect spec on the 501 gate. const resources: ResultOf = { resources: [] }; const providers: ResultOf = { providers: [] }; diff --git a/frontend/e2e/mocks/cassettes/flows.ts b/frontend/e2e/mocks/cassettes/flows.ts index 0b51e212..d92d8b80 100644 --- a/frontend/e2e/mocks/cassettes/flows.ts +++ b/frontend/e2e/mocks/cassettes/flows.ts @@ -181,8 +181,8 @@ export const flowsCassette = (override: Cassette = {}): Cassette => { frames: [ ...FLOW_A_STREAMED_IDS.map((id) => addedFrame(makeMessage(id, '5'), 80)), - // Not a copy-paste: a resubscribe replays what the refetch already - // delivered, and the sentinel after it proves the replay was received. + // A resubscribe replays what the refetch already delivered; the sentinel + // after it proves the replay was received. { ...addedFrame(makeMessage(FLOW_A_RECONNECT_ID, '5'), 0), whenFlag: REPLAY_FLAG, diff --git a/frontend/e2e/mocks/ws-graphql.ts b/frontend/e2e/mocks/ws-graphql.ts index 8273b42e..395832eb 100644 --- a/frontend/e2e/mocks/ws-graphql.ts +++ b/frontend/e2e/mocks/ws-graphql.ts @@ -54,7 +54,7 @@ export const handleWsConnection = ( case 'connection_init': { // Ack immediately; a frame sent before the ack is a graphql-ws protocol - // violation that closes the socket. Ack-first ordering is pinned by the unit test. + // violation that closes the socket. send({ type: 'connection_ack' }); break; } diff --git a/frontend/e2e/playwright.config.ts b/frontend/e2e/playwright.config.ts index d1de0ee0..fb5b7fc8 100644 --- a/frontend/e2e/playwright.config.ts +++ b/frontend/e2e/playwright.config.ts @@ -45,14 +45,10 @@ if ((tier === 'stand' || tier === 'local') && !TIERS[tier].baseURL) { } export default defineConfig({ - // A colour change must move enough pixels to fail. The baselines and the run both - // render in the pinned container (the host path is blocked above), so there is no - // host-vs-CI rasterisation noise to absorb — tighten the per-pixel threshold below - // Playwright's default 0.2 so a one-step palette shift is caught, with a small - // maxDiffPixelRatio for anti-aliasing at glyph edges. cross/contrast.spec.ts still - // measures colour numerically as the belt-and-suspenders check. // Absolute, not a ratio: 1% of a 1280x720 baseline is 9,216 pixels, more than any one foreground - // token covers, so a token could change hue with every baseline still matching. + // token covers, so a token could change hue with every baseline still matching. Baselines and + // run both render in the pinned container (the host path is blocked above), so 200px absorbs the + // glyph anti-aliasing without host-vs-CI noise. expect: { toHaveScreenshot: { maxDiffPixels: 200, threshold: 0.02 }, }, diff --git a/frontend/e2e/specs/cross/palette.spec.ts b/frontend/e2e/specs/cross/palette.spec.ts index 92dd06e2..ac0d1a89 100644 --- a/frontend/e2e/specs/cross/palette.spec.ts +++ b/frontend/e2e/specs/cross/palette.spec.ts @@ -56,8 +56,7 @@ const SANCTIONED_VARIANTS = [ * its path; a tab panel by `${path} [${tab}]`. Exact strings: they waive one node, not a rule. */ const ACCEPTED: Record = { - // file-manager.tsx expand-all control; changing it moves pixels, so it goes with the design pass. - // Same control renders in the flow Files tab, so it is waived there under the same rationale. + // file-manager.tsx expand-all control, on /resources and embedded in the flow Files tab. '/resources': ['button: hover:text-blue-400'], [`${routes.flow('5')} [Files]`]: ['button: hover:text-blue-400'], }; diff --git a/frontend/e2e/specs/crud/resources.spec.ts b/frontend/e2e/specs/crud/resources.spec.ts index 54c2a5c4..65347952 100644 --- a/frontend/e2e/specs/crud/resources.spec.ts +++ b/frontend/e2e/specs/crud/resources.spec.ts @@ -72,7 +72,6 @@ test.describe('resources', { tag: '@coverage' }, () => { await page.goto('/resources'); await expect(page.getByText('No resources yet')).toBeVisible(); - // The CTA, not just the title — the empty state's whole point is the upload affordance. // Scope to the drop zone (its hint is unique) so the toolbar's Upload button is excluded. const dropZone = page.locator('div').filter({ hasText: 'Up to 300 MB per file' }).last(); diff --git a/frontend/e2e/specs/flows/subscriptions.spec.ts b/frontend/e2e/specs/flows/subscriptions.spec.ts index 9a306b9c..9bd0b2f0 100644 --- a/frontend/e2e/specs/flows/subscriptions.spec.ts +++ b/frontend/e2e/specs/flows/subscriptions.spec.ts @@ -69,9 +69,9 @@ test.describe('flow subscriptions', { tag: ['@flows', '@smoke'] }, () => { }); /** - * Not a duplicate of the round trip above: leaving via `/flows` unmounts FlowProvider (app.tsx - * wraps only `flows/:flowId`), so stream A is already closed there. Only the pager keeps the - * provider mounted and merely swaps the subscription variables. + * Leaving via `/flows` unmounts FlowProvider (app.tsx wraps only `flows/:flowId`), so stream A is + * already closed there. Only the pager keeps the provider mounted and merely swaps the subscription + * variables — the one path where a superseded stream can still write into the flow on screen. */ test.describe('flow subscriptions across a pager switch', { tag: '@flows' }, () => { test.use({ cassette: pagerStreamsCassette() }); diff --git a/frontend/e2e/specs/real/flow-run.spec.ts b/frontend/e2e/specs/real/flow-run.spec.ts index bff2eff2..7bebd427 100644 --- a/frontend/e2e/specs/real/flow-run.spec.ts +++ b/frontend/e2e/specs/real/flow-run.spec.ts @@ -4,8 +4,8 @@ import { readTerminalBuffer } from '../../helpers/terminal.ts'; test.describe('real backend flow run', { tag: '@real' }, () => { test('runs a flow end-to-end through the mock LLM', async ({ page }) => { - // Above the sum of the step timeouts below (60+30+90+90+90+60 = 420s); at 240s a legitimately - // slow-but-passing real run was killed mid-step with a generic timeout, masking the real state. + // Above the sum of the step timeouts below (60+30+90+90+90+60 = 420s), so a slow-but-passing + // run is not killed mid-step with a generic timeout that masks the real state. test.setTimeout(450_000); const pageErrors: string[] = []; diff --git a/frontend/e2e/tools/run-local-tier.sh b/frontend/e2e/tools/run-local-tier.sh index 2e83c0f5..f46ffcb3 100755 --- a/frontend/e2e/tools/run-local-tier.sh +++ b/frontend/e2e/tools/run-local-tier.sh @@ -23,9 +23,8 @@ compose() { # sandboxes for any flow id starting with 9. remove_e2e_sandboxes() { docker ps -aq --filter 'name=^/?pentagi-terminal-9[0-9]{4,}$' | xargs -r docker rm -f - # Each sandbox also gets a `-data` volume, created on the socket - # outside the compose project — removing the container leaves it behind, so - # every Tier-2 run used to add one more dangling volume. + # Each sandbox also gets a `-data` volume, created on the socket outside the compose + # project — removing the container leaves it behind, so it needs its own cleanup. docker volume ls -q --filter 'name=^pentagi-terminal-9[0-9]{4,}-data$' | xargs -r docker volume rm } diff --git a/frontend/src/pages/settings/settings-provider.test.tsx b/frontend/src/pages/settings/settings-provider.test.tsx index b52a5fd6..3af4c6a3 100644 --- a/frontend/src/pages/settings/settings-provider.test.tsx +++ b/frontend/src/pages/settings/settings-provider.test.tsx @@ -159,8 +159,6 @@ describe('SettingsProvider create-form type guards', () => { expect(navigate).not.toHaveBeenCalled(); }); - // The seeding effect re-runs on every settingsProviders refetch (a fresh `data` reference under - // cache-and-network); keepDirtyValues is what stops that reset from wiping an in-flight edit. it('preserves an in-flight edit across a background refetch', () => { state.providerId = 'edit-1'; const { rerender } = render(); @@ -171,7 +169,6 @@ describe('SettingsProvider create-form type guards', () => { fireEvent.change(nameInput, { target: { value: 'My Unsaved Edit' } }); - // A refetch delivers a new `data` object with the same server content, re-firing the effect. queryResult.data = { settingsProviders }; rerender(); diff --git a/frontend/src/pages/settings/settings-provider.tsx b/frontend/src/pages/settings/settings-provider.tsx index 440b7212..85f04b48 100644 --- a/frontend/src/pages/settings/settings-provider.tsx +++ b/frontend/src/pages/settings/settings-provider.tsx @@ -1205,7 +1205,7 @@ function SettingsProvider() { }, // The seeding effect re-runs on every settingsProviders refetch (cache-and-network + // replaceWithIncoming gives a fresh `data`); without this a background refetch landing - // mid-edit silently wipes the unsaved form. Matches settings-prompt and template. + // mid-edit silently wipes the unsaved form. resetOptions: { keepDirtyValues: true }, schema: formSchema, }); diff --git a/frontend/src/providers/flow-provider.tsx b/frontend/src/providers/flow-provider.tsx index 1a19f24a..113db421 100644 --- a/frontend/src/providers/flow-provider.tsx +++ b/frontend/src/providers/flow-provider.tsx @@ -39,8 +39,8 @@ import { Log } from '@/lib/log'; /** * Under `errorPolicy:'all'` a partial not-found error surfaces alongside a flow that loaded fine, so - * the not-found disjunct gates on `!flowData?.flow` — mirroring `flowLoadError` and the toast below. - * Without the gate that partial error redirects the user off a flow that rendered correctly. + * the not-found disjunct gates on `!flowData?.flow`. Without the gate that partial error redirects + * the user off a flow that rendered correctly. */ export const deriveFlowMissing = ( flowData: null | undefined | { flow: unknown },