diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx
index d8bf14c..f6d535d 100644
--- a/components/scenario-form.jsx
+++ b/components/scenario-form.jsx
@@ -33,6 +33,28 @@ export async function submitScenarioForStartCase(fetchImpl, scenario) {
});
}
+export function isUnavailableStartResponse(response, data) {
+ return response.status === 503 &&
+ data?.success === false &&
+ data?.error === "Reasoning service is temporarily unavailable.";
+}
+
+export function UnavailableStartPanel({ onRetry }) {
+ return (
+
+
Confidence Engine is temporarily unavailable.
+
We couldn't process this right now. Your scenario is still here and you can try again.
+
+
+ );
+}
+
export async function submitAnswerForUpdateCase(
fetchImpl,
{ situationGraph, previousQuestion, answer, findings },
@@ -672,8 +694,7 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
updateStatus === "loading"
);
- const handleSubmit = async (e) => {
- e.preventDefault();
+ const handleStart = async () => {
setStatus("loading");
setResult(null);
setAnswer("");
@@ -699,6 +720,8 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
/* ── v0.59a — provenance: first meaningful change sets revision to 1 ── */
setInvestigationRevision(1);
persist({ id: investigationId, scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [], investigationReport, investigationRevision: 1 });
+ } else if (isUnavailableStartResponse(res, data)) {
+ setStatus("unavailable");
} else {
setStatus("error");
setCurrentUnderstanding(data.summary ?? null);
@@ -710,6 +733,11 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
}
};
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ void handleStart();
+ };
+
const handleUpdate = async (e) => {
e.preventDefault();
@@ -799,7 +827,7 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
return (
{/* ── Idle form for scenario input ─ */}
- {!result?.situationGraph && status === "idle" && (
+ {!result?.situationGraph && (status === "idle" || status === "unavailable") && (
diff --git a/tests/ui/scenario-form.test.jsx b/tests/ui/scenario-form.test.jsx
index 96df7bd..de8c698 100644
--- a/tests/ui/scenario-form.test.jsx
+++ b/tests/ui/scenario-form.test.jsx
@@ -14,7 +14,9 @@ import ReasoningWorkspace, {
} from "@/components/reasoning-workspace.jsx";
import {
ScenarioResultPanels,
+ UnavailableStartPanel,
UpdateErrorPanel,
+ isUnavailableStartResponse,
submitAnswerForUpdateCase,
submitScenarioForStartCase,
} from "@/components/scenario-form.jsx";
@@ -372,6 +374,26 @@ function makeCommercialUpdateSuccess(overrides = {}) {
}
describe("scenario-form UI helpers", () => {
+ it("recognises only the established sanitized unavailable start response", () => {
+ expect(isUnavailableStartResponse(
+ { status: 503 },
+ { success: false, error: "Reasoning service is temporarily unavailable." },
+ )).toBe(true);
+ expect(isUnavailableStartResponse(
+ { status: 500 },
+ { success: false, error: "Reasoning service is temporarily unavailable." },
+ )).toBe(false);
+ });
+
+ it("presents generic recovery without provider details", () => {
+ const html = renderToStaticMarkup();
+
+ expect(html).toContain("Confidence Engine is temporarily unavailable.");
+ expect(html).toContain("Your scenario is still here and you can try again.");
+ expect(html).toContain(">Retry<");
+ expect(html).not.toMatch(/ollama|provider|local model|server|infrastructure/i);
+ });
+
it("submits to /api/cases/start", async () => {
const fetchImpl = vi.fn().mockResolvedValue({ ok: true });