diff --git a/.agents/skills/e2e/SKILL.md b/.agents/skills/e2e/SKILL.md index fa4bca2d9c..fd92adc4ef 100644 --- a/.agents/skills/e2e/SKILL.md +++ b/.agents/skills/e2e/SKILL.md @@ -145,12 +145,12 @@ Start by matching CI as closely as possible, then add pressure deliberately: full spec file or full shard with the same resource limits before declaring a flake non-reproducible. -A test that passes only after a Playwright retry is still a failure signal. -During triage disable per-spec retry overrides and use `--retries=0`. When -isolated repeats pass but the same shard position fails, binary-search the -preceding spec files under one worker until the smallest ordered sequence -reproduces the shared-state leak; the fix is complete only when that sequence -passes with retries disabled. +The config uses `failOnFlakyTests: !CI`: local runs fail on a flaky retry, while +CI temporarily tolerates one. Either result is a failure signal for agents: +reproduce it locally with per-spec overrides disabled and `--retries=0`; never +rerun until it happens to pass. If isolated repeats stay green but the shard +fails, binary-search preceding specs in one worker. The fix is complete only +when the smallest reproducing sequence passes without retries. Record the exact command, resource limits, repeat number, and failure artifact path. Always inspect `error-context.md`; mobile/terminal flakes often show diff --git a/.gitignore b/.gitignore index c8e4a99796..9c3d438e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,6 @@ normalized-*.jsonl # Auth screenshot artifacts (generated by auth-screenshots.spec.ts) apps/web/e2e/.auth-screenshots/ + +# Restart-action screenshot artifacts (generated by feature-toggles-restart.spec.ts) +apps/web/e2e/.restart-screenshots/ diff --git a/apps/web/components/github/pr-detail-panel.test.ts b/apps/web/components/github/pr-detail-panel.test.ts index 85297c54be..39c59507e2 100644 --- a/apps/web/components/github/pr-detail-panel.test.ts +++ b/apps/web/components/github/pr-detail-panel.test.ts @@ -514,16 +514,27 @@ describe("PRDetailContent persistent request expiry", () => { it("clears the expiry timer when feedback confirms the request", async () => { vi.useFakeTimers(); - feedbackMocks.value = makeFeedback({ reviews: [dismissedReview()] }); reviewMocks.requestReviewers.mockResolvedValue({ requested: true }); - const view = renderPRDetail(); - await act(async () => fireEvent.click(screen.getByTestId(RE_REQUEST_BUTTON))); + let reviewRequest: ReturnType | undefined; + const onReady = (value: ReturnType) => { + reviewRequest = value; + }; + const view = render( + createElement(ReviewRequestHarness, { + onReady, + reviews: [dismissedReview()], + }), + ); + + await act(async () => reviewRequest?.reRequest("octocat")); expect(vi.getTimerCount()).toBe(1); - feedbackMocks.value = makeFeedback({ - pr: { requested_reviewers: [{ login: "octocat", type: "user" }] }, - }); - view.rerender(prDetailTree()); + view.rerender( + createElement(ReviewRequestHarness, { + onReady, + requestedReviewers: [{ login: "octocat", type: "user" }], + }), + ); await act(async () => undefined); expect(vi.getTimerCount()).toBe(0); diff --git a/apps/web/components/settings/system/feature-toggles-route.tsx b/apps/web/components/settings/system/feature-toggles-route.tsx new file mode 100644 index 0000000000..de37540fff --- /dev/null +++ b/apps/web/components/settings/system/feature-toggles-route.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useRestartCapability } from "@/hooks/domains/system/use-restart-capability"; +import { FeatureTogglesSettings } from "./feature-toggles-settings"; + +/** + * Route wrapper that resolves restart capability for the Feature Toggles page. + * + * Capability is a property of the *running* backend — it depends on whether a + * restart-capable supervisor launched it — so it must be fetched rather than + * baked in at build time. Toggle cards render immediately; the capability + * arrives after and only affects the pending-restart notice. A failed request + * stays `null`, which falls back to manual restart guidance rather than + * offering a button that cannot work. + */ +export function FeatureTogglesRoute() { + const { capability: restartCapability } = useRestartCapability(); + + return ; +} diff --git a/apps/web/components/settings/system/feature-toggles-settings.test.tsx b/apps/web/components/settings/system/feature-toggles-settings.test.tsx index 4b5a3d03ef..5bd8e75bdf 100644 --- a/apps/web/components/settings/system/feature-toggles-settings.test.tsx +++ b/apps/web/components/settings/system/feature-toggles-settings.test.tsx @@ -92,6 +92,21 @@ describe("FeatureTogglesSettings", () => { expect(updateRuntimeFlagMock).toHaveBeenCalledWith(initial.key, true); }); + it("offers the restart action when the supervisor supports restart", () => { + render( + + + , + ); + + expect(screen.getByText("Restart required")).not.toBeNull(); + expect(screen.getByRole("button", { name: "Restart" })).not.toBeNull(); + expect(screen.queryByText(/terminal or service manager/)).toBeNull(); + }); + it("shows restart support details without offering restart when unsupported", () => { render( diff --git a/apps/web/components/settings/system/feature-toggles-settings.tsx b/apps/web/components/settings/system/feature-toggles-settings.tsx index 44b084dbb9..102e44a5f4 100644 --- a/apps/web/components/settings/system/feature-toggles-settings.tsx +++ b/apps/web/components/settings/system/feature-toggles-settings.tsx @@ -20,7 +20,7 @@ import { useSettingsSaveContributor } from "../settings-save-provider"; type Props = { initialFlags: RuntimeFlagState[]; - restartCapability: RestartCapability | null; + restartCapability: RestartCapability | null | undefined; }; let bootstrapRuntimeFlagsRequest: ReturnType | null = null; @@ -200,30 +200,31 @@ function RestartRequiredAlert({ restarting, onRestart, }: { - capability: RestartCapability | null; + capability: RestartCapability | null | undefined; restarting: boolean; onRestart: () => void; }) { const { t } = useTranslation(); - const supported = capability?.supported === true; + const loading = capability === undefined; + const supported = !loading && capability?.supported === true; return ( {t("system:restartRequired")} - + {!loading && } {t("system:restartPendingChanges")} - {!supported && ` ${t("system:restartManualHint")}`} + {!loading && !supported && ` ${t("system:restartManualHint")}`} - {supported && ( + {!loading && supported && (