@dxos/react-ui-feed 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1 @@
1
- import { A as useItemSelection, C as highlights, D as useSelectionGroup, E as createSelectionGroup, M as useFeedNavigation, N as DecorationsProvider, O as ItemSelectionProvider, P as useDecorations, S as highlightTheme, T as SelectionGroupContext, _ as HtmlBlock, a as useJumpDetector, b as createWidgetStateStore, c as FeedModel, d as defaultRenderer, f as isPrompt, g as MarkdownBlock, h as sliceFeed, i as useMessageList, j as useItemSelectionValue, k as isAdditive, l as fromMessages, m as searchFeed, n as Minimap, o as usePositionLog, p as messageText, r as MessageList, s as useFeedModel, t as Outline, u as chatRenderer, v as WidgetScopeProvider, w as setHighlights, x as useWidgetState, y as WidgetStateProvider } from "./chunk-src.mjs";
2
- export { DecorationsProvider, FeedModel, HtmlBlock, ItemSelectionProvider, MarkdownBlock, MessageList, Minimap, Outline, SelectionGroupContext, WidgetScopeProvider, WidgetStateProvider, chatRenderer, createSelectionGroup, createWidgetStateStore, defaultRenderer, fromMessages, highlightTheme, highlights, isAdditive, isPrompt, messageText, searchFeed, setHighlights, sliceFeed, useDecorations, useFeedModel, useFeedNavigation, useItemSelection, useItemSelectionValue, useJumpDetector, useMessageList, usePositionLog, useSelectionGroup, useWidgetState };
1
+ export { N as DecorationsProvider, c as FeedModel, _ as HtmlBlock, O as ItemSelectionProvider, g as MarkdownBlock, r as MessageList, n as Minimap, t as Outline, T as SelectionGroupContext, v as WidgetScopeProvider, y as WidgetStateProvider, u as chatRenderer, E as createSelectionGroup, b as createWidgetStateStore, d as defaultRenderer, l as fromMessages, S as highlightTheme, C as highlights, k as isAdditive, f as isPrompt, p as messageText, m as searchFeed, w as setHighlights, h as sliceFeed, P as useDecorations, s as useFeedModel, M as useFeedNavigation, A as useItemSelection, j as useItemSelectionValue, a as useJumpDetector, i as useMessageList, o as usePositionLog, D as useSelectionGroup, x as useWidgetState } from "./chunk-src.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","names":[],"sources":["../../src/testing/debug/FeedStats.tsx","../../src/testing/debug/sweep.ts","../../src/testing/debug/use-feed-debug.ts","../../src/testing/generator.ts","../../src/testing/controls.tsx","../../src/testing/widgets.tsx","../../src/testing/scenarios.tsx","../../src/testing/stream.ts","../../src/testing/turn.ts","../../src/testing/FeedStory.tsx","../../src/testing/MessageWindow.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport * as Button from '@dxos/react-ui/Button';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useMessageList } from '../../components/index.ts';\nimport { type FrameMeter, type Stat, Stats, warnAbove } from '../../debug/index.ts';\n\n/** What the feed currently holds, as opposed to how well it is holding it. */\nconst FEED_STATS: Stat[] = [\n {\n id: 'range',\n label: 'range',\n },\n {\n id: 'index',\n label: 'index',\n },\n {\n id: 'rows',\n label: 'rows',\n },\n {\n id: 'widgets',\n label: 'widgets',\n },\n {\n id: 'selected',\n label: 'selected',\n },\n {\n id: 'hits',\n label: 'hits',\n },\n];\n\n/**\n * The scroll-quality verdict: live rate, the rate at the pass's slowest fifth, the worst frame, and\n * how many frames stuttered. Only meaningful at a real keyboard — an agent's browser throttles the\n * frames this counts.\n *\n * `jumps` are rows moving against the scroll, sampled per frame; `shifts` are rows whose offset\n * changed after they were laid out; `breaks` are shifts that broke the order. All three should be\n * zero on a still feed, so each is coloured the moment it is not.\n */\nconst FRAME_STATS: Stat[] = [\n {\n id: 'fps',\n label: 'render',\n unit: 'fps',\n classNames: (value) => Number(value) > 0 && Number(value) < 50 && 'text-warning-text',\n },\n {\n id: 'p95',\n label: 'p95',\n unit: 'ms',\n },\n {\n id: 'worst',\n label: 'worst',\n unit: 'ms',\n },\n {\n id: 'hitches',\n label: 'hitches',\n classNames: warnAbove,\n },\n {\n id: 'jumps',\n label: 'jumps',\n classNames: warnAbove,\n },\n {\n id: 'shifts',\n label: 'shifts',\n classNames: warnAbove,\n },\n {\n id: 'breaks',\n label: 'breaks',\n classNames: warnAbove,\n },\n];\n\nexport type FeedStatsProps = Util.ThemedClassName<{\n meter: FrameMeter;\n streaming?: boolean;\n selected?: number;\n hits?: number;\n}>;\n\n/**\n * Everything measured about a feed, floating over it.\n *\n * It sits above the viewport rather than in a statusbar because a statusbar is part of the layout\n * being measured: a block of readouts that grows or wraps changes the height of the scroll\n * container, and every number in it would then describe a viewport the readouts themselves resized.\n */\nexport const FeedStats = ({ classNames, meter, streaming, selected = 0, hits = 0 }: FeedStatsProps) => {\n const { range, currentIndex, count, mountedRows, mountedWidgets, jumps, shifts, breaks, resetShifts } =\n useMessageList('FeedStats');\n\n // A pass is the interval between two presses, so the control is start/stop rather than a reset.\n const [recording, setRecording] = useState(false);\n const onRecord = useCallback(() => {\n meter.record();\n setRecording((value) => !value);\n }, [meter]);\n\n const values = useMemo(\n () => ({\n range: range ? `${range.startIndex} / ${range.endIndex}` : '—',\n index: `${currentIndex} / ${count}`,\n rows: mountedRows,\n widgets: mountedWidgets,\n selected,\n hits,\n fps: meter.fps,\n p95: meter.p95,\n worst: meter.worst,\n hitches: meter.hitches,\n jumps: jumps.count,\n shifts,\n breaks,\n }),\n [range, currentIndex, count, mountedRows, mountedWidgets, selected, hits, meter, jumps.count, shifts, breaks],\n );\n\n const label = `${meter.label} — p50 ${meter.p50} · ${meter.frames} frames · ${(meter.duration / 1000).toFixed(1)}s`;\n\n return (\n <div\n // Fixed width, not fitted: the widest line is the pass summary, whose length changes with the\n // elapsed time, so a panel sized to its content would resize once a second while being read.\n className={mx(\n 'z-10 absolute bottom-3 right-3 w-[12rem] grid p-2 rounded-sm border border-separator bg-base-surface text-xs text-fg-muted',\n classNames,\n )}\n data-testid='feed.stats'\n >\n <div className='flex items-center min-w-0'>\n <span className='grow truncate' data-testid='feed.stream.state'>\n {streaming ? 'streaming…' : 'idle'}\n </span>\n <Button.Root\n icon={recording ? 'ph--stop--regular' : 'ph--record--regular'}\n iconOnly\n label={recording ? 'End the pass' : 'Start a pass'}\n variant='ghost'\n iconSize='xs'\n data-testid='feed.debug.record'\n onClick={onRecord}\n />\n <Button.Root\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Reset counters'\n variant='ghost'\n iconSize='xs'\n data-testid='feed.debug.reset'\n onClick={resetShifts}\n />\n </div>\n\n <Stats stats={FEED_STATS} values={values} />\n\n {/* The pass's extent rides in the tooltip: it is context for the readings rather than one of\n them, and its width changes with the elapsed time. */}\n <Stats\n stats={FRAME_STATS}\n values={values}\n classNames='border-t border-separator-subtle pt-1'\n title={label}\n data-testid='feed.frames'\n />\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * Travel speed (px/s) of a measured pass: about what a hard trackpad fling reaches on a 120Hz\n * display, and the speed at which a reader still expects to see content rather than a blur.\n *\n * Not \"the whole document in N seconds\", which was the first attempt: 2,000 rows in six seconds is\n * 40,000 px/s — some 400 rows a second, each one a CodeMirror view to mount and measure — and it\n * collapsed the frame rate to 3fps on hardware that idles at 120. That measured the sweep, not the\n * list.\n */\nconst VELOCITY = 3_000;\n\n/** Time spent travelling in each direction (ms), so a pass covers the same distance in every story. */\nconst LEG_DURATION = 5_000;\n\nexport type SweepOptions = {\n /** Travel speed in px/s. @default 3000 */\n velocity?: number;\n /** Time for one leg (ms). @default 5000 */\n duration?: number;\n /** Called once the return leg lands, or when cancelled. */\n onDone?: () => void;\n};\n\n/**\n * Scrolls an element down and back at a fixed speed.\n *\n * A measured pass has to be one repeatable gesture or the numbers cannot be compared between\n * stories, and a 2,000-row feed is far too long to fling by hand. Fixed speed for a fixed time —\n * rather than a fixed fraction of the document — so the two stories being compared travel the same\n * distance even though they disagree about how tall the document is until their rows have measured.\n */\nexport const sweepScroll = (\n element: HTMLElement,\n { velocity = VELOCITY, duration = LEG_DURATION, onDone }: SweepOptions = {},\n) => {\n let frame = 0;\n let cancelled = false;\n let start = performance.now();\n let last = start;\n let leg: 'down' | 'up' = 'down';\n\n const finish = () => {\n cancelAnimationFrame(frame);\n onDone?.();\n };\n\n const tick = (now: number) => {\n if (cancelled) {\n return;\n }\n\n // Stepped by elapsed time rather than by frame, so a dropped frame costs smoothness but not\n // distance — two passes over the same feed cover the same ground however each one rendered.\n const step = (velocity * (now - last)) / 1000;\n last = now;\n const limit = element.scrollHeight - element.clientHeight;\n element.scrollTop = Math.max(0, Math.min(element.scrollTop + (leg === 'down' ? step : -step), limit));\n\n if (now - start < duration) {\n frame = requestAnimationFrame(tick);\n return;\n }\n\n if (leg === 'down') {\n leg = 'up';\n start = now;\n last = now;\n frame = requestAnimationFrame(tick);\n return;\n }\n\n finish();\n };\n\n frame = requestAnimationFrame(tick);\n\n return () => {\n cancelled = true;\n finish();\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { type FrameMeter, useFrameMeter } from '../../debug/index.ts';\nimport { sweepScroll } from './sweep.ts';\n\nexport type FeedDebugOptions = {\n /** What the pass ran under; the recorded line carries these rather than a story name. */\n scenario?: string;\n count: number;\n estimateSize?: number;\n streaming: boolean;\n /** Whether the outlines start on. Off: they are for a question, not for every reading. */\n enabled?: boolean;\n};\n\nexport type FeedDebug = {\n /** Whether the item and block outlines are drawn. */\n debug: boolean;\n toggleDebug: () => void;\n /** Frame statistics and the control that records a pass. */\n meter: FrameMeter;\n /** Attach to the viewport: the sweep needs the element it is going to scroll. */\n viewportRef: RefObject<HTMLDivElement | null>;\n sweeping: boolean;\n /** Start a measured pass, or cancel the one running. */\n onSweep: () => void;\n};\n\n/**\n * Everything the harness measures itself with, in one place.\n *\n * Kept apart from the story deliberately: instrumentation accumulates — a meter, a sweep, a toggle,\n * a label, the refs and teardown each needs — and threaded through a component it slowly becomes\n * the component. As an aspect the story stays about the feed, and what is being measured can be\n * read, changed or dropped in one file.\n */\nexport const useFeedDebug = ({\n scenario,\n count,\n estimateSize,\n streaming,\n enabled = false,\n}: FeedDebugOptions): FeedDebug => {\n const [debug, setDebug] = useState(enabled);\n const toggleDebug = useCallback(() => setDebug((value) => !value), []);\n\n const label = useMemo(\n () =>\n [scenario ?? 'mixed', `${count} msgs`, estimateSize ? `est ${estimateSize}` : undefined, streaming && 'streaming']\n .filter(Boolean)\n .join(' · '),\n [scenario, count, estimateSize, streaming],\n );\n\n const meter = useFrameMeter({ label });\n const viewportRef = useRef<HTMLDivElement>(null);\n const [sweeping, setSweeping] = useState(false);\n const cancelSweep = useRef<(() => void) | null>(null);\n\n // A measured pass has to be one repeatable gesture. Flinging 2,000 rows by hand is neither, so the\n // sweep runs the traversal and records what it cost: click, wait, read the clipboard.\n const onSweep = useCallback(() => {\n if (sweeping) {\n cancelSweep.current?.();\n return;\n }\n\n const viewport = viewportRef.current;\n if (!viewport) {\n return;\n }\n\n meter.record();\n setSweeping(true);\n cancelSweep.current = sweepScroll(viewport, {\n onDone: () => {\n cancelSweep.current = null;\n setSweeping(false);\n meter.record();\n },\n });\n }, [sweeping, meter]);\n\n useEffect(() => () => cancelSweep.current?.(), []);\n\n return { debug, toggleDebug, meter, viewportRef, sweeping, onSweep };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { random } from '@dxos/random';\nimport { Message } from '@dxos/types';\n\nexport type MessageKind = 'markdown' | 'html' | 'code' | 'short';\n\nexport type GeneratorOptions = {\n count: number;\n /** Relative weights per kind; a mixed feed is the realistic case (email threads, AI chats). */\n kinds?: MessageKind[];\n seed?: number;\n};\n\nconst SENDERS = ['user', 'assistant'] as const;\n\n/**\n * Synthetic feed for measuring the engine.\n *\n * Heights are deliberately uneven — a uniform-height feed would hide exactly the measurement\n * problem the spike exists to answer.\n */\nexport const createMessages = ({\n count,\n kinds = ['markdown', 'markdown', 'short', 'code', 'html'],\n seed = 999,\n}: GeneratorOptions): Message.Message[] => {\n random.seed(seed);\n const start = Date.now() - count * 60_000;\n\n return Array.from({ length: count }, (_, index) => {\n const kind = kinds[index % kinds.length];\n const role = SENDERS[index % SENDERS.length];\n return Message.make({\n created: new Date(start + index * 60_000).toISOString(),\n sender: { role, name: role === 'user' ? 'Alice' : 'Assistant' },\n blocks: [blockFor(kind, index)],\n properties: { subject: `Message ${index}`, kind },\n });\n });\n};\n\nconst blockFor = (kind: MessageKind, index: number) => {\n switch (kind) {\n case 'short':\n return { _tag: 'text' as const, text: `**${index}.** ${random.lorem.sentence(6)}` };\n\n case 'code':\n return {\n _tag: 'text' as const,\n text: [\n `**${index}.** ${random.lorem.sentence(8)}`,\n '',\n '```ts',\n 'const feed = useQuery(db, Query.from(feed));',\n 'const hits = searchFeed(messages, renderer, query);',\n '```',\n '',\n random.lorem.sentence(12),\n ].join('\\n'),\n };\n\n case 'html':\n return {\n _tag: 'text' as const,\n mimeType: 'text/html',\n text: [\n `<p><strong>${index}.</strong> ${random.lorem.sentence(10)}</p>`,\n `<blockquote>${random.lorem.sentence(14)}</blockquote>`,\n '<ul><li>attachment.pdf</li><li>invoice.csv</li></ul>',\n ].join(''),\n };\n\n case 'markdown':\n default:\n return {\n _tag: 'text' as const,\n text: [\n `**${index}.** ${random.lorem.sentence(10)}`,\n '',\n random.lorem.paragraph(),\n '',\n `- ${random.lorem.sentence(5)}`,\n `- ${random.lorem.sentence(7)}`,\n ].join('\\n'),\n };\n }\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { EditorState, type Extension } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\nimport React, { useLayoutEffect, useRef } from 'react';\n\nimport { type Message } from '@dxos/types';\nimport {\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n} from '@dxos/ui-editor';\n\nimport { MarkdownBlock } from '../components/index.ts';\n\n/**\n * Controls for attributing what a row costs to mount.\n *\n * `baseline/mount` measures a whole feed with its fixtures already built and finds ~2ms for a row\n * with no editor against ~15ms for a row with one — while the same editor constructed into an\n * offscreen container costs 0.4ms (`baseline/construction`). These render the *same* rows through\n * the same chrome, differing only in what is inside them, so the gap can be attributed rather than\n * argued about.\n */\nexport type ControlContent = { data?: unknown };\n\n/** The floor: the row's text, and no editor at all. */\nexport const TextItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <p className='py-1 text-sm'>{String(content.data ?? '')}</p>\n);\n\n/** An editor with nothing but wrapping: no theme, no keymap, no markdown, no decoration. */\nconst BARE: Extension[] = [EditorView.editable.of(false), EditorState.readOnly.of(true), EditorView.lineWrapping];\n\n/** Wrapping plus the theme — the extension the isolated profile shows to be nearly free. */\nconst THEMED: Extension[] = [\n createBasicExtensions({ readOnly: true, editable: false, lineWrapping: true }),\n createThemeExtensions({ themeMode: 'light' }),\n];\n\n/** The theme plus the markdown language: parsing, but nothing rendered from what it parsed. */\nconst MARKDOWN: Extension[] = [...THEMED, createMarkdownExtensions()];\n\n/** Everything the item has bar its highlight layer — headings, lists and links drawn as themselves. */\nconst DECORATED: Extension[] = [...MARKDOWN, decorateMarkdown()];\n\nexport const BareEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={BARE} />\n);\n\nexport const ThemedEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={THEMED} />\n);\n\nexport const MarkdownEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={MARKDOWN} />\n);\n\nexport const DecoratedEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={DECORATED} />\n);\n\n/**\n * The real item, reached the same way the probes are.\n *\n * The rung that separates *what the editor is configured with* from *what the component does around\n * it*: everything outside this component is identical to `uniform-decorated`.\n */\nexport const MarkdownProbeItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <MarkdownBlock text={String(content.data ?? '')} />\n);\n\n/**\n * A deliberate copy of `MarkdownBlock`'s construction, reduced to the part being measured.\n *\n * Copied rather than shared: the point of a control is that it does not change when the thing it is\n * a control for does.\n */\nconst ProbeEditor = ({ text, extensions }: { text: string; extensions: Extension[] }) => {\n const rootRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const parent = rootRef.current;\n if (!parent) {\n return;\n }\n\n const view = new EditorView({ parent, state: EditorState.create({ doc: text, extensions }) });\n return () => view.destroy();\n }, [text, extensions]);\n\n return <div ref={rootRef} />;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport { TogglePanel } from '@dxos/react-ui-components';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport { type WidgetProps, type XmlWidgetRegistry, getXmlTextChild } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useWidgetState } from '../components/index.ts';\n\n/**\n * Widgets for the block kinds an assistant turn contains.\n *\n * Approximations of `plugin-assistant`'s, deliberately: what the engine has to prove is that a\n * per-message document can carry block widgets at all — that they mount, measure and survive\n * streaming — not that these are the widgets the plugin will ship. The real ones move down when the\n * plugin does.\n *\n * Every widget renders from the tag alone. The plugin keeps tool state beside the document and\n * re-applies it after a remount; an item here is rebuilt from its message, so state that is not in\n * the document has nowhere to survive.\n */\n\n/**\n * A collapsible block: the header is what the reader sees while scrolling past, the body is what\n * they open when they care. Reasoning and tool traces are long, incidental and frequent — rendering\n * them expanded turns a chat into a log.\n *\n * The disclosure animates, and every frame of it is measured twice — by CodeMirror, to re-place the\n * lines under the widget, and by the virtualizer, to re-place the rows under the item. That is what\n * it costs for the content below to travel with the panel instead of jumping to its new place; the\n * geometry stays consistent because `MarkdownBlock` re-measures the document as the widget resizes.\n */\nconst Panel = ({\n stateKey,\n icon,\n title,\n children,\n classNames,\n}: {\n /** Identifies this panel within its message, so its open flag can outlive the row. */\n stateKey: string;\n icon: string;\n title: string;\n children: React.ReactNode;\n classNames?: string;\n}) => {\n const [open, setOpen] = useWidgetState(stateKey, false);\n\n return (\n // The open flag is published to the DOM so a test can ask whether it survived the row being\n // unmounted — which is the whole question a virtualized feed raises about widget state.\n <div data-testid='feed.widget' data-open={open}>\n <TogglePanel.Root open={open} onChangeOpen={setOpen}>\n {/* A minimum height, not a fixed one: the widget's content is portaled and paints after\n CodeMirror has placed the box, so without a floor the row is measured empty and grows a\n frame later — a row jumping under the reader. `estimatedHeight` in the registry looks like\n the answer and is not: it sets `height` and `overflow: hidden` on the widget root, which\n pins the panel shut. */}\n <TogglePanel.Content classNames={mx('min-h-[2.125rem] rounded border border-separator-subtle', classNames)}>\n <TogglePanel.Header classNames='flex items-center gap-2 px-2 py-1 text-sm'>\n <span className='grow text-fg-muted truncate'>{title}</span>\n <Layout.Block>\n <Icon.Icon icon={icon} size='md' />\n </Layout.Block>\n </TogglePanel.Header>\n <TogglePanel.Body>\n <TogglePanel.Viewport classNames='px-2 pb-1 text-sm'>{children}</TogglePanel.Viewport>\n </TogglePanel.Body>\n </TogglePanel.Content>\n </TogglePanel.Root>\n </div>\n );\n};\n\nconst Frame = ({ icon, title, children, classNames }: WidgetProps<any> & { classNames?: string }) => (\n <div className={mx('flex gap-2 px-2 py-1 rounded border border-separator-subtle text-sm', classNames)}>\n {icon && <Icon.Icon icon={icon} size='md' classNames='mt-1' tone='muted' />}\n <div className='min-w-0'>\n {title && <p className='text-xs text-fg-muted'>{title}</p>}\n {children}\n </div>\n </div>\n);\n\nconst Reasoning = ({ children, range }: WidgetProps) => {\n const text = getXmlTextChild(children ?? []) ?? '';\n return (\n <Panel\n stateKey={`reasoning:${range?.from}`}\n icon='ph--brain--regular'\n title={text.slice(0, 80)}\n classNames='opacity-70'\n >\n <p>{text}</p>\n </Panel>\n );\n};\n\nconst Status = ({ children }: WidgetProps) => (\n <p className='px-2 py-1 text-sm text-fg-muted animate-pulse'>{getXmlTextChild(children ?? [])}</p>\n);\n\nconst ToolCall = ({ name, pending, range }: WidgetProps<{ name?: string; pending?: string }>) => (\n <Panel\n stateKey={`toolCall:${range?.from}`}\n icon={pending ? 'ph--circle-notch--regular' : 'ph--wrench--regular'}\n title={`Calling ${name}`}\n >\n <p className='font-mono text-xs'>{name}</p>\n </Panel>\n);\n\nconst ToolResult = ({ children, for: tool, range }: WidgetProps<{ for?: string }>) => (\n <Panel stateKey={`toolResult:${range?.from}`} icon='ph--check--regular' title={`${tool} returned`}>\n <p className='font-mono text-xs'>{getXmlTextChild(children ?? [])}</p>\n </Panel>\n);\n\n// Inline, so a run of them wraps as chips rather than stacking one per line.\nconst Suggestion = ({ children }: WidgetProps) => (\n <button type='button' className='me-1 px-2 py-0.5 rounded-full border border-separator text-sm'>\n {getXmlTextChild(children ?? [])}\n </button>\n);\n\nconst Select = ({ children }: WidgetProps) => {\n const options = (children ?? [])\n .map((option: any) => option?._tag === 'option' && getXmlTextChild(option.children))\n .filter(Boolean) as string[];\n\n return (\n <div className='flex flex-col gap-1 py-1'>\n {options.map((option) => (\n <button key={option} type='button' className='px-2 py-1 text-left rounded border border-separator text-sm'>\n {option}\n </button>\n ))}\n </div>\n );\n};\n\nconst Json = ({ children }: WidgetProps) => (\n <pre className='px-2 py-1 overflow-x-auto rounded bg-input-surface text-xs'>{getXmlTextChild(children ?? [])}</pre>\n);\n\n/**\n * Floor for a panel's collapsed height, reserved before its portaled content paints.\n *\n * `heightMode: 'min'` is what makes this a floor rather than a pin — the default pins the box and\n * clips it, so an opened panel stays shut. Without any reservation the row is measured at the height\n * of an empty box and grows a frame later, which is a row moving under a reader scrolling up.\n */\nconst COLLAPSED_HEIGHT = 42;\n\nexport const chatRegistry: XmlWidgetRegistry = {\n // No widget: the reader's own words stay in the document, where they can be selected and searched\n // like any other text. Registered so the markdown parser keeps the tag as one block — an\n // unregistered tag opens a paragraph that swallows the lines after it.\n prompt: { block: true },\n reasoning: {\n block: true,\n streaming: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: Reasoning,\n },\n status: {\n block: true,\n streaming: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: Status,\n },\n toolCall: { block: true, estimatedHeight: () => COLLAPSED_HEIGHT, heightMode: 'min', Component: ToolCall },\n toolResult: { block: true, estimatedHeight: () => COLLAPSED_HEIGHT, heightMode: 'min', Component: ToolResult },\n suggestion: { block: false, Component: Suggestion },\n select: { block: true, Component: Select },\n json: { block: true, Component: Json },\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type ComponentType } from 'react';\n\nimport { random } from '@dxos/random';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport { type ContentBlock, Message } from '@dxos/types';\nimport { type XmlWidgetRegistry } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type MessageChromeProps, type MessageRenderer, chatRenderer, defaultRenderer } from '../index.ts';\nimport {\n BareEditorItem,\n DecoratedEditorItem,\n MarkdownEditorItem,\n MarkdownProbeItem,\n TextItem,\n ThemedEditorItem,\n} from './controls.tsx';\nimport { chatRegistry } from './widgets.tsx';\n\nconst UNIFORM_CONTROLS = {\n 'uniform-text': TextItem,\n 'uniform-bare': BareEditorItem,\n 'uniform-themed': ThemedEditorItem,\n 'uniform-markdown': MarkdownEditorItem,\n 'uniform-decorated': DecoratedEditorItem,\n 'uniform-item': MarkdownProbeItem,\n} as const;\n\n/**\n * The five places in the repo that render a thread of messages, approximated against one engine.\n *\n * The point is not the fixtures — it is that all five differ only in the renderer, the chrome and a\n * couple of options, while the list, the virtualization, the selection and the search are the same\n * code. Where a scenario needs something the engine does not have, that is the finding.\n */\nexport type FeedScenario =\n /** No editor at all: fixed-height divs, so the list is measured against content that cannot move. */\n | 'plain'\n /** One editor per row, every row identical — the same document, so every height is the same. */\n | 'uniform'\n /** The `uniform` rows with their text as a paragraph: the same feed without a document at all. */\n | 'uniform-text'\n /** The `uniform` rows with an editor carrying nothing but line wrapping. */\n | 'uniform-bare'\n /** The `uniform` rows with wrapping and the theme, but no markdown and no decoration. */\n | 'uniform-themed'\n /** The `uniform` rows with the markdown language, but nothing drawn from what it parses. */\n | 'uniform-markdown'\n /** The `uniform` rows with markdown drawn as itself: headings, lists, links. */\n | 'uniform-decorated'\n /** The `uniform` rows rendered by the real item, reached through `Custom` like the probes. */\n | 'uniform-item'\n | 'assistant'\n | 'email'\n | 'thread'\n | 'comments'\n | 'transcript';\n\nexport type ScenarioDefinition = {\n messages: Message.Message[];\n renderer: MessageRenderer;\n /** Widgets for the tags the renderer emits; only the assistant turn has non-prose blocks. */\n registry?: XmlWidgetRegistry;\n Chrome: ComponentType<MessageChromeProps>;\n /** Renders a `custom` item, for the scenarios that deliberately avoid an editor. */\n Custom?: ComponentType<{ content: any; message: Message.Message }>;\n /** Whether the feed follows its tail: true where messages arrive, false where they are read. */\n stickyBottom: boolean;\n /**\n * Height a row is assumed to have before it is measured. A function where rows differ widely: a\n * one-line prompt and a long answer share no useful average, and a row measured for the first time\n * re-lays everything below it — which the reader sees as flicker when scrolling up, into rows that\n * have never been measured.\n */\n estimateSize: number | ((message: Message.Message, index: number) => number);\n /** Which messages the arrows and the navigation buttons stop on. */\n isAnchor?: (message: Message.Message, index: number) => boolean;\n};\n\nexport type ScenarioOptions = {\n scenario: FeedScenario;\n count: number;\n seed?: number;\n};\n\nexport const createScenario = ({ scenario, count, seed = 999 }: ScenarioOptions): ScenarioDefinition => {\n random.seed(seed);\n switch (scenario) {\n // The control: nothing here builds anything after it mounts, so a row's first measurement is its\n // last. Any movement in this feed belongs to the list, not to what it is rendering.\n case 'plain':\n return {\n messages: createPlainMessages(count),\n renderer: (message) => ({ kind: 'custom', key: 'plain', data: message.properties?.height ?? 120 }),\n Chrome: PlainChrome,\n Custom: PlainItem,\n stickyBottom: true,\n estimateSize: (message) => Number(message.properties?.height ?? 120) + PLAIN_CHROME,\n };\n\n // One rung up: a real editor per row, but every row the same single paragraph, so every row is\n // the same height and the estimate can be exactly right.\n case 'uniform':\n return {\n messages: createUniformMessages(count),\n renderer: defaultRenderer,\n Chrome: PlainChrome,\n stickyBottom: true,\n estimateSize: UNIFORM_HEIGHT,\n };\n\n // The controls for `uniform`: identical messages, identical chrome, identical estimate — only\n // what the row contains changes, so the difference between any two of them is that content.\n case 'uniform-text':\n case 'uniform-bare':\n case 'uniform-themed':\n case 'uniform-markdown':\n case 'uniform-decorated':\n case 'uniform-item':\n return {\n messages: createUniformMessages(count),\n renderer: (message) => ({ kind: 'custom', key: scenario, data: Message.extractText(message) }),\n Chrome: PlainChrome,\n Custom: UNIFORM_CONTROLS[scenario],\n stickyBottom: true,\n estimateSize: 84,\n };\n\n case 'assistant':\n return {\n messages: createAssistantMessages(count),\n renderer: chatRenderer,\n registry: chatRegistry,\n Chrome: AssistantChrome,\n stickyBottom: true,\n estimateSize: estimateAssistantRow,\n // A turn is a prompt and everything the model said in reply; stopping on each of the model's\n // messages would make the reader step through an answer rather than through the conversation.\n isAnchor: (message) => message.sender.role === 'user',\n };\n\n case 'email':\n return {\n messages: createEmailMessages(count),\n renderer: defaultRenderer,\n Chrome: EmailChrome,\n // A conversation is read from its start; the newest message is not where the reader is going.\n stickyBottom: false,\n estimateSize: 220,\n };\n\n case 'thread':\n return {\n messages: createThreadMessages(count),\n renderer: defaultRenderer,\n Chrome: ThreadChrome,\n stickyBottom: true,\n estimateSize: 64,\n };\n\n case 'comments':\n return {\n messages: createCommentMessages(count),\n renderer: defaultRenderer,\n Chrome: CommentChrome,\n stickyBottom: false,\n estimateSize: 96,\n };\n\n case 'transcript':\n return {\n messages: createTranscriptMessages(count),\n renderer: defaultRenderer,\n Chrome: TranscriptChrome,\n stickyBottom: true,\n estimateSize: 48,\n };\n }\n};\n\n/** Chrome around every assistant row: the sender line, the padding and the separator. */\nconst ROW_CHROME = 46;\n\n/** Height of a wrapped line of body text, and how many characters fit on one at the story's width. */\nconst LINE_HEIGHT = 24;\nconst LINE_CHARS = 90;\n\n/** A collapsed panel: reasoning, a tool call, a tool result. */\nconst PANEL_HEIGHT = 50;\n\n/**\n * What a row will measure, from the message alone.\n *\n * Rough on purpose — it is an estimate, and its only job is to be close enough that measuring the row\n * does not move the rows below it. Text is counted in wrapped lines, blank lines between blocks are\n * counted, and every block that renders as a widget is counted at its collapsed height.\n */\nconst estimateAssistantRow = (message: Message.Message): number => {\n let height = ROW_CHROME;\n for (const block of message.blocks) {\n switch (block._tag) {\n case 'text': {\n const paragraphs = block.text.split('\\n\\n');\n for (const paragraph of paragraphs) {\n height += Math.max(1, Math.ceil(paragraph.length / LINE_CHARS)) * LINE_HEIGHT;\n }\n break;\n }\n case 'suggestion':\n height += LINE_HEIGHT + 8;\n break;\n case 'select':\n height += block.options.length * (LINE_HEIGHT + 10);\n break;\n default:\n height += PANEL_HEIGHT;\n }\n }\n\n return height;\n};\n\n//\n// Fixtures\n//\n\n/** Fixed heights, cycling so the feed is uneven — but known in advance and never corrected. */\nconst PLAIN_HEIGHTS = [64, 120, 200, 88, 320, 56];\n\n/**\n * What the chrome adds to a plain row: the separator beneath it.\n *\n * The estimate has to include this. A control scenario whose estimate is the *content* height is off\n * by the chrome on every single row, and the list corrects each one — which looks exactly like the\n * defect the control exists to rule out.\n */\nconst PLAIN_CHROME = 1;\n\nconst createPlainMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index),\n sender: { role: index % 2 === 0 ? 'user' : 'assistant', name: index % 2 === 0 ? 'Alice' : 'Assistant' },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(index % 8) }],\n properties: { height: PLAIN_HEIGHTS[index % PLAIN_HEIGHTS.length] },\n }),\n );\n\n/**\n * What one uniform row measures, chrome included.\n *\n * Exact on purpose, and checked against the DOM rather than guessed: this scenario's whole job is to\n * be the case where the estimate is right, and it was declared at 84 against a real 24. A viewport\n * filled from an estimate three times too large covers a third of the screen once the rows measure,\n * so the feed arrived in two visible pieces — in the one story that exists to have nothing wrong\n * with it.\n */\nconst UNIFORM_HEIGHT = 24;\n\n/** Every row the same document, so a single estimate is exactly right for all of them. */\nconst createUniformMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index),\n sender: { role: index % 2 === 0 ? 'user' : 'assistant', name: index % 2 === 0 ? 'Alice' : 'Assistant' },\n // Not `${index}. …`: markdown reads a leading number and a dot as an ordered list and renumbers\n // it, so the row would show a number that is not its index. The index is in the chrome.\n blocks: [{ _tag: 'text', text: random.lorem.sentence(index % 16 === 0 ? 100 : 1) }],\n }),\n );\n\nconst NAMES = ['Alice', 'Bob', 'Charlie', 'Dana'];\n\nconst at = (index: number, step = 60_000) => new Date(Date.now() - (2_000 - index) * step).toISOString();\n\n/** A turn: the reader's prompt, then an answer carrying the block kinds a model actually emits. */\nconst createAssistantMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) => {\n if (index % 2 === 0) {\n return Message.make({\n created: at(index),\n sender: { role: 'user', name: 'Alice' },\n blocks: [{ _tag: 'text', text: random.lorem.paragraph() }],\n });\n }\n\n // Every answer is several blocks — reasoning, sometimes a tool round-trip, prose, and closing\n // affordances. A single text block is the easy case and the one the engine already handled.\n const blocks: ContentBlock.Any[] = [{ _tag: 'reasoning', reasoningText: random.lorem.paragraph() }];\n if (index % 4 === 1) {\n const toolCallId = `tool-${index}`;\n blocks.push({\n _tag: 'toolCall',\n toolCallId,\n name: 'search',\n input: '{}',\n providerExecuted: false,\n });\n blocks.push({\n _tag: 'toolResult',\n toolCallId,\n name: 'search',\n result: random.lorem.paragraphs(2),\n providerExecuted: false,\n });\n }\n\n blocks.push({ _tag: 'text', text: answerText(index) });\n if (index % 6 === 3) {\n blocks.push({ _tag: 'suggestion', text: 'Tell me more' });\n blocks.push({ _tag: 'suggestion', text: 'Show the sources' });\n }\n if (index % 8 === 5) {\n blocks.push({ _tag: 'select', options: ['Option 1', 'Option 2', 'Option 3'] });\n }\n\n return Message.make({ created: at(index), sender: { role: 'assistant', name: 'Assistant' }, blocks });\n });\n\nconst answerText = (index: number): string =>\n [\n `**${index}.** ${random.lorem.sentence(10)}`,\n '',\n random.lorem.paragraph(),\n '',\n `- ${random.lorem.sentence(5)}`,\n `- ${random.lorem.sentence(7)}`,\n ].join('\\n');\n\n/** An email conversation: HTML bodies, quoted history, and a subject that never changes. */\nconst createEmailMessages = (count: number): Message.Message[] => {\n const subject = `Re: ${random.lorem.words(4)}`;\n return Array.from({ length: count }, (_, index) => {\n const from = NAMES[index % NAMES.length];\n return Message.make({\n created: at(index, 3_600_000),\n sender: { role: 'user', name: from, email: `${from.toLowerCase()}@example.com` },\n blocks: [\n {\n _tag: 'text',\n mimeType: 'text/html',\n text: [\n `<p>${random.lorem.paragraph()}</p>`,\n `<p>${random.lorem.paragraph()}</p>`,\n '<ul><li>attachment.pdf</li><li>invoice.csv</li></ul>',\n `<blockquote>${random.lorem.sentence(16)}</blockquote>`,\n ].join(''),\n },\n ],\n properties: { subject },\n });\n });\n};\n\n/** Human chat: short turns, many of them, often several in a row from one speaker. */\nconst createThreadMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) => {\n const name = NAMES[Math.floor(index / 3) % NAMES.length];\n return Message.make({\n created: at(index, 30_000),\n sender: { role: 'user', name },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(random.number.int({ min: 4, max: 20 })) }],\n });\n });\n\n/** Comments: each anchored to a range of a document, and resolvable. */\nconst createCommentMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index, 120_000),\n sender: { role: 'user', name: NAMES[index % NAMES.length] },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(random.number.int({ min: 6, max: 24 })) }],\n properties: { anchor: random.lorem.sentence(6), resolved: index % 7 === 0 },\n }),\n );\n\n/** Transcription: one utterance per message, seconds apart, arriving while the reader watches. */\nconst createTranscriptMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index, 6_000),\n sender: { role: 'user', name: NAMES[index % 2] },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(random.number.int({ min: 5, max: 18 })) }],\n }),\n );\n\n/**\n * A row of a known height: the content is a box, not a document.\n *\n * The height is the point — it is declared, never measured — so it is stated in the row rather than\n * left to be inferred, and the text sits at the top like every other scenario's does.\n */\nconst PlainItem = ({ content, message }: { content: { data?: unknown }; message: Message.Message }) => {\n const height = Number(content.data ?? 120);\n\n return (\n <div className='flex flex-col gap-1 py-2 text-sm' style={{ height }}>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name}</span>\n <span className='tabular-nums'>{height}px</span>\n </div>\n <p>{Message.extractText(message)}</p>\n </div>\n );\n};\n\n//\n// Chrome\n//\n\n/** No hover controls and no separators: nothing here can change a row's height. */\n/**\n * The index is shown in the gutter because these are the control scenarios: when a row moves, the\n * first question is *which* row, and reading it off the screen beats hunting for it in the DOM.\n */\nconst PlainChrome = ({ index, children }: MessageChromeProps) => (\n <Row classNames='py-0 grid grid-cols-[3rem_1fr] gap-2'>\n <div className='self-start flex items-center h-[1lh] text-base leading-normal'>\n <span className='text-xs text-fg-subtle tabular-nums'>{index}</span>\n </div>\n <div className='min-w-0'>{children}</div>\n </Row>\n);\n\n//\n// Every scenario's difference that is not the renderer lives here — which is the claim being\n// tested: chrome is the host's, and the engine only has to keep it out of the measurement.\n//\n\nconst timeOf = (message: Message.Message, options?: Intl.DateTimeFormatOptions) =>\n new Date(message.created).toLocaleTimeString([], options ?? { hour: '2-digit', minute: '2-digit' });\n\nconst Row = ({ children, classNames }: { children: React.ReactNode; classNames?: string }) => (\n <div className={mx('group relative px-2 py-2', classNames)} data-testid='feed.message'>\n {children}\n </div>\n);\n\n/**\n * Assistant: the reader's prompts and the model's answers are framed differently, because they are\n * different kinds of thing — a prompt is an instruction that can be rewound to, an answer is a\n * result that can be forked from.\n *\n * A prompt carries its rewind toolbar underneath; an answer carries its timestamp at the foot, where\n * it reads as when the answer finished rather than when the turn began. Both are always in flow — a\n * control that appears on hover changes the row's height, and a pointer travelling down a scrolling\n * list then moves every row below it.\n */\n/** Copies the message's extracted text — the model's truth, not the DOM's partial render. */\nconst CopyButton = ({ message }: { message: Message.Message }) => (\n <SystemButton.Clipboard iconOnly label='Copy' variant='ghost' size='sm' onCopy={() => Message.extractText(message)} />\n);\n\nconst AssistantChrome = ({ message, index, selected, children }: MessageChromeProps) => {\n const prompt = message.sender.role === 'user';\n\n return (\n <Row classNames={mx(selected && 'bg-hover-surface')}>\n {prompt ? (\n // The reader's own words sit apart from the model's: right-aligned, at most two thirds of\n // the viewport — the chat convention that makes whose-turn legible at a glance.\n <div className='min-w-0 flex flex-col items-end'>\n <div className='max-w-[66%] min-w-0'>\n {/* The reader's own words, framed: a prompt is an instruction the thread can be rewound\n to, and it reads as one only if it is visibly not the model's prose. */}\n <div className='px-4 py-3 border-s-2 border-accent-bg rounded-sm bg-input-surface'>{children}</div>\n {/* Revealed on hover, but never removed from flow: chrome that appears and disappears\n changes the row's height, and a pointer travelling down a scrolling list would then\n move every row below it. Opacity costs nothing to measure. The toolbar follows the\n bubble's edge — right-aligned, like the words it belongs to. */}\n <div className='flex items-center justify-end gap-1 pt-1 text-xs text-fg-muted opacity-0 transition-opacity group-hover:opacity-100'>\n <CopyButton message={message} />\n <Button.Root\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Rewind'\n variant='ghost'\n size='sm'\n />\n <Button.Root icon='ph--git-branch--regular' iconOnly label='Fork' variant='ghost' size='sm' />\n <span className='text-fg-subtle'>#{index}</span>\n <span>{timeOf(message)}</span>\n </div>\n </div>\n </div>\n ) : (\n <div className='min-w-0'>\n {children}\n <div className='flex items-center gap-1 pt-1 text-xs text-fg-muted opacity-0 transition-opacity group-hover:opacity-100'>\n <CopyButton message={message} />\n <Button.Root icon='ph--arrow-bend-up-left--regular' iconOnly label='Reply' variant='ghost' size='sm' />\n <span className='text-fg-subtle'>#{index}</span>\n <span>{timeOf(message)}</span>\n </div>\n </div>\n )}\n </Row>\n );\n};\n\n/** Email: a header of its own — sender, address, date — and a card-like body. */\nconst EmailChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames='border-b border-separator'>\n <div className='flex items-baseline gap-2'>\n <span className='font-medium'>{message.sender.name}</span>\n <span className='text-xs text-fg-muted'>{message.sender.email}</span>\n <span className='grow' />\n <span className='text-xs text-fg-muted'>{new Date(message.created).toLocaleString()}</span>\n </div>\n <p className='text-xs text-fg-muted'>{String(message.properties?.subject ?? '')}</p>\n <div className='mt-1'>{children}</div>\n </Row>\n);\n\n/** Human chat: an avatar, and consecutive turns from one speaker read as one block. */\nconst ThreadChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames='grid grid-cols-[2rem_1fr] gap-2'>\n <div className='w-6 h-6 rounded-full bg-input-surface grid place-items-center text-xs'>\n {message.sender.name?.[0]}\n </div>\n <div className='min-w-0'>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name}</span>\n <span>{timeOf(message)}</span>\n </div>\n {children}\n </div>\n </Row>\n);\n\n/** Comments: the quoted anchor above the comment, and a resolve control. */\nconst CommentChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames={mx('border-b border-separator-subtle', message.properties?.resolved && 'opacity-50')}>\n <p className='mb-1 ps-2 border-s-2 border-separator text-xs text-fg-muted line-clamp-1'>\n {String(message.properties?.anchor ?? '')}\n </p>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name}</span>\n <span>{timeOf(message)}</span>\n <span className='grow' />\n <Button.Root\n icon={message.properties?.resolved ? 'ph--check-circle--regular' : 'ph--circle--regular'}\n iconOnly\n label='Resolve'\n variant='ghost'\n iconSize='xs'\n />\n </div>\n {children}\n </Row>\n);\n\n/** Transcription: a timestamp and speaker in the gutter, and no separators — it reads as one text. */\nconst TranscriptChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames='grid grid-cols-[5rem_1fr] gap-2 py-0.5'>\n <div className='text-xs text-fg-muted tabular-nums'>\n {timeOf(message, { hour: '2-digit', minute: '2-digit', second: '2-digit' })}\n </div>\n <div className='min-w-0'>\n <span className='me-2 text-xs font-medium text-fg-muted'>{message.sender.name}</span>\n {children}\n </div>\n </Row>\n);\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { random } from '@dxos/random';\n\nexport type TextStreamOptions = {\n /** Delay between chunks in ms. */\n chunkDelay?: number;\n /** Variance in timing (0-1). */\n variance?: number;\n /** Words per chunk. */\n wordsPerChunk?: number;\n};\n\n/**\n * Word-by-word stream, the shape a model emits: chunks of a few words at an uneven cadence.\n *\n * Whitespace is preserved as its own token so a chunk boundary never fuses two words, which would\n * corrupt the markdown the item is parsing mid-stream.\n */\nexport async function* textStream(text: string, options: TextStreamOptions = {}): AsyncGenerator<string, void> {\n const { chunkDelay = 120, variance = 0.4, wordsPerChunk = 4 } = options;\n // Floored rather than validated: this is driven by a storybook control, and a size of zero never\n // advances the cursor — the generator would yield empty chunks for ever, which presents as a hang\n // rather than a mistake.\n const chunkSize = Math.max(1, Math.floor(wordsPerChunk));\n const tokens = text.match(/\\S+|\\s+/g) ?? [];\n\n let index = 0;\n while (index < tokens.length) {\n const chunk: string[] = [];\n let words = 0;\n while (index < tokens.length && words < chunkSize) {\n const token = tokens[index++];\n chunk.push(token);\n if (token.trim()) {\n words++;\n }\n }\n\n yield chunk.join('');\n await new Promise((resolve) => setTimeout(resolve, chunkDelay * (1 + (Math.random() - 0.5) * variance * 2)));\n }\n}\n\n/**\n * A plausible assistant answer: prose, a list, a code block and a closing paragraph — enough\n * structure that the item re-parses markdown as the text arrives, rather than growing one flat\n * paragraph.\n */\nexport const createAnswer = (): string =>\n [\n random.lorem.paragraph(),\n '',\n `## ${random.lorem.sentence(3)}`,\n '',\n random.lorem.paragraph(),\n '',\n `- **${random.lorem.words(2)}** — ${random.lorem.sentence(8)}`,\n `- **${random.lorem.words(2)}** — ${random.lorem.sentence(10)}`,\n `- **${random.lorem.words(2)}** — ${random.lorem.sentence(6)}`,\n '',\n '```ts',\n 'const feed = useQuery(db, Query.from(feed));',\n 'const hits = searchFeed(messages, renderer, query);',\n '```',\n '',\n random.lorem.paragraph(),\n ].join('\\n');\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { random } from '@dxos/random';\nimport { type ContentBlock } from '@dxos/types';\n\nimport { createAnswer, textStream } from './stream.ts';\n\nexport type TurnOptions = {\n chunkDelay?: number;\n wordsPerChunk?: number;\n /** Tool calls in the run, so a panel that summarises a run is driven with more than one. */\n calls?: number;\n /** 1-based call that comes back as an error, exercising the failure summary. */\n failAt?: number;\n};\n\n/**\n * One assistant turn as the blocks a model actually emits, arriving in order.\n *\n * The text-only stream exercises the easy half of reconciliation: a tail that only ever grows, which\n * the item appends. A real turn is harder in three ways, and each is a separate way for an item to\n * be rebuilt instead of extended —\n *\n * 1. a **status** block appears first and is removed once work begins, so the document shrinks;\n * 2. **reasoning** streams inside an unclosed tag and is closed when it completes, which rewrites\n * the end of the document rather than appending to it;\n * 3. a **tool call** lands whole, then its **result** arrives as a second block after it — and a\n * run of them arrives one call at a time, which is what a panel summarising the run has to\n * track while it is still growing.\n *\n * Yields the complete block array at each step; the caller replaces the message's blocks with it.\n */\nexport async function* streamTurn({\n chunkDelay = 120,\n wordsPerChunk = 4,\n calls = 1,\n failAt,\n}: TurnOptions = {}): AsyncGenerator<ContentBlock.Any[], void> {\n const pause = () => new Promise((resolve) => setTimeout(resolve, chunkDelay));\n\n // A status the reader sees before anything else exists, and which is gone by the answer.\n const status: ContentBlock.Any = { _tag: 'status', statusText: 'Thinking…', pending: true };\n yield [status];\n await pause();\n\n let reasoning = '';\n for await (const chunk of textStream(random.lorem.paragraph(), { chunkDelay, wordsPerChunk })) {\n reasoning += chunk;\n yield [status, { _tag: 'reasoning', reasoningText: reasoning, pending: true }];\n }\n\n // Closing the tag and dropping the status in one step: the document does not grow here, and an\n // item that only knows how to append would replace itself instead.\n const reasoned: ContentBlock.Any = { _tag: 'reasoning', reasoningText: reasoning };\n yield [reasoned];\n await pause();\n\n // A run arrives one call at a time, each unanswered until its result lands — the state a panel\n // reports as \"in flight\" before it can report a count.\n const names = ['search', 'read_document', 'write_document', 'summarize'];\n const tools: ContentBlock.Any[] = [];\n for (let index = 0; index < calls; index++) {\n const toolCallId = `tool-${random.number.int({ min: 1000, max: 9999 })}`;\n const name = names[index % names.length];\n const call: ContentBlock.Any = {\n _tag: 'toolCall',\n toolCallId,\n name,\n input: JSON.stringify({ query: random.lorem.word() }),\n providerExecuted: false,\n pending: true,\n };\n yield [reasoned, ...tools, call];\n await pause();\n\n tools.push({ ...call, pending: false });\n tools.push(\n failAt === index + 1\n ? {\n _tag: 'toolResult',\n toolCallId,\n name,\n error: 'ENOENT: no such file or directory',\n providerExecuted: false,\n }\n : {\n _tag: 'toolResult',\n toolCallId,\n name,\n result: random.lorem.sentence(12),\n providerExecuted: false,\n },\n );\n yield [reasoned, ...tools];\n await pause();\n }\n\n let text = '';\n for await (const chunk of textStream(createAnswer(), { chunkDelay, wordsPerChunk })) {\n text += chunk;\n yield [reasoned, ...tools, { _tag: 'text', text, pending: true }];\n }\n\n // Suggestions arrive only once the answer is complete, so the last step both closes the text and\n // extends the message — the two kinds of change at once.\n yield [\n reasoned,\n ...tools,\n { _tag: 'text', text },\n { _tag: 'suggestion', text: 'Tell me more' },\n { _tag: 'suggestion', text: 'Show the sources' },\n ];\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { random } from '@dxos/random';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Panel from '@dxos/react-ui/Panel';\nimport * as Toolbar from '@dxos/react-ui/Toolbar';\nimport * as Util from '@dxos/react-ui/Util';\nimport { Message } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\n\nimport {\n type MessageChromeProps,\n MessageList,\n Outline,\n type OutlineMarker,\n useMessageList,\n} from '../components/index.ts';\nimport { type Decoration, DecorationsProvider, ItemSelectionProvider } from '../hooks/index.ts';\nimport { SearchHit, defaultRenderer, isPrompt, searchFeed, sliceFeed, useFeedModel } from '../model/index.ts';\nimport { FeedStats, useFeedDebug } from './debug/index.ts';\nimport { createMessages } from './generator.ts';\nimport { type FeedScenario, createScenario } from './scenarios.tsx';\nimport { createAnswer, textStream } from './stream.ts';\nimport { streamTurn } from './turn.ts';\n\n/** Pause between one answer finishing and the next question arriving. */\nconst TURN_DELAY = 800;\n\n/** A turn that is only a growing text tail: what every scenario but the assistant chat produces. */\nasync function* textTurn(options: { wordsPerChunk: number; chunkDelay: number }) {\n let text = '';\n for await (const chunk of textStream(createAnswer(), options)) {\n text += chunk;\n yield [{ _tag: 'text' as const, text, pending: true }];\n }\n yield [{ _tag: 'text' as const, text }];\n}\n\nconst makeQuestion = () =>\n Message.make({\n sender: { role: 'user', name: 'Alice' },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(8) }],\n });\n\nconst makeAnswer = () =>\n Message.make({\n sender: { role: 'assistant', name: 'Assistant' },\n blocks: [{ _tag: 'text', text: '' }],\n });\n\n//\n// Chrome\n//\n\n/**\n * Per-message chrome, supplied by the host rather than the engine — the thing a single thread-wide\n * document cannot do without injecting widgets into its own markdown.\n */\nconst TestChrome = ({ message, index, selected, onSelect, children }: MessageChromeProps) => {\n const role = message.sender.role ?? 'user';\n const time = new Date(message.created).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n\n return (\n <div\n className={mx('group relative grid grid-cols-[2rem_1fr] gap-2 px-2 py-2', selected && 'bg-hover-surface')}\n data-testid='feed.message'\n >\n <div className='flex flex-col items-center gap-1'>\n <Field.Root>\n <Input.Checkbox\n checked={selected}\n onCheckedChange={() => onSelect(message.id, true)}\n data-testid='feed.message.select'\n />\n </Field.Root>\n </div>\n\n {/*\n Fork / rewind / reply — chrome, not content. Positioned out of flow and toggled by opacity:\n anything that changes a row's height on hover re-triggers the virtualizer's measurement, and\n a pointer moving down the list during a scroll then shifts every row below it.\n */}\n <div className='absolute right-1 top-1 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100'>\n <Button.Root icon='ph--git-branch--regular' iconOnly label='Fork' variant='ghost' iconSize='xs' />\n <Button.Root\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Rewind'\n variant='ghost'\n iconSize='xs'\n />\n <Button.Root icon='ph--arrow-bend-up-left--regular' iconOnly label='Reply' variant='ghost' iconSize='xs' />\n </div>\n\n <div className='min-w-0'>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name ?? role}</span>\n <span>{time}</span>\n <span className='text-fg-subtle'>#{index}</span>\n </div>\n {children}\n </div>\n </div>\n );\n};\n\n//\n// Story\n//\n\nexport type FeedStoryProps = {\n /**\n * Which downstream call site to approximate. Each supplies its own renderer, chrome and follow\n * behaviour; everything else — the list, virtualization, selection, search — is the same engine.\n * Omitted, the story runs the synthetic mixed feed the measurements were taken against.\n */\n scenario?: FeedScenario;\n /** Messages of seeded history; 0 starts empty. */\n count?: number;\n /** Outline every item and every block inside it; the toolbar toggles it too. */\n debug?: boolean;\n /** Begin streaming turns on mount. */\n streaming?: boolean;\n wordsPerChunk?: number;\n chunkDelay?: number;\n estimateSize?: number;\n /** Blank lines kept below the last row, part of the resting view. */\n tailLines?: number;\n};\n\n/**\n * The one renderer every `MessageList` story uses; the stories differ only by args.\n *\n * Seeded history, a search over the model, message-set selection, and a play control that streams\n * turns into the tail until stopped — one harness, so a story is a set of arguments rather than\n * another component to keep in step.\n */\nexport const FeedStory = ({\n scenario,\n tailLines,\n count = 0,\n debug: debugProp,\n streaming: streamingProp = false,\n wordsPerChunk = 4,\n chunkDelay = 120,\n estimateSize,\n}: FeedStoryProps) => {\n const definition = useMemo(() => (scenario ? createScenario({ scenario, count }) : undefined), [scenario, count]);\n const [messages, setMessages] = useState<Message.Message[]>(() => definition?.messages ?? createMessages({ count }));\n const [streaming, setStreaming] = useState(streamingProp);\n const [answerId, setAnswerId] = useState<string | undefined>();\n const [selectedIds, setSelectedIds] = useState<ReadonlySet<string>>(new Set());\n const [copied, setCopied] = useState<string | null>(null);\n const [query, setQuery] = useState('');\n\n const hits = useMemo(() => searchFeed(messages, defaultRenderer, query), [messages, query]);\n\n // Search is a decoration producer, not a list prop (SPEC §Aspects): the ranges reach the items\n // through the provider below, and the list neither knows nor routes them.\n const decorations = useMemo<Decoration[]>(\n () => hits.map(({ messageId, offset, length }) => ({ id: messageId, range: { offset, length }, kind: 'search' })),\n [hits],\n );\n\n // The model owns identity, stops and streaming; the array survives as this adapter (SPEC F-7.3).\n const isAnchor = definition?.isAnchor;\n const model = useFeedModel(messages, { stops: isAnchor ?? 'message' });\n\n // Instrumentation as an aspect: the meter, the sweep, the outlines and the pass label live in\n // `useFeedDebug` so this component stays about the feed.\n const { debug, toggleDebug, meter, viewportRef, sweeping, onSweep } = useFeedDebug({\n scenario,\n count,\n estimateSize,\n streaming,\n enabled: debugProp,\n });\n\n const handleAppend = useCallback(() => setMessages((prev) => [...prev, makeQuestion()]), []);\n\n const handleReset = useCallback(() => {\n setStreaming(false);\n setAnswerId(undefined);\n setMessages(definition?.messages ?? createMessages({ count }));\n }, [count, definition]);\n\n const handleCopy = useCallback(() => {\n // Copy across message boundaries is reconstructed from the model: unmounted items are not in the\n // DOM, so a native selection could never have included them.\n if (messages.length < 3) {\n return;\n }\n\n setCopied(\n sliceFeed(messages, defaultRenderer, {\n from: { messageId: messages[0].id, offset: 0 },\n to: { messageId: messages[2].id, offset: 20 },\n }),\n );\n }, [messages]);\n\n // Turns keep arriving until stopped: an unbounded conversation is what the list has to survive,\n // and the only way to watch the follow behave across more than one answer.\n useEffect(() => {\n if (!streaming) {\n return;\n }\n\n let cancelled = false;\n void (async () => {\n while (!cancelled) {\n const answer = makeAnswer();\n setMessages((prev) => [...prev, makeQuestion(), answer]);\n setAnswerId(answer.id);\n\n // A chat turn arrives as blocks — status, reasoning, a tool call, then the answer — which is\n // what makes reconciliation hard: the document shrinks and rewrites as well as grows. Every\n // other scenario streams a plain text tail, the easy half.\n const turn =\n scenario === 'assistant'\n ? streamTurn({ wordsPerChunk, chunkDelay })\n : textTurn({ wordsPerChunk, chunkDelay });\n\n for await (const blocks of turn) {\n if (cancelled) {\n return;\n }\n setMessages((prev) =>\n prev.map((message) => (message.id === answer.id ? ({ ...message, blocks } as Message.Message) : message)),\n );\n }\n\n setAnswerId(undefined);\n await new Promise((resolve) => setTimeout(resolve, TURN_DELAY));\n }\n })();\n\n return () => {\n cancelled = true;\n // A turn interrupted before its first chunk leaves an empty answer behind, which renders as a\n // tall blank row. Strict mode runs this teardown on mount, so it is the common case.\n setMessages((prev) => {\n const last = prev.at(-1);\n return last && last.sender.role === 'assistant' && !Message.extractText(last).length ? prev.slice(0, -1) : prev;\n });\n };\n }, [streaming, wordsPerChunk, chunkDelay, scenario]);\n\n const onSelect = useCallback((id: string, additive: boolean) => {\n setSelectedIds((current) => {\n const next = new Set(additive ? current : []);\n if (additive && next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n }, []);\n\n return (\n // Root is headless, so the toolbar and statusbar sit outside the scroll container and still read\n // the list's state through `useMessageList`. The providers around it are the aspect pattern:\n // selection and decorations reach the items by id, and the list never sees either.\n <DecorationsProvider decorations={decorations}>\n <ItemSelectionProvider selectedIds={selectedIds} onSelect={onSelect}>\n <MessageList.Root\n model={model}\n renderer={definition?.renderer}\n debug={debug}\n Chrome={definition?.Chrome ?? TestChrome}\n Custom={definition?.Custom}\n registry={definition?.registry}\n estimateSize={estimateSize ?? definition?.estimateSize}\n stickyBottom={definition?.stickyBottom ?? true}\n tailLines={tailLines}\n >\n <Panel.Root>\n <Panel.Header>\n <Toolbar.Root>\n <Button.Root\n icon={streaming ? 'ph--stop--regular' : 'ph--play--regular'}\n iconOnly\n label={streaming ? 'Stop' : 'Start'}\n data-testid='feed.stream.toggle'\n onClick={() => setStreaming((value) => !value)}\n />\n <Button.Root\n icon='ph--plus--regular'\n iconOnly\n label='Add message'\n data-testid='feed.stream.append'\n onClick={handleAppend}\n />\n <Button.Root icon='ph--trash--regular' iconOnly label='Reset' onClick={handleReset} />\n <Button.Root\n icon={debug ? 'ph--bounding-box--fill' : 'ph--bounding-box--regular'}\n iconOnly\n label={debug ? 'Hide block outlines' : 'Show block outlines'}\n data-testid='feed.debug.toggle'\n onClick={toggleDebug}\n />\n <Toolbar.Separator />\n <Field.Root>\n <Input.Root\n placeholder='Search…'\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n data-testid='feed.search'\n />\n </Field.Root>\n <FindButton hits={hits} />\n <Button.Root icon='ph--copy--regular' iconOnly label='Copy range' onClick={handleCopy} />\n <Button.Root\n icon={sweeping ? 'ph--stop--regular' : 'ph--arrows-down-up--regular'}\n iconOnly\n label={sweeping ? 'Stop sweep' : 'Sweep (measure a pass)'}\n data-testid='feed.sweep'\n onClick={onSweep}\n />\n <div className='grow' />\n <MessageList.Nav classNames='contents' />\n </Toolbar.Root>\n </Panel.Header>\n\n <Panel.Body classNames='relative'>\n <div className='z-10 absolute left-0 top-0 bottom-0 grid grid-rows-[1fr_4fr_1fr] justify-center'>\n <FeedOutline classNames='row-start-2' messages={messages} />\n </div>\n <MessageList.Viewport classNames='dx-cover' ref={viewportRef} />\n </Panel.Body>\n </Panel.Root>\n <FeedStats meter={meter} streaming={streaming} selected={selectedIds.size} hits={hits.length} />\n </MessageList.Root>\n </ItemSelectionProvider>\n </DecorationsProvider>\n );\n};\n\n/**\n * The outline of a feed: one tick per prompt, in message-index space.\n *\n * `OutlineMarker.range` is \"the document's own position space\", which for a feed is the message\n * index — so a marker is `[index, index + 1)` and the visible range is the mounted window. No\n * pixel offsets are involved, which is what makes this survive rows whose heights are still\n * estimates.\n */\nconst FeedOutline = ({ classNames, messages }: Util.ThemedClassName<{ messages: readonly Message.Message[] }>) => {\n const { currentIndex, scrollToIndex } = useMessageList('FeedOutline');\n\n const markers = useMemo<OutlineMarker[]>(() => {\n // Every prompt is passed; the rail thins them to whatever its height affords.\n return messages\n .map((message, index) => ({ message, index }))\n .filter(({ message }) => isPrompt(message))\n .map(({ message, index }) => ({\n id: message.id,\n title: `#${index} · ${message.sender.name ?? message.sender.role}`,\n // Tags stripped: an html-kind message's text is markup, which reads as noise in a tooltip.\n description: Message.extractText(message)\n .replace(/<[^>]*>/g, ' ')\n .split('\\n')\n .map((line) => line.trim())\n .find(Boolean)\n ?.slice(0, 160),\n // A tick marks one message, not a span: which one is current is decided below, so the range\n // stays honest about what the tick points at.\n range: { from: index, to: index + 1 },\n }));\n }, [messages]);\n\n // Exactly one tick reads as current — the last prompt at or before the cursor. Passing the\n // mounted window instead would light up whichever ticks happen to fall inside it: none for most\n // of a sampled rail, two when the window straddles a boundary.\n const current = useMemo(() => {\n const marker = [...markers].reverse().find(({ range: { from } }) => from <= currentIndex) ?? markers[0];\n return marker?.range;\n }, [markers, currentIndex]);\n\n const handleSelect = useCallback(\n (marker: OutlineMarker) => scrollToIndex(marker.range.from, { align: 'start', behavior: 'smooth' }),\n [scrollToIndex],\n );\n\n if (!markers.length) {\n return <div />;\n }\n\n return (\n <Outline\n classNames={['justify-self-center', classNames]}\n markers={markers}\n visibleRange={current}\n onSelect={handleSelect}\n />\n );\n};\n\n/** Scroll-to-hit lives in the toolbar, outside the viewport — the reason Root is headless. */\nconst FindButton = ({ hits }: { hits: readonly SearchHit[] }) => {\n const { scrollToIndex } = useMessageList('FindButton');\n const handleFind = useCallback(() => {\n const hit = hits[0];\n if (hit) {\n scrollToIndex(hit.index, { align: 'start', behavior: 'smooth' });\n }\n }, [hits, scrollToIndex]);\n\n return <Button.Root icon='ph--magnifying-glass--regular' iconOnly label='Find' onClick={handleFind} />;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type ComponentType, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Window, type WindowController, type WindowState, useListModel } from '@dxos/react-ui-virtual';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport { type Message } from '@dxos/types';\n\nimport { HtmlBlock, MarkdownBlock, type MessageChromeProps } from '../components/index.ts';\nimport { type MessageRenderer, defaultRenderer } from '../model/index.ts';\n\n/**\n * A feed of real messages placed by `Window`, and nothing else.\n *\n * The bridge between the placement layer and the thing it has to replace. `placement/*` proves the\n * shape against boxes that cannot lie about their size; `baseline/*` proves the current engine\n * against real editors. This is where those meet: the same messages, the same renderer and the same\n * chrome as a feed, but placed by the new module — so the baseline invariants can be pointed at it\n * and answer whether the replacement is ready, rather than the question being settled by argument.\n *\n * Deliberately *not* wired into `MessageList.Root`. Root owns the follow, the sticky tail, the\n * anchors and the cursor as well as the virtualizer, so swapping the placement inside it is not a\n * swap but a reimplementation — and doing that before this exists would leave nothing to check it\n * against.\n */\nexport type MessageWindowProps = {\n messages: Message.Message[];\n renderer?: MessageRenderer;\n Chrome?: ComponentType<MessageChromeProps>;\n Custom?: ComponentType<{ content: any; message: Message.Message }>;\n /** Extent of a row before it has been measured; a function where rows differ widely. */\n estimateSize?: number | ((message: Message.Message, index: number) => number);\n gutter?: 'sm' | 'md' | 'lg';\n /** Keep the last message against the bottom as content arrives — a chat's standing intent. */\n sticky?: boolean;\n /**\n * Empty extent after the last row, so it can be brought to the top of the viewport.\n *\n * A number, computed by the host from its own container. Not a `scrollPastEnd` mode: with the\n * window placed rather than the rows, the reserve is a term in the sizer and nothing else in the\n * engine knows about it (§7) — which is what the old engine could not say, since its reserve was\n * a DOM spacer outside the virtualizer's coordinate space.\n */\n reserve?: number;\n onChange?: (state: WindowState) => void;\n controllerRef?: React.Ref<WindowController>;\n};\n\nconst DefaultChrome = ({ children }: MessageChromeProps) => <>{children}</>;\n\nexport const MessageWindow = ({\n messages,\n renderer = defaultRenderer,\n Chrome = DefaultChrome,\n Custom,\n estimateSize = 120,\n gutter,\n sticky,\n reserve,\n onChange,\n controllerRef,\n}: MessageWindowProps) => {\n const [selectedIds, setSelectedIds] = useState(new Set<string>());\n const onSelect = useCallback((id: string) => setSelectedIds((current) => new Set(current).add(id)), []);\n\n const model = useListModel(messages, (message: Message.Message) => message.id);\n const extents = useMemo(\n () => ({\n of: (index: number) => {\n const message = messages[index];\n if (!message) {\n return typeof estimateSize === 'number' ? estimateSize : 120;\n }\n\n return typeof estimateSize === 'function' ? estimateSize(message, index) : estimateSize;\n },\n }),\n [messages, estimateSize],\n );\n\n return (\n <Window\n classNames='h-full'\n model={model}\n extents={extents}\n sticky={sticky}\n reserve={reserve}\n onChange={onChange}\n controllerRef={controllerRef}\n >\n {(index) => {\n const message = messages[index];\n if (!message) {\n return null;\n }\n\n return (\n <Layout.Container gutter={gutter ?? 'lg'}>\n <Chrome message={message} index={index} selected={selectedIds.has(message.id)} onSelect={onSelect}>\n <Item content={renderer(message)} message={message} Custom={Custom} />\n </Chrome>\n </Layout.Container>\n );\n }}\n </Window>\n );\n};\n\n/**\n * The item's content, resolved without the list.\n *\n * `MessageList.Item` reads the renderer, the registry, the hits and the widget census from\n * `MessageList.Root`'s context, so it cannot be rendered outside the very thing this exists to\n * replace. Repeated here rather than refactored: making the item take its dependencies as props is\n * the right change and belongs in the retrofit, not in a harness written to measure it.\n */\nconst Item = ({\n content,\n message,\n Custom,\n}: {\n content: ReturnType<MessageRenderer>;\n message: Message.Message;\n Custom?: ComponentType<{ content: any; message: Message.Message }>;\n}) => (\n <>\n {content.kind === 'markdown' && <MarkdownBlock text={content.text} />}\n {content.kind === 'html' && <HtmlBlock html={content.html} />}\n {content.kind === 'custom' && Custom && <Custom content={content} message={message} />}\n </>\n);\n\n/** Kept for the stories that drive it; the ref is the same handle `placement/*` uses. */\nexport const useMessageWindow = () => useRef<WindowController>(null);\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","names":["Panel","_Fragment"],"sources":["../../src/testing/debug/FeedStats.tsx","../../src/testing/debug/sweep.ts","../../src/testing/debug/use-feed-debug.ts","../../src/testing/generator.ts","../../src/testing/controls.tsx","../../src/testing/widgets.tsx","../../src/testing/scenarios.tsx","../../src/testing/stream.ts","../../src/testing/turn.ts","../../src/testing/FeedStory.tsx","../../src/testing/MessageWindow.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport * as Button from '@dxos/react-ui/Button';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useMessageList } from '../../components/index.ts';\nimport { type FrameMeter, type Stat, Stats, warnAbove } from '../../debug/index.ts';\n\n/** What the feed currently holds, as opposed to how well it is holding it. */\nconst FEED_STATS: Stat[] = [\n {\n id: 'range',\n label: 'range',\n },\n {\n id: 'index',\n label: 'index',\n },\n {\n id: 'rows',\n label: 'rows',\n },\n {\n id: 'widgets',\n label: 'widgets',\n },\n {\n id: 'selected',\n label: 'selected',\n },\n {\n id: 'hits',\n label: 'hits',\n },\n];\n\n/**\n * The scroll-quality verdict: live rate, the rate at the pass's slowest fifth, the worst frame, and\n * how many frames stuttered. Only meaningful at a real keyboard — an agent's browser throttles the\n * frames this counts.\n *\n * `jumps` are rows moving against the scroll, sampled per frame; `shifts` are rows whose offset\n * changed after they were laid out; `breaks` are shifts that broke the order. All three should be\n * zero on a still feed, so each is coloured the moment it is not.\n */\nconst FRAME_STATS: Stat[] = [\n {\n id: 'fps',\n label: 'render',\n unit: 'fps',\n classNames: (value) => Number(value) > 0 && Number(value) < 50 && 'text-warning-text',\n },\n {\n id: 'p95',\n label: 'p95',\n unit: 'ms',\n },\n {\n id: 'worst',\n label: 'worst',\n unit: 'ms',\n },\n {\n id: 'hitches',\n label: 'hitches',\n classNames: warnAbove,\n },\n {\n id: 'jumps',\n label: 'jumps',\n classNames: warnAbove,\n },\n {\n id: 'shifts',\n label: 'shifts',\n classNames: warnAbove,\n },\n {\n id: 'breaks',\n label: 'breaks',\n classNames: warnAbove,\n },\n];\n\nexport type FeedStatsProps = Util.ThemedClassName<{\n meter: FrameMeter;\n streaming?: boolean;\n selected?: number;\n hits?: number;\n}>;\n\n/**\n * Everything measured about a feed, floating over it.\n *\n * It sits above the viewport rather than in a statusbar because a statusbar is part of the layout\n * being measured: a block of readouts that grows or wraps changes the height of the scroll\n * container, and every number in it would then describe a viewport the readouts themselves resized.\n */\nexport const FeedStats = ({ classNames, meter, streaming, selected = 0, hits = 0 }: FeedStatsProps) => {\n const { range, currentIndex, count, mountedRows, mountedWidgets, jumps, shifts, breaks, resetShifts } =\n useMessageList('FeedStats');\n\n // A pass is the interval between two presses, so the control is start/stop rather than a reset.\n const [recording, setRecording] = useState(false);\n const onRecord = useCallback(() => {\n meter.record();\n setRecording((value) => !value);\n }, [meter]);\n\n const values = useMemo(\n () => ({\n range: range ? `${range.startIndex} / ${range.endIndex}` : '—',\n index: `${currentIndex} / ${count}`,\n rows: mountedRows,\n widgets: mountedWidgets,\n selected,\n hits,\n fps: meter.fps,\n p95: meter.p95,\n worst: meter.worst,\n hitches: meter.hitches,\n jumps: jumps.count,\n shifts,\n breaks,\n }),\n [range, currentIndex, count, mountedRows, mountedWidgets, selected, hits, meter, jumps.count, shifts, breaks],\n );\n\n const label = `${meter.label} — p50 ${meter.p50} · ${meter.frames} frames · ${(meter.duration / 1000).toFixed(1)}s`;\n\n return (\n <div\n // Fixed width, not fitted: the widest line is the pass summary, whose length changes with the\n // elapsed time, so a panel sized to its content would resize once a second while being read.\n className={mx(\n 'z-10 absolute bottom-3 right-3 w-[12rem] grid p-2 rounded-sm border border-separator bg-base-surface text-xs text-fg-muted',\n classNames,\n )}\n data-testid='feed.stats'\n >\n <div className='flex items-center min-w-0'>\n <span className='grow truncate' data-testid='feed.stream.state'>\n {streaming ? 'streaming…' : 'idle'}\n </span>\n <Button.Root\n icon={recording ? 'ph--stop--regular' : 'ph--record--regular'}\n iconOnly\n label={recording ? 'End the pass' : 'Start a pass'}\n variant='ghost'\n iconSize='xs'\n data-testid='feed.debug.record'\n onClick={onRecord}\n />\n <Button.Root\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Reset counters'\n variant='ghost'\n iconSize='xs'\n data-testid='feed.debug.reset'\n onClick={resetShifts}\n />\n </div>\n\n <Stats stats={FEED_STATS} values={values} />\n\n {/* The pass's extent rides in the tooltip: it is context for the readings rather than one of\n them, and its width changes with the elapsed time. */}\n <Stats\n stats={FRAME_STATS}\n values={values}\n classNames='border-t border-separator-subtle pt-1'\n title={label}\n data-testid='feed.frames'\n />\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * Travel speed (px/s) of a measured pass: about what a hard trackpad fling reaches on a 120Hz\n * display, and the speed at which a reader still expects to see content rather than a blur.\n *\n * Not \"the whole document in N seconds\", which was the first attempt: 2,000 rows in six seconds is\n * 40,000 px/s — some 400 rows a second, each one a CodeMirror view to mount and measure — and it\n * collapsed the frame rate to 3fps on hardware that idles at 120. That measured the sweep, not the\n * list.\n */\nconst VELOCITY = 3_000;\n\n/** Time spent travelling in each direction (ms), so a pass covers the same distance in every story. */\nconst LEG_DURATION = 5_000;\n\nexport type SweepOptions = {\n /** Travel speed in px/s. @default 3000 */\n velocity?: number;\n /** Time for one leg (ms). @default 5000 */\n duration?: number;\n /** Called once the return leg lands, or when cancelled. */\n onDone?: () => void;\n};\n\n/**\n * Scrolls an element down and back at a fixed speed.\n *\n * A measured pass has to be one repeatable gesture or the numbers cannot be compared between\n * stories, and a 2,000-row feed is far too long to fling by hand. Fixed speed for a fixed time —\n * rather than a fixed fraction of the document — so the two stories being compared travel the same\n * distance even though they disagree about how tall the document is until their rows have measured.\n */\nexport const sweepScroll = (\n element: HTMLElement,\n { velocity = VELOCITY, duration = LEG_DURATION, onDone }: SweepOptions = {},\n) => {\n let frame = 0;\n let cancelled = false;\n let start = performance.now();\n let last = start;\n let leg: 'down' | 'up' = 'down';\n\n const finish = () => {\n cancelAnimationFrame(frame);\n onDone?.();\n };\n\n const tick = (now: number) => {\n if (cancelled) {\n return;\n }\n\n // Stepped by elapsed time rather than by frame, so a dropped frame costs smoothness but not\n // distance — two passes over the same feed cover the same ground however each one rendered.\n const step = (velocity * (now - last)) / 1000;\n last = now;\n const limit = element.scrollHeight - element.clientHeight;\n element.scrollTop = Math.max(0, Math.min(element.scrollTop + (leg === 'down' ? step : -step), limit));\n\n if (now - start < duration) {\n frame = requestAnimationFrame(tick);\n return;\n }\n\n if (leg === 'down') {\n leg = 'up';\n start = now;\n last = now;\n frame = requestAnimationFrame(tick);\n return;\n }\n\n finish();\n };\n\n frame = requestAnimationFrame(tick);\n\n return () => {\n cancelled = true;\n finish();\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { type FrameMeter, useFrameMeter } from '../../debug/index.ts';\nimport { sweepScroll } from './sweep.ts';\n\nexport type FeedDebugOptions = {\n /** What the pass ran under; the recorded line carries these rather than a story name. */\n scenario?: string;\n count: number;\n estimateSize?: number;\n streaming: boolean;\n /** Whether the outlines start on. Off: they are for a question, not for every reading. */\n enabled?: boolean;\n};\n\nexport type FeedDebug = {\n /** Whether the item and block outlines are drawn. */\n debug: boolean;\n toggleDebug: () => void;\n /** Frame statistics and the control that records a pass. */\n meter: FrameMeter;\n /** Attach to the viewport: the sweep needs the element it is going to scroll. */\n viewportRef: RefObject<HTMLDivElement | null>;\n sweeping: boolean;\n /** Start a measured pass, or cancel the one running. */\n onSweep: () => void;\n};\n\n/**\n * Everything the harness measures itself with, in one place.\n *\n * Kept apart from the story deliberately: instrumentation accumulates — a meter, a sweep, a toggle,\n * a label, the refs and teardown each needs — and threaded through a component it slowly becomes\n * the component. As an aspect the story stays about the feed, and what is being measured can be\n * read, changed or dropped in one file.\n */\nexport const useFeedDebug = ({\n scenario,\n count,\n estimateSize,\n streaming,\n enabled = false,\n}: FeedDebugOptions): FeedDebug => {\n const [debug, setDebug] = useState(enabled);\n const toggleDebug = useCallback(() => setDebug((value) => !value), []);\n\n const label = useMemo(\n () =>\n [scenario ?? 'mixed', `${count} msgs`, estimateSize ? `est ${estimateSize}` : undefined, streaming && 'streaming']\n .filter(Boolean)\n .join(' · '),\n [scenario, count, estimateSize, streaming],\n );\n\n const meter = useFrameMeter({ label });\n const viewportRef = useRef<HTMLDivElement>(null);\n const [sweeping, setSweeping] = useState(false);\n const cancelSweep = useRef<(() => void) | null>(null);\n\n // A measured pass has to be one repeatable gesture. Flinging 2,000 rows by hand is neither, so the\n // sweep runs the traversal and records what it cost: click, wait, read the clipboard.\n const onSweep = useCallback(() => {\n if (sweeping) {\n cancelSweep.current?.();\n return;\n }\n\n const viewport = viewportRef.current;\n if (!viewport) {\n return;\n }\n\n meter.record();\n setSweeping(true);\n cancelSweep.current = sweepScroll(viewport, {\n onDone: () => {\n cancelSweep.current = null;\n setSweeping(false);\n meter.record();\n },\n });\n }, [sweeping, meter]);\n\n useEffect(() => () => cancelSweep.current?.(), []);\n\n return { debug, toggleDebug, meter, viewportRef, sweeping, onSweep };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { random } from '@dxos/random';\nimport { Message } from '@dxos/types';\n\nexport type MessageKind = 'markdown' | 'html' | 'code' | 'short';\n\nexport type GeneratorOptions = {\n count: number;\n /** Relative weights per kind; a mixed feed is the realistic case (email threads, AI chats). */\n kinds?: MessageKind[];\n seed?: number;\n};\n\nconst SENDERS = ['user', 'assistant'] as const;\n\n/**\n * Synthetic feed for measuring the engine.\n *\n * Heights are deliberately uneven — a uniform-height feed would hide exactly the measurement\n * problem the spike exists to answer.\n */\nexport const createMessages = ({\n count,\n kinds = ['markdown', 'markdown', 'short', 'code', 'html'],\n seed = 999,\n}: GeneratorOptions): Message.Message[] => {\n random.seed(seed);\n const start = Date.now() - count * 60_000;\n\n return Array.from({ length: count }, (_, index) => {\n const kind = kinds[index % kinds.length];\n const role = SENDERS[index % SENDERS.length];\n return Message.make({\n created: new Date(start + index * 60_000).toISOString(),\n sender: { role, name: role === 'user' ? 'Alice' : 'Assistant' },\n blocks: [blockFor(kind, index)],\n properties: { subject: `Message ${index}`, kind },\n });\n });\n};\n\nconst blockFor = (kind: MessageKind, index: number) => {\n switch (kind) {\n case 'short':\n return { _tag: 'text' as const, text: `**${index}.** ${random.lorem.sentence(6)}` };\n\n case 'code':\n return {\n _tag: 'text' as const,\n text: [\n `**${index}.** ${random.lorem.sentence(8)}`,\n '',\n '```ts',\n 'const feed = useQuery(db, Query.from(feed));',\n 'const hits = searchFeed(messages, renderer, query);',\n '```',\n '',\n random.lorem.sentence(12),\n ].join('\\n'),\n };\n\n case 'html':\n return {\n _tag: 'text' as const,\n mimeType: 'text/html',\n text: [\n `<p><strong>${index}.</strong> ${random.lorem.sentence(10)}</p>`,\n `<blockquote>${random.lorem.sentence(14)}</blockquote>`,\n '<ul><li>attachment.pdf</li><li>invoice.csv</li></ul>',\n ].join(''),\n };\n\n case 'markdown':\n default:\n return {\n _tag: 'text' as const,\n text: [\n `**${index}.** ${random.lorem.sentence(10)}`,\n '',\n random.lorem.paragraph(),\n '',\n `- ${random.lorem.sentence(5)}`,\n `- ${random.lorem.sentence(7)}`,\n ].join('\\n'),\n };\n }\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { EditorState, type Extension } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\nimport React, { useLayoutEffect, useRef } from 'react';\n\nimport { type Message } from '@dxos/types';\nimport {\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n} from '@dxos/ui-editor';\n\nimport { MarkdownBlock } from '../components/index.ts';\n\n/**\n * Controls for attributing what a row costs to mount.\n *\n * `baseline/mount` measures a whole feed with its fixtures already built and finds ~2ms for a row\n * with no editor against ~15ms for a row with one — while the same editor constructed into an\n * offscreen container costs 0.4ms (`baseline/construction`). These render the *same* rows through\n * the same chrome, differing only in what is inside them, so the gap can be attributed rather than\n * argued about.\n */\nexport type ControlContent = { data?: unknown };\n\n/** The floor: the row's text, and no editor at all. */\nexport const TextItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <p className='py-1 text-sm'>{String(content.data ?? '')}</p>\n);\n\n/** An editor with nothing but wrapping: no theme, no keymap, no markdown, no decoration. */\nconst BARE: Extension[] = [EditorView.editable.of(false), EditorState.readOnly.of(true), EditorView.lineWrapping];\n\n/** Wrapping plus the theme — the extension the isolated profile shows to be nearly free. */\nconst THEMED: Extension[] = [\n createBasicExtensions({ readOnly: true, editable: false, lineWrapping: true }),\n createThemeExtensions({ themeMode: 'light' }),\n];\n\n/** The theme plus the markdown language: parsing, but nothing rendered from what it parsed. */\nconst MARKDOWN: Extension[] = [...THEMED, createMarkdownExtensions()];\n\n/** Everything the item has bar its highlight layer — headings, lists and links drawn as themselves. */\nconst DECORATED: Extension[] = [...MARKDOWN, decorateMarkdown()];\n\nexport const BareEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={BARE} />\n);\n\nexport const ThemedEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={THEMED} />\n);\n\nexport const MarkdownEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={MARKDOWN} />\n);\n\nexport const DecoratedEditorItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <ProbeEditor text={String(content.data ?? '')} extensions={DECORATED} />\n);\n\n/**\n * The real item, reached the same way the probes are.\n *\n * The rung that separates *what the editor is configured with* from *what the component does around\n * it*: everything outside this component is identical to `uniform-decorated`.\n */\nexport const MarkdownProbeItem = ({ content }: { content: ControlContent; message: Message.Message }) => (\n <MarkdownBlock text={String(content.data ?? '')} />\n);\n\n/**\n * A deliberate copy of `MarkdownBlock`'s construction, reduced to the part being measured.\n *\n * Copied rather than shared: the point of a control is that it does not change when the thing it is\n * a control for does.\n */\nconst ProbeEditor = ({ text, extensions }: { text: string; extensions: Extension[] }) => {\n const rootRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const parent = rootRef.current;\n if (!parent) {\n return;\n }\n\n const view = new EditorView({ parent, state: EditorState.create({ doc: text, extensions }) });\n return () => view.destroy();\n }, [text, extensions]);\n\n return <div ref={rootRef} />;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport { TogglePanel } from '@dxos/react-ui-components';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport { type WidgetProps, type XmlWidgetRegistry, getXmlTextChild } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useWidgetState } from '../components/index.ts';\n\n/**\n * Widgets for the block kinds an assistant turn contains.\n *\n * Approximations of `plugin-assistant`'s, deliberately: what the engine has to prove is that a\n * per-message document can carry block widgets at all — that they mount, measure and survive\n * streaming — not that these are the widgets the plugin will ship. The real ones move down when the\n * plugin does.\n *\n * Every widget renders from the tag alone. The plugin keeps tool state beside the document and\n * re-applies it after a remount; an item here is rebuilt from its message, so state that is not in\n * the document has nowhere to survive.\n */\n\n/**\n * A collapsible block: the header is what the reader sees while scrolling past, the body is what\n * they open when they care. Reasoning and tool traces are long, incidental and frequent — rendering\n * them expanded turns a chat into a log.\n *\n * The disclosure animates, and every frame of it is measured twice — by CodeMirror, to re-place the\n * lines under the widget, and by the virtualizer, to re-place the rows under the item. That is what\n * it costs for the content below to travel with the panel instead of jumping to its new place; the\n * geometry stays consistent because `MarkdownBlock` re-measures the document as the widget resizes.\n */\nconst Panel = ({\n stateKey,\n icon,\n title,\n children,\n classNames,\n}: {\n /** Identifies this panel within its message, so its open flag can outlive the row. */\n stateKey: string;\n icon: string;\n title: string;\n children: React.ReactNode;\n classNames?: string;\n}) => {\n const [open, setOpen] = useWidgetState(stateKey, false);\n\n return (\n // The open flag is published to the DOM so a test can ask whether it survived the row being\n // unmounted — which is the whole question a virtualized feed raises about widget state.\n <div data-testid='feed.widget' data-open={open}>\n <TogglePanel.Root open={open} onChangeOpen={setOpen}>\n {/* A minimum height, not a fixed one: the widget's content is portaled and paints after\n CodeMirror has placed the box, so without a floor the row is measured empty and grows a\n frame later — a row jumping under the reader. `estimatedHeight` in the registry looks like\n the answer and is not: it sets `height` and `overflow: hidden` on the widget root, which\n pins the panel shut. */}\n <TogglePanel.Content classNames={mx('min-h-[2.125rem] rounded border border-separator-subtle', classNames)}>\n <TogglePanel.Header classNames='flex items-center gap-2 px-2 py-1 text-sm'>\n <span className='grow text-fg-muted truncate'>{title}</span>\n <Layout.Block>\n <Icon.Icon icon={icon} size='md' />\n </Layout.Block>\n </TogglePanel.Header>\n <TogglePanel.Body>\n <TogglePanel.Viewport classNames='px-2 pb-1 text-sm'>{children}</TogglePanel.Viewport>\n </TogglePanel.Body>\n </TogglePanel.Content>\n </TogglePanel.Root>\n </div>\n );\n};\n\nconst Frame = ({ icon, title, children, classNames }: WidgetProps<any> & { classNames?: string }) => (\n <div className={mx('flex gap-2 px-2 py-1 rounded border border-separator-subtle text-sm', classNames)}>\n {icon && <Icon.Icon icon={icon} size='md' classNames='mt-1' tone='muted' />}\n <div className='min-w-0'>\n {title && <p className='text-xs text-fg-muted'>{title}</p>}\n {children}\n </div>\n </div>\n);\n\nconst Reasoning = ({ children, range }: WidgetProps) => {\n const text = getXmlTextChild(children ?? []) ?? '';\n return (\n <Panel\n stateKey={`reasoning:${range?.from}`}\n icon='ph--brain--regular'\n title={text.slice(0, 80)}\n classNames='opacity-70'\n >\n <p>{text}</p>\n </Panel>\n );\n};\n\nconst Status = ({ children }: WidgetProps) => (\n <p className='px-2 py-1 text-sm text-fg-muted animate-pulse'>{getXmlTextChild(children ?? [])}</p>\n);\n\nconst ToolCall = ({ name, pending, range }: WidgetProps<{ name?: string; pending?: string }>) => (\n <Panel\n stateKey={`toolCall:${range?.from}`}\n icon={pending ? 'ph--circle-notch--regular' : 'ph--wrench--regular'}\n title={`Calling ${name}`}\n >\n <p className='font-mono text-xs'>{name}</p>\n </Panel>\n);\n\nconst ToolResult = ({ children, for: tool, range }: WidgetProps<{ for?: string }>) => (\n <Panel stateKey={`toolResult:${range?.from}`} icon='ph--check--regular' title={`${tool} returned`}>\n <p className='font-mono text-xs'>{getXmlTextChild(children ?? [])}</p>\n </Panel>\n);\n\n// Inline, so a run of them wraps as chips rather than stacking one per line.\nconst Suggestion = ({ children }: WidgetProps) => (\n <button type='button' className='me-1 px-2 py-0.5 rounded-full border border-separator text-sm'>\n {getXmlTextChild(children ?? [])}\n </button>\n);\n\nconst Select = ({ children }: WidgetProps) => {\n const options = (children ?? [])\n .map((option: any) => option?._tag === 'option' && getXmlTextChild(option.children))\n .filter(Boolean) as string[];\n\n return (\n <div className='flex flex-col gap-1 py-1'>\n {options.map((option) => (\n <button key={option} type='button' className='px-2 py-1 text-left rounded border border-separator text-sm'>\n {option}\n </button>\n ))}\n </div>\n );\n};\n\nconst Json = ({ children }: WidgetProps) => (\n <pre className='px-2 py-1 overflow-x-auto rounded bg-input-surface text-xs'>{getXmlTextChild(children ?? [])}</pre>\n);\n\n/**\n * Floor for a panel's collapsed height, reserved before its portaled content paints.\n *\n * `heightMode: 'min'` is what makes this a floor rather than a pin — the default pins the box and\n * clips it, so an opened panel stays shut. Without any reservation the row is measured at the height\n * of an empty box and grows a frame later, which is a row moving under a reader scrolling up.\n */\nconst COLLAPSED_HEIGHT = 42;\n\nexport const chatRegistry: XmlWidgetRegistry = {\n // No widget: the reader's own words stay in the document, where they can be selected and searched\n // like any other text. Registered so the markdown parser keeps the tag as one block — an\n // unregistered tag opens a paragraph that swallows the lines after it.\n prompt: { block: true },\n reasoning: {\n block: true,\n streaming: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: Reasoning,\n },\n status: {\n block: true,\n streaming: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: Status,\n },\n toolCall: { block: true, estimatedHeight: () => COLLAPSED_HEIGHT, heightMode: 'min', Component: ToolCall },\n toolResult: { block: true, estimatedHeight: () => COLLAPSED_HEIGHT, heightMode: 'min', Component: ToolResult },\n suggestion: { block: false, Component: Suggestion },\n select: { block: true, Component: Select },\n json: { block: true, Component: Json },\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type ComponentType } from 'react';\n\nimport { random } from '@dxos/random';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as SystemButton from '@dxos/react-ui/SystemButton';\nimport { type ContentBlock, Message } from '@dxos/types';\nimport { type XmlWidgetRegistry } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type MessageChromeProps, type MessageRenderer, chatRenderer, defaultRenderer } from '../index.ts';\nimport {\n BareEditorItem,\n DecoratedEditorItem,\n MarkdownEditorItem,\n MarkdownProbeItem,\n TextItem,\n ThemedEditorItem,\n} from './controls.tsx';\nimport { chatRegistry } from './widgets.tsx';\n\nconst UNIFORM_CONTROLS = {\n 'uniform-text': TextItem,\n 'uniform-bare': BareEditorItem,\n 'uniform-themed': ThemedEditorItem,\n 'uniform-markdown': MarkdownEditorItem,\n 'uniform-decorated': DecoratedEditorItem,\n 'uniform-item': MarkdownProbeItem,\n} as const;\n\n/**\n * The five places in the repo that render a thread of messages, approximated against one engine.\n *\n * The point is not the fixtures — it is that all five differ only in the renderer, the chrome and a\n * couple of options, while the list, the virtualization, the selection and the search are the same\n * code. Where a scenario needs something the engine does not have, that is the finding.\n */\nexport type FeedScenario =\n /** No editor at all: fixed-height divs, so the list is measured against content that cannot move. */\n | 'plain'\n /** One editor per row, every row identical — the same document, so every height is the same. */\n | 'uniform'\n /** The `uniform` rows with their text as a paragraph: the same feed without a document at all. */\n | 'uniform-text'\n /** The `uniform` rows with an editor carrying nothing but line wrapping. */\n | 'uniform-bare'\n /** The `uniform` rows with wrapping and the theme, but no markdown and no decoration. */\n | 'uniform-themed'\n /** The `uniform` rows with the markdown language, but nothing drawn from what it parses. */\n | 'uniform-markdown'\n /** The `uniform` rows with markdown drawn as itself: headings, lists, links. */\n | 'uniform-decorated'\n /** The `uniform` rows rendered by the real item, reached through `Custom` like the probes. */\n | 'uniform-item'\n | 'assistant'\n | 'email'\n | 'thread'\n | 'comments'\n | 'transcript';\n\nexport type ScenarioDefinition = {\n messages: Message.Message[];\n renderer: MessageRenderer;\n /** Widgets for the tags the renderer emits; only the assistant turn has non-prose blocks. */\n registry?: XmlWidgetRegistry;\n Chrome: ComponentType<MessageChromeProps>;\n /** Renders a `custom` item, for the scenarios that deliberately avoid an editor. */\n Custom?: ComponentType<{ content: any; message: Message.Message }>;\n /** Whether the feed follows its tail: true where messages arrive, false where they are read. */\n stickyBottom: boolean;\n /**\n * Height a row is assumed to have before it is measured. A function where rows differ widely: a\n * one-line prompt and a long answer share no useful average, and a row measured for the first time\n * re-lays everything below it — which the reader sees as flicker when scrolling up, into rows that\n * have never been measured.\n */\n estimateSize: number | ((message: Message.Message, index: number) => number);\n /** Which messages the arrows and the navigation buttons stop on. */\n isAnchor?: (message: Message.Message, index: number) => boolean;\n};\n\nexport type ScenarioOptions = {\n scenario: FeedScenario;\n count: number;\n seed?: number;\n};\n\nexport const createScenario = ({ scenario, count, seed = 999 }: ScenarioOptions): ScenarioDefinition => {\n random.seed(seed);\n switch (scenario) {\n // The control: nothing here builds anything after it mounts, so a row's first measurement is its\n // last. Any movement in this feed belongs to the list, not to what it is rendering.\n case 'plain':\n return {\n messages: createPlainMessages(count),\n renderer: (message) => ({ kind: 'custom', key: 'plain', data: message.properties?.height ?? 120 }),\n Chrome: PlainChrome,\n Custom: PlainItem,\n stickyBottom: true,\n estimateSize: (message) => Number(message.properties?.height ?? 120) + PLAIN_CHROME,\n };\n\n // One rung up: a real editor per row, but every row the same single paragraph, so every row is\n // the same height and the estimate can be exactly right.\n case 'uniform':\n return {\n messages: createUniformMessages(count),\n renderer: defaultRenderer,\n Chrome: PlainChrome,\n stickyBottom: true,\n estimateSize: UNIFORM_HEIGHT,\n };\n\n // The controls for `uniform`: identical messages, identical chrome, identical estimate — only\n // what the row contains changes, so the difference between any two of them is that content.\n case 'uniform-text':\n case 'uniform-bare':\n case 'uniform-themed':\n case 'uniform-markdown':\n case 'uniform-decorated':\n case 'uniform-item':\n return {\n messages: createUniformMessages(count),\n renderer: (message) => ({ kind: 'custom', key: scenario, data: Message.extractText(message) }),\n Chrome: PlainChrome,\n Custom: UNIFORM_CONTROLS[scenario],\n stickyBottom: true,\n estimateSize: 84,\n };\n\n case 'assistant':\n return {\n messages: createAssistantMessages(count),\n renderer: chatRenderer,\n registry: chatRegistry,\n Chrome: AssistantChrome,\n stickyBottom: true,\n estimateSize: estimateAssistantRow,\n // A turn is a prompt and everything the model said in reply; stopping on each of the model's\n // messages would make the reader step through an answer rather than through the conversation.\n isAnchor: (message) => message.sender.role === 'user',\n };\n\n case 'email':\n return {\n messages: createEmailMessages(count),\n renderer: defaultRenderer,\n Chrome: EmailChrome,\n // A conversation is read from its start; the newest message is not where the reader is going.\n stickyBottom: false,\n estimateSize: 220,\n };\n\n case 'thread':\n return {\n messages: createThreadMessages(count),\n renderer: defaultRenderer,\n Chrome: ThreadChrome,\n stickyBottom: true,\n estimateSize: 64,\n };\n\n case 'comments':\n return {\n messages: createCommentMessages(count),\n renderer: defaultRenderer,\n Chrome: CommentChrome,\n stickyBottom: false,\n estimateSize: 96,\n };\n\n case 'transcript':\n return {\n messages: createTranscriptMessages(count),\n renderer: defaultRenderer,\n Chrome: TranscriptChrome,\n stickyBottom: true,\n estimateSize: 48,\n };\n }\n};\n\n/** Chrome around every assistant row: the sender line, the padding and the separator. */\nconst ROW_CHROME = 46;\n\n/** Height of a wrapped line of body text, and how many characters fit on one at the story's width. */\nconst LINE_HEIGHT = 24;\nconst LINE_CHARS = 90;\n\n/** A collapsed panel: reasoning, a tool call, a tool result. */\nconst PANEL_HEIGHT = 50;\n\n/**\n * What a row will measure, from the message alone.\n *\n * Rough on purpose — it is an estimate, and its only job is to be close enough that measuring the row\n * does not move the rows below it. Text is counted in wrapped lines, blank lines between blocks are\n * counted, and every block that renders as a widget is counted at its collapsed height.\n */\nconst estimateAssistantRow = (message: Message.Message): number => {\n let height = ROW_CHROME;\n for (const block of message.blocks) {\n switch (block._tag) {\n case 'text': {\n const paragraphs = block.text.split('\\n\\n');\n for (const paragraph of paragraphs) {\n height += Math.max(1, Math.ceil(paragraph.length / LINE_CHARS)) * LINE_HEIGHT;\n }\n break;\n }\n case 'suggestion':\n height += LINE_HEIGHT + 8;\n break;\n case 'select':\n height += block.options.length * (LINE_HEIGHT + 10);\n break;\n default:\n height += PANEL_HEIGHT;\n }\n }\n\n return height;\n};\n\n//\n// Fixtures\n//\n\n/** Fixed heights, cycling so the feed is uneven — but known in advance and never corrected. */\nconst PLAIN_HEIGHTS = [64, 120, 200, 88, 320, 56];\n\n/**\n * What the chrome adds to a plain row: the separator beneath it.\n *\n * The estimate has to include this. A control scenario whose estimate is the *content* height is off\n * by the chrome on every single row, and the list corrects each one — which looks exactly like the\n * defect the control exists to rule out.\n */\nconst PLAIN_CHROME = 1;\n\nconst createPlainMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index),\n sender: { role: index % 2 === 0 ? 'user' : 'assistant', name: index % 2 === 0 ? 'Alice' : 'Assistant' },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(index % 8) }],\n properties: { height: PLAIN_HEIGHTS[index % PLAIN_HEIGHTS.length] },\n }),\n );\n\n/**\n * What one uniform row measures, chrome included.\n *\n * Exact on purpose, and checked against the DOM rather than guessed: this scenario's whole job is to\n * be the case where the estimate is right, and it was declared at 84 against a real 24. A viewport\n * filled from an estimate three times too large covers a third of the screen once the rows measure,\n * so the feed arrived in two visible pieces — in the one story that exists to have nothing wrong\n * with it.\n */\nconst UNIFORM_HEIGHT = 24;\n\n/** Every row the same document, so a single estimate is exactly right for all of them. */\nconst createUniformMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index),\n sender: { role: index % 2 === 0 ? 'user' : 'assistant', name: index % 2 === 0 ? 'Alice' : 'Assistant' },\n // Not `${index}. …`: markdown reads a leading number and a dot as an ordered list and renumbers\n // it, so the row would show a number that is not its index. The index is in the chrome.\n blocks: [{ _tag: 'text', text: random.lorem.sentence(index % 16 === 0 ? 100 : 1) }],\n }),\n );\n\nconst NAMES = ['Alice', 'Bob', 'Charlie', 'Dana'];\n\nconst at = (index: number, step = 60_000) => new Date(Date.now() - (2_000 - index) * step).toISOString();\n\n/** A turn: the reader's prompt, then an answer carrying the block kinds a model actually emits. */\nconst createAssistantMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) => {\n if (index % 2 === 0) {\n return Message.make({\n created: at(index),\n sender: { role: 'user', name: 'Alice' },\n blocks: [{ _tag: 'text', text: random.lorem.paragraph() }],\n });\n }\n\n // Every answer is several blocks — reasoning, sometimes a tool round-trip, prose, and closing\n // affordances. A single text block is the easy case and the one the engine already handled.\n const blocks: ContentBlock.Any[] = [{ _tag: 'reasoning', reasoningText: random.lorem.paragraph() }];\n if (index % 4 === 1) {\n const toolCallId = `tool-${index}`;\n blocks.push({\n _tag: 'toolCall',\n toolCallId,\n name: 'search',\n input: '{}',\n providerExecuted: false,\n });\n blocks.push({\n _tag: 'toolResult',\n toolCallId,\n name: 'search',\n result: random.lorem.paragraphs(2),\n providerExecuted: false,\n });\n }\n\n blocks.push({ _tag: 'text', text: answerText(index) });\n if (index % 6 === 3) {\n blocks.push({ _tag: 'suggestion', text: 'Tell me more' });\n blocks.push({ _tag: 'suggestion', text: 'Show the sources' });\n }\n if (index % 8 === 5) {\n blocks.push({ _tag: 'select', options: ['Option 1', 'Option 2', 'Option 3'] });\n }\n\n return Message.make({ created: at(index), sender: { role: 'assistant', name: 'Assistant' }, blocks });\n });\n\nconst answerText = (index: number): string =>\n [\n `**${index}.** ${random.lorem.sentence(10)}`,\n '',\n random.lorem.paragraph(),\n '',\n `- ${random.lorem.sentence(5)}`,\n `- ${random.lorem.sentence(7)}`,\n ].join('\\n');\n\n/** An email conversation: HTML bodies, quoted history, and a subject that never changes. */\nconst createEmailMessages = (count: number): Message.Message[] => {\n const subject = `Re: ${random.lorem.words(4)}`;\n return Array.from({ length: count }, (_, index) => {\n const from = NAMES[index % NAMES.length];\n return Message.make({\n created: at(index, 3_600_000),\n sender: { role: 'user', name: from, email: `${from.toLowerCase()}@example.com` },\n blocks: [\n {\n _tag: 'text',\n mimeType: 'text/html',\n text: [\n `<p>${random.lorem.paragraph()}</p>`,\n `<p>${random.lorem.paragraph()}</p>`,\n '<ul><li>attachment.pdf</li><li>invoice.csv</li></ul>',\n `<blockquote>${random.lorem.sentence(16)}</blockquote>`,\n ].join(''),\n },\n ],\n properties: { subject },\n });\n });\n};\n\n/** Human chat: short turns, many of them, often several in a row from one speaker. */\nconst createThreadMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) => {\n const name = NAMES[Math.floor(index / 3) % NAMES.length];\n return Message.make({\n created: at(index, 30_000),\n sender: { role: 'user', name },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(random.number.int({ min: 4, max: 20 })) }],\n });\n });\n\n/** Comments: each anchored to a range of a document, and resolvable. */\nconst createCommentMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index, 120_000),\n sender: { role: 'user', name: NAMES[index % NAMES.length] },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(random.number.int({ min: 6, max: 24 })) }],\n properties: { anchor: random.lorem.sentence(6), resolved: index % 7 === 0 },\n }),\n );\n\n/** Transcription: one utterance per message, seconds apart, arriving while the reader watches. */\nconst createTranscriptMessages = (count: number): Message.Message[] =>\n Array.from({ length: count }, (_, index) =>\n Message.make({\n created: at(index, 6_000),\n sender: { role: 'user', name: NAMES[index % 2] },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(random.number.int({ min: 5, max: 18 })) }],\n }),\n );\n\n/**\n * A row of a known height: the content is a box, not a document.\n *\n * The height is the point — it is declared, never measured — so it is stated in the row rather than\n * left to be inferred, and the text sits at the top like every other scenario's does.\n */\nconst PlainItem = ({ content, message }: { content: { data?: unknown }; message: Message.Message }) => {\n const height = Number(content.data ?? 120);\n\n return (\n <div className='flex flex-col gap-1 py-2 text-sm' style={{ height }}>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name}</span>\n <span className='tabular-nums'>{height}px</span>\n </div>\n <p>{Message.extractText(message)}</p>\n </div>\n );\n};\n\n//\n// Chrome\n//\n\n/** No hover controls and no separators: nothing here can change a row's height. */\n/**\n * The index is shown in the gutter because these are the control scenarios: when a row moves, the\n * first question is *which* row, and reading it off the screen beats hunting for it in the DOM.\n */\nconst PlainChrome = ({ index, children }: MessageChromeProps) => (\n <Row classNames='py-0 grid grid-cols-[3rem_1fr] gap-2'>\n <div className='self-start flex items-center h-[1lh] text-base leading-normal'>\n <span className='text-xs text-fg-subtle tabular-nums'>{index}</span>\n </div>\n <div className='min-w-0'>{children}</div>\n </Row>\n);\n\n//\n// Every scenario's difference that is not the renderer lives here — which is the claim being\n// tested: chrome is the host's, and the engine only has to keep it out of the measurement.\n//\n\nconst timeOf = (message: Message.Message, options?: Intl.DateTimeFormatOptions) =>\n new Date(message.created).toLocaleTimeString([], options ?? { hour: '2-digit', minute: '2-digit' });\n\nconst Row = ({ children, classNames }: { children: React.ReactNode; classNames?: string }) => (\n <div className={mx('group relative px-2 py-2', classNames)} data-testid='feed.message'>\n {children}\n </div>\n);\n\n/**\n * Assistant: the reader's prompts and the model's answers are framed differently, because they are\n * different kinds of thing — a prompt is an instruction that can be rewound to, an answer is a\n * result that can be forked from.\n *\n * A prompt carries its rewind toolbar underneath; an answer carries its timestamp at the foot, where\n * it reads as when the answer finished rather than when the turn began. Both are always in flow — a\n * control that appears on hover changes the row's height, and a pointer travelling down a scrolling\n * list then moves every row below it.\n */\n/** Copies the message's extracted text — the model's truth, not the DOM's partial render. */\nconst CopyButton = ({ message }: { message: Message.Message }) => (\n <SystemButton.Clipboard iconOnly label='Copy' variant='ghost' size='sm' onCopy={() => Message.extractText(message)} />\n);\n\nconst AssistantChrome = ({ message, index, selected, children }: MessageChromeProps) => {\n const prompt = message.sender.role === 'user';\n\n return (\n <Row classNames={mx(selected && 'bg-hover-surface')}>\n {prompt ? (\n // The reader's own words sit apart from the model's: right-aligned, at most two thirds of\n // the viewport — the chat convention that makes whose-turn legible at a glance.\n <div className='min-w-0 flex flex-col items-end'>\n <div className='max-w-[66%] min-w-0'>\n {/* The reader's own words, framed: a prompt is an instruction the thread can be rewound\n to, and it reads as one only if it is visibly not the model's prose. */}\n <div className='px-4 py-3 border-s-2 border-accent-bg rounded-sm bg-input-surface'>{children}</div>\n {/* Revealed on hover, but never removed from flow: chrome that appears and disappears\n changes the row's height, and a pointer travelling down a scrolling list would then\n move every row below it. Opacity costs nothing to measure. The toolbar follows the\n bubble's edge — right-aligned, like the words it belongs to. */}\n <div className='flex items-center justify-end gap-1 pt-1 text-xs text-fg-muted opacity-0 transition-opacity group-hover:opacity-100'>\n <CopyButton message={message} />\n <Button.Root\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Rewind'\n variant='ghost'\n size='sm'\n />\n <Button.Root icon='ph--git-branch--regular' iconOnly label='Fork' variant='ghost' size='sm' />\n <span className='text-fg-subtle'>#{index}</span>\n <span>{timeOf(message)}</span>\n </div>\n </div>\n </div>\n ) : (\n <div className='min-w-0'>\n {children}\n <div className='flex items-center gap-1 pt-1 text-xs text-fg-muted opacity-0 transition-opacity group-hover:opacity-100'>\n <CopyButton message={message} />\n <Button.Root icon='ph--arrow-bend-up-left--regular' iconOnly label='Reply' variant='ghost' size='sm' />\n <span className='text-fg-subtle'>#{index}</span>\n <span>{timeOf(message)}</span>\n </div>\n </div>\n )}\n </Row>\n );\n};\n\n/** Email: a header of its own — sender, address, date — and a card-like body. */\nconst EmailChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames='border-b border-separator'>\n <div className='flex items-baseline gap-2'>\n <span className='font-medium'>{message.sender.name}</span>\n <span className='text-xs text-fg-muted'>{message.sender.email}</span>\n <span className='grow' />\n <span className='text-xs text-fg-muted'>{new Date(message.created).toLocaleString()}</span>\n </div>\n <p className='text-xs text-fg-muted'>{String(message.properties?.subject ?? '')}</p>\n <div className='mt-1'>{children}</div>\n </Row>\n);\n\n/** Human chat: an avatar, and consecutive turns from one speaker read as one block. */\nconst ThreadChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames='grid grid-cols-[2rem_1fr] gap-2'>\n <div className='w-6 h-6 rounded-full bg-input-surface grid place-items-center text-xs'>\n {message.sender.name?.[0]}\n </div>\n <div className='min-w-0'>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name}</span>\n <span>{timeOf(message)}</span>\n </div>\n {children}\n </div>\n </Row>\n);\n\n/** Comments: the quoted anchor above the comment, and a resolve control. */\nconst CommentChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames={mx('border-b border-separator-subtle', message.properties?.resolved && 'opacity-50')}>\n <p className='mb-1 ps-2 border-s-2 border-separator text-xs text-fg-muted line-clamp-1'>\n {String(message.properties?.anchor ?? '')}\n </p>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name}</span>\n <span>{timeOf(message)}</span>\n <span className='grow' />\n <Button.Root\n icon={message.properties?.resolved ? 'ph--check-circle--regular' : 'ph--circle--regular'}\n iconOnly\n label='Resolve'\n variant='ghost'\n iconSize='xs'\n />\n </div>\n {children}\n </Row>\n);\n\n/** Transcription: a timestamp and speaker in the gutter, and no separators — it reads as one text. */\nconst TranscriptChrome = ({ message, children }: MessageChromeProps) => (\n <Row classNames='grid grid-cols-[5rem_1fr] gap-2 py-0.5'>\n <div className='text-xs text-fg-muted tabular-nums'>\n {timeOf(message, { hour: '2-digit', minute: '2-digit', second: '2-digit' })}\n </div>\n <div className='min-w-0'>\n <span className='me-2 text-xs font-medium text-fg-muted'>{message.sender.name}</span>\n {children}\n </div>\n </Row>\n);\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { random } from '@dxos/random';\n\nexport type TextStreamOptions = {\n /** Delay between chunks in ms. */\n chunkDelay?: number;\n /** Variance in timing (0-1). */\n variance?: number;\n /** Words per chunk. */\n wordsPerChunk?: number;\n};\n\n/**\n * Word-by-word stream, the shape a model emits: chunks of a few words at an uneven cadence.\n *\n * Whitespace is preserved as its own token so a chunk boundary never fuses two words, which would\n * corrupt the markdown the item is parsing mid-stream.\n */\nexport async function* textStream(text: string, options: TextStreamOptions = {}): AsyncGenerator<string, void> {\n const { chunkDelay = 120, variance = 0.4, wordsPerChunk = 4 } = options;\n // Floored rather than validated: this is driven by a storybook control, and a size of zero never\n // advances the cursor — the generator would yield empty chunks for ever, which presents as a hang\n // rather than a mistake.\n const chunkSize = Math.max(1, Math.floor(wordsPerChunk));\n const tokens = text.match(/\\S+|\\s+/g) ?? [];\n\n let index = 0;\n while (index < tokens.length) {\n const chunk: string[] = [];\n let words = 0;\n while (index < tokens.length && words < chunkSize) {\n const token = tokens[index++];\n chunk.push(token);\n if (token.trim()) {\n words++;\n }\n }\n\n yield chunk.join('');\n await new Promise((resolve) => setTimeout(resolve, chunkDelay * (1 + (Math.random() - 0.5) * variance * 2)));\n }\n}\n\n/**\n * A plausible assistant answer: prose, a list, a code block and a closing paragraph — enough\n * structure that the item re-parses markdown as the text arrives, rather than growing one flat\n * paragraph.\n */\nexport const createAnswer = (): string =>\n [\n random.lorem.paragraph(),\n '',\n `## ${random.lorem.sentence(3)}`,\n '',\n random.lorem.paragraph(),\n '',\n `- **${random.lorem.words(2)}** — ${random.lorem.sentence(8)}`,\n `- **${random.lorem.words(2)}** — ${random.lorem.sentence(10)}`,\n `- **${random.lorem.words(2)}** — ${random.lorem.sentence(6)}`,\n '',\n '```ts',\n 'const feed = useQuery(db, Query.from(feed));',\n 'const hits = searchFeed(messages, renderer, query);',\n '```',\n '',\n random.lorem.paragraph(),\n ].join('\\n');\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { random } from '@dxos/random';\nimport { type ContentBlock } from '@dxos/types';\n\nimport { createAnswer, textStream } from './stream.ts';\n\nexport type TurnOptions = {\n chunkDelay?: number;\n wordsPerChunk?: number;\n /** Tool calls in the run, so a panel that summarises a run is driven with more than one. */\n calls?: number;\n /** 1-based call that comes back as an error, exercising the failure summary. */\n failAt?: number;\n};\n\n/**\n * One assistant turn as the blocks a model actually emits, arriving in order.\n *\n * The text-only stream exercises the easy half of reconciliation: a tail that only ever grows, which\n * the item appends. A real turn is harder in three ways, and each is a separate way for an item to\n * be rebuilt instead of extended —\n *\n * 1. a **status** block appears first and is removed once work begins, so the document shrinks;\n * 2. **reasoning** streams inside an unclosed tag and is closed when it completes, which rewrites\n * the end of the document rather than appending to it;\n * 3. a **tool call** lands whole, then its **result** arrives as a second block after it — and a\n * run of them arrives one call at a time, which is what a panel summarising the run has to\n * track while it is still growing.\n *\n * Yields the complete block array at each step; the caller replaces the message's blocks with it.\n */\nexport async function* streamTurn({\n chunkDelay = 120,\n wordsPerChunk = 4,\n calls = 1,\n failAt,\n}: TurnOptions = {}): AsyncGenerator<ContentBlock.Any[], void> {\n const pause = () => new Promise((resolve) => setTimeout(resolve, chunkDelay));\n\n // A status the reader sees before anything else exists, and which is gone by the answer.\n const status: ContentBlock.Any = { _tag: 'status', statusText: 'Thinking…', pending: true };\n yield [status];\n await pause();\n\n let reasoning = '';\n for await (const chunk of textStream(random.lorem.paragraph(), { chunkDelay, wordsPerChunk })) {\n reasoning += chunk;\n yield [status, { _tag: 'reasoning', reasoningText: reasoning, pending: true }];\n }\n\n // Closing the tag and dropping the status in one step: the document does not grow here, and an\n // item that only knows how to append would replace itself instead.\n const reasoned: ContentBlock.Any = { _tag: 'reasoning', reasoningText: reasoning };\n yield [reasoned];\n await pause();\n\n // A run arrives one call at a time, each unanswered until its result lands — the state a panel\n // reports as \"in flight\" before it can report a count.\n const names = ['search', 'read_document', 'write_document', 'summarize'];\n const tools: ContentBlock.Any[] = [];\n for (let index = 0; index < calls; index++) {\n const toolCallId = `tool-${random.number.int({ min: 1000, max: 9999 })}`;\n const name = names[index % names.length];\n const call: ContentBlock.Any = {\n _tag: 'toolCall',\n toolCallId,\n name,\n input: JSON.stringify({ query: random.lorem.word() }),\n providerExecuted: false,\n pending: true,\n };\n yield [reasoned, ...tools, call];\n await pause();\n\n tools.push({ ...call, pending: false });\n tools.push(\n failAt === index + 1\n ? {\n _tag: 'toolResult',\n toolCallId,\n name,\n error: 'ENOENT: no such file or directory',\n providerExecuted: false,\n }\n : {\n _tag: 'toolResult',\n toolCallId,\n name,\n result: random.lorem.sentence(12),\n providerExecuted: false,\n },\n );\n yield [reasoned, ...tools];\n await pause();\n }\n\n let text = '';\n for await (const chunk of textStream(createAnswer(), { chunkDelay, wordsPerChunk })) {\n text += chunk;\n yield [reasoned, ...tools, { _tag: 'text', text, pending: true }];\n }\n\n // Suggestions arrive only once the answer is complete, so the last step both closes the text and\n // extends the message — the two kinds of change at once.\n yield [\n reasoned,\n ...tools,\n { _tag: 'text', text },\n { _tag: 'suggestion', text: 'Tell me more' },\n { _tag: 'suggestion', text: 'Show the sources' },\n ];\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { random } from '@dxos/random';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Panel from '@dxos/react-ui/Panel';\nimport * as Toolbar from '@dxos/react-ui/Toolbar';\nimport * as Util from '@dxos/react-ui/Util';\nimport { Message } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\n\nimport {\n type MessageChromeProps,\n MessageList,\n Outline,\n type OutlineMarker,\n useMessageList,\n} from '../components/index.ts';\nimport { type Decoration, DecorationsProvider, ItemSelectionProvider } from '../hooks/index.ts';\nimport { SearchHit, defaultRenderer, isPrompt, searchFeed, sliceFeed, useFeedModel } from '../model/index.ts';\nimport { FeedStats, useFeedDebug } from './debug/index.ts';\nimport { createMessages } from './generator.ts';\nimport { type FeedScenario, createScenario } from './scenarios.tsx';\nimport { createAnswer, textStream } from './stream.ts';\nimport { streamTurn } from './turn.ts';\n\n/** Pause between one answer finishing and the next question arriving. */\nconst TURN_DELAY = 800;\n\n/** A turn that is only a growing text tail: what every scenario but the assistant chat produces. */\nasync function* textTurn(options: { wordsPerChunk: number; chunkDelay: number }) {\n let text = '';\n for await (const chunk of textStream(createAnswer(), options)) {\n text += chunk;\n yield [{ _tag: 'text' as const, text, pending: true }];\n }\n yield [{ _tag: 'text' as const, text }];\n}\n\nconst makeQuestion = () =>\n Message.make({\n sender: { role: 'user', name: 'Alice' },\n blocks: [{ _tag: 'text', text: random.lorem.sentence(8) }],\n });\n\nconst makeAnswer = () =>\n Message.make({\n sender: { role: 'assistant', name: 'Assistant' },\n blocks: [{ _tag: 'text', text: '' }],\n });\n\n//\n// Chrome\n//\n\n/**\n * Per-message chrome, supplied by the host rather than the engine — the thing a single thread-wide\n * document cannot do without injecting widgets into its own markdown.\n */\nconst TestChrome = ({ message, index, selected, onSelect, children }: MessageChromeProps) => {\n const role = message.sender.role ?? 'user';\n const time = new Date(message.created).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n\n return (\n <div\n className={mx('group relative grid grid-cols-[2rem_1fr] gap-2 px-2 py-2', selected && 'bg-hover-surface')}\n data-testid='feed.message'\n >\n <div className='flex flex-col items-center gap-1'>\n <Field.Root>\n <Input.Checkbox\n checked={selected}\n onCheckedChange={() => onSelect(message.id, true)}\n data-testid='feed.message.select'\n />\n </Field.Root>\n </div>\n\n {/*\n Fork / rewind / reply — chrome, not content. Positioned out of flow and toggled by opacity:\n anything that changes a row's height on hover re-triggers the virtualizer's measurement, and\n a pointer moving down the list during a scroll then shifts every row below it.\n */}\n <div className='absolute right-1 top-1 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100'>\n <Button.Root icon='ph--git-branch--regular' iconOnly label='Fork' variant='ghost' iconSize='xs' />\n <Button.Root\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Rewind'\n variant='ghost'\n iconSize='xs'\n />\n <Button.Root icon='ph--arrow-bend-up-left--regular' iconOnly label='Reply' variant='ghost' iconSize='xs' />\n </div>\n\n <div className='min-w-0'>\n <div className='flex items-center gap-2 text-xs text-fg-muted'>\n <span className='font-medium'>{message.sender.name ?? role}</span>\n <span>{time}</span>\n <span className='text-fg-subtle'>#{index}</span>\n </div>\n {children}\n </div>\n </div>\n );\n};\n\n//\n// Story\n//\n\nexport type FeedStoryProps = {\n /**\n * Which downstream call site to approximate. Each supplies its own renderer, chrome and follow\n * behaviour; everything else — the list, virtualization, selection, search — is the same engine.\n * Omitted, the story runs the synthetic mixed feed the measurements were taken against.\n */\n scenario?: FeedScenario;\n /** Messages of seeded history; 0 starts empty. */\n count?: number;\n /** Outline every item and every block inside it; the toolbar toggles it too. */\n debug?: boolean;\n /** Begin streaming turns on mount. */\n streaming?: boolean;\n wordsPerChunk?: number;\n chunkDelay?: number;\n estimateSize?: number;\n /** Blank lines kept below the last row, part of the resting view. */\n tailLines?: number;\n};\n\n/**\n * The one renderer every `MessageList` story uses; the stories differ only by args.\n *\n * Seeded history, a search over the model, message-set selection, and a play control that streams\n * turns into the tail until stopped — one harness, so a story is a set of arguments rather than\n * another component to keep in step.\n */\nexport const FeedStory = ({\n scenario,\n tailLines,\n count = 0,\n debug: debugProp,\n streaming: streamingProp = false,\n wordsPerChunk = 4,\n chunkDelay = 120,\n estimateSize,\n}: FeedStoryProps) => {\n const definition = useMemo(() => (scenario ? createScenario({ scenario, count }) : undefined), [scenario, count]);\n const [messages, setMessages] = useState<Message.Message[]>(() => definition?.messages ?? createMessages({ count }));\n const [streaming, setStreaming] = useState(streamingProp);\n const [answerId, setAnswerId] = useState<string | undefined>();\n const [selectedIds, setSelectedIds] = useState<ReadonlySet<string>>(new Set());\n const [copied, setCopied] = useState<string | null>(null);\n const [query, setQuery] = useState('');\n\n const hits = useMemo(() => searchFeed(messages, defaultRenderer, query), [messages, query]);\n\n // Search is a decoration producer, not a list prop (SPEC §Aspects): the ranges reach the items\n // through the provider below, and the list neither knows nor routes them.\n const decorations = useMemo<Decoration[]>(\n () => hits.map(({ messageId, offset, length }) => ({ id: messageId, range: { offset, length }, kind: 'search' })),\n [hits],\n );\n\n // The model owns identity, stops and streaming; the array survives as this adapter (SPEC F-7.3).\n const isAnchor = definition?.isAnchor;\n const model = useFeedModel(messages, { stops: isAnchor ?? 'message' });\n\n // Instrumentation as an aspect: the meter, the sweep, the outlines and the pass label live in\n // `useFeedDebug` so this component stays about the feed.\n const { debug, toggleDebug, meter, viewportRef, sweeping, onSweep } = useFeedDebug({\n scenario,\n count,\n estimateSize,\n streaming,\n enabled: debugProp,\n });\n\n const handleAppend = useCallback(() => setMessages((prev) => [...prev, makeQuestion()]), []);\n\n const handleReset = useCallback(() => {\n setStreaming(false);\n setAnswerId(undefined);\n setMessages(definition?.messages ?? createMessages({ count }));\n }, [count, definition]);\n\n const handleCopy = useCallback(() => {\n // Copy across message boundaries is reconstructed from the model: unmounted items are not in the\n // DOM, so a native selection could never have included them.\n if (messages.length < 3) {\n return;\n }\n\n setCopied(\n sliceFeed(messages, defaultRenderer, {\n from: { messageId: messages[0].id, offset: 0 },\n to: { messageId: messages[2].id, offset: 20 },\n }),\n );\n }, [messages]);\n\n // Turns keep arriving until stopped: an unbounded conversation is what the list has to survive,\n // and the only way to watch the follow behave across more than one answer.\n useEffect(() => {\n if (!streaming) {\n return;\n }\n\n let cancelled = false;\n void (async () => {\n while (!cancelled) {\n const answer = makeAnswer();\n setMessages((prev) => [...prev, makeQuestion(), answer]);\n setAnswerId(answer.id);\n\n // A chat turn arrives as blocks — status, reasoning, a tool call, then the answer — which is\n // what makes reconciliation hard: the document shrinks and rewrites as well as grows. Every\n // other scenario streams a plain text tail, the easy half.\n const turn =\n scenario === 'assistant'\n ? streamTurn({ wordsPerChunk, chunkDelay })\n : textTurn({ wordsPerChunk, chunkDelay });\n\n for await (const blocks of turn) {\n if (cancelled) {\n return;\n }\n setMessages((prev) =>\n prev.map((message) => (message.id === answer.id ? ({ ...message, blocks } as Message.Message) : message)),\n );\n }\n\n setAnswerId(undefined);\n await new Promise((resolve) => setTimeout(resolve, TURN_DELAY));\n }\n })();\n\n return () => {\n cancelled = true;\n // A turn interrupted before its first chunk leaves an empty answer behind, which renders as a\n // tall blank row. Strict mode runs this teardown on mount, so it is the common case.\n setMessages((prev) => {\n const last = prev.at(-1);\n return last && last.sender.role === 'assistant' && !Message.extractText(last).length ? prev.slice(0, -1) : prev;\n });\n };\n }, [streaming, wordsPerChunk, chunkDelay, scenario]);\n\n const onSelect = useCallback((id: string, additive: boolean) => {\n setSelectedIds((current) => {\n const next = new Set(additive ? current : []);\n if (additive && next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n }, []);\n\n return (\n // Root is headless, so the toolbar and statusbar sit outside the scroll container and still read\n // the list's state through `useMessageList`. The providers around it are the aspect pattern:\n // selection and decorations reach the items by id, and the list never sees either.\n <DecorationsProvider decorations={decorations}>\n <ItemSelectionProvider selectedIds={selectedIds} onSelect={onSelect}>\n <MessageList.Root\n model={model}\n renderer={definition?.renderer}\n debug={debug}\n Chrome={definition?.Chrome ?? TestChrome}\n Custom={definition?.Custom}\n registry={definition?.registry}\n estimateSize={estimateSize ?? definition?.estimateSize}\n stickyBottom={definition?.stickyBottom ?? true}\n tailLines={tailLines}\n >\n <Panel.Root>\n <Panel.Header>\n <Toolbar.Root>\n <Button.Root\n icon={streaming ? 'ph--stop--regular' : 'ph--play--regular'}\n iconOnly\n label={streaming ? 'Stop' : 'Start'}\n data-testid='feed.stream.toggle'\n onClick={() => setStreaming((value) => !value)}\n />\n <Button.Root\n icon='ph--plus--regular'\n iconOnly\n label='Add message'\n data-testid='feed.stream.append'\n onClick={handleAppend}\n />\n <Button.Root icon='ph--trash--regular' iconOnly label='Reset' onClick={handleReset} />\n <Button.Root\n icon={debug ? 'ph--bounding-box--fill' : 'ph--bounding-box--regular'}\n iconOnly\n label={debug ? 'Hide block outlines' : 'Show block outlines'}\n data-testid='feed.debug.toggle'\n onClick={toggleDebug}\n />\n <Toolbar.Separator />\n <Field.Root>\n <Input.Root\n placeholder='Search…'\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n data-testid='feed.search'\n />\n </Field.Root>\n <FindButton hits={hits} />\n <Button.Root icon='ph--copy--regular' iconOnly label='Copy range' onClick={handleCopy} />\n <Button.Root\n icon={sweeping ? 'ph--stop--regular' : 'ph--arrows-down-up--regular'}\n iconOnly\n label={sweeping ? 'Stop sweep' : 'Sweep (measure a pass)'}\n data-testid='feed.sweep'\n onClick={onSweep}\n />\n <div className='grow' />\n <MessageList.Nav classNames='contents' />\n </Toolbar.Root>\n </Panel.Header>\n\n <Panel.Body classNames='relative'>\n <div className='z-10 absolute left-0 top-0 bottom-0 grid grid-rows-[1fr_4fr_1fr] justify-center'>\n <FeedOutline classNames='row-start-2' messages={messages} />\n </div>\n <MessageList.Viewport classNames='dx-cover' ref={viewportRef} />\n </Panel.Body>\n </Panel.Root>\n <FeedStats meter={meter} streaming={streaming} selected={selectedIds.size} hits={hits.length} />\n </MessageList.Root>\n </ItemSelectionProvider>\n </DecorationsProvider>\n );\n};\n\n/**\n * The outline of a feed: one tick per prompt, in message-index space.\n *\n * `OutlineMarker.range` is \"the document's own position space\", which for a feed is the message\n * index — so a marker is `[index, index + 1)` and the visible range is the mounted window. No\n * pixel offsets are involved, which is what makes this survive rows whose heights are still\n * estimates.\n */\nconst FeedOutline = ({ classNames, messages }: Util.ThemedClassName<{ messages: readonly Message.Message[] }>) => {\n const { currentIndex, scrollToIndex } = useMessageList('FeedOutline');\n\n const markers = useMemo<OutlineMarker[]>(() => {\n // Every prompt is passed; the rail thins them to whatever its height affords.\n return messages\n .map((message, index) => ({ message, index }))\n .filter(({ message }) => isPrompt(message))\n .map(({ message, index }) => ({\n id: message.id,\n title: `#${index} · ${message.sender.name ?? message.sender.role}`,\n // Tags stripped: an html-kind message's text is markup, which reads as noise in a tooltip.\n description: Message.extractText(message)\n .replace(/<[^>]*>/g, ' ')\n .split('\\n')\n .map((line) => line.trim())\n .find(Boolean)\n ?.slice(0, 160),\n // A tick marks one message, not a span: which one is current is decided below, so the range\n // stays honest about what the tick points at.\n range: { from: index, to: index + 1 },\n }));\n }, [messages]);\n\n // Exactly one tick reads as current — the last prompt at or before the cursor. Passing the\n // mounted window instead would light up whichever ticks happen to fall inside it: none for most\n // of a sampled rail, two when the window straddles a boundary.\n const current = useMemo(() => {\n const marker = [...markers].reverse().find(({ range: { from } }) => from <= currentIndex) ?? markers[0];\n return marker?.range;\n }, [markers, currentIndex]);\n\n const handleSelect = useCallback(\n (marker: OutlineMarker) => scrollToIndex(marker.range.from, { align: 'start', behavior: 'smooth' }),\n [scrollToIndex],\n );\n\n if (!markers.length) {\n return <div />;\n }\n\n return (\n <Outline\n classNames={['justify-self-center', classNames]}\n markers={markers}\n visibleRange={current}\n onSelect={handleSelect}\n />\n );\n};\n\n/** Scroll-to-hit lives in the toolbar, outside the viewport — the reason Root is headless. */\nconst FindButton = ({ hits }: { hits: readonly SearchHit[] }) => {\n const { scrollToIndex } = useMessageList('FindButton');\n const handleFind = useCallback(() => {\n const hit = hits[0];\n if (hit) {\n scrollToIndex(hit.index, { align: 'start', behavior: 'smooth' });\n }\n }, [hits, scrollToIndex]);\n\n return <Button.Root icon='ph--magnifying-glass--regular' iconOnly label='Find' onClick={handleFind} />;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type ComponentType, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Window, type WindowController, type WindowState, useListModel } from '@dxos/react-ui-virtual';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport { type Message } from '@dxos/types';\n\nimport { HtmlBlock, MarkdownBlock, type MessageChromeProps } from '../components/index.ts';\nimport { type MessageRenderer, defaultRenderer } from '../model/index.ts';\n\n/**\n * A feed of real messages placed by `Window`, and nothing else.\n *\n * The bridge between the placement layer and the thing it has to replace. `placement/*` proves the\n * shape against boxes that cannot lie about their size; `baseline/*` proves the current engine\n * against real editors. This is where those meet: the same messages, the same renderer and the same\n * chrome as a feed, but placed by the new module — so the baseline invariants can be pointed at it\n * and answer whether the replacement is ready, rather than the question being settled by argument.\n *\n * Deliberately *not* wired into `MessageList.Root`. Root owns the follow, the sticky tail, the\n * anchors and the cursor as well as the virtualizer, so swapping the placement inside it is not a\n * swap but a reimplementation — and doing that before this exists would leave nothing to check it\n * against.\n */\nexport type MessageWindowProps = {\n messages: Message.Message[];\n renderer?: MessageRenderer;\n Chrome?: ComponentType<MessageChromeProps>;\n Custom?: ComponentType<{ content: any; message: Message.Message }>;\n /** Extent of a row before it has been measured; a function where rows differ widely. */\n estimateSize?: number | ((message: Message.Message, index: number) => number);\n gutter?: 'sm' | 'md' | 'lg';\n /** Keep the last message against the bottom as content arrives — a chat's standing intent. */\n sticky?: boolean;\n /**\n * Empty extent after the last row, so it can be brought to the top of the viewport.\n *\n * A number, computed by the host from its own container. Not a `scrollPastEnd` mode: with the\n * window placed rather than the rows, the reserve is a term in the sizer and nothing else in the\n * engine knows about it (§7) — which is what the old engine could not say, since its reserve was\n * a DOM spacer outside the virtualizer's coordinate space.\n */\n reserve?: number;\n onChange?: (state: WindowState) => void;\n controllerRef?: React.Ref<WindowController>;\n};\n\nconst DefaultChrome = ({ children }: MessageChromeProps) => <>{children}</>;\n\nexport const MessageWindow = ({\n messages,\n renderer = defaultRenderer,\n Chrome = DefaultChrome,\n Custom,\n estimateSize = 120,\n gutter,\n sticky,\n reserve,\n onChange,\n controllerRef,\n}: MessageWindowProps) => {\n const [selectedIds, setSelectedIds] = useState(new Set<string>());\n const onSelect = useCallback((id: string) => setSelectedIds((current) => new Set(current).add(id)), []);\n\n const model = useListModel(messages, (message: Message.Message) => message.id);\n const extents = useMemo(\n () => ({\n of: (index: number) => {\n const message = messages[index];\n if (!message) {\n return typeof estimateSize === 'number' ? estimateSize : 120;\n }\n\n return typeof estimateSize === 'function' ? estimateSize(message, index) : estimateSize;\n },\n }),\n [messages, estimateSize],\n );\n\n return (\n <Window\n classNames='h-full'\n model={model}\n extents={extents}\n sticky={sticky}\n reserve={reserve}\n onChange={onChange}\n controllerRef={controllerRef}\n >\n {(index) => {\n const message = messages[index];\n if (!message) {\n return null;\n }\n\n return (\n <Layout.Container gutter={gutter ?? 'lg'}>\n <Chrome message={message} index={index} selected={selectedIds.has(message.id)} onSelect={onSelect}>\n <Item content={renderer(message)} message={message} Custom={Custom} />\n </Chrome>\n </Layout.Container>\n );\n }}\n </Window>\n );\n};\n\n/**\n * The item's content, resolved without the list.\n *\n * `MessageList.Item` reads the renderer, the registry, the hits and the widget census from\n * `MessageList.Root`'s context, so it cannot be rendered outside the very thing this exists to\n * replace. Repeated here rather than refactored: making the item take its dependencies as props is\n * the right change and belongs in the retrofit, not in a harness written to measure it.\n */\nconst Item = ({\n content,\n message,\n Custom,\n}: {\n content: ReturnType<MessageRenderer>;\n message: Message.Message;\n Custom?: ComponentType<{ content: any; message: Message.Message }>;\n}) => (\n <>\n {content.kind === 'markdown' && <MarkdownBlock text={content.text} />}\n {content.kind === 'html' && <HtmlBlock html={content.html} />}\n {content.kind === 'custom' && Custom && <Custom content={content} message={message} />}\n </>\n);\n\n/** Kept for the stories that drive it; the ref is the same handle `placement/*` uses. */\nexport const useMessageWindow = () => useRef<WindowController>(null);\n"],"mappings":"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"}