import { AssistantToolStep } from '../../ui/AssistantToolActivity'; /** * ★★ THE UNION OF BOTH VOCABULARIES IN USE, not either one of them. * * - this package's tool steps: `queued | running | completed | failed` * - BigConsole's build steps: `pending | running | done | skipped` * * `failed` and `skipped` each exist in exactly one of them, and that asymmetry * is not cosmetic: BigConsole's own pip renders four statuses and falls through * to the not-started dot for anything else, so feeding it a `failed` tool step * would have drawn a FAILED tool as one that never started. Whatever this * surface accepts, it must draw — see `AssistantStepRail`, which has a branch * for all five. */ export type AssistantPreviewStepStatus = 'pending' | 'running' | 'done' | 'failed' | 'skipped'; /** * Which step the surface follows when the viewer has not pinned a tab. * * ★★★ THE DEFAULT IS `produced`, AND THAT IS NOT THE OBVIOUS CHOICE. * "Follow the running step" is the intuitive rule and it is wrong for this * surface: these tabs show the ENTITIES a step produced, and a step that is * still running has not produced one yet — so following it parks the viewer on * an empty tab for as long as the work takes. The real consumer had already * worked this out and written it down (BigConsole's `AiPreviewPanel`: "so the * moment the data sink is created you see its rows, not an empty 'waiting for * the dashboard' tab"). The rail still shows the spinner on the next step, so * nothing about progress is lost. * * `running` remains available for a surface whose tabs show live output of the * step itself rather than its result. */ export type AssistantPreviewFollow = 'produced' | 'running'; export interface AssistantPreviewStep { /** Stable within the run. Matched against tab ids to follow the run. */ readonly id: string; readonly label: string; readonly status: AssistantPreviewStepStatus; } /** * Adapts this package's parsed tool activity into the surface's vocabulary, so * a product on the `preview` capability can render the same rail as a product * driving the surface from its own store. * * ★ Total by construction: `FROM_TOOL_STATUS` is keyed on the tool status type, * so adding a status there is a compile error here rather than a step that * silently renders as `pending`. */ export declare function stepsFromToolActivity(steps: readonly AssistantToolStep[]): AssistantPreviewStep[]; /** * Which tab the surface is showing, and whether the viewer chose it. * * ★★ EXTRACTED AS A PURE REDUCER BECAUSE IT CANNOT BE TESTED ANY OTHER WAY. * This package has no DOM in tests — every render test uses * `renderToStaticMarkup`, which runs NO effects and cannot click anything. A * follow-the-run rule expressed as a `useEffect` would be literally unreachable * from the suite, so it lives here and the component is a thin wrapper. */ export interface ActiveTabState { /** The tab currently shown. DERIVED every render, and persisted by the caller * only so it can survive a moment when the run points nowhere. */ readonly activeId: string | null; /** * The tab the viewer CHOSE, kept separately from the one on screen. * * ★★★ THESE TWO WERE ONE FIELD AND THAT WAS A BUG. When a pinned tab dropped * out of `tabIds` for a moment — tabs arrive from props and can blink — the * reducer put the run's fallback in `activeId` while leaving the pin set, and * the caller persisted the whole thing. The fallback then WAS the pin: the * viewer's actual choice was gone even after their tab came back. * * ★★ Without this, a pin cannot survive its own tab's absence, which is * exactly the case a pin is for. */ readonly pinnedId: string | null; /** * The run ALL of this state belongs to — the shown tab as much as the pin. * * ★★ One key for both, because carrying either across runs is the same * mistake. An earlier revision keyed only the pin, so an UNPINNED surface * that finished `r1` on a later tab opened `r2` still showing it. */ readonly runKey: string | null; } export declare const IDLE_ACTIVE_TAB: ActiveTabState; export interface ActiveTabInput { /** * Identifies the run. A CHANGE releases the viewer's pin. * * ★ null means "no run" and does NOT release it: a surface sitting idle * between runs must not reshuffle itself under a reader. */ readonly runKey: string | null; /** Tab ids, in display order. */ readonly tabIds: readonly string[]; /** Steps, in pipeline order. Ids are matched against `tabIds`. */ readonly steps: readonly AssistantPreviewStep[]; /** Defaults to `produced` — see `AssistantPreviewFollow` for why. */ readonly follow?: AssistantPreviewFollow; } /** * The surface's only decision, applied on every render. * * Order matters and each clause earns its place: * 1. a new run releases the pin — the viewer's choice was about the last run * 2. a live pin wins over the run, but only while its tab still exists * 3. otherwise follow the run (running step, else the last settled one) * 4. otherwise keep what is showing, if it still exists * 5. otherwise the first tab — and null only when there are no tabs at all */ export declare function nextActiveTab(state: ActiveTabState, input: ActiveTabInput): ActiveTabState; /** * The viewer chose a tab. Pins it to the CURRENT run, so the pin is released * by the next one rather than outliving it. */ export declare function pinActiveTab(activeId: string, runKey: string | null): ActiveTabState; /** * The tab an arrow key moves to, or null when the key is not ours. * * ★★ A tablist using roving `tabIndex` MUST handle these. With only the * selected tab in the tab order and no key handling, every other tab is * unreachable by keyboard — strictly worse than leaving them all tabbable. * * ★ Wraps at both ends, which is what `role="tablist"` implies, and is pure so * it can be tested in a package with no DOM. */ export declare function tabForArrowKey(key: string, tabIds: readonly string[], activeId: string | null, /** * ★★ Arrow keys follow the VISUAL direction, so they swap in RTL: with the * tabs laid out right-to-left, ArrowRight moves to the PREVIOUS one. Getting * this wrong is not a rough edge for an Arabic or Hebrew reader — every * arrow press goes the opposite way from the one they pressed. */ rtl?: boolean): string | null; /** * Whether SELECTING `tabId` — a click, or Enter/Space on a focused tab — should * be recorded as the viewer's choice. * * ★★★ COMPARED AGAINST THE PIN, NOT AGAINST WHAT IS ON SCREEN. * * Selecting the tab you are already reading is the commonest way to say "stay * here", and it used to do nothing. The guard compared against * `pinnedId ?? activeId`, so with nothing pinned a click on the displayed tab * was swallowed as "no preference different from what the surface chose". In a * real run that is exactly backwards: the surface puts you on the entity that * just landed, you start reading it, the next one lands seconds later and takes * you away — and the one tab you want to click is the only one that is inert. * Reaching a pin meant clicking some other tab and clicking back. * * Comparing against the pin alone keeps the case the old guard was written for. * A pinned tab that disappears KEEPS its pin while a fallback is shown, so * clicking that fallback still replaces a pin the viewer can no longer see. * * The only selection worth ignoring is one that is already the pin. */ export declare function shouldPinOnSelect(tabId: string, pinnedId: string | null): boolean; /** * Whether MOVING to `nextId` with an arrow/Home/End key should create a pin. * * ★★★ A DIFFERENT QUESTION FROM `shouldPinOnSelect`, and the difference is the * point. A click says "I choose this one" even when it is already displayed. A * navigation key says "go there" — and when there is nowhere to go it has said * nothing at all. * * `tabForArrowKey` wraps, so in a ONE-TAB list every arrow resolves back to the * tab you are on, and Home/End do the same at their edges. A one-tab list is * not exotic here: it is what a run looks like before the second entity lands. * Pinning on that keypress would opt the viewer out of follow-the-run for the * rest of the build — silently, for a key that moved nothing. * * So this compares against what is DISPLAYED, falling back through the pin: a * key that lands on the tab already on screen pins nothing, while one that * lands anywhere else — including on the fallback shown in place of a pin whose * tab has disappeared — still records the choice. */ export declare function shouldPinOnMove(nextId: string, pinnedId: string | null, activeId: string | null): boolean; //# sourceMappingURL=entityPreviewState.d.ts.map