From 0498541baec6ce3242007059cfe920026fa3e1c6 Mon Sep 17 00:00:00 2001 From: "Brian J. Fox" Date: Mon, 2 Nov 2026 10:06:41 +0000 Subject: [PATCH] fix(web): open a default tab when nothing is chosen in the document pane After a reload with nothing chosen and nothing newly filed, the pane's selection was null: the body rendered empty and the UI kit logged that the value named no tab and no pane. The tab the person needed, the live preview, had also scrolled off the strip's edge, and an unselected strip scrolls nothing into view. With nothing chosen, show the live app once the build has started, else ask the page to open the document furthest along the project's flow, so it is read like any the person picks. A selected tab scrolls into view. Closes #92 --- .../pages/workspace/artifact-pane.test.tsx | 19 ++++++++- .../web/src/pages/workspace/artifact-pane.tsx | 40 ++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) 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 >({});