diff --git a/packages/studio/src/components/StudioHeader.embed.dom.test.tsx b/packages/studio/src/components/StudioHeader.embed.dom.test.tsx
deleted file mode 100644
index c6c30e47468..00000000000
--- a/packages/studio/src/components/StudioHeader.embed.dom.test.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-// @vitest-environment happy-dom
-
-/** Embedded in promptwise: no header; the Timeline tab's toolbar drives Inspector and Export by message. */
-import React, { act } from "react";
-import { createRoot } from "react-dom/client";
-import { expect, it, vi } from "vitest";
-
-(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
-
-const layout = { rightCollapsed: true, setRightCollapsed: vi.fn(), setRightPanelTab: vi.fn() };
-
-vi.mock("../promptwise/embed", () => ({ embeddedInPromptwise: true }));
-vi.mock("../contexts/StudioContext", () => ({
- useStudioShellContext: () => ({
- projectId: "773d81ab-4ac0-4f9d-acd7-4cd8838312dd",
- renderQueue: { isRendering: false, ffmpegMissing: false },
- }),
-}));
-vi.mock("../contexts/PanelLayoutContext", () => ({ usePanelLayoutContext: () => layout }));
-vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent: vi.fn() }));
-
-const { StudioHeader } = await import("./StudioHeader");
-
-function command(name: string) {
- act(() => {
- window.dispatchEvent(
- new MessageEvent("message", {
- data: { type: "promptwise:command", command: name },
- source: window.parent,
- }),
- );
- });
-}
-
-it("draws no header, runs the toolbar's presses and reports their state", () => {
- const posted: unknown[] = [];
- const postMessage = vi.spyOn(window.parent, "postMessage").mockImplementation((message) => {
- posted.push(message);
- });
- const onExport = vi.fn();
- const host = document.createElement("div");
- document.body.append(host);
- const root = createRoot(host);
- act(() =>
- root.render(
- ,
- ),
- );
-
- expect(host.innerHTML).toBe("");
- expect(posted).toEqual([{ type: "promptwise:state", inspectorOpen: false, rendering: false }]);
-
- command("inspector");
- expect(layout.setRightPanelTab).toHaveBeenLastCalledWith("design");
- expect(layout.setRightCollapsed).toHaveBeenLastCalledWith(false);
-
- command("export");
- expect(layout.setRightPanelTab).toHaveBeenLastCalledWith("renders");
- expect(onExport).toHaveBeenCalledOnce();
-
- // Only the parent page may drive Studio, and only with the commands it knows.
- window.dispatchEvent(
- new MessageEvent("message", { data: { type: "promptwise:command", command: "export" } }),
- );
- command("delete-everything");
- expect(onExport).toHaveBeenCalledOnce();
-
- act(() => root.unmount());
- host.remove();
- postMessage.mockRestore();
-});
diff --git a/packages/studio/src/components/StudioHeader.tsx b/packages/studio/src/components/StudioHeader.tsx
index 4347e015756..87a3a019592 100644
--- a/packages/studio/src/components/StudioHeader.tsx
+++ b/packages/studio/src/components/StudioHeader.tsx
@@ -7,8 +7,6 @@ import { Button, cn, Tooltip } from "./ui";
import { ButtonGroup, ButtonGroupItem } from "../boardui/components/base/buttons/button-group";
import { Dock } from "./dock/Dock";
import { PromptwiseMark } from "../promptwise/PromptwiseMark";
-import { embeddedInPromptwise } from "../promptwise/embed";
-import { usePromptwiseToolbar } from "../promptwise/usePromptwiseToolbar";
import { ShowThemeToggle, ThemeToggle } from "./ThemeToggle";
interface StudioHeaderProps {
@@ -57,39 +55,6 @@ export const StudioHeader = memo(function StudioHeader({
const isRendering = renderQueue.isRendering;
const ffmpegMissing = renderQueue.ffmpegMissing;
- const toggleInspector = () => {
- if (shouldOpenInspector(rightCollapsed, inspectorPanelActive)) {
- trackStudioEvent("panel_toggle", { panel: "inspector", collapsed: false });
- setRightPanelTab("design");
- setRightCollapsed(false);
- return;
- }
- trackStudioEvent("panel_toggle", { panel: "inspector", collapsed: true });
- // Keep the current selection when collapsing the Inspector — closing
- // the panel shouldn't deselect the element.
- setRightCollapsed(true);
- };
-
- const exportVideo = () => {
- if (isRendering) return;
- setRightPanelTab("renders");
- setRightCollapsed(false);
- // Without an encoder this render cannot finish, so the click
- // delivers the user to the prompt that fixes it instead of
- // queueing a job that exists only to fail. Disabling the button
- // would leave them staring at a dead control with no route to
- // the explanation.
- if (ffmpegMissing) return;
- onExport?.();
- };
-
- usePromptwiseToolbar(
- { inspectorOpen: inspectorButtonActive, rendering: isRendering },
- { inspector: toggleInspector, export: exportVideo },
- );
- // promptwise's Timeline tab has its own toolbar and tab name, so a second header would only repeat them.
- if (embeddedInPromptwise) return null;
-
return (
@@ -139,7 +104,18 @@ export const StudioHeader = memo(function StudioHeader({
aria-pressed={inspectorButtonActive}
selected={inspectorButtonActive}
leadingIcon={RiLayoutRightLine}
- onClick={toggleInspector}
+ onClick={() => {
+ if (shouldOpenInspector(rightCollapsed, inspectorPanelActive)) {
+ trackStudioEvent("panel_toggle", { panel: "inspector", collapsed: false });
+ setRightPanelTab("design");
+ setRightCollapsed(false);
+ return;
+ }
+ trackStudioEvent("panel_toggle", { panel: "inspector", collapsed: true });
+ // Keep the current selection when collapsing the Inspector — closing
+ // the panel shouldn't deselect the element.
+ setRightCollapsed(true);
+ }}
>
Inspector
@@ -161,7 +137,18 @@ export const StudioHeader = memo(function StudioHeader({
variant="primary"
data-testid="header-export"
disabled={isRendering}
- onClick={exportVideo}
+ onClick={() => {
+ if (isRendering) return;
+ setRightPanelTab("renders");
+ setRightCollapsed(false);
+ // Without an encoder this render cannot finish, so the click
+ // delivers the user to the prompt that fixes it instead of
+ // queueing a job that exists only to fail. Disabling the button
+ // would leave them staring at a dead control with no route to
+ // the explanation.
+ if (ffmpegMissing) return;
+ onExport?.();
+ }}
>
{isRendering ? "Rendering…" : "Export"}
diff --git a/packages/studio/src/components/dock/dockLayout.test.ts b/packages/studio/src/components/dock/dockLayout.test.ts
index 40c3a1271c2..bddf62e8da8 100644
--- a/packages/studio/src/components/dock/dockLayout.test.ts
+++ b/packages/studio/src/components/dock/dockLayout.test.ts
@@ -132,40 +132,3 @@ describe("applySideMinimums", () => {
expect(timeline.group.minimumWidth).toBe(120);
});
});
-
-describe("buildEditLayout on a narrow dock", () => {
- let api: DockviewApi;
- beforeEach(() => {
- ({ api } = mountBareDockview());
- });
- afterEach(() => {
- api.dispose();
- document.body.innerHTML = "";
- });
-
- it("puts the preview over the timeline and one hidden side group beside it, as in promptwise's tab", () => {
- api.layout(640, 820);
- buildEditLayout(api, 640);
- const side = api.getPanel("design")!.group;
- expect(side.panels.map((panel) => panel.id)).toEqual([
- "design",
- "layers",
- "renders",
- "compositions",
- "assets",
- ]);
- expect(side.api.isVisible).toBe(false);
- expect(api.getPanel("preview")!.group.api.isVisible).toBe(true);
- expect(api.getPanel("preview")!.group.width).toBe(640);
- expect(api.getPanel("timeline")!.group.width).toBe(640);
- expect(api.getPanel("timeline")!.group.height).toBe(260);
- expect(api.getPanel("code")).toBeUndefined();
- });
-
- it("keeps the three columns on a wide one", () => {
- api.layout(1200, 700);
- buildEditLayout(api, 1200);
- expect(api.getPanel("compositions")!.group).not.toBe(api.getPanel("design")!.group);
- expect(api.getPanel("design")!.group.api.isVisible).toBe(true);
- });
-});
diff --git a/packages/studio/src/components/dock/dockLayout.ts b/packages/studio/src/components/dock/dockLayout.ts
index acd9f8c1a35..94886d93aea 100644
--- a/packages/studio/src/components/dock/dockLayout.ts
+++ b/packages/studio/src/components/dock/dockLayout.ts
@@ -10,11 +10,6 @@ const MIN_SIDE_W_FLOOR = 120;
const DEFAULT_TIMELINE_H = 360;
const DEFAULT_LEFT = ["compositions", "assets", "code", "catalog"] as const;
const DEFAULT_RIGHT = ["design", "layers", "renders", "variables", "motionBlur"] as const;
-/** Below this dock width (promptwise's Timeline tab beside its chat) two side columns would crush the preview. */
-const COMPACT_DOCK_W = 1000;
-const COMPACT_SIDE = ["design", "layers", "renders", "compositions", "assets"] as const;
-/** Room for three tracks under the toolbar and ruler; the preview gets the rest. */
-const COMPACT_TIMELINE_H = 260;
/** Preferred side widths; when they overflow the preview's floor the right yields first, then the left. */
export function defaultSideWidths(viewportWidth: number) {
@@ -104,20 +99,12 @@ function addSideColumn(
return first;
}
-/**
- * The default Edit layout: [library | preview | inspector] over a full-width timeline, or on a narrow
- * dock the preview over the timeline with one side group beside the preview, shown when asked for
- * (Inspector, a selection) since it would leave the preview too little room.
- */
+/** The default Edit layout: [library | preview | inspector] over a full-width timeline. */
export function buildEditLayout(
api: DockviewApi,
viewportWidth: number,
panels: readonly PanelId[] = PANEL_IDS,
) {
- if (viewportWidth < COMPACT_DOCK_W) {
- buildCompactLayout(api, viewportWidth, panels);
- return;
- }
api.clear();
const widths = defaultSideWidths(viewportWidth);
addRegisteredPanel(api, "preview");
@@ -129,15 +116,3 @@ export function buildEditLayout(
if (right) api.getPanel(right)?.group.api.setSize({ width: widths.right });
api.getPanel("timeline")?.group.api.setSize({ height: DEFAULT_TIMELINE_H });
}
-
-function buildCompactLayout(api: DockviewApi, viewportWidth: number, panels: readonly PanelId[]) {
- api.clear();
- addRegisteredPanel(api, "preview");
- addRegisteredPanel(api, "timeline", { referencePanel: "preview", direction: "below" });
- const side = addSideColumn(api, COMPACT_SIDE, panels, "right");
- applySideMinimums(api, viewportWidth);
- api.getPanel("timeline")?.group.api.setSize({ height: COMPACT_TIMELINE_H });
- const group = side ? api.getPanel(side)?.group : undefined;
- group?.api.setSize({ width: Math.max(MIN_SIDE_W, defaultSideWidths(viewportWidth).right) });
- group?.api.setVisible(false);
-}
diff --git a/packages/studio/src/components/editor/PropertyPanelEmptyState.embed.dom.test.tsx b/packages/studio/src/components/editor/PropertyPanelEmptyState.embed.dom.test.tsx
deleted file mode 100644
index 2b21a6f6328..00000000000
--- a/packages/studio/src/components/editor/PropertyPanelEmptyState.embed.dom.test.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-// @vitest-environment happy-dom
-
-/** In promptwise most changes go through the agent, so its button leads the empty Design panel. */
-import React, { act } from "react";
-import { createRoot } from "react-dom/client";
-import { expect, it, vi } from "vitest";
-
-(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
-
-vi.mock("../../promptwise/embed", async (original) => ({
- ...(await original()),
- embeddedInPromptwise: true,
-}));
-
-const { PropertyPanelEmptyState } = await import("./PropertyPanelEmptyState");
-
-it("puts the agent first, as the panel's one primary button, and asks promptwise to open its chat", () => {
- const posted: unknown[] = [];
- const postMessage = vi.spyOn(window.parent, "postMessage").mockImplementation((message) => {
- posted.push(message);
- });
- const host = document.createElement("div");
- document.body.append(host);
- const root = createRoot(host);
- act(() => root.render());
-
- const text = host.textContent ?? "";
- expect(text.indexOf("Ask the agent")).toBeLessThan(text.indexOf("Record a gesture"));
- const agent = host.querySelector("button")!;
- expect(agent.textContent).toContain("Ask the agent");
- act(() => agent.click());
- expect(posted).toEqual([{ type: "promptwise:ask-agent" }]);
-
- act(() => root.unmount());
- host.remove();
- postMessage.mockRestore();
-});
diff --git a/packages/studio/src/components/editor/PropertyPanelEmptyState.test.tsx b/packages/studio/src/components/editor/PropertyPanelEmptyState.test.tsx
index 1578a77bb71..2848c1ee0e3 100644
--- a/packages/studio/src/components/editor/PropertyPanelEmptyState.test.tsx
+++ b/packages/studio/src/components/editor/PropertyPanelEmptyState.test.tsx
@@ -28,7 +28,7 @@ describe("PropertyPanelEmptyState — flat empty", () => {
expect(host.textContent).toContain("Nothing selected");
expect(host.textContent).toContain("Record a gesture");
// The agent chip only shows inside promptwise, whose chat it opens.
- expect(host.textContent).not.toContain("Ask the agent");
+ expect(host.textContent).not.toContain("Describe a change to the agent");
act(() => root.unmount());
});
});
diff --git a/packages/studio/src/components/editor/PropertyPanelEmptyState.tsx b/packages/studio/src/components/editor/PropertyPanelEmptyState.tsx
index 8385c190baa..067d9068210 100644
--- a/packages/studio/src/components/editor/PropertyPanelEmptyState.tsx
+++ b/packages/studio/src/components/editor/PropertyPanelEmptyState.tsx
@@ -3,7 +3,6 @@ import type { DomEditSelection } from "./domEditingTypes";
import { canHideSelections } from "../../utils/timelineInspector";
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
import { askPromptwiseAgent, embeddedInPromptwise } from "../../promptwise/embed";
-import { Button } from "../ui";
function FlatEmptyState() {
return (
@@ -25,17 +24,6 @@ function FlatEmptyState() {
Click any element on the canvas to edit it, or drag to select several.
- {/* In promptwise the agent is the way most changes are made, so it comes first. */}
- {embeddedInPromptwise && (
-
- )}
●
@@ -45,6 +33,16 @@ function FlatEmptyState() {
R
+ {embeddedInPromptwise && (
+
+ )}
);
diff --git a/packages/studio/src/components/editor/SnapToolbar.tsx b/packages/studio/src/components/editor/SnapToolbar.tsx
index 63637d484b5..8d5c72a2d55 100644
--- a/packages/studio/src/components/editor/SnapToolbar.tsx
+++ b/packages/studio/src/components/editor/SnapToolbar.tsx
@@ -5,10 +5,6 @@ import { usePlayerStore } from "../../player/store/playerStore";
import { usePreviewOverlayContext } from "./PreviewOverlayProvider";
import { ownsPlainKeys } from "../../utils/typingTarget";
-/** A preview overlay button that's off: BoardUI's light surface with a hairline, readable over any frame. */
-const overlayButtonOff =
- "bg-background-primary-default/90 text-foreground-icon-secondary shadow-xs ring-1 ring-border-button-default hover:bg-background-primary-hover hover:text-foreground-icon-primary";
-
// fallow-ignore-next-line complexity
export const SnapToolbar = memo(function SnapToolbar() {
const [gridPopoverOpen, setGridPopoverOpen] = useState(false);
@@ -91,7 +87,9 @@ export const SnapToolbar = memo(function SnapToolbar() {