diff --git a/docs/plans/2026-01-26-dock-supercontrols.md b/docs/plans/2026-01-26-dock-supercontrols.md new file mode 100644 index 0000000..c115fe1 --- /dev/null +++ b/docs/plans/2026-01-26-dock-supercontrols.md @@ -0,0 +1,466 @@ +# Dockable Supercontrols Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Replace Generate/Edit sidebar tabs with a unified dock system and add a filesystem-backed Tools dock as the primary entry point for tool discovery and execution. + +**Architecture:** Introduce a small dock layout framework (layout + column + panel + registry) and refactor existing sidebar sections into dock panels. The dock state (order/open/column) is persisted locally and panels are shown/hidden based on app context. + +**Tech Stack:** React + TypeScript, existing UI component library, localStorage. + +--- + +### Task 0: Add minimal test tooling for UI + +**Files:** +- Modify: `packages/comfystudio-ui/package.json` +- Create: `packages/comfystudio-ui/vitest.config.ts` +- Create: `packages/comfystudio-ui/src/test/setup.ts` + +**Step 1: Write the failing test** + +```ts +import { describe, expect, it } from "vitest"; + +describe("test setup", () => { + it("runs vitest", () => { + expect(true).toBe(true); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL with "Unknown command 'test'". + +**Step 3: Write minimal implementation** + +```json +{ + "scripts": { + "test": "vitest" + }, + "devDependencies": { + "jsdom": "^24.0.0", + "vitest": "^2.0.0", + "@testing-library/react": "^16.0.0", + "@testing-library/jest-dom": "^6.0.0" + } +} +``` + +```ts +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig({ + plugins: [react(), tsconfigPaths()], + test: { + environment: "jsdom", + setupFiles: ["./src/test/setup.ts"], + }, +}); +``` + +```ts +import "@testing-library/jest-dom"; +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/package.json packages/comfystudio-ui/vitest.config.ts packages/comfystudio-ui/src/test/setup.ts + git commit -m "test(ui): add vitest setup" +``` + +--- + +### Task 1: Create dock layout primitives + +**Files:** +- Create: `packages/comfystudio-ui/src/Dock/index.tsx` +- Create: `packages/comfystudio-ui/src/Dock/DockLayout.tsx` +- Create: `packages/comfystudio-ui/src/Dock/Column.tsx` +- Create: `packages/comfystudio-ui/src/Dock/Panel.tsx` +- Create: `packages/comfystudio-ui/src/Dock/State.ts` + +**Step 1: Write the failing test** + +```ts +import { describe, expect, it } from "vitest"; +import { DockState } from "~/Dock/State"; + +describe("DockState", () => { + it("initializes with default panels", () => { + const state = DockState.createDefault(); + expect(state.panels.length).toBeGreaterThan(0); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL with "test script not found" (will be added in a later task). + +**Step 3: Write minimal implementation** + +```ts +export type DockPanelState = { + id: string; + column: "left" | "right"; + order: number; + open: boolean; +}; + +export type DockLayoutState = { + panels: DockPanelState[]; +}; + +export namespace DockState { + export const createDefault = (): DockLayoutState => ({ + panels: [], + }); +} +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: Still FAIL until test tooling is added (resolved in Task 0). + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/Dock +git commit -m "feat(ui): add dock layout primitives" +``` + +--- + +### Task 2: Add dock layout rendering and persistence + +**Files:** +- Modify: `packages/comfystudio-ui/src/Dock/State.ts` +- Modify: `packages/comfystudio-ui/src/Dock/DockLayout.tsx` +- Modify: `packages/comfystudio-ui/src/Dock/Panel.tsx` + +**Step 1: Write the failing test** + +```ts +import { describe, expect, it } from "vitest"; +import { DockState } from "~/Dock/State"; + +describe("DockState persistence", () => { + it("hydrates from localStorage", () => { + localStorage.setItem("dock-layout.v1", JSON.stringify({ panels: [{ id: "tools", column: "left", order: 0, open: true }] })); + const state = DockState.load(); + expect(state.panels[0].id).toBe("tools"); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL until `DockState.load()` is implemented and test tooling exists. + +**Step 3: Write minimal implementation** + +```ts +const STORAGE_KEY = "dock-layout.v1"; + +export namespace DockState { + export const load = (): DockLayoutState => { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return createDefault(); + try { + return JSON.parse(raw) as DockLayoutState; + } catch { + return createDefault(); + } + }; + + export const save = (state: DockLayoutState): void => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + }; +} +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: Still FAIL until Task 0 adds testing setup. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/Dock/State.ts +git commit -m "feat(ui): persist dock layout state" +``` + +--- + +### Task 3: Extract existing sidebar content into dock panels + +**Files:** +- Create: `packages/comfystudio-ui/src/Dock/Panels/PromptPanel.tsx` +- Create: `packages/comfystudio-ui/src/Dock/Panels/InputPanel.tsx` +- Create: `packages/comfystudio-ui/src/Dock/Panels/AdvancedPanel.tsx` +- Create: `packages/comfystudio-ui/src/Dock/Panels/EditorToolPanel.tsx` +- Create: `packages/comfystudio-ui/src/Dock/Panels/LayersPanel.tsx` +- Modify: `packages/comfystudio-ui/src/Generation/Image/Sidebar/index.tsx` +- Modify: `packages/comfystudio-ui/src/Editor/Sidebar/index.tsx` + +**Step 1: Write the failing test** + +```tsx +import React from "react"; +import { describe, expect, it } from "vitest"; +import { PromptPanel } from "~/Dock/Panels/PromptPanel"; + +describe("PromptPanel", () => { + it("returns a React element", () => { + const element = PromptPanel({ inputId: "test" }); + expect(React.isValidElement(element)).toBe(true); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL until panel components and testing setup exist. + +**Step 3: Write minimal implementation** + +```tsx +export function PromptPanel({ inputId }: { inputId: ID }) { + return ; +} +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: Still FAIL until Task 0 adds testing setup. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/Dock/Panels + git commit -m "feat(ui): extract sidebar panels for docking" +``` + +--- + +### Task 4: Add Tools dock (filesystem-backed list stub) + +**Files:** +- Create: `packages/comfystudio-ui/src/Dock/Panels/ToolsPanel.tsx` +- Create: `packages/comfystudio-ui/src/Tools/Registry.ts` +- Create: `packages/comfystudio-ui/src/Tools/Types.ts` + +**Step 1: Write the failing test** + +```ts +import { describe, expect, it } from "vitest"; +import { ToolRegistry } from "~/Tools/Registry"; + +describe("ToolRegistry", () => { + it("returns an empty list when no backend is configured", async () => { + const tools = await ToolRegistry.list(); + expect(tools).toEqual([]); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL until registry exists and test tooling exists. + +**Step 3: Write minimal implementation** + +```ts +export namespace ToolRegistry { + export const list = async () => []; +} +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: Still FAIL until Task 0 adds testing setup. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/Tools packages/comfystudio-ui/src/Dock/Panels/ToolsPanel.tsx + git commit -m "feat(ui): add tools dock stub" +``` + +--- + +### Task 5: Replace sidebar tabs with unified dock layout + +**Files:** +- Modify: `packages/comfystudio-ui/src/Dock/DockLayout.tsx` +- Modify: `packages/comfystudio-ui/src/App/Sidebar/Sidebars.tsx` +- Modify: `packages/comfystudio-ui/src/App/index.tsx` +- Modify: `packages/comfystudio-ui/src/App/BottomBar/index.tsx` +- Modify: `packages/comfystudio-ui/src/Generation/Image/Sidebar/index.tsx` +- Modify: `packages/comfystudio-ui/src/Editor/Sidebar/index.tsx` + +**Step 1: Write the failing test** + +```tsx +import { describe, expect, it } from "vitest"; +import { render } from "@testing-library/react"; +import { DockLayout } from "~/Dock/DockLayout"; + +describe("DockLayout", () => { + it("renders the tools panel", () => { + const { getByText } = render(); + expect(getByText(/tools/i)).toBeTruthy(); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL until DockLayout is wired and testing setup exists. + +**Step 3: Write minimal implementation** + +```tsx +export function DockLayout() { + return ( + + + + ); +} +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: Still FAIL until Task 0 adds testing setup. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/Dock packages/comfystudio-ui/src/App + git commit -m "feat(ui): replace sidebar tabs with dock layout" +``` + +--- + +### Task 6: Wire dock panels to context and visibility rules + +**Files:** +- Modify: `packages/comfystudio-ui/src/Dock/DockLayout.tsx` +- Modify: `packages/comfystudio-ui/src/Dock/State.ts` +- Modify: `packages/comfystudio-ui/src/Dock/Panels/EditorToolPanel.tsx` + +**Step 1: Write the failing test** + +```ts +import { describe, expect, it } from "vitest"; +import { isPanelVisible } from "~/Dock/State"; + +describe("Dock visibility", () => { + it("hides editor tool panel when no brush tool is active", () => { + const visible = isPanelVisible("editor-tool", { activeTool: "select" }); + expect(visible).toBe(false); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL until `isPanelVisible` exists and testing setup exists. + +**Step 3: Write minimal implementation** + +```ts +export const isPanelVisible = ( + panelId: string, + ctx: { activeTool?: string } +) => { + if (panelId === "editor-tool") return ctx.activeTool === "brush"; + return true; +}; +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: Still FAIL until Task 0 adds testing setup. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/Dock/State.ts + git commit -m "feat(ui): add dock panel visibility rules" +``` + +--- + +### Task 8: Remove sidebar tab wiring and tidy references + +**Files:** +- Modify: `packages/comfystudio-ui/src/Generation/Image/Sidebar/index.tsx` +- Modify: `packages/comfystudio-ui/src/Editor/Sidebar/index.tsx` +- Modify: `packages/comfystudio-ui/src/App/Sidebar/Tab/*` +- Modify: `packages/comfystudio-ui/src/App/Sidebar/Sidebars.tsx` + +**Step 1: Write the failing test** + +```ts +import { describe, expect, it } from "vitest"; +import { render } from "@testing-library/react"; +import { App } from "~/App"; + +describe("Dock-only sidebar", () => { + it("renders without sidebar tabs", () => { + const { queryByText } = render(); + expect(queryByText(/Generate/i)).toBeNull(); + }); +}); +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: FAIL until tab UI is removed/hidden. + +**Step 3: Write minimal implementation** + +```tsx +export function Sidebars() { + return ; +} +``` + +**Step 4: Run test to verify it passes** + +Run: `yarn workspace @comfystudio/ui test --run` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add packages/comfystudio-ui/src/App/Sidebar packages/comfystudio-ui/src/Generation/Image/Sidebar packages/comfystudio-ui/src/Editor/Sidebar + git commit -m "refactor(ui): remove sidebar tabs in favor of docks" +```