/** * The conversation map: what was asked, what the agent did about it, what it * delegated, where it failed, and what CFC said — read top to bottom. * * Vertical because a conversation is: turns follow turns, a delegation nests * under the call that made it, and a reader scans down rather than sideways. * Every node leads to its step, so this is also how you jump around a run. */ import { html, LitElement, nothing, type TemplateResult } from "lit"; import type { ConsoleCellLabelsSummary } from "../cell-labels.ts"; import type { ConsoleFlow, ConsoleFlowNode } from "./api.ts"; import "./cell-chip.ts"; /** How long a numeric run has to be before it reads as a channel. */ const WIDE_NUMERIC_RUN = 32; /** * What the run's per-cell labels amount to, in one line. * * A cell drawn with no atoms says one thing under a run whose space was read * and another under a run where nobody asked, and the chip cannot tell those * apart on its own. The header carries the difference, so an empty chip is * read against a stated regime rather than as an absence of secrets. * * "So many of so many carry one" is a count over a reading that covered every * cell it names. Where a cell of the run was read only in part, the count is * a floor — another label may sit at a path nothing was read at — so the line * leads with the reading being partial rather than with a total. */ export const labelRegime = (labels: ConsoleCellLabelsSummary): string => { if (labels.status === "absent") { return "cell labels not read"; } if (labels.status === "unavailable") { return `cell labels unavailable${ labels.detail === undefined ? "" : ` · ${labels.detail}` }`; } const carry = `${labels.cellsLabelled} of ${labels.cellsRead} carry one`; return labels.cellsPartial === 0 ? `cell labels read · ${carry}` : `cell labels read in part · ${carry}, ${labels.cellsPartial} only partly read`; }; export class ConsoleFlowView extends LitElement { static override properties = { flow: { attribute: false }, focusStep: { attribute: false }, focusRunId: { attribute: false }, }; declare flow: ConsoleFlow | undefined; /** The step the timeline is on, marked here so the two stay in step. */ declare focusStep: number | undefined; /** The run that step belongs to, since a child's steps number from zero. */ declare focusRunId: string | undefined; protected override createRenderRoot(): HTMLElement { return this; } #node(node: ConsoleFlowNode): TemplateResult { const here = node.step === this.focusStep && (this.focusRunId === undefined || node.runId === this.focusRunId); const wide = (node.longestNumericRun ?? 0) >= WIDE_NUMERIC_RUN; return html`
Reading…
`; } if (flow.turns.length === 0) { return html`This run did nothing to map.
`; } return html`