@dxos/react-ui-feed 0.11.1
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.
- package/LICENSE +105 -0
- package/dist/lib/chunk-src.mjs +2228 -0
- package/dist/lib/chunk-src.mjs.map +1 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/testing.mjs +1923 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/components/Block/HtmlBlock.d.ts +14 -0
- package/dist/types/src/components/Block/HtmlBlock.d.ts.map +1 -0
- package/dist/types/src/components/Block/MarkdownBlock.d.ts +24 -0
- package/dist/types/src/components/Block/MarkdownBlock.d.ts.map +1 -0
- package/dist/types/src/components/Block/block.stories.d.ts +21 -0
- package/dist/types/src/components/Block/block.stories.d.ts.map +1 -0
- package/dist/types/src/components/Block/construction.d.ts +36 -0
- package/dist/types/src/components/Block/construction.d.ts.map +1 -0
- package/dist/types/src/components/Block/extensions.d.ts +23 -0
- package/dist/types/src/components/Block/extensions.d.ts.map +1 -0
- package/dist/types/src/components/Block/highlight.d.ts +14 -0
- package/dist/types/src/components/Block/highlight.d.ts.map +1 -0
- package/dist/types/src/components/Block/index.d.ts +6 -0
- package/dist/types/src/components/Block/index.d.ts.map +1 -0
- package/dist/types/src/components/Block/selection-group.d.ts +21 -0
- package/dist/types/src/components/Block/selection-group.d.ts.map +1 -0
- package/dist/types/src/components/Block/widget-state.d.ts +33 -0
- package/dist/types/src/components/Block/widget-state.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/MessageList.d.ts +129 -0
- package/dist/types/src/components/MessageList/MessageList.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/MessageList.stories.d.ts +47 -0
- package/dist/types/src/components/MessageList/MessageList.stories.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/index.d.ts +3 -0
- package/dist/types/src/components/MessageList/index.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/position-log.d.ts +73 -0
- package/dist/types/src/components/MessageList/position-log.d.ts.map +1 -0
- package/dist/types/src/components/Minimap/Minimap.d.ts +28 -0
- package/dist/types/src/components/Minimap/Minimap.d.ts.map +1 -0
- package/dist/types/src/components/Minimap/Minimap.stories.d.ts +21 -0
- package/dist/types/src/components/Minimap/Minimap.stories.d.ts.map +1 -0
- package/dist/types/src/components/Minimap/index.d.ts +2 -0
- package/dist/types/src/components/Minimap/index.d.ts.map +1 -0
- package/dist/types/src/components/Outline/Outline.d.ts +46 -0
- package/dist/types/src/components/Outline/Outline.d.ts.map +1 -0
- package/dist/types/src/components/Outline/Outline.stories.d.ts +37 -0
- package/dist/types/src/components/Outline/Outline.stories.d.ts.map +1 -0
- package/dist/types/src/components/Outline/index.d.ts +2 -0
- package/dist/types/src/components/Outline/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +5 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/debug/Debug.d.ts +17 -0
- package/dist/types/src/debug/Debug.d.ts.map +1 -0
- package/dist/types/src/debug/Stats.d.ts +30 -0
- package/dist/types/src/debug/Stats.d.ts.map +1 -0
- package/dist/types/src/debug/debug-model.d.ts +32 -0
- package/dist/types/src/debug/debug-model.d.ts.map +1 -0
- package/dist/types/src/debug/debug-model.test.d.ts +2 -0
- package/dist/types/src/debug/debug-model.test.d.ts.map +1 -0
- package/dist/types/src/debug/frame-meter.d.ts +48 -0
- package/dist/types/src/debug/frame-meter.d.ts.map +1 -0
- package/dist/types/src/debug/frame-meter.test.d.ts +2 -0
- package/dist/types/src/debug/frame-meter.test.d.ts.map +1 -0
- package/dist/types/src/debug/index.d.ts +6 -0
- package/dist/types/src/debug/index.d.ts.map +1 -0
- package/dist/types/src/debug/use-debug.d.ts +19 -0
- package/dist/types/src/debug/use-debug.d.ts.map +1 -0
- package/dist/types/src/hooks/follow.d.ts +89 -0
- package/dist/types/src/hooks/follow.d.ts.map +1 -0
- package/dist/types/src/hooks/follow.stories.d.ts +25 -0
- package/dist/types/src/hooks/follow.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/follow.test.d.ts +2 -0
- package/dist/types/src/hooks/follow.test.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +6 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useDecorations.d.ts +31 -0
- package/dist/types/src/hooks/useDecorations.d.ts.map +1 -0
- package/dist/types/src/hooks/useFeedNavigation.d.ts +44 -0
- package/dist/types/src/hooks/useFeedNavigation.d.ts.map +1 -0
- package/dist/types/src/hooks/useFollow.d.ts +46 -0
- package/dist/types/src/hooks/useFollow.d.ts.map +1 -0
- package/dist/types/src/hooks/useItemSelection.d.ts +34 -0
- package/dist/types/src/hooks/useItemSelection.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/model/feed-model.d.ts +67 -0
- package/dist/types/src/model/feed-model.d.ts.map +1 -0
- package/dist/types/src/model/feed-model.test.d.ts +2 -0
- package/dist/types/src/model/feed-model.test.d.ts.map +1 -0
- package/dist/types/src/model/feed.d.ts +66 -0
- package/dist/types/src/model/feed.d.ts.map +1 -0
- package/dist/types/src/model/feed.test.d.ts +2 -0
- package/dist/types/src/model/feed.test.d.ts.map +1 -0
- package/dist/types/src/model/index.d.ts +5 -0
- package/dist/types/src/model/index.d.ts.map +1 -0
- package/dist/types/src/model/list-model.d.ts +61 -0
- package/dist/types/src/model/list-model.d.ts.map +1 -0
- package/dist/types/src/model/list-model.test.d.ts +2 -0
- package/dist/types/src/model/list-model.test.d.ts.map +1 -0
- package/dist/types/src/model/useListModel.d.ts +22 -0
- package/dist/types/src/model/useListModel.d.ts.map +1 -0
- package/dist/types/src/stories/assistant.stories.d.ts +38 -0
- package/dist/types/src/stories/assistant.stories.d.ts.map +1 -0
- package/dist/types/src/stories/baseline.stories.d.ts +32 -0
- package/dist/types/src/stories/baseline.stories.d.ts.map +1 -0
- package/dist/types/src/stories/bridge.stories.d.ts +72 -0
- package/dist/types/src/stories/bridge.stories.d.ts.map +1 -0
- package/dist/types/src/stories/construction.stories.d.ts +17 -0
- package/dist/types/src/stories/construction.stories.d.ts.map +1 -0
- package/dist/types/src/stories/fill.stories.d.ts +51 -0
- package/dist/types/src/stories/fill.stories.d.ts.map +1 -0
- package/dist/types/src/stories/horizontal.stories.d.ts +17 -0
- package/dist/types/src/stories/horizontal.stories.d.ts.map +1 -0
- package/dist/types/src/stories/mount.stories.d.ts +19 -0
- package/dist/types/src/stories/mount.stories.d.ts.map +1 -0
- package/dist/types/src/stories/navigation.stories.d.ts +33 -0
- package/dist/types/src/stories/navigation.stories.d.ts.map +1 -0
- package/dist/types/src/stories/streaming.stories.d.ts +17 -0
- package/dist/types/src/stories/streaming.stories.d.ts.map +1 -0
- package/dist/types/src/stories/tail.stories.d.ts +47 -0
- package/dist/types/src/stories/tail.stories.d.ts.map +1 -0
- package/dist/types/src/stories/widget.stories.d.ts +16 -0
- package/dist/types/src/stories/widget.stories.d.ts.map +1 -0
- package/dist/types/src/testing/FeedStory.d.ts +30 -0
- package/dist/types/src/testing/FeedStory.d.ts.map +1 -0
- package/dist/types/src/testing/MessageWindow.d.ts +48 -0
- package/dist/types/src/testing/MessageWindow.d.ts.map +1 -0
- package/dist/types/src/testing/controls.d.ts +46 -0
- package/dist/types/src/testing/controls.d.ts.map +1 -0
- package/dist/types/src/testing/debug/FeedStats.d.ts +18 -0
- package/dist/types/src/testing/debug/FeedStats.d.ts.map +1 -0
- package/dist/types/src/testing/debug/index.d.ts +4 -0
- package/dist/types/src/testing/debug/index.d.ts.map +1 -0
- package/dist/types/src/testing/debug/sweep.d.ts +18 -0
- package/dist/types/src/testing/debug/sweep.d.ts.map +1 -0
- package/dist/types/src/testing/debug/use-feed-debug.d.ts +33 -0
- package/dist/types/src/testing/debug/use-feed-debug.d.ts.map +1 -0
- package/dist/types/src/testing/generator.d.ts +16 -0
- package/dist/types/src/testing/generator.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +10 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/scenarios.d.ts +58 -0
- package/dist/types/src/testing/scenarios.d.ts.map +1 -0
- package/dist/types/src/testing/stream.d.ts +22 -0
- package/dist/types/src/testing/stream.d.ts.map +1 -0
- package/dist/types/src/testing/turn.d.ts +21 -0
- package/dist/types/src/testing/turn.d.ts.map +1 -0
- package/dist/types/src/testing/widgets.d.ts +3 -0
- package/dist/types/src/testing/widgets.d.ts.map +1 -0
- package/dist/types/src/virtualizer/Window.d.ts +137 -0
- package/dist/types/src/virtualizer/Window.d.ts.map +1 -0
- package/dist/types/src/virtualizer/Window.stories.d.ts +112 -0
- package/dist/types/src/virtualizer/Window.stories.d.ts.map +1 -0
- package/dist/types/src/virtualizer/index.d.ts +3 -0
- package/dist/types/src/virtualizer/index.d.ts.map +1 -0
- package/dist/types/src/virtualizer/placement.d.ts +188 -0
- package/dist/types/src/virtualizer/placement.d.ts.map +1 -0
- package/dist/types/src/virtualizer/placement.test.d.ts +2 -0
- package/dist/types/src/virtualizer/placement.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
- package/src/components/Block/HtmlBlock.tsx +26 -0
- package/src/components/Block/MarkdownBlock.tsx +157 -0
- package/src/components/Block/block.stories.tsx +140 -0
- package/src/components/Block/construction.ts +77 -0
- package/src/components/Block/extensions.ts +95 -0
- package/src/components/Block/highlight.ts +53 -0
- package/src/components/Block/index.ts +10 -0
- package/src/components/Block/selection-group.ts +53 -0
- package/src/components/Block/widget-state.tsx +75 -0
- package/src/components/MessageList/MessageList.stories.tsx +111 -0
- package/src/components/MessageList/MessageList.tsx +647 -0
- package/src/components/MessageList/index.ts +6 -0
- package/src/components/MessageList/position-log.ts +167 -0
- package/src/components/Minimap/Minimap.stories.tsx +129 -0
- package/src/components/Minimap/Minimap.tsx +84 -0
- package/src/components/Minimap/index.ts +5 -0
- package/src/components/Outline/Outline.stories.tsx +196 -0
- package/src/components/Outline/Outline.tsx +383 -0
- package/src/components/Outline/index.ts +5 -0
- package/src/components/index.ts +8 -0
- package/src/debug/Debug.tsx +90 -0
- package/src/debug/Stats.tsx +57 -0
- package/src/debug/debug-model.test.ts +36 -0
- package/src/debug/debug-model.ts +66 -0
- package/src/debug/frame-meter.test.ts +49 -0
- package/src/debug/frame-meter.ts +188 -0
- package/src/debug/index.ts +9 -0
- package/src/debug/use-debug.tsx +41 -0
- package/src/hooks/follow.stories.tsx +200 -0
- package/src/hooks/follow.test.ts +105 -0
- package/src/hooks/follow.ts +241 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useDecorations.tsx +54 -0
- package/src/hooks/useFeedNavigation.ts +90 -0
- package/src/hooks/useFollow.ts +258 -0
- package/src/hooks/useItemSelection.tsx +53 -0
- package/src/index.ts +7 -0
- package/src/model/feed-model.test.ts +84 -0
- package/src/model/feed-model.ts +214 -0
- package/src/model/feed.test.ts +65 -0
- package/src/model/feed.ts +157 -0
- package/src/model/index.ts +8 -0
- package/src/model/list-model.test.ts +71 -0
- package/src/model/list-model.ts +152 -0
- package/src/model/useListModel.ts +53 -0
- package/src/stories/assistant.stories.tsx +425 -0
- package/src/stories/baseline.stories.tsx +78 -0
- package/src/stories/bridge.stories.tsx +376 -0
- package/src/stories/construction.stories.tsx +147 -0
- package/src/stories/fill.stories.tsx +271 -0
- package/src/stories/horizontal.stories.tsx +129 -0
- package/src/stories/mount.stories.tsx +233 -0
- package/src/stories/navigation.stories.tsx +145 -0
- package/src/stories/streaming.stories.tsx +132 -0
- package/src/stories/tail.stories.tsx +149 -0
- package/src/stories/widget.stories.tsx +107 -0
- package/src/testing/FeedStory.tsx +452 -0
- package/src/testing/MessageWindow.tsx +139 -0
- package/src/testing/controls.tsx +96 -0
- package/src/testing/debug/FeedStats.tsx +182 -0
- package/src/testing/debug/index.ts +16 -0
- package/src/testing/debug/sweep.ts +85 -0
- package/src/testing/debug/use-feed-debug.ts +91 -0
- package/src/testing/generator.ts +90 -0
- package/src/testing/index.ts +13 -0
- package/src/testing/scenarios.tsx +552 -0
- package/src/testing/stream.ts +70 -0
- package/src/testing/turn.ts +91 -0
- package/src/testing/widgets.tsx +183 -0
- package/src/virtualizer/Window.stories.tsx +683 -0
- package/src/virtualizer/Window.tsx +435 -0
- package/src/virtualizer/index.ts +6 -0
- package/src/virtualizer/placement.test.ts +312 -0
- package/src/virtualizer/placement.ts +403 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":[],"sources":["../../src/debug/Stats.tsx","../../src/debug/use-debug.tsx","../../src/debug/frame-meter.ts","../../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, { Fragment } from 'react';\n\nimport { type ThemedClassName } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\n/** Readouts are mostly counts, but a range (`463–499`) is one reading and reads as one cell. */\nexport type StatValue = number | string;\n\nexport type Stat = {\n id: string;\n label?: string;\n unit?: string;\n /** Emphasis for a value that means something is wrong; called with the current value. */\n classNames?: (value: StatValue) => string | false | undefined;\n};\n\nexport type StatsProps = ThemedClassName<{\n 'stats': Stat[];\n 'values': Record<string, StatValue>;\n /** Readouts side by side, for a panel that is wider than it is tall. @default 1 */\n 'columns'?: number;\n 'title'?: string;\n 'data-testid'?: string;\n}>;\n\n/**\n * A block of readouts, in fixed tracks.\n *\n * Every number here changes while the feed scrolls, so the value and unit columns are given widths\n * rather than sized to their contents: a column that fits itself to `4` and then to `1054` moves\n * every digit beside it, and an instrument that moves cannot be read at a glance.\n */\nexport const Stats = ({ stats, values, columns = 1, classNames, title, ...props }: StatsProps) => (\n <div\n {...props}\n title={title}\n className={mx('grid gap-x-1 tabular-nums whitespace-nowrap', classNames)}\n style={{ gridTemplateColumns: `repeat(${columns}, 1fr 5rem 1.5rem)` }}\n >\n {stats.map(({ id, label, unit, classNames }) => (\n <Fragment key={id}>\n <span className='text-subdued'>{label}</span>\n <span className={mx('text-right', classNames?.(values[id]))} data-testid={`feed.${id}`}>\n {values[id]}\n </span>\n <span className='text-subdued'>{unit ?? ''}</span>\n </Fragment>\n ))}\n </div>\n);\n\n/** Anything above zero is a defect, not a reading. */\nexport const warnAbove = (value: StatValue) => Number(value) > 0 && 'text-warning-text';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type PropsWithChildren, createContext, useContext, useEffect, useMemo } from 'react';\n\nimport { DebugModel, type DebugProbe } from './debug-model';\n\nconst DebugContext = createContext<DebugModel | undefined>(undefined);\n\nexport type DebugProviderProps = PropsWithChildren<{ model?: DebugModel }>;\n\n/**\n * Scope for probes. Anything under it may instrument itself; a `Debug` table under the same\n * provider renders whatever registered. No provider, no cost: the hooks below no-op.\n */\nexport const DebugProvider = ({ model: provided, children }: DebugProviderProps) => {\n const model = useMemo(() => provided ?? new DebugModel(), [provided]);\n return <DebugContext.Provider value={model}>{children}</DebugContext.Provider>;\n};\n\nexport const useDebugModel = (): DebugModel | undefined => useContext(DebugContext);\n\n/**\n * Register live readouts for as long as the component is mounted.\n *\n * The probe reads state through a closure, so the component never re-renders for the table's sake;\n * `deps` re-registers when the closure would go stale (a rebuilt model, a new element).\n */\nexport const useDebugProbes = (probes: () => DebugProbe[], deps: unknown[] = []): void => {\n const model = useDebugModel();\n useEffect(() => {\n if (!model) {\n return;\n }\n\n const unregister = probes().map((probe) => model.register(probe));\n return () => unregister.forEach((remove) => remove());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [model, ...deps]);\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/** How often the live readout is published. Per-frame state would itself be the thing dropping frames. */\nconst REPORT_INTERVAL = 250;\n\n/**\n * A frame longer than this counts as a hitch: two intervals of a 60Hz display, the point at which a\n * pause stops reading as motion and starts reading as a stutter. Deliberately absolute rather than\n * derived from the display's rate — the question is whether a reader notices, not whether the\n * hardware's budget was met.\n */\nconst HITCH_MS = 32;\n\n/** Frame durations are bucketed by the millisecond; anything slower than this lands in the last bucket. */\nconst MAX_BUCKET = 512;\n\n/** Hitches traced per pass. A pass that produces more than this has already failed. */\nconst MAX_TRACE = 100;\n\nexport type FrameStats = {\n /** Frames per second over the last reporting window — what the eye checks while a gesture runs. */\n fps: number;\n /** Rate at the median frame: the pass's typical smoothness. */\n p50: number;\n /** Rate at the 95th-percentile (slowest) frame: what the pass felt like at its worst fifth. */\n p95: number;\n /** Longest frame (ms) since the pass began. */\n worst: number;\n /** Frames over {@link HITCH_MS} since the pass began. */\n hitches: number;\n /** Frames sampled, so a reading of zero hitches can be weighed against how long it was measured. */\n frames: number;\n /** Length of the pass so far (ms). */\n duration: number;\n};\n\nconst EMPTY: FrameStats = { fps: 0, p50: 0, p95: 0, worst: 0, hitches: 0, frames: 0, duration: 0 };\n\n/**\n * Rate (fps) at the frame standing at `quantile` of a pass, counting from the fast end — so 0.95 is\n * the slow tail, the fifth of the pass a reader would call the stutter.\n *\n * `buckets` counts frames by their duration in whole milliseconds. Exported for its test: this is\n * the number the engine's verdict rests on, and an off-by-one in the walk would flatter it.\n */\nexport const rateAtQuantile = (buckets: Uint32Array, frames: number, quantile: number): number => {\n if (!frames) {\n return 0;\n }\n\n const target = frames * quantile;\n let seen = 0;\n for (let ms = 0; ms < buckets.length; ++ms) {\n seen += buckets[ms];\n if (seen >= target) {\n return ms > 0 ? Math.round(1000 / ms) : 0;\n }\n }\n\n return 0;\n};\n\nexport type FrameMeterOptions = {\n /** Names the pass in the recorded line — the story's arguments, since they are what varies. */\n label?: string;\n};\n\n/** Live statistics plus the control that ends one pass and begins the next. */\nexport type FrameMeter = FrameStats & { label?: string; record: () => string };\n\n/**\n * Samples animation-frame intervals and records a pass on demand.\n *\n * This exists because scroll smoothness is the deciding criterion for the feed engine and no\n * automated harness can report it: an agent's browser throttles `requestAnimationFrame`, so any\n * number collected there describes the harness. Averages hide the defect — a scroll that holds\n * 60fps and stalls once for 200ms reads as smooth in a mean and as broken to a reader — so the\n * percentiles, the worst frame and the hitch count are reported together, and each hitch is traced\n * to the console as it happens so a bad number can be turned into a cause.\n *\n * Durations are bucketed by the millisecond rather than kept, so a pass costs the same however long\n * it runs; percentiles come out of the histogram.\n */\nexport const useFrameMeter = ({ label }: FrameMeterOptions = {}): FrameMeter => {\n const [stats, setStats] = useState<FrameStats>(EMPTY);\n\n const buckets = useRef(new Uint32Array(MAX_BUCKET));\n const worst = useRef(0);\n const hitches = useRef(0);\n const frames = useRef(0);\n const started = useRef(performance.now());\n const labelRef = useRef(label);\n labelRef.current = label;\n\n const clear = useCallback(() => {\n buckets.current.fill(0);\n worst.current = 0;\n hitches.current = 0;\n frames.current = 0;\n started.current = performance.now();\n setStats(EMPTY);\n }, []);\n\n const rateAt = useCallback((quantile: number) => rateAtQuantile(buckets.current, frames.current, quantile), []);\n\n /**\n * Ends the pass: returns its one-line summary, copies it to the clipboard, logs it, and starts a\n * fresh pass. One control, because a pass is exactly the interval between two of these — start a\n * clean one by clicking, run the gesture, click again to record it.\n */\n const record = useCallback((): string => {\n const seconds = (performance.now() - started.current) / 1000;\n const summary = [\n labelRef.current ?? 'pass',\n `p50 ${rateAt(0.5)}`,\n `p95 ${rateAt(0.95)}`,\n `worst ${Math.round(worst.current)}ms`,\n `${hitches.current} hitches`,\n `${frames.current} frames`,\n `${seconds.toFixed(1)}s`,\n ].join(' · ');\n\n // eslint-disable-next-line no-console\n console.log('[feed] pass', summary);\n void navigator.clipboard?.writeText(summary).catch(() => {\n // Clipboard access is denied in some embedding contexts; the log above is the fallback.\n });\n\n clear();\n return summary;\n }, [rateAt, clear]);\n\n useEffect(() => {\n let handle = 0;\n let last = performance.now();\n let windowStart = last;\n let windowFrames = 0;\n let traced = 0;\n\n const tick = (now: number) => {\n const delta = now - last;\n last = now;\n frames.current++;\n windowFrames++;\n buckets.current[Math.min(Math.round(delta), MAX_BUCKET - 1)]++;\n if (delta > worst.current) {\n worst.current = delta;\n }\n if (delta > HITCH_MS) {\n hitches.current++;\n if (traced++ < MAX_TRACE) {\n // eslint-disable-next-line no-console\n console.debug('[feed] hitch', {\n ms: Math.round(delta),\n at: Math.round(now - started.current),\n label: labelRef.current,\n });\n }\n }\n\n const elapsed = now - windowStart;\n if (elapsed >= REPORT_INTERVAL) {\n setStats({\n fps: Math.round((windowFrames * 1000) / elapsed),\n p50: rateAt(0.5),\n p95: rateAt(0.95),\n worst: Math.round(worst.current),\n hitches: hitches.current,\n frames: frames.current,\n duration: Math.round(now - started.current),\n });\n windowStart = now;\n windowFrames = 0;\n }\n\n handle = requestAnimationFrame(tick);\n };\n\n handle = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(handle);\n }, [rateAt]);\n\n return { ...stats, label, record };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { IconButton, type ThemedClassName } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useMessageList } from '../../components';\nimport { type FrameMeter, type Stat, Stats, warnAbove } from '../../debug';\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 = 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-description',\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 <IconButton\n icon={recording ? 'ph--stop--regular' : 'ph--record--regular'}\n iconOnly\n label={recording ? 'End the pass' : 'Start a pass'}\n variant='ghost'\n size={3}\n data-testid='feed.debug.record'\n onClick={onRecord}\n />\n <IconButton\n icon='ph--arrow-counter-clockwise--regular'\n iconOnly\n label='Reset counters'\n variant='ghost'\n size={3}\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-subdued-separator 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';\nimport { sweepScroll } from './sweep';\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';\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 { Icon, IconBlock } from '@dxos/react-ui';\nimport { TogglePanel } from '@dxos/react-ui-components';\nimport { type XmlWidgetProps, type XmlWidgetRegistry, getXmlTextChild } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useWidgetState } from '../components';\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-subdued-separator', classNames)}>\n <TogglePanel.Header classNames='flex items-center gap-2 px-2 py-1 text-sm'>\n <span className='grow text-description truncate'>{title}</span>\n <IconBlock>\n <Icon icon={icon} size={4} />\n </IconBlock>\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 }: XmlWidgetProps<any> & { classNames?: string }) => (\n <div className={mx('flex gap-2 px-2 py-1 rounded border border-subdued-separator text-sm', classNames)}>\n {icon && <Icon icon={icon} size={4} classNames='mt-1 shrink-0 text-description' />}\n <div className='min-w-0'>\n {title && <p className='text-xs text-description'>{title}</p>}\n {children}\n </div>\n </div>\n);\n\nconst Reasoning = ({ children, range }: XmlWidgetProps) => {\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 }: XmlWidgetProps) => (\n <p className='px-2 py-1 text-sm text-description animate-pulse'>{getXmlTextChild(children ?? [])}</p>\n);\n\nconst ToolCall = ({ name, pending, range }: XmlWidgetProps<{ 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 }: XmlWidgetProps<{ 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 }: XmlWidgetProps) => (\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 }: XmlWidgetProps) => {\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 }: XmlWidgetProps) => (\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 { IconButton } from '@dxos/react-ui';\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 '../';\nimport {\n BareEditorItem,\n DecoratedEditorItem,\n MarkdownEditorItem,\n MarkdownProbeItem,\n TextItem,\n ThemedEditorItem,\n} from './controls';\nimport { chatRegistry } from './widgets';\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-description'>\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-subdued 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 */\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 <div className='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='ps-2 pe-2 pt-1 pb-1 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. */}\n <div className='flex items-center gap-1 pt-1 text-xs text-description opacity-0 transition-opacity group-hover:opacity-100'>\n <IconButton icon='ph--arrow-counter-clockwise--regular' iconOnly label='Rewind' variant='ghost' size={3} />\n <IconButton icon='ph--git-branch--regular' iconOnly label='Fork' variant='ghost' size={3} />\n <span>{timeOf(message)}</span>\n <span className='text-subdued'>#{index}</span>\n </div>\n </div>\n ) : (\n <div className='min-w-0'>\n {children}\n <div className='flex items-center gap-2 pt-1 text-xs text-description opacity-0 transition-opacity group-hover:opacity-100'>\n <span className='font-medium'>{message.sender.name}</span>\n <span>{timeOf(message)}</span>\n <span className='text-subdued'>#{index}</span>\n <span className='grow' />\n <IconButton icon='ph--arrow-bend-up-left--regular' iconOnly label='Reply' variant='ghost' size={3} />\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-description'>{message.sender.email}</span>\n <span className='grow' />\n <span className='text-xs text-description'>{new Date(message.created).toLocaleString()}</span>\n </div>\n <p className='text-xs text-description'>{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-description'>\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-subdued-separator', message.properties?.resolved && 'opacity-50')}>\n <p className='mb-1 ps-2 border-s-2 border-separator text-xs text-description line-clamp-1'>\n {String(message.properties?.anchor ?? '')}\n </p>\n <div className='flex items-center gap-2 text-xs text-description'>\n <span className='font-medium'>{message.sender.name}</span>\n <span>{timeOf(message)}</span>\n <span className='grow' />\n <IconButton\n icon={message.properties?.resolved ? 'ph--check-circle--regular' : 'ph--circle--regular'}\n iconOnly\n label='Resolve'\n variant='ghost'\n size={3}\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-description 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-description'>{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';\n\nexport type TurnOptions = {\n chunkDelay?: number;\n wordsPerChunk?: 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.\n *\n * Yields the complete block array at each step; the caller replaces the message's blocks with it.\n */\nexport async function* streamTurn({ chunkDelay = 120, wordsPerChunk = 4 }: TurnOptions = {}): AsyncGenerator<\n ContentBlock.Any[],\n void\n> {\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 const toolCallId = `tool-${random.number.int({ min: 1000, max: 9999 })}`;\n const call: ContentBlock.Any = {\n _tag: 'toolCall',\n toolCallId,\n name: 'search',\n input: '{}',\n providerExecuted: false,\n pending: true,\n };\n yield [reasoned, call];\n await pause();\n\n const called: ContentBlock.Any = { ...call, pending: false };\n const result: ContentBlock.Any = {\n _tag: 'toolResult',\n toolCallId,\n name: 'search',\n result: random.lorem.sentence(12),\n providerExecuted: false,\n };\n yield [reasoned, called, result];\n await pause();\n\n let text = '';\n for await (const chunk of textStream(createAnswer(), { chunkDelay, wordsPerChunk })) {\n text += chunk;\n yield [reasoned, called, result, { _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 called,\n result,\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 { IconButton, Input, Panel, ThemedClassName, Toolbar } from '@dxos/react-ui';\nimport { Message } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\n\nimport { Outline, type OutlineMarker } from '../components';\nimport { type MessageChromeProps, MessageList, useMessageList } from '../components';\nimport { type Decoration, DecorationsProvider, ItemSelectionProvider } from '../hooks';\nimport { SearchHit, defaultRenderer, searchFeed, sliceFeed, useFeedModel } from '../model';\nimport { FeedStats, useFeedDebug } from './debug';\nimport { createMessages } from './generator';\nimport { type FeedScenario, createScenario } from './scenarios';\nimport { createAnswer, textStream } from './stream';\nimport { streamTurn } from './turn';\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 <Input.Root>\n <Input.Checkbox\n checked={selected}\n onCheckedChange={() => onSelect(message.id, true)}\n data-testid='feed.message.select'\n />\n </Input.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 <IconButton icon='ph--git-branch--regular' iconOnly label='Fork' variant='ghost' size={3} />\n <IconButton icon='ph--arrow-counter-clockwise--regular' iconOnly label='Rewind' variant='ghost' size={3} />\n <IconButton icon='ph--arrow-bend-up-left--regular' iconOnly label='Reply' variant='ghost' size={3} />\n </div>\n\n <div className='min-w-0'>\n <div className='flex items-center gap-2 text-xs text-description'>\n <span className='font-medium'>{message.sender.name ?? role}</span>\n <span>{time}</span>\n <span className='text-subdued'>#{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 /** Reserve space below the last row so any message can be brought to the top. */\n scrollPastEnd?: boolean;\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 scrollPastEnd,\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 scrollPastEnd={scrollPastEnd}\n >\n <Panel.Root>\n <Panel.Toolbar asChild>\n <Toolbar.Root>\n <IconButton\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 <IconButton\n icon='ph--plus--regular'\n iconOnly\n label='Add message'\n data-testid='feed.stream.append'\n onClick={handleAppend}\n />\n <IconButton icon='ph--trash--regular' iconOnly label='Reset' onClick={handleReset} />\n <IconButton\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 <Input.Root>\n <Input.TextInput\n placeholder='Search…'\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n data-testid='feed.search'\n />\n </Input.Root>\n <FindButton hits={hits} />\n <IconButton icon='ph--copy--regular' iconOnly label='Copy range' onClick={handleCopy} />\n <IconButton\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 <NavButtons />\n </Toolbar.Root>\n </Panel.Toolbar>\n\n <Panel.Content classNames='relative dx-container'>\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='absolute inset-0' padding ref={viewportRef} />\n </Panel.Content>\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 }: 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 }) => message.sender.role === 'user')\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: 'center', behavior: 'smooth' });\n }\n }, [hits, scrollToIndex]);\n\n return <IconButton icon='ph--magnifying-glass--regular' iconOnly label='Find' onClick={handleFind} />;\n};\n\n/** Move by one message, or to either end, from the list's cursor (what the arrow keys also move). */\nconst NavButtons = () => {\n const { currentIndex: current, count, navigation, scrollToIndex, scrollToBottom } = useMessageList('NavButtons');\n\n // The same seam the arrow keys use (SPEC F-3.2), so a reader who switches between them does not\n // change places.\n const step = navigation.step;\n\n return (\n <>\n <IconButton\n icon='ph--arrow-line-up--regular'\n iconOnly\n label='First message'\n variant='ghost'\n disabled={current <= 0}\n data-testid='feed.nav.top'\n // Smooth is requested, but a jump across more than a few rows travels towards an estimated\n // offset, so `scrollToIndex` takes these instantly. Same call, honest either way.\n onClick={() => scrollToIndex(0, { align: 'start', behavior: 'smooth' })}\n />\n <IconButton\n icon='ph--caret-up--regular'\n iconOnly\n label='Previous message'\n variant='ghost'\n disabled={current <= 0}\n data-testid='feed.nav.back'\n onClick={() => step(-1)}\n />\n <IconButton\n icon='ph--caret-down--regular'\n iconOnly\n label='Next message'\n variant='ghost'\n disabled={current >= count - 1}\n data-testid='feed.nav.forward'\n onClick={() => step(1)}\n />\n <IconButton\n icon='ph--arrow-line-down--regular'\n iconOnly\n label='Last message'\n variant='ghost'\n disabled={count === 0}\n data-testid='feed.nav.bottom'\n onClick={() => scrollToBottom({ behavior: 'smooth' })}\n />\n </>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type ComponentType, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Column } from '@dxos/react-ui';\nimport { type Message } from '@dxos/types';\n\nimport { HtmlBlock, MarkdownBlock, type MessageChromeProps } from '../components';\nimport { useListModel } from '../model';\nimport { type MessageRenderer, defaultRenderer } from '../model';\nimport { Window, type WindowController, type WindowState } from '../virtualizer';\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 <Column.Root gutter={gutter}>\n <Column.Center>\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 </Column.Center>\n </Column.Root>\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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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type HtmlBlockProps = {
|
|
3
|
+
html: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* A message rendered as sanitized HTML — the email case, which is not markdown and cannot be a
|
|
7
|
+
* CodeMirror document.
|
|
8
|
+
*
|
|
9
|
+
* Its presence is the reason the item renderer is generalized rather than markdown-only: the
|
|
10
|
+
* engine owns virtualization, chrome and selection anchors, not what a message looks like inside.
|
|
11
|
+
* As plain (non-contenteditable) DOM it is also the control case for cross-item selection.
|
|
12
|
+
*/
|
|
13
|
+
export declare const HtmlBlock: React.MemoExoticComponent<({ html }: HtmlBlockProps) => React.JSX.Element>;
|
|
14
|
+
//# sourceMappingURL=HtmlBlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HtmlBlock.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/HtmlBlock.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,uCAAmB,cAAc,uBAIrD,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type XmlWidgetRegistry } from '@dxos/ui-editor';
|
|
3
|
+
import { type HighlightRange } from './highlight';
|
|
4
|
+
export type MarkdownBlockProps = {
|
|
5
|
+
text: string;
|
|
6
|
+
/**
|
|
7
|
+
* When false the view mounts with `EditorView.editable.of(false)`, which drops `contenteditable`
|
|
8
|
+
* from the DOM. Browsers refuse to extend one selection across two contenteditable hosts, so this
|
|
9
|
+
* is what decides whether a drag can span adjacent items.
|
|
10
|
+
*/
|
|
11
|
+
editable?: boolean;
|
|
12
|
+
registry?: XmlWidgetRegistry;
|
|
13
|
+
hits?: readonly HighlightRange[];
|
|
14
|
+
/** Number of block widgets this item currently has mounted; 0 once it unmounts. */
|
|
15
|
+
onWidgetsChange?: (count: number) => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* One message as its own markdown document.
|
|
19
|
+
*
|
|
20
|
+
* Each item owns a document, so streaming appends and per-message editing stay local — where a
|
|
21
|
+
* single thread-wide document needs a cursor and a range table to know which message it is touching.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MarkdownBlock: React.MemoExoticComponent<({ text, editable, registry, hits, onWidgetsChange }: MarkdownBlockProps) => React.JSX.Element>;
|
|
24
|
+
//# sourceMappingURL=MarkdownBlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownBlock.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/MarkdownBlock.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAsE,MAAM,OAAO,CAAC;AAI3F,OAAO,EAAE,KAAK,iBAAiB,EAAuB,MAAM,iBAAiB,CAAC;AAG9E,OAAO,EAAE,KAAK,cAAc,EAAiB,MAAM,aAAa,CAAC;AAGjE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,IAAI,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACjC,mFAAmF;IACnF,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,kFAAsE,kBAAkB,uBAsHhH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
2
|
+
/**
|
|
3
|
+
* One story per item kind, outside any list: the item is the unit the feed mounts per message, and
|
|
4
|
+
* this is where its rendering is judged on its own — a defect here is the item's, never the
|
|
5
|
+
* placement's. Kinds mirror `ItemContent`: markdown (a read-only CodeMirror document), markdown
|
|
6
|
+
* with block widgets (the assistant turn's shape), markdown with highlights (what decorations
|
|
7
|
+
* paint), and html (sanitized prose, the email case).
|
|
8
|
+
*/
|
|
9
|
+
type StoryArgs = {
|
|
10
|
+
kind: 'markdown' | 'widgets' | 'highlighted' | 'html';
|
|
11
|
+
};
|
|
12
|
+
declare const meta: Meta<StoryArgs>;
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<StoryArgs>;
|
|
15
|
+
/** Passive: flip `kind` in the controls. */
|
|
16
|
+
export declare const Default: Story;
|
|
17
|
+
export declare const Markdown: Story;
|
|
18
|
+
export declare const Widgets: Story;
|
|
19
|
+
export declare const Highlighted: Story;
|
|
20
|
+
export declare const Html: Story;
|
|
21
|
+
//# sourceMappingURL=block.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/block.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAYjE;;;;;;GAMG;AACH,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;CACvD,CAAC;AAwDF,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,CAQzB,CAAC;eAEa,IAAI;AAEnB,KAAK,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAEjC,4CAA4C;AAC5C,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC;AAEjC,eAAO,MAAM,QAAQ,EAAE,KAOtB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAYrB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAUzB,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAMlB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Extension } from '@codemirror/state';
|
|
2
|
+
/**
|
|
3
|
+
* What one item costs to build.
|
|
4
|
+
*
|
|
5
|
+
* The list's first fill mounts a viewport's worth of rows at once, so the wall-clock cost of that
|
|
6
|
+
* fill is the per-item construction cost times the number of rows on screen — which is why this is
|
|
7
|
+
* measured per extension set rather than per story: the answer has to say *what* to drop.
|
|
8
|
+
*
|
|
9
|
+
* Only meaningful in a real browser. Construction is dominated by style resolution and the forced
|
|
10
|
+
* layout that follows it, neither of which a DOM shim performs.
|
|
11
|
+
*/
|
|
12
|
+
export type ConstructionCase = {
|
|
13
|
+
name: string;
|
|
14
|
+
extensions: Extension[];
|
|
15
|
+
};
|
|
16
|
+
export type ConstructionResult = {
|
|
17
|
+
name: string;
|
|
18
|
+
/** Mean milliseconds to construct one view and force its layout. */
|
|
19
|
+
mean: number;
|
|
20
|
+
/** Slowest single construction in the run, which is what a dropped frame is made of. */
|
|
21
|
+
worst: number;
|
|
22
|
+
};
|
|
23
|
+
export type ConstructionProfileOptions = {
|
|
24
|
+
parent: HTMLElement;
|
|
25
|
+
cases: ConstructionCase[];
|
|
26
|
+
doc: string;
|
|
27
|
+
runs?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Times construction of `runs` views per case, including the layout each one forces.
|
|
31
|
+
*
|
|
32
|
+
* Views are destroyed after each case rather than at the end: a hundred live editors in one parent
|
|
33
|
+
* make every subsequent layout progressively more expensive, which would report the harness.
|
|
34
|
+
*/
|
|
35
|
+
export declare const profileConstruction: ({ parent, cases, doc, runs, }: ConstructionProfileOptions) => ConstructionResult[];
|
|
36
|
+
//# sourceMappingURL=construction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"construction.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/construction.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGhE;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,kCAK7B,0BAA0B,KAAG,kBAAkB,EAoB9C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Extension } from '@codemirror/state';
|
|
2
|
+
import { type XmlWidgetRegistry, type XmlWidgetState } from '@dxos/ui-editor';
|
|
3
|
+
export type ItemExtensionOptions = {
|
|
4
|
+
registry?: XmlWidgetRegistry;
|
|
5
|
+
editable?: boolean;
|
|
6
|
+
themeMode?: 'light' | 'dark';
|
|
7
|
+
setWidgets?: (widgets: XmlWidgetState[]) => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The extension set one item's document is built from.
|
|
11
|
+
*
|
|
12
|
+
* Everything that does not depend on the item is **built once and shared**, and that is the point of
|
|
13
|
+
* this module rather than a tidiness. `EditorView.theme()` mints a new `StyleModule` and a new
|
|
14
|
+
* generated class on every call, so a set built per item injects one stylesheet per row and
|
|
15
|
+
* invalidates the whole document's style each time it does. Measured at ~14ms per row against ~2ms
|
|
16
|
+
* for the identical set shared (`baseline/mount`) — six times the cost of the first fill, for
|
|
17
|
+
* stylesheets that are all the same.
|
|
18
|
+
*
|
|
19
|
+
* Extensions are descriptors, not instances: a `StateField` or `ViewPlugin` in a shared array is
|
|
20
|
+
* still instantiated per view, so sharing changes nothing but the identity CodeMirror dedupes on.
|
|
21
|
+
*/
|
|
22
|
+
export declare const createBlockExtensions: ({ registry, editable, themeMode, setWidgets, }?: ItemExtensionOptions) => Extension[];
|
|
23
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/extensions.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EASpB,MAAM,iBAAiB,CAAC;AAIzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;CAClD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,oDAK/B,oBAAoB,KAAQ,SAAS,EAIvC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StateField } from '@codemirror/state';
|
|
2
|
+
import { type DecorationSet } from '@codemirror/view';
|
|
3
|
+
/** Ranges to highlight, as [from, to) offsets into the item's document. */
|
|
4
|
+
export type HighlightRange = readonly [number, number];
|
|
5
|
+
export declare const setHighlights: import("@codemirror/state").StateEffectType<readonly HighlightRange[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Highlights pushed in from the model.
|
|
8
|
+
*
|
|
9
|
+
* Search runs over the feed's projection rather than per-view (most messages are never mounted), so
|
|
10
|
+
* the item receives ranges instead of owning search state.
|
|
11
|
+
*/
|
|
12
|
+
export declare const highlights: StateField<DecorationSet>;
|
|
13
|
+
export declare const highlightTheme: import("@codemirror/state").Extension;
|
|
14
|
+
//# sourceMappingURL=highlight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/highlight.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAc,KAAK,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAE9E,2EAA2E;AAC3E,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,aAAa,wEAAkD,CAAC;AAM7E;;;;;GAKG;AACH,eAAO,MAAM,UAAU,2BAsBrB,CAAC;AAEH,eAAO,MAAM,cAAc,uCAMzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/index.ts"],"names":[],"mappings":"AAIA,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type EditorView } from '@codemirror/view';
|
|
2
|
+
/**
|
|
3
|
+
* Coordinates selection across the items of one feed.
|
|
4
|
+
*
|
|
5
|
+
* Each item is its own editor with its own selection state, and `drawSelection` paints that state
|
|
6
|
+
* whether or not the view has focus — so without a coordinator every message the reader has ever
|
|
7
|
+
* selected in stays highlighted at once. The feed has one selection, so claiming it in one item
|
|
8
|
+
* collapses it everywhere else.
|
|
9
|
+
*/
|
|
10
|
+
export type SelectionGroup = {
|
|
11
|
+
/** Adds a view to the group; returns the disposer. */
|
|
12
|
+
register: (view: EditorView) => () => void;
|
|
13
|
+
/** Makes `view` the sole owner of the feed's selection. */
|
|
14
|
+
claim: (view: EditorView) => void;
|
|
15
|
+
};
|
|
16
|
+
/** Creates a group; one per feed, provided to its items by `MessageList.Root`. */
|
|
17
|
+
export declare const createSelectionGroup: () => SelectionGroup;
|
|
18
|
+
export declare const SelectionGroupContext: import("react").Context<SelectionGroup | undefined>;
|
|
19
|
+
/** The enclosing feed's selection group, or a private one when an item is used standalone. */
|
|
20
|
+
export declare const useSelectionGroup: () => SelectionGroup;
|
|
21
|
+
//# sourceMappingURL=selection-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-group.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/selection-group.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,sDAAsD;IACtD,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,IAAI,CAAC;IAC3C,2DAA2D;IAC3D,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,QAAO,cAoBvC,CAAC;AAEF,eAAO,MAAM,qBAAqB,qDAAuD,CAAC;AAE1F,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,QAAO,cAIpC,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { type PropsWithChildren } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* State a widget keeps that must outlive the row it is drawn in.
|
|
4
|
+
*
|
|
5
|
+
* A virtualized feed destroys a row when it leaves the window, and React state inside a widget dies
|
|
6
|
+
* with it — so a reader who expands a tool panel, scrolls away and comes back finds it shut. In a
|
|
7
|
+
* thread that is one long document the question never arises, because nothing ever unmounts; it is
|
|
8
|
+
* the cost of virtualization, and it has to be paid somewhere above the row.
|
|
9
|
+
*
|
|
10
|
+
* Here, in a map owned by `MessageList.Root`. Not in the message, because whether a panel is open is
|
|
11
|
+
* the reader's business and not part of what was said.
|
|
12
|
+
*/
|
|
13
|
+
export type WidgetStateStore = {
|
|
14
|
+
read: (key: string) => unknown;
|
|
15
|
+
write: (key: string, value: unknown) => void;
|
|
16
|
+
};
|
|
17
|
+
export declare const WidgetStateProvider: ({ store, children }: PropsWithChildren<{
|
|
18
|
+
store: WidgetStateStore;
|
|
19
|
+
}>) => React.JSX.Element;
|
|
20
|
+
export declare const WidgetScopeProvider: ({ scope, children }: PropsWithChildren<{
|
|
21
|
+
scope: string;
|
|
22
|
+
}>) => React.JSX.Element;
|
|
23
|
+
/** Creates the store a feed's widgets share. One per `MessageList.Root`. */
|
|
24
|
+
export declare const createWidgetStateStore: () => WidgetStateStore;
|
|
25
|
+
/**
|
|
26
|
+
* `useState` for a widget, keyed so it survives the row being destroyed and rebuilt.
|
|
27
|
+
*
|
|
28
|
+
* The key must identify the widget within its message — its tag and position in the document, say —
|
|
29
|
+
* and is scoped to the message automatically. Outside a feed this degrades to plain `useState`, so a
|
|
30
|
+
* widget can still be rendered on its own.
|
|
31
|
+
*/
|
|
32
|
+
export declare const useWidgetState: <T>(key: string, initial: T) => [T, (value: T) => void];
|
|
33
|
+
//# sourceMappingURL=widget-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-state.d.ts","sourceRoot":"","sources":["../../../../../src/components/Block/widget-state.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAA6D,MAAM,OAAO,CAAC;AAEjH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9C,CAAC;AAOF,eAAO,MAAM,mBAAmB,wBAAyB,iBAAiB,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAAC,sBAEtG,CAAC;AAEF,eAAO,MAAM,mBAAmB,wBAAyB,iBAAiB,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,sBAE5F,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,QAAO,gBAQzC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,OAAQ,MAAM,WAAW,CAAC,KAAG,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAqBlF,CAAC"}
|