+ {/* NOTE: Header / toolbar will be hoisted into the main workspace toolbar in later iterations */}
+
+
+ Flow (List View)
+
+ {steps.length} steps • {totalActions} actions
+
+
+
+ Transitional component
+
+
+
+
selectStep(id)}
+ onActionSelect={(actionId) =>
+ selectedStepId && actionId
+ ? selectAction(selectedStepId, actionId)
+ : undefined
+ }
+ onStepDelete={handleStepDelete}
+ onStepUpdate={handleStepUpdate}
+ onActionDelete={handleActionDelete}
+ emptyState={
+
+ No steps yet. Use the + Step button to add your first step.
+
+ }
+ headerRight={
+
+ (Add Step control will move to global toolbar)
+
+ }
+ />
+
+
+ );
+}
+
+export default FlowListView;
diff --git a/src/components/experiments/designer/layout/BottomStatusBar.tsx b/src/components/experiments/designer/layout/BottomStatusBar.tsx
new file mode 100644
index 0000000..86bd199
--- /dev/null
+++ b/src/components/experiments/designer/layout/BottomStatusBar.tsx
@@ -0,0 +1,339 @@
+"use client";
+
+import React, { useCallback, useMemo } from "react";
+import {
+ Save,
+ RefreshCw,
+ Download,
+ Hash,
+ AlertTriangle,
+ CheckCircle2,
+ UploadCloud,
+ Wand2,
+ Sparkles,
+ GitBranch,
+ Keyboard,
+} from "lucide-react";
+import { Button } from "~/components/ui/button";
+import { Badge } from "~/components/ui/badge";
+import { Separator } from "~/components/ui/separator";
+import { cn } from "~/lib/utils";
+import { useDesignerStore } from "../state/store";
+
+/**
+ * BottomStatusBar
+ *
+ * Compact, persistent status + quick-action bar for the Experiment Designer.
+ * Shows:
+ * - Validation / drift / unsaved state
+ * - Short design hash & version
+ * - Aggregate counts (steps / actions)
+ * - Last persisted hash (if available)
+ * - Quick actions (Save, Validate, Export, Command Palette)
+ *
+ * The bar is intentionally UI-only: callback props are used so that higher-level
+ * orchestration (e.g. DesignerRoot / Shell) controls actual side effects.
+ */
+
+export interface BottomStatusBarProps {
+ onSave?: () => void;
+ onValidate?: () => void;
+ onExport?: () => void;
+ onOpenCommandPalette?: () => void;
+ onToggleVersionStrategy?: () => void;
+ className?: string;
+ saving?: boolean;
+ validating?: boolean;
+ exporting?: boolean;
+ /**
+ * Optional externally supplied last saved Date for relative display.
+ */
+ lastSavedAt?: Date;
+}
+
+export function BottomStatusBar({
+ onSave,
+ onValidate,
+ onExport,
+ onOpenCommandPalette,
+ onToggleVersionStrategy,
+ className,
+ saving,
+ validating,
+ exporting,
+ lastSavedAt,
+}: BottomStatusBarProps) {
+ /* ------------------------------------------------------------------------ */
+ /* Store Selectors */
+ /* ------------------------------------------------------------------------ */
+ const steps = useDesignerStore((s) => s.steps);
+ const lastPersistedHash = useDesignerStore((s) => s.lastPersistedHash);
+ const currentDesignHash = useDesignerStore((s) => s.currentDesignHash);
+ const lastValidatedHash = useDesignerStore((s) => s.lastValidatedHash);
+ const pendingSave = useDesignerStore((s) => s.pendingSave);
+ const versionStrategy = useDesignerStore((s) => s.versionStrategy);
+ const autoSaveEnabled = useDesignerStore((s) => s.autoSaveEnabled);
+
+ const actionCount = useMemo(
+ () => steps.reduce((sum, st) => sum + st.actions.length, 0),
+ [steps],
+ );
+
+ const hasUnsaved = useMemo(
+ () =>
+ Boolean(currentDesignHash) &&
+ currentDesignHash !== lastPersistedHash &&
+ !pendingSave,
+ [currentDesignHash, lastPersistedHash, pendingSave],
+ );
+
+ const validationStatus = useMemo<"unvalidated" | "valid" | "drift">(() => {
+ if (!currentDesignHash || !lastValidatedHash) return "unvalidated";
+ if (currentDesignHash !== lastValidatedHash) return "drift";
+ return "valid";
+ }, [currentDesignHash, lastValidatedHash]);
+
+ const shortHash = useMemo(
+ () => (currentDesignHash ? currentDesignHash.slice(0, 8) : "—"),
+ [currentDesignHash],
+ );
+
+ const lastPersistedShort = useMemo(
+ () => (lastPersistedHash ? lastPersistedHash.slice(0, 8) : null),
+ [lastPersistedHash],
+ );
+
+ /* ------------------------------------------------------------------------ */
+ /* Derived Display Helpers */
+ /* ------------------------------------------------------------------------ */
+ function formatRelative(date?: Date): string {
+ if (!date) return "—";
+ const now = Date.now();
+ const diffMs = now - date.getTime();
+ if (diffMs < 30_000) return "just now";
+ const mins = Math.floor(diffMs / 60_000);
+ if (mins < 60) return `${mins}m ago`;
+ const hrs = Math.floor(mins / 60);
+ if (hrs < 24) return `${hrs}h ago`;
+ const days = Math.floor(hrs / 24);
+ return `${days}d ago`;
+ }
+
+ const relSaved = formatRelative(lastSavedAt);
+
+ const validationBadge = (() => {
+ switch (validationStatus) {
+ case "valid":
+ return (
+
+ {/* Left Cluster: Validation & Hash */}
+
+ {validationBadge}
+ {unsavedBadge}
+ {savingIndicator}
+
+
+
+ {shortHash}
+ {lastPersistedShort && lastPersistedShort !== shortHash && (
+
+ / {lastPersistedShort}
+
+ )}
+
+
+
+ {/* Middle Cluster: Aggregate Counts */}
+
+
+
+ {steps.length} steps
+
+
+
+ {actionCount} actions
+
+
+
+ {autoSaveEnabled ? "auto-save on" : "auto-save off"}
+
+
+
+ {versionStrategy.replace(/_/g, " ")}
+
+
+ Saved {relSaved}
+
+
+
+ {/* Flexible Spacer */}
+
+
+ {/* Right Cluster: Quick Actions */}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default BottomStatusBar;
diff --git a/src/components/experiments/designer/layout/PanelsContainer.tsx b/src/components/experiments/designer/layout/PanelsContainer.tsx
new file mode 100644
index 0000000..0364dd0
--- /dev/null
+++ b/src/components/experiments/designer/layout/PanelsContainer.tsx
@@ -0,0 +1,389 @@
+"use client";
+
+import React, {
+ useCallback,
+ useEffect,
+ useLayoutEffect,
+ useRef,
+ useState,
+ type ReactNode,
+} from "react";
+import { cn } from "~/lib/utils";
+
+/**
+ * PanelsContainer
+ *
+ * Structural layout component for the Experiment Designer refactor.
+ * Provides:
+ * - Optional left + right side panels (resizable + collapsible)
+ * - Central workspace (always present)
+ * - Persistent panel widths (localStorage)
+ * - Keyboard-accessible resize handles
+ * - Minimal DOM repaint during drag (inline styles)
+ *
+ * NOT responsible for:
+ * - Business logic or data fetching
+ * - Panel content semantics (passed via props)
+ *
+ * Accessibility:
+ * - Resize handles are