export interface SplitEmbed { appId: string; payload: unknown; } export interface SplitViewState { /** Whether the workspace layout is active. */ expanded: boolean; /** The user's explicit pick; undefined = follow the latest embed. */ selectedAppId: string | undefined; /** Registered app embeds in thread order (latest last). */ embeds: SplitEmbed[]; /** BUILDS whose plan-time "stage" hint has already had its one auto-open shot (spec §2 G1: nothing may open itself twice, and nothing re-opens after the user has closed it). RULING 23 — these are build keys, not app ids. Keyed by app, the ledger was per-app for the LIFE OF THE SURFACE: after a user collapsed the stage, an EXPLICIT new build request for the same app never staged again. G1 forbids the UI opening ITSELF; answering a fresh request is not that. */ autoStaged: string[]; /** Whether the workspace on screen is the USER's (their Expand affordance) rather than a build's auto-opened stage. Only the latter goes away with its embed. */ userExpanded: boolean; } export declare const initialSplitViewState: SplitViewState; export type SplitViewAction = /** `auto` marks a build's stage opening itself (the §5 V4 hint); absent, this is the user's own Expand and the workspace becomes theirs to close. */ { type: "expand"; auto?: boolean; } | { type: "collapse"; } | { type: "toggle"; } /** An explicit user pick (clicking an app embed in the rail). */ | { type: "feature"; appId: string; } /** An app embed rendered (or its payload updated) in the thread. A repeat registration moves the embed to "latest" only when its payload changed message identity — re-renders keep order. */ | { type: "embed"; appId: string; payload: unknown; } /** The plan-time display hint spending its ONE auto-open shot for a BUILD (ruling 23 — `buildKey` identifies the turn's view part, not the app). */ | { type: "auto-stage"; buildKey: string; } /** The embed left the thread (unmounted with the conversation). */ | { type: "remove-embed"; appId: string; }; export declare function splitViewReducer(state: SplitViewState, action: SplitViewAction): SplitViewState; /** * The stage's app: the explicit pick, else the most recent embed in the thread. * * A pick is AUTHORITATIVE — it does not fall back. A ✦ on a pinned app can name * an app this conversation has never embedded, and "then show the latest" put * some OTHER app on the stage while the composer named the right one, which * reads as the app having been swapped under the user. Nothing until the pick * has something to show is the honest answer. `remove-embed` already clears a * pick it deletes, so no existing path reaches the fallback anyway. */ export declare function featuredEmbed(state: SplitViewState): SplitEmbed | undefined; /** Escape order: collapse the workspace first, close the overlay second. */ export declare function escapeIntent(state: SplitViewState): "collapse" | "close"; /** What the overlay hands the thread subtree. Null outside a split-capable surface (an embedded VendoThread) — app cards then behave exactly as before. */ export interface SplitViewContextValue { expanded: boolean; featuredAppId: string | undefined; feature(appId: string): void; /** Expand the workspace with THIS app featured — the compact card's prominent Expand affordance (2026-07 demo feedback). A USER gesture. */ expandTo(appId: string): void; /** The plan-time display hint (§5 V4) asking for the stage. Idempotent per BUILD (`buildKey` — the turn's own view part), so a hint can never fight the user: after Back-to-chat this build's workspace stays closed until they open it themselves (§2 G1 — nothing auto-opens or auto-folds), while a NEW build they asked for still gets its stage (ruling 23). Callers do NOT need their own "already fired" bookkeeping. */ autoStage(appId: string, buildKey: string): void; registerEmbed(appId: string, payload: unknown): void; removeEmbed(appId: string): void; } export declare const SplitViewContext: import("react").Context; export declare function useSplitView(): SplitViewContextValue | null; export interface MorphRect { top: number; left: number; width: number; height: number; } /** Where the stage PANE will sit once the expand transition settles — the * target rect for the embed's FLIP ghost. Computed (not measured) because a * CSS transition interpolates: at flight time the DOM still reports the * compact layout, and suppressing the transitions to measure would kill the * panel spring the ghost rides alongside. The stage is the RIGHT pane, so * its left edge sits past the rail. */ export declare function expandedStageRect(viewport: { width: number; height: number; }): MorphRect;