diff --git a/apps/web/src/pages/workspace/artifact-pane.test.tsx b/apps/web/src/pages/workspace/artifact-pane.test.tsx index 53a92ecb..bcb9c771 100644 --- a/apps/web/src/pages/workspace/artifact-pane.test.tsx +++ b/apps/web/src/pages/workspace/artifact-pane.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; import type { ProjectArtifact } from "@corbits-builder/client"; -import { ArtifactPane } from "./artifact-pane.tsx"; +import { ArtifactPane, LIVE_TAB, defaultTab } from "./artifact-pane.tsx"; import { APPROVAL_PACKAGE_MEDIA_TYPE } from "./approval-package-preview.tsx"; import type { DeliverableState } from "./deliverable-block.tsx"; import { writeWorkspaceApprovalsEnabled } from "../../workspace-approvals-setting.ts"; @@ -199,3 +199,20 @@ describe("persistent artifact pane", () => { writeWorkspaceApprovalsEnabled("on"); }); }); + +describe("default tab", () => { + const plan: Deliverable = { + artifactId: "plan", + kind: "build_plan", + title: "Build plan", + version: 1, + sha256: "b".repeat(64), + }; + test("nothing chosen shows the live app once the build has started", () => { + expect(defaultTab(true, [ready, plan])).toBe(LIVE_TAB); + }); + test("before the build it shows the document furthest along, and nothing before any", () => { + expect(defaultTab(false, [ready, plan])).toBe("build_plan"); + expect(defaultTab(false, [])).toBeNull(); + }); +}); diff --git a/apps/web/src/pages/workspace/artifact-pane.tsx b/apps/web/src/pages/workspace/artifact-pane.tsx index 4e3e63b6..54d0f185 100644 --- a/apps/web/src/pages/workspace/artifact-pane.tsx +++ b/apps/web/src/pages/workspace/artifact-pane.tsx @@ -79,6 +79,35 @@ const draftIds = panelTabIds(TAB_PREFIX, DRAFT_TAB); // The filed design is one document read as two tabs: its plan under the design's kind, its mockup here. const MOCKUP_TAB = "design-mockup"; export const LIVE_TAB = "live-app"; + +/** + * What the pane shows when nothing is chosen: the live app once the build has started, else the + * filed document furthest along the project's flow, else nothing. + */ +export function defaultTab( + liveApp: boolean, + deliverables: readonly Deliverable[], +): string | null { + if (liveApp) return LIVE_TAB; + return deliverables.at(-1)?.kind ?? null; +} + +/** + * The pane's selection once the person's choice is read: theirs when they made one; else, after a + * reload, the default, which the pane shows itself when it is the live app and asks the page to + * open when it is a document, so the document is read like any the person picks. + */ +export function resolveSelection( + chosenId: string | null, + liveApp: boolean, + deliverables: readonly Deliverable[], +): { selectedId: string | null; documentToOpen: string | null } { + if (chosenId !== null) return { selectedId: chosenId, documentToOpen: null }; + const fallback = defaultTab(liveApp, deliverables); + return fallback === LIVE_TAB + ? { selectedId: LIVE_TAB, documentToOpen: null } + : { selectedId: null, documentToOpen: fallback }; +} const liveIds = panelTabIds(TAB_PREFIX, LIVE_TAB); const THEMES_TAB = "design-themes"; const themesIds = panelTabIds(TAB_PREFIX, THEMES_TAB); @@ -209,7 +238,7 @@ export function ArtifactPane({ }, [openTab]); const sideConcept = side === null ? undefined : conceptOfTab(side); const conceptShown = conceptViews.some((view) => view.label === sideConcept); - const selectedId = + const chosenId = side === DRAFT_TAB && draftShown ? DRAFT_TAB : conceptShown @@ -221,6 +250,15 @@ export function ArtifactPane({ : selected?.kind === "design" && designPart === "mockup" ? MOCKUP_TAB : (selected?.kind ?? null); + const { selectedId, documentToOpen } = resolveSelection( + chosenId, + liveApp !== null, + deliverables, + ); + // A document is chosen through the page, which reads it; the live app the pane shows itself. + useEffect(() => { + if (documentToOpen !== null) onSelect(documentToOpen); + }, [documentToOpen]); const [requestedVersions, setRequestedVersions] = useState< Record >({});