/**
* InteractiveEditor — tabbed editor for one interactive experience.
*
* UI-2a placeholder — just renders the project header and a tab
* strip with the six future sub-panels disabled. The real panel
* bodies land in UI-3 / UI-4 / UI-5:
*
* UI-3: Graph — DAG visualizer over ix_nodes + ix_edges
* Catalog — action_catalog CRUD with level/scheme gates
* UI-4: Rules — personalization-rule DSL builder
* QA — run checks, render issues
* Publish — channel picker + publish flow
* UI-5: Analytics — experience + session rollups
*
* The editor owns the currently-selected tab in local state;
* switching tabs never unmounts the project header so the
* breadcrumb + status badge stay stable.
*/
import React, { useMemo, useState } from "react";
import { BarChart3, CheckSquare, GitBranch, ListChecks, Play, Send, Sparkles, } from "lucide-react";
import { createInteractiveApi } from "./interactive/api";
import { AnalyticsPanel } from "./interactive/AnalyticsPanel";
import { CatalogPanel } from "./interactive/CatalogPanel";
import { GraphPanel } from "./interactive/GraphPanel";
import { PublishPanel } from "./interactive/PublishPanel";
import { QAPanel } from "./interactive/QAPanel";
import { RulesPanel } from "./interactive/RulesPanel";
import { ErrorBanner, StatusBadge, useAsyncResource, } from "./interactive/ui";
const TABS = [
{ key: "graph", label: "Graph", icon: , description: "Scenes, branches, and transitions" },
{ key: "catalog", label: "Catalog", icon: , description: "Actions viewers can take" },
{ key: "rules", label: "Rules", icon: , description: "Personalization routing" },
{ key: "qa", label: "QA", icon: , description: "Readiness checks" },
{ key: "publish", label: "Publish", icon: , description: "Channels + versions" },
{ key: "analytics", label: "Analytics", icon: , description: "Viewer engagement" },
];
export function InteractiveEditor({ backendUrl, apiKey, projectId, onPlay, }) {
const api = useMemo(() => createInteractiveApi(backendUrl, apiKey), [backendUrl, apiKey]);
const [activeTab, setActiveTab] = useState("graph");
const exp = useAsyncResource((signal) => api.getExperience(projectId, signal), [api, projectId]);
// Same shell pattern as the wizard: project header + tab strip
// are pinned; only the active panel scrolls. Keeps the tab row
// visible at all times even when a panel (analytics cards,
// catalog table) is tall.
return (
{exp.error ? (
) : exp.loading || !exp.data ? () : ()}
{activeTab === "graph" &&
}
{activeTab === "catalog" && }
{activeTab === "rules" && }
{activeTab === "qa" && }
{activeTab === "publish" && }
{activeTab === "analytics" && }
);
}
// ────────────────────────────────────────────────────────────────
// Project header (title, status, description)
// ────────────────────────────────────────────────────────────────
function ProjectHeader({ experience, onPlay, }) {
return (
{experience.title || "(untitled)"}
{(experience.description || experience.objective) && (
{experience.description || experience.objective}
)}
mode: {experience.experience_mode}
profile: {experience.policy_profile_id}
{typeof experience.branch_count === "number" && ({experience.branch_count} {experience.branch_count === 1 ? "branch" : "branches"})}
{typeof experience.max_depth === "number" && (depth {experience.max_depth})}
{onPlay && ()}
);
}
function ProjectHeaderSkeleton() {
return ();
}
// ────────────────────────────────────────────────────────────────
// Tab strip
// ────────────────────────────────────────────────────────────────
function TabStrip({ active, onChange, }) {
return ();
}