diff --git a/apps/fabro-web/app/components/playground/files/render-toml.test.ts b/apps/fabro-web/app/components/playground/files/render-toml.test.ts index 22b143711..a1599dee2 100644 --- a/apps/fabro-web/app/components/playground/files/render-toml.test.ts +++ b/apps/fabro-web/app/components/playground/files/render-toml.test.ts @@ -4,7 +4,10 @@ import { createInitialDraft } from "../state/draft"; import { renderProjectToml, renderWorkflowToml } from "./render-toml"; describe("renderWorkflowToml", () => { - test("points the workflow at workflow.fabro and pins sandbox to local", () => { + test("points the workflow at its graph without an unsupported [run.sandbox] section", () => { + // The server's RunLayer parses workflow.toml with deny_unknown_fields and + // has no `sandbox` field, so a `[run.sandbox]` section makes the manifest + // unparseable. Sandbox selection lives in project.toml instead. expect(renderWorkflowToml(createInitialDraft())).toBe( [ "_version = 1", @@ -12,16 +15,13 @@ describe("renderWorkflowToml", () => { "[workflow]", 'graph = "workflow.fabro"', "", - "[run.sandbox]", - 'provider = "local"', - "", ].join("\n"), ); }); }); describe("renderProjectToml", () => { - test("enables draft PRs by default", () => { + test("enables draft PRs and pins the default environment to the local sandbox", () => { expect(renderProjectToml(createInitialDraft())).toBe( [ "_version = 1", @@ -30,6 +30,9 @@ describe("renderProjectToml", () => { "enabled = true", "draft = true", "", + "[environments.default]", + 'provider = "local"', + "", ].join("\n"), ); }); diff --git a/apps/fabro-web/app/components/playground/files/render-toml.ts b/apps/fabro-web/app/components/playground/files/render-toml.ts index 7218f82e4..f680d107d 100644 --- a/apps/fabro-web/app/components/playground/files/render-toml.ts +++ b/apps/fabro-web/app/components/playground/files/render-toml.ts @@ -12,9 +12,11 @@ import type { WorkflowDraft } from "../state/draft"; /** * The contents of `.fabro/workflows//workflow.toml`. * - * Points the workflow at its `.fabro` graph and pins the sandbox provider to - * `local` so the downloaded artifact runs against the user's own machine - * without any further setup. + * Points the workflow at its `.fabro` graph. Sandbox selection deliberately + * does NOT live here: the server parses this file into a `RunLayer` with + * `deny_unknown_fields` and no `sandbox` field, so a `[run.sandbox]` section + * makes the whole run manifest unparseable. The local sandbox is pinned at the + * project level instead — see `renderProjectToml`. */ export function renderWorkflowToml(_draft: WorkflowDraft): string { return [ @@ -23,17 +25,17 @@ export function renderWorkflowToml(_draft: WorkflowDraft): string { "[workflow]", 'graph = "workflow.fabro"', "", - "[run.sandbox]", - 'provider = "local"', - "", ].join("\n"); } /** * The contents of `.fabro/project.toml`. * - * Mirrors the defaults shown in the explainer: PRs enabled and draft, so - * a successful run opens a draft PR the user can review. + * Mirrors the defaults shown in the explainer: PRs enabled and draft, so a + * successful run opens a draft PR the user can review. Also pins the default + * environment to the `local` sandbox so the workflow runs against the user's + * own machine without any further setup (the supported home for sandbox + * selection, unlike workflow.toml's rejected `[run.sandbox]`). */ export function renderProjectToml(_draft: WorkflowDraft): string { return [ @@ -43,5 +45,8 @@ export function renderProjectToml(_draft: WorkflowDraft): string { "enabled = true", "draft = true", "", + "[environments.default]", + 'provider = "local"', + "", ].join("\n"); } diff --git a/apps/fabro-web/app/components/playground/playground.tsx b/apps/fabro-web/app/components/playground/playground.tsx index 98a6808c1..ea6733670 100644 --- a/apps/fabro-web/app/components/playground/playground.tsx +++ b/apps/fabro-web/app/components/playground/playground.tsx @@ -14,6 +14,7 @@ import DownloadButton from "./ui/download-button"; import NodeInspector from "./ui/node-inspector"; import ResetButton from "./ui/reset-button"; import RunForRealButton, { type RealRunRedirect } from "./ui/run-for-real-button"; +import SaveToRunsButton from "./ui/save-to-runs-button"; import RunTrace from "./ui/run-trace"; import SimulationControls from "./ui/simulation-controls"; import WorkflowHeader from "./ui/workflow-header"; @@ -113,6 +114,7 @@ export default function Playground({
+ {!isChatOpen && ( +
+ ); +} + +async function readErrorDetail(response: Response): Promise { + try { + const body = (await response.clone().json()) as { + errors?: { detail?: string; title?: string }[]; + }; + const first = body.errors?.[0]; + return first?.detail ?? first?.title ?? null; + } catch { + return null; + } +}