@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.
Files changed (230) hide show
  1. package/LICENSE +105 -0
  2. package/dist/lib/chunk-src.mjs +2228 -0
  3. package/dist/lib/chunk-src.mjs.map +1 -0
  4. package/dist/lib/index.mjs +2 -0
  5. package/dist/lib/testing.mjs +1923 -0
  6. package/dist/lib/testing.mjs.map +1 -0
  7. package/dist/types/src/components/Block/HtmlBlock.d.ts +14 -0
  8. package/dist/types/src/components/Block/HtmlBlock.d.ts.map +1 -0
  9. package/dist/types/src/components/Block/MarkdownBlock.d.ts +24 -0
  10. package/dist/types/src/components/Block/MarkdownBlock.d.ts.map +1 -0
  11. package/dist/types/src/components/Block/block.stories.d.ts +21 -0
  12. package/dist/types/src/components/Block/block.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/Block/construction.d.ts +36 -0
  14. package/dist/types/src/components/Block/construction.d.ts.map +1 -0
  15. package/dist/types/src/components/Block/extensions.d.ts +23 -0
  16. package/dist/types/src/components/Block/extensions.d.ts.map +1 -0
  17. package/dist/types/src/components/Block/highlight.d.ts +14 -0
  18. package/dist/types/src/components/Block/highlight.d.ts.map +1 -0
  19. package/dist/types/src/components/Block/index.d.ts +6 -0
  20. package/dist/types/src/components/Block/index.d.ts.map +1 -0
  21. package/dist/types/src/components/Block/selection-group.d.ts +21 -0
  22. package/dist/types/src/components/Block/selection-group.d.ts.map +1 -0
  23. package/dist/types/src/components/Block/widget-state.d.ts +33 -0
  24. package/dist/types/src/components/Block/widget-state.d.ts.map +1 -0
  25. package/dist/types/src/components/MessageList/MessageList.d.ts +129 -0
  26. package/dist/types/src/components/MessageList/MessageList.d.ts.map +1 -0
  27. package/dist/types/src/components/MessageList/MessageList.stories.d.ts +47 -0
  28. package/dist/types/src/components/MessageList/MessageList.stories.d.ts.map +1 -0
  29. package/dist/types/src/components/MessageList/index.d.ts +3 -0
  30. package/dist/types/src/components/MessageList/index.d.ts.map +1 -0
  31. package/dist/types/src/components/MessageList/position-log.d.ts +73 -0
  32. package/dist/types/src/components/MessageList/position-log.d.ts.map +1 -0
  33. package/dist/types/src/components/Minimap/Minimap.d.ts +28 -0
  34. package/dist/types/src/components/Minimap/Minimap.d.ts.map +1 -0
  35. package/dist/types/src/components/Minimap/Minimap.stories.d.ts +21 -0
  36. package/dist/types/src/components/Minimap/Minimap.stories.d.ts.map +1 -0
  37. package/dist/types/src/components/Minimap/index.d.ts +2 -0
  38. package/dist/types/src/components/Minimap/index.d.ts.map +1 -0
  39. package/dist/types/src/components/Outline/Outline.d.ts +46 -0
  40. package/dist/types/src/components/Outline/Outline.d.ts.map +1 -0
  41. package/dist/types/src/components/Outline/Outline.stories.d.ts +37 -0
  42. package/dist/types/src/components/Outline/Outline.stories.d.ts.map +1 -0
  43. package/dist/types/src/components/Outline/index.d.ts +2 -0
  44. package/dist/types/src/components/Outline/index.d.ts.map +1 -0
  45. package/dist/types/src/components/index.d.ts +5 -0
  46. package/dist/types/src/components/index.d.ts.map +1 -0
  47. package/dist/types/src/debug/Debug.d.ts +17 -0
  48. package/dist/types/src/debug/Debug.d.ts.map +1 -0
  49. package/dist/types/src/debug/Stats.d.ts +30 -0
  50. package/dist/types/src/debug/Stats.d.ts.map +1 -0
  51. package/dist/types/src/debug/debug-model.d.ts +32 -0
  52. package/dist/types/src/debug/debug-model.d.ts.map +1 -0
  53. package/dist/types/src/debug/debug-model.test.d.ts +2 -0
  54. package/dist/types/src/debug/debug-model.test.d.ts.map +1 -0
  55. package/dist/types/src/debug/frame-meter.d.ts +48 -0
  56. package/dist/types/src/debug/frame-meter.d.ts.map +1 -0
  57. package/dist/types/src/debug/frame-meter.test.d.ts +2 -0
  58. package/dist/types/src/debug/frame-meter.test.d.ts.map +1 -0
  59. package/dist/types/src/debug/index.d.ts +6 -0
  60. package/dist/types/src/debug/index.d.ts.map +1 -0
  61. package/dist/types/src/debug/use-debug.d.ts +19 -0
  62. package/dist/types/src/debug/use-debug.d.ts.map +1 -0
  63. package/dist/types/src/hooks/follow.d.ts +89 -0
  64. package/dist/types/src/hooks/follow.d.ts.map +1 -0
  65. package/dist/types/src/hooks/follow.stories.d.ts +25 -0
  66. package/dist/types/src/hooks/follow.stories.d.ts.map +1 -0
  67. package/dist/types/src/hooks/follow.test.d.ts +2 -0
  68. package/dist/types/src/hooks/follow.test.d.ts.map +1 -0
  69. package/dist/types/src/hooks/index.d.ts +6 -0
  70. package/dist/types/src/hooks/index.d.ts.map +1 -0
  71. package/dist/types/src/hooks/useDecorations.d.ts +31 -0
  72. package/dist/types/src/hooks/useDecorations.d.ts.map +1 -0
  73. package/dist/types/src/hooks/useFeedNavigation.d.ts +44 -0
  74. package/dist/types/src/hooks/useFeedNavigation.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useFollow.d.ts +46 -0
  76. package/dist/types/src/hooks/useFollow.d.ts.map +1 -0
  77. package/dist/types/src/hooks/useItemSelection.d.ts +34 -0
  78. package/dist/types/src/hooks/useItemSelection.d.ts.map +1 -0
  79. package/dist/types/src/index.d.ts +4 -0
  80. package/dist/types/src/index.d.ts.map +1 -0
  81. package/dist/types/src/model/feed-model.d.ts +67 -0
  82. package/dist/types/src/model/feed-model.d.ts.map +1 -0
  83. package/dist/types/src/model/feed-model.test.d.ts +2 -0
  84. package/dist/types/src/model/feed-model.test.d.ts.map +1 -0
  85. package/dist/types/src/model/feed.d.ts +66 -0
  86. package/dist/types/src/model/feed.d.ts.map +1 -0
  87. package/dist/types/src/model/feed.test.d.ts +2 -0
  88. package/dist/types/src/model/feed.test.d.ts.map +1 -0
  89. package/dist/types/src/model/index.d.ts +5 -0
  90. package/dist/types/src/model/index.d.ts.map +1 -0
  91. package/dist/types/src/model/list-model.d.ts +61 -0
  92. package/dist/types/src/model/list-model.d.ts.map +1 -0
  93. package/dist/types/src/model/list-model.test.d.ts +2 -0
  94. package/dist/types/src/model/list-model.test.d.ts.map +1 -0
  95. package/dist/types/src/model/useListModel.d.ts +22 -0
  96. package/dist/types/src/model/useListModel.d.ts.map +1 -0
  97. package/dist/types/src/stories/assistant.stories.d.ts +38 -0
  98. package/dist/types/src/stories/assistant.stories.d.ts.map +1 -0
  99. package/dist/types/src/stories/baseline.stories.d.ts +32 -0
  100. package/dist/types/src/stories/baseline.stories.d.ts.map +1 -0
  101. package/dist/types/src/stories/bridge.stories.d.ts +72 -0
  102. package/dist/types/src/stories/bridge.stories.d.ts.map +1 -0
  103. package/dist/types/src/stories/construction.stories.d.ts +17 -0
  104. package/dist/types/src/stories/construction.stories.d.ts.map +1 -0
  105. package/dist/types/src/stories/fill.stories.d.ts +51 -0
  106. package/dist/types/src/stories/fill.stories.d.ts.map +1 -0
  107. package/dist/types/src/stories/horizontal.stories.d.ts +17 -0
  108. package/dist/types/src/stories/horizontal.stories.d.ts.map +1 -0
  109. package/dist/types/src/stories/mount.stories.d.ts +19 -0
  110. package/dist/types/src/stories/mount.stories.d.ts.map +1 -0
  111. package/dist/types/src/stories/navigation.stories.d.ts +33 -0
  112. package/dist/types/src/stories/navigation.stories.d.ts.map +1 -0
  113. package/dist/types/src/stories/streaming.stories.d.ts +17 -0
  114. package/dist/types/src/stories/streaming.stories.d.ts.map +1 -0
  115. package/dist/types/src/stories/tail.stories.d.ts +47 -0
  116. package/dist/types/src/stories/tail.stories.d.ts.map +1 -0
  117. package/dist/types/src/stories/widget.stories.d.ts +16 -0
  118. package/dist/types/src/stories/widget.stories.d.ts.map +1 -0
  119. package/dist/types/src/testing/FeedStory.d.ts +30 -0
  120. package/dist/types/src/testing/FeedStory.d.ts.map +1 -0
  121. package/dist/types/src/testing/MessageWindow.d.ts +48 -0
  122. package/dist/types/src/testing/MessageWindow.d.ts.map +1 -0
  123. package/dist/types/src/testing/controls.d.ts +46 -0
  124. package/dist/types/src/testing/controls.d.ts.map +1 -0
  125. package/dist/types/src/testing/debug/FeedStats.d.ts +18 -0
  126. package/dist/types/src/testing/debug/FeedStats.d.ts.map +1 -0
  127. package/dist/types/src/testing/debug/index.d.ts +4 -0
  128. package/dist/types/src/testing/debug/index.d.ts.map +1 -0
  129. package/dist/types/src/testing/debug/sweep.d.ts +18 -0
  130. package/dist/types/src/testing/debug/sweep.d.ts.map +1 -0
  131. package/dist/types/src/testing/debug/use-feed-debug.d.ts +33 -0
  132. package/dist/types/src/testing/debug/use-feed-debug.d.ts.map +1 -0
  133. package/dist/types/src/testing/generator.d.ts +16 -0
  134. package/dist/types/src/testing/generator.d.ts.map +1 -0
  135. package/dist/types/src/testing/index.d.ts +10 -0
  136. package/dist/types/src/testing/index.d.ts.map +1 -0
  137. package/dist/types/src/testing/scenarios.d.ts +58 -0
  138. package/dist/types/src/testing/scenarios.d.ts.map +1 -0
  139. package/dist/types/src/testing/stream.d.ts +22 -0
  140. package/dist/types/src/testing/stream.d.ts.map +1 -0
  141. package/dist/types/src/testing/turn.d.ts +21 -0
  142. package/dist/types/src/testing/turn.d.ts.map +1 -0
  143. package/dist/types/src/testing/widgets.d.ts +3 -0
  144. package/dist/types/src/testing/widgets.d.ts.map +1 -0
  145. package/dist/types/src/virtualizer/Window.d.ts +137 -0
  146. package/dist/types/src/virtualizer/Window.d.ts.map +1 -0
  147. package/dist/types/src/virtualizer/Window.stories.d.ts +112 -0
  148. package/dist/types/src/virtualizer/Window.stories.d.ts.map +1 -0
  149. package/dist/types/src/virtualizer/index.d.ts +3 -0
  150. package/dist/types/src/virtualizer/index.d.ts.map +1 -0
  151. package/dist/types/src/virtualizer/placement.d.ts +188 -0
  152. package/dist/types/src/virtualizer/placement.d.ts.map +1 -0
  153. package/dist/types/src/virtualizer/placement.test.d.ts +2 -0
  154. package/dist/types/src/virtualizer/placement.test.d.ts.map +1 -0
  155. package/dist/types/tsconfig.tsbuildinfo +1 -0
  156. package/package.json +72 -0
  157. package/src/components/Block/HtmlBlock.tsx +26 -0
  158. package/src/components/Block/MarkdownBlock.tsx +157 -0
  159. package/src/components/Block/block.stories.tsx +140 -0
  160. package/src/components/Block/construction.ts +77 -0
  161. package/src/components/Block/extensions.ts +95 -0
  162. package/src/components/Block/highlight.ts +53 -0
  163. package/src/components/Block/index.ts +10 -0
  164. package/src/components/Block/selection-group.ts +53 -0
  165. package/src/components/Block/widget-state.tsx +75 -0
  166. package/src/components/MessageList/MessageList.stories.tsx +111 -0
  167. package/src/components/MessageList/MessageList.tsx +647 -0
  168. package/src/components/MessageList/index.ts +6 -0
  169. package/src/components/MessageList/position-log.ts +167 -0
  170. package/src/components/Minimap/Minimap.stories.tsx +129 -0
  171. package/src/components/Minimap/Minimap.tsx +84 -0
  172. package/src/components/Minimap/index.ts +5 -0
  173. package/src/components/Outline/Outline.stories.tsx +196 -0
  174. package/src/components/Outline/Outline.tsx +383 -0
  175. package/src/components/Outline/index.ts +5 -0
  176. package/src/components/index.ts +8 -0
  177. package/src/debug/Debug.tsx +90 -0
  178. package/src/debug/Stats.tsx +57 -0
  179. package/src/debug/debug-model.test.ts +36 -0
  180. package/src/debug/debug-model.ts +66 -0
  181. package/src/debug/frame-meter.test.ts +49 -0
  182. package/src/debug/frame-meter.ts +188 -0
  183. package/src/debug/index.ts +9 -0
  184. package/src/debug/use-debug.tsx +41 -0
  185. package/src/hooks/follow.stories.tsx +200 -0
  186. package/src/hooks/follow.test.ts +105 -0
  187. package/src/hooks/follow.ts +241 -0
  188. package/src/hooks/index.ts +9 -0
  189. package/src/hooks/useDecorations.tsx +54 -0
  190. package/src/hooks/useFeedNavigation.ts +90 -0
  191. package/src/hooks/useFollow.ts +258 -0
  192. package/src/hooks/useItemSelection.tsx +53 -0
  193. package/src/index.ts +7 -0
  194. package/src/model/feed-model.test.ts +84 -0
  195. package/src/model/feed-model.ts +214 -0
  196. package/src/model/feed.test.ts +65 -0
  197. package/src/model/feed.ts +157 -0
  198. package/src/model/index.ts +8 -0
  199. package/src/model/list-model.test.ts +71 -0
  200. package/src/model/list-model.ts +152 -0
  201. package/src/model/useListModel.ts +53 -0
  202. package/src/stories/assistant.stories.tsx +425 -0
  203. package/src/stories/baseline.stories.tsx +78 -0
  204. package/src/stories/bridge.stories.tsx +376 -0
  205. package/src/stories/construction.stories.tsx +147 -0
  206. package/src/stories/fill.stories.tsx +271 -0
  207. package/src/stories/horizontal.stories.tsx +129 -0
  208. package/src/stories/mount.stories.tsx +233 -0
  209. package/src/stories/navigation.stories.tsx +145 -0
  210. package/src/stories/streaming.stories.tsx +132 -0
  211. package/src/stories/tail.stories.tsx +149 -0
  212. package/src/stories/widget.stories.tsx +107 -0
  213. package/src/testing/FeedStory.tsx +452 -0
  214. package/src/testing/MessageWindow.tsx +139 -0
  215. package/src/testing/controls.tsx +96 -0
  216. package/src/testing/debug/FeedStats.tsx +182 -0
  217. package/src/testing/debug/index.ts +16 -0
  218. package/src/testing/debug/sweep.ts +85 -0
  219. package/src/testing/debug/use-feed-debug.ts +91 -0
  220. package/src/testing/generator.ts +90 -0
  221. package/src/testing/index.ts +13 -0
  222. package/src/testing/scenarios.tsx +552 -0
  223. package/src/testing/stream.ts +70 -0
  224. package/src/testing/turn.ts +91 -0
  225. package/src/testing/widgets.tsx +183 -0
  226. package/src/virtualizer/Window.stories.tsx +683 -0
  227. package/src/virtualizer/Window.tsx +435 -0
  228. package/src/virtualizer/index.ts +6 -0
  229. package/src/virtualizer/placement.test.ts +312 -0
  230. package/src/virtualizer/placement.ts +403 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-src.mjs","names":[],"sources":["../../src/hooks/follow.ts","../../src/hooks/useDecorations.tsx","../../src/hooks/useFeedNavigation.ts","../../src/hooks/useFollow.ts","../../src/hooks/useItemSelection.tsx","../../src/components/Block/selection-group.ts","../../src/components/Block/highlight.ts","../../src/components/Block/widget-state.tsx","../../src/components/Block/HtmlBlock.tsx","../../src/components/Block/extensions.ts","../../src/components/Block/MarkdownBlock.tsx","../../src/model/feed-model.ts","../../src/model/list-model.ts","../../src/model/feed.ts","../../src/model/useListModel.ts","../../src/virtualizer/placement.ts","../../src/virtualizer/Window.tsx","../../src/components/MessageList/position-log.ts","../../src/components/MessageList/MessageList.tsx","../../src/components/Minimap/Minimap.tsx","../../src/components/Outline/Outline.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * Speeds are expressed in **rows**, not pixels: a feed of tall messages and one of short replies\n * should travel at the same perceived rate, and only a row-relative unit gives that.\n */\nexport type FollowOptions = {\n /** Ceiling on travel speed, rows/s. */\n maxSpeed?: number;\n /** Rate the speed ramps up, rows/s². */\n acceleration?: number;\n /** Rate the speed sheds on approach, rows/s². Lower than `acceleration` gives a longer landing. */\n deceleration?: number;\n /** Height of a row in px, sampled each frame so a measured list can refine it. */\n rowHeight?: () => number;\n /** Empty space reserved below the last row, which the follow must stop short of. */\n trailing?: () => number;\n};\n\nconst DEFAULT_MAX_SPEED = 2;\nconst DEFAULT_ACCELERATION = 12;\nconst DEFAULT_DECELERATION = 6;\n/**\n * Gentler than the ramp up, and deliberately so.\n *\n * Braking distance is `v²/2d`, so matching the two rates sheds full speed in three rows — a travel\n * of any length then cruises to within a few rows of the target and stops, which reads as an abrupt\n * halt however correct the curve is. At a third of the acceleration the landing occupies nine rows\n * and a second and a half, which is long enough to see.\n */\nconst DEFAULT_ROW_HEIGHT = 120;\n\n/** Within this distance the follow has arrived; below it the animation would only jitter. */\nconst ARRIVED = 0.5;\n\n/** The physics runs in pixels; rows are converted at the boundary by {@link ScrollFollower}. */\nexport type VelocityStep = {\n /** Current speed, px/s. */\n velocity: number;\n /** Remaining distance to the target, px. */\n distance: number;\n /** Elapsed time since the last step, seconds. */\n dt: number;\n /** Ceiling, px/s. */\n maxSpeed: number;\n /** Ramp-up rate, px/s². */\n acceleration: number;\n /** Approach rate, px/s². Defaults to `acceleration`. */\n deceleration?: number;\n};\n\n/**\n * Speed for the next frame of a follow.\n *\n * Ramps up to `maxSpeed` and back down so the travel starts and ends gently rather than jumping.\n * The cap is whichever is lower: the ceiling, or the speed from which the remaining distance can\n * still be shed at `acceleration` (`v = sqrt(2·a·d)`) — that second term is what turns the arrival\n * into a deceleration instead of a stop, and what keeps a growing target at cruise speed.\n *\n * Acceleration is rate-limited so a start is gradual; **deceleration is not**, because the braking\n * curve is already a smooth ramp to zero and easing onto it instead leaves the speed above the\n * curve for the whole approach — the follow then runs out of distance while still moving and the\n * arrival reads as a stop rather than a landing.\n */\nexport const stepVelocity = ({\n velocity,\n distance,\n dt,\n maxSpeed,\n acceleration,\n deceleration = acceleration,\n}: VelocityStep): number => {\n const braking = Math.sqrt(2 * deceleration * Math.max(distance, 0));\n const desired = Math.min(maxSpeed, braking);\n return velocity < desired ? Math.min(desired, velocity + acceleration * dt) : desired;\n};\n\n/**\n * **Not wired into `MessageList` at present.** The placement engine follows the tail by holding an\n * intent and correcting the offset, which arrives instantly; this class is the animated travel the\n * old engine used, kept with its tests because the glide is worth having back and nothing about it\n * was wrong. Wiring it is a decision about feel, not a repair.\n */\n/**\n * Keeps an element scrolled to its bottom while its content grows.\n *\n * Native `scrollTo({ behavior: 'smooth' })` restarts its easing on every call, so a target that\n * moves every chunk never leaves the start of the curve — the motion reads as a stutter. This\n * carries velocity across frames instead: it accelerates as a stream begins, cruises while content\n * keeps arriving, and decelerates onto the tail when it stops.\n *\n * `@dxos/ui-editor`'s `createCrawler` follows a streaming CodeMirror document the same way, with a\n * critically-damped spring. The two are deliberately separate: that one is bound to `view.scrollDOM`\n * and driven by editor effects, where a feed owns its own scroll container. The models differ in\n * feel — a spring's speed falls off with distance, where this holds a ceiling and brakes onto the\n * target, which keeps a fast stream at a constant rate rather than an ever-widening lag.\n */\nexport class ScrollFollower {\n readonly #maxSpeed: number;\n readonly #acceleration: number;\n readonly #deceleration: number;\n readonly #rowHeight: () => number;\n readonly #trailing: () => number;\n\n #frame: number | undefined;\n #velocity = 0;\n #timestamp = 0;\n #braking = false;\n\n constructor(\n private readonly _element: HTMLElement,\n {\n maxSpeed = DEFAULT_MAX_SPEED,\n acceleration = DEFAULT_ACCELERATION,\n deceleration = DEFAULT_DECELERATION,\n rowHeight = () => DEFAULT_ROW_HEIGHT,\n trailing = () => 0,\n }: FollowOptions = {},\n ) {\n this.#maxSpeed = maxSpeed;\n this.#acceleration = acceleration;\n this.#deceleration = deceleration;\n this.#rowHeight = rowHeight;\n this.#trailing = trailing;\n }\n\n /**\n * The bottom of the last row, which is not the bottom of the document.\n *\n * A feed may reserve empty space below its last row so that row can be brought to the top of the\n * viewport. That space is somewhere the reader may go and nowhere the follow should: chasing the\n * element's own maximum walks the feed off the end of the conversation and keeps going.\n */\n get #bottom(): number {\n return this._element.scrollHeight - this.#trailing() - this._element.clientHeight;\n }\n\n get running(): boolean {\n return this.#frame !== undefined;\n }\n\n /** Current speed, rows/s. Exposed so a harness can show the ramp. */\n get velocity(): number {\n return this.#velocity / Math.max(this.#rowHeight(), 1);\n }\n\n /** Starts (or continues) travelling towards the bottom. Safe to call on every content change. */\n start(): void {\n this.#braking = false;\n if (this.#frame !== undefined) {\n return;\n }\n\n this.#timestamp = performance.now();\n this.#frame = requestAnimationFrame(this.#tick);\n }\n\n /**\n * Coasts to a halt, shedding speed at `deceleration` — what a stream ending or a stop control\n * means. Cutting the animation instead would halt mid-glide, which is the one motion the reader\n * cannot have caused and so reads as a fault.\n */\n stop(): void {\n if (this.#frame === undefined) {\n this.#velocity = 0;\n return;\n }\n\n this.#braking = true;\n }\n\n /**\n * Halts immediately, discarding the velocity — the reader has taken over, or the list is going\n * away. Coasting here would fight a hand already on the scrollbar.\n */\n cancel(): void {\n if (this.#frame !== undefined) {\n cancelAnimationFrame(this.#frame);\n this.#frame = undefined;\n }\n this.#braking = false;\n this.#velocity = 0;\n }\n\n /** Jumps to the bottom with no animation. */\n jump(): void {\n this.cancel();\n this._element.scrollTop = Math.max(0, this.#bottom);\n }\n\n readonly #tick = (timestamp: number): void => {\n // Clamped because a backgrounded tab resumes with a large gap, which would otherwise apply one\n // enormous step and teleport the viewport.\n const dt = Math.min((timestamp - this.#timestamp) / 1_000, 0.05);\n this.#timestamp = timestamp;\n\n const target = Math.max(0, this.#bottom);\n const distance = target - this._element.scrollTop;\n\n // Coasting: the target no longer matters, only shedding what speed is left. Bounded by the\n // bottom so a stop late in a travel still cannot run past it.\n if (this.#braking) {\n const rowHeight = Math.max(this.#rowHeight(), 1);\n this.#velocity = Math.max(0, this.#velocity - this.#deceleration * rowHeight * dt);\n if (this.#velocity <= 0 || distance <= ARRIVED) {\n this.#frame = undefined;\n this.#velocity = 0;\n this.#braking = false;\n return;\n }\n\n this._element.scrollTop = Math.min(target, this._element.scrollTop + this.#velocity * dt);\n this.#frame = requestAnimationFrame(this.#tick);\n return;\n }\n\n if (distance <= ARRIVED) {\n this._element.scrollTop = target;\n this.#frame = undefined;\n this.#velocity = 0;\n return;\n }\n\n // Rows are converted to pixels every frame rather than once, so a list whose measured rows grow\n // (a streaming message) travels at the same rate in rows throughout.\n const rowHeight = Math.max(this.#rowHeight(), 1);\n this.#velocity = stepVelocity({\n velocity: this.#velocity,\n distance,\n dt,\n maxSpeed: this.#maxSpeed * rowHeight,\n acceleration: this.#acceleration * rowHeight,\n deceleration: this.#deceleration * rowHeight,\n });\n\n this._element.scrollTop = Math.min(target, this._element.scrollTop + this.#velocity * dt);\n this.#frame = requestAnimationFrame(this.#tick);\n };\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react';\n\n/**\n * A range of one item's rendered text that some producer wants painted (SPEC: Decoration).\n *\n * Search is ONE producer — mentions, lint results and diff spans are others — which is why this is\n * not called a hit: the item painting the range does not care where it came from, and the list does\n * not know it exists at all.\n */\nexport type Decoration = {\n /** Item (message) id. */\n id: string;\n range: { offset: number; length: number };\n /** Producer tag: 'search' | 'mention' | ... */\n kind: string;\n};\n\nconst DecorationsContext = createContext<ReadonlyMap<string, readonly Decoration[]>>(new Map());\n\nexport type DecorationsProviderProps = PropsWithChildren<{\n decorations?: readonly Decoration[];\n}>;\n\n/**\n * THE PATTERN for cross-cutting per-item data (SPEC §Aspects), not a feed feature: a provider at\n * the host carries what producers made, and an item asks for its own by id. The list neither knows\n * nor routes — `hits` left its API entirely. Grouped once per change rather than filtered per item,\n * so a long feed stays O(decorations).\n */\nexport const DecorationsProvider = ({ decorations, children }: DecorationsProviderProps) => {\n const byId = useMemo(() => {\n const map = new Map<string, Decoration[]>();\n for (const decoration of decorations ?? []) {\n const list = map.get(decoration.id) ?? [];\n list.push(decoration);\n map.set(decoration.id, list);\n }\n\n return map;\n }, [decorations]);\n\n return <DecorationsContext.Provider value={byId}>{children}</DecorationsContext.Provider>;\n};\n\nconst NONE: readonly Decoration[] = [];\n\n/** An item's own decorations. Items work without a provider — they are simply undecorated. */\nexport const useDecorations = (id: string): readonly Decoration[] => {\n return useContext(DecorationsContext).get(id) ?? NONE;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type RefObject, useCallback } from 'react';\n\nimport { type Stop } from '../model';\nimport { type WindowController } from '../virtualizer';\n\nexport type UseFeedNavigationOptions = {\n controller: RefObject<WindowController | null>;\n /** The stops policy's current answer; asked per press, so a policy change needs no rewiring. */\n stops: () => readonly Stop[];\n /** The row containing the scroll offset — the cursor, derived and never set beside the scroll. */\n current: () => number;\n count: () => number;\n /** Align the last stop to the end unless space is reserved past it (the reserve is scrollable). */\n scrollPastEnd?: boolean;\n /**\n * Told before every jump, with the target index — the follow's `onNavigate` goes here. Without\n * it a navigation races the follow's correction effect, which runs before the jump's scroll\n * event can withdraw the intent and snaps the reader straight back to the tail they just left.\n * The follow may move the view only while content is arriving AND the reader has pinned\n * themselves to the bottom; navigating into the feed un-pins by definition.\n */\n onNavigate?: (index: number) => void;\n};\n\nexport type FeedNavigation = {\n /** Move by `delta` stops. Every press travels: see below. */\n step: (delta: number) => void;\n /** Jump to a stop (or a raw index), aligning the last row to the end where that is what it means. */\n jumpTo: (index: number, behavior?: ScrollBehavior) => void;\n first: () => void;\n last: () => void;\n};\n\n/**\n * The one navigation seam (SPEC: useFeedNavigation): toolbar buttons, arrow keys, the outline rail\n * and the minimap all call this, so they cannot disagree about what a step is.\n *\n * Stepping is from the row containing the scroll offset. The stop above it begins strictly higher\n * and the stop below strictly lower, so every press moves the viewport — an index compared against\n * a position cannot promise that while positions are still being measured. Steps glide (corrections\n * move the window, not the scroll, so nothing cancels the animation); far jumps are instant, since\n * a glide across unmounted rows is a journey over a blank screen.\n *\n * Distinct from react-ui-list's `useListNavigation`, which is focus traversal over fully-mounted\n * rows — a different problem in the same naming family.\n */\nexport const useFeedNavigation = ({\n controller,\n stops,\n current,\n count,\n scrollPastEnd,\n onNavigate,\n}: UseFeedNavigationOptions): FeedNavigation => {\n const jumpTo = useCallback(\n (index: number, behavior: ScrollBehavior = 'auto') => {\n onNavigate?.(index);\n const align = !scrollPastEnd && index >= count() - 1 ? 'end' : 'start';\n controller.current?.scrollToIndex(index, align, behavior);\n },\n [controller, count, scrollPastEnd, onNavigate],\n );\n\n const step = useCallback(\n (delta: number) => {\n const all = stops();\n if (!all.length) {\n return;\n }\n\n const at = current();\n const next =\n delta > 0\n ? (all.find(({ index }) => index > at) ?? all[all.length - 1])\n : ([...all].reverse().find(({ index }) => index < at) ?? all[0]);\n\n jumpTo(next.index, 'smooth');\n },\n [stops, current, jumpTo],\n );\n\n const first = useCallback(() => jumpTo(0), [jumpTo]);\n const last = useCallback(() => jumpTo(count() - 1), [jumpTo, count]);\n\n return { step, jumpTo, first, last };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type RefObject, useCallback, useLayoutEffect, useMemo, useRef } from 'react';\n\nimport { type Placement } from '../virtualizer/placement';\nimport { ScrollFollower } from './follow';\n\n/** Distance from the end within which the reader counts as being at it. */\nconst STICKY_THRESHOLD = 32;\n\n/** How long after a gesture a scroll is still attributable to the reader. */\nconst GESTURE_WINDOW = 300;\n\nexport type FollowHandle = {\n /**\n * A navigation is the reader answering \"do you want the tail?\" — and it must answer *before* the\n * scroll moves. The intent is otherwise withdrawn by the scroll event the jump raises, which is\n * asynchronous: the correction effect can run in between, still believe the reader is following,\n * and snap them straight back to the tail they just left. Found by the outline rail — a click\n * near the top of a sticky feed moved the list a few pixels and no further.\n */\n onNavigate: (index: number) => void;\n};\n\nexport type UseFollowOptions = {\n scrollerRef: RefObject<HTMLElement | null>;\n /** The placement whose `endOffset` is the tail's resting place. */\n placement: Placement;\n /** Total content extent — the correction re-runs when it changes, which is how growth is noticed. */\n extent: number;\n count: number;\n axis?: 'block' | 'inline';\n /** Empty extent after the last row; the end the follow rests at is before it. */\n reserve?: number;\n /** The standing intent: off means this hook does nothing at all. */\n enabled?: boolean;\n /**\n * Travel to a moved tail instead of teleporting (DECIDED: default on).\n *\n * The glide only covers gaps up to a viewport: beyond that the reader could not have followed the\n * motion anyway, and the travel would cross rows whose extents are still estimates.\n */\n glide?: boolean;\n};\n\n/**\n * The sticky tail as an intent (SPEC: useFollow) — a policy over the window, not part of it.\n *\n * Withdrawn by one thing only: a scroll that moves *backwards*. That is the reader; a correction\n * never moves the offset back, it moves the end away. Re-deriving the intent from proximity on\n * every commit cannot survive its own corrections — measuring the tail grows the document under a\n * reader who has not moved, the gap opens past any threshold worth having, and the follow reads\n * that as the reader leaving and disengages for good (`bridge/Tail`, the second defect).\n *\n * Reusable for any streaming container — transcripts, logs — because nothing here knows about\n * messages: it needs a scroller, a placement, and the two numbers that say the content grew.\n */\nexport const useFollow = ({\n scrollerRef,\n placement,\n extent,\n count,\n axis = 'block',\n reserve = 0,\n enabled,\n glide = true,\n}: UseFollowOptions): FollowHandle => {\n const following = useRef(!!enabled);\n const wasEnabled = useRef(enabled);\n if (wasEnabled.current !== enabled) {\n wasEnabled.current = enabled;\n following.current = !!enabled;\n }\n\n /**\n * Where the reader has to be for the last row's rendered edge to rest on the viewport's.\n *\n * From the row's own rectangle when it is mounted, and from the arithmetic only when it is not:\n * measured extents are rounded to integers and rendered rows are fractional, so over a mounted\n * window the sum drifts from the layout by tens of pixels — an \"arrived\" that the reader can see\n * is short. The rect already includes the window's transform, so the delta is exact.\n */\n const endTarget = useCallback(() => {\n const scroller = scrollerRef.current;\n if (!scroller) {\n return 0;\n }\n\n const last = scroller.querySelector<HTMLElement>(`[data-index=\"${count - 1}\"]`);\n if (last) {\n const current = axis === 'block' ? scroller.scrollTop : scroller.scrollLeft;\n const delta =\n axis === 'block'\n ? last.getBoundingClientRect().bottom - scroller.getBoundingClientRect().bottom\n : last.getBoundingClientRect().right - scroller.getBoundingClientRect().right;\n return Math.max(0, Math.round(current + delta));\n }\n\n return placement.endOffset();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [count, axis, placement, scrollerRef.current]);\n\n // The glide, built once per scroller. Speeds are in rows, so it asks the placement for a live\n // median of what is mounted rather than being handed a constant.\n const follower = useMemo(() => {\n const scroller = scrollerRef.current;\n if (!scroller || axis !== 'block') {\n return undefined;\n }\n\n return new ScrollFollower(scroller, {\n // Faster than the class's defaults, which were tuned for a single arrival: a streaming answer\n // grows at hundreds of px/s, and a follow that cannot outrun the growth reads as falling\n // behind for the whole turn. The ramp is what keeps this from feeling like a snap.\n maxSpeed: 8,\n acceleration: 32,\n deceleration: 10,\n // The follower's target is DOM-derived (scrollHeight - trailing - viewport); the authoritative\n // end is the placement's (the last row's own edge, which the document disagrees with by the\n // drift while estimates are being replaced). Expressing the placement's end *as* a trailing\n // makes the two the same number.\n trailing: () => scroller.scrollHeight - scroller.clientHeight - endTarget(),\n rowHeight: () => {\n const { first, last } = placement.layout();\n const sizes = [];\n for (let index = first; index <= last; index++) {\n sizes.push(placement.extentOf(index));\n }\n\n sizes.sort((a, b) => a - b);\n const median = sizes.length ? sizes[Math.floor(sizes.length / 2)] : 100;\n return Math.min(median, scroller.clientHeight || median);\n },\n });\n // The scroller element is stable for the life of the binding; reserve is read through the closure.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [scrollerRef.current, axis, placement, endTarget]);\n useLayoutEffect(() => () => follower?.cancel(), [follower]);\n\n // The intent, read from gestures. Own listener and own last-offset, deliberately: the window's\n // scroll handler updates `placement.scroll` before this runs, so comparing against it would read\n // every scroll as stationary.\n // Why a backwards scroll alone cannot be the withdrawal: the machinery scrolls backwards too. A\n // shrinking turn makes the browser clamp the offset by itself; a correction can move up; and none\n // of it is the reader. Direction plus pre-announcement was tried and kept dying in races — this\n // is the old engine's proven rule instead: the follow writes `scrollTop`, it does not turn wheels\n // or press keys, so a scroll counts as the reader's only when a gesture preceded it. Returning to\n // the tail, by any means, opts back in.\n const lastOffset = useRef(0);\n const gestureAt = useRef(0);\n useLayoutEffect(() => {\n const scroller = scrollerRef.current;\n if (!scroller || !enabled) {\n return;\n }\n\n const onGesture = () => {\n gestureAt.current = performance.now();\n };\n\n const onScroll = () => {\n const current = axis === 'block' ? scroller.scrollTop : scroller.scrollLeft;\n const viewport = axis === 'block' ? scroller.clientHeight : scroller.clientWidth;\n const total = axis === 'block' ? scroller.scrollHeight : scroller.scrollWidth;\n const back = current < lastOffset.current - 1;\n lastOffset.current = current;\n if (total - reserve - current - viewport <= STICKY_THRESHOLD) {\n following.current = true;\n } else if (back && performance.now() - gestureAt.current < GESTURE_WINDOW) {\n following.current = false;\n follower?.cancel();\n }\n };\n\n scroller.addEventListener('scroll', onScroll, { passive: true });\n // Every way a reader can move a scroll container by hand: the wheel, a touch drag, the\n // keyboard, and a pointer on the scrollbar itself.\n for (const event of ['wheel', 'touchmove', 'keydown', 'pointerdown'] as const) {\n scroller.addEventListener(event, onGesture, { passive: true });\n }\n\n return () => {\n scroller.removeEventListener('scroll', onScroll);\n for (const event of ['wheel', 'touchmove', 'keydown', 'pointerdown'] as const) {\n scroller.removeEventListener(event, onGesture);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [scrollerRef.current, enabled, axis, reserve, follower]);\n\n // The correction. Following is a navigation, not a correction in the §7 sense: the content the\n // reader is pinned to has moved, so the scroll has to as well.\n //\n // Through `endOffset` rather than a jump: a jump re-bases the model on a sum of estimates, and\n // this effect runs again every time the document's extent changes — so each answer mounted rows\n // whose measurement changed the sum the next answer was drawn from. That recurs rather than\n // converging, and React ends it by exceeding the update limit. Keyed on the extent as well as the\n // count: measuring the rows a scroll reveals moves the end, so a tail pinned once drifts off it\n // as the estimates are replaced.\n // Whether the feed has arrived at its tail once. Opening a populated feed is not motion to\n // follow — the reader was not watching the travel, so it arrives; the glide is for content\n // arriving at a tail they are looking at.\n const positioned = useRef(false);\n\n useLayoutEffect(() => {\n const scroller = scrollerRef.current;\n if (!enabled || !following.current || !count || !scroller) {\n return;\n }\n\n const target = endTarget();\n const current = axis === 'block' ? scroller.scrollTop : scroller.scrollLeft;\n const gap = target - current;\n if (Math.abs(gap) <= 1) {\n positioned.current = true;\n return;\n }\n\n const viewport = axis === 'block' ? scroller.clientHeight : scroller.clientWidth;\n if (positioned.current && glide && follower && gap > 0 && gap <= viewport) {\n // The follower recomputes its target every frame, so content arriving faster than the travel\n // keeps the target ahead; when the arrivals stop, the travel lands by decelerating.\n follower.start();\n return;\n }\n\n // A far tail, a backwards correction, or no glide: arrive rather than travel. Written directly,\n // never followed by an invalidate — the write raises a scroll event, which re-renders, which\n // changes the extent this effect watches; invalidating here as well is a loop, and React says so.\n follower?.cancel();\n placement.scrollTo(target);\n // Pre-announced, so the write's own scroll event cannot read as a gesture: a correction can\n // move the offset *backwards* (an over-estimated tail shrinking), and the intent listener would\n // otherwise take that for the reader leaving and withdraw the follow for good.\n lastOffset.current = target;\n if (axis === 'block') {\n scroller.scrollTop = target;\n } else {\n scroller.scrollLeft = target;\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, count, extent, placement, axis, glide, follower, endTarget, scrollerRef.current]);\n\n const onNavigate = useCallback(\n (index: number) => {\n const wants = index >= count - 1;\n following.current = !!enabled && wants;\n if (!wants) {\n follower?.cancel();\n }\n },\n [enabled, count, follower],\n );\n\n return { onNavigate };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type MouseEvent, type PropsWithChildren, createContext, useCallback, useContext } from 'react';\n\n/** What an item can do about its own selection — the item-side face of the host's selection model. */\nexport type ItemSelection = {\n selected: boolean;\n /** Additive when the gesture says so (meta/ctrl); the mode is the provider's business. */\n toggle: (additive?: boolean) => void;\n};\n\nexport type ItemSelectionValue = {\n selectedIds?: ReadonlySet<string>;\n onSelect?: (id: string, additive: boolean) => void;\n};\n\nconst ItemSelectionContext = createContext<ItemSelectionValue>({});\n\nexport type ItemSelectionProviderProps = PropsWithChildren<ItemSelectionValue>;\n\n/**\n * The item-side hook of the decoration pattern applied to selection (SPEC: useItemSelection): an\n * item asks for its own `{ selected, toggle }` by id instead of the list threading `selectedIds`\n * through props. Selection is model-space ids, so virtualization is untouched: an unmounted item is\n * still selected, and mounts knowing it.\n *\n * The provider is deliberately the thinnest thing that works — a set and a callback. Mode policies\n * (single | multi | range) belong to `react-ui-list`'s `useListSelection`, which a host composes\n * *above* this provider; teaching this package about modes would be rebuilding that hook.\n */\nexport const ItemSelectionProvider = ({ selectedIds, onSelect, children }: ItemSelectionProviderProps) => (\n <ItemSelectionContext.Provider value={{ selectedIds, onSelect }}>{children}</ItemSelectionContext.Provider>\n);\n\n/**\n * The whole selection value, for a component that renders many items in one pass.\n *\n * A row loop cannot call `useItemSelection` per row — hooks do not run in loops — so the viewport\n * reads the value once and derives per-row state; item-shaped components use the per-id hook.\n */\nexport const useItemSelectionValue = (): ItemSelectionValue => useContext(ItemSelectionContext);\n\nexport const useItemSelection = (id: string): ItemSelection => {\n const { selectedIds, onSelect } = useContext(ItemSelectionContext);\n const toggle = useCallback((additive = true) => onSelect?.(id, additive), [id, onSelect]);\n return { selected: selectedIds?.has(id) ?? false, toggle };\n};\n\n/** Reads the additive intent off a gesture, so callers do not each invent the modifier rule. */\nexport const isAdditive = (event: MouseEvent | globalThis.MouseEvent): boolean =>\n event.metaKey || event.ctrlKey || event.shiftKey;\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\nimport { createContext, useContext, useMemo } from 'react';\n\n/**\n * Coordinates selection across the items of one feed.\n *\n * Each item is its own editor with its own selection state, and `drawSelection` paints that state\n * whether or not the view has focus — so without a coordinator every message the reader has ever\n * selected in stays highlighted at once. The feed has one selection, so claiming it in one item\n * collapses it everywhere else.\n */\nexport type SelectionGroup = {\n /** Adds a view to the group; returns the disposer. */\n register: (view: EditorView) => () => void;\n /** Makes `view` the sole owner of the feed's selection. */\n claim: (view: EditorView) => void;\n};\n\n/** Creates a group; one per feed, provided to its items by `MessageList.Root`. */\nexport const createSelectionGroup = (): SelectionGroup => {\n const views = new Set<EditorView>();\n\n return {\n register: (view) => {\n views.add(view);\n return () => views.delete(view);\n },\n\n claim: (view) => {\n for (const other of views) {\n if (other === view || other.state.selection.main.empty) {\n continue;\n }\n // Collapsing re-enters this listener for `other`, but with an empty selection, which claims\n // nothing — so the cascade terminates after one pass.\n other.dispatch({ selection: { anchor: 0 } });\n }\n },\n };\n};\n\nexport const SelectionGroupContext = createContext<SelectionGroup | undefined>(undefined);\n\n/** The enclosing feed's selection group, or a private one when an item is used standalone. */\nexport const useSelectionGroup = (): SelectionGroup => {\n const provided = useContext(SelectionGroupContext);\n const fallback = useMemo(createSelectionGroup, []);\n return provided ?? fallback;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { StateEffect, StateField } from '@codemirror/state';\nimport { Decoration, type DecorationSet, EditorView } from '@codemirror/view';\n\n/** Ranges to highlight, as [from, to) offsets into the item's document. */\nexport type HighlightRange = readonly [number, number];\n\nexport const setHighlights = StateEffect.define<readonly HighlightRange[]>();\n\nconst mark = Decoration.mark({ class: 'dx-feed-hit' });\n\nconst clamp = (offset: number, length: number) => Math.max(0, Math.min(offset, length));\n\n/**\n * Highlights pushed in from the model.\n *\n * Search runs over the feed's projection rather than per-view (most messages are never mounted), so\n * the item receives ranges instead of owning search state.\n */\nexport const highlights = StateField.define<DecorationSet>({\n create: () => Decoration.none,\n update: (value, tr) => {\n for (const effect of tr.effects) {\n if (effect.is(setHighlights)) {\n const length = tr.state.doc.length;\n return Decoration.set(\n effect.value\n // Clamp against the current document at both ends: a hit computed on the model can\n // outrun a view whose streaming tail has not arrived yet, and a decoration outside\n // `[0, length]` throws rather than being ignored.\n .map(([from, to]) => [clamp(from, length), clamp(to, length)] as const)\n .filter(([from, to]) => from < to)\n .map(([from, to]) => mark.range(from, to)),\n true,\n );\n }\n }\n\n return tr.docChanged ? value.map(tr.changes) : value;\n },\n provide: (field) => EditorView.decorations.from(field),\n});\n\nexport const highlightTheme = EditorView.baseTheme({\n '.dx-feed-hit': {\n backgroundColor: 'var(--dx-accentFill)',\n color: 'var(--dx-accentFillText)',\n borderRadius: '2px',\n },\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type PropsWithChildren, createContext, useCallback, useContext, useMemo, useState } from 'react';\n\n/**\n * State a widget keeps that must outlive the row it is drawn in.\n *\n * A virtualized feed destroys a row when it leaves the window, and React state inside a widget dies\n * with it — so a reader who expands a tool panel, scrolls away and comes back finds it shut. In a\n * thread that is one long document the question never arises, because nothing ever unmounts; it is\n * the cost of virtualization, and it has to be paid somewhere above the row.\n *\n * Here, in a map owned by `MessageList.Root`. Not in the message, because whether a panel is open is\n * the reader's business and not part of what was said.\n */\nexport type WidgetStateStore = {\n read: (key: string) => unknown;\n write: (key: string, value: unknown) => void;\n};\n\nconst WidgetStateContext = createContext<WidgetStateStore | undefined>(undefined);\n\n/** Scopes a widget's key to its message, so two messages' widgets cannot collide. */\nconst WidgetScopeContext = createContext<string>('');\n\nexport const WidgetStateProvider = ({ store, children }: PropsWithChildren<{ store: WidgetStateStore }>) => (\n <WidgetStateContext.Provider value={store}>{children}</WidgetStateContext.Provider>\n);\n\nexport const WidgetScopeProvider = ({ scope, children }: PropsWithChildren<{ scope: string }>) => (\n <WidgetScopeContext.Provider value={scope}>{children}</WidgetScopeContext.Provider>\n);\n\n/** Creates the store a feed's widgets share. One per `MessageList.Root`. */\nexport const createWidgetStateStore = (): WidgetStateStore => {\n const values = new Map<string, unknown>();\n return {\n read: (key) => values.get(key),\n write: (key, value) => {\n values.set(key, value);\n },\n };\n};\n\n/**\n * `useState` for a widget, keyed so it survives the row being destroyed and rebuilt.\n *\n * The key must identify the widget within its message — its tag and position in the document, say —\n * and is scoped to the message automatically. Outside a feed this degrades to plain `useState`, so a\n * widget can still be rendered on its own.\n */\nexport const useWidgetState = <T,>(key: string, initial: T): [T, (value: T) => void] => {\n const store = useContext(WidgetStateContext);\n const scope = useContext(WidgetScopeContext);\n const scoped = useMemo(() => `${scope}:${key}`, [scope, key]);\n\n // Seeded from the store on mount, which is the whole point: a remounted widget starts from what\n // the reader last left it at rather than from the default.\n const [value, setValue] = useState<T>(() => {\n const stored = store?.read(scoped);\n return (stored === undefined ? initial : stored) as T;\n });\n\n const set = useCallback(\n (next: T) => {\n store?.write(scoped, next);\n setValue(next);\n },\n [store, scoped],\n );\n\n return [value, set];\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport DOMPurify from 'dompurify';\nimport React, { memo, useMemo } from 'react';\n\nexport type HtmlBlockProps = {\n html: string;\n};\n\n/**\n * A message rendered as sanitized HTML — the email case, which is not markdown and cannot be a\n * CodeMirror document.\n *\n * Its presence is the reason the item renderer is generalized rather than markdown-only: the\n * engine owns virtualization, chrome and selection anchors, not what a message looks like inside.\n * As plain (non-contenteditable) DOM it is also the control case for cross-item selection.\n */\nexport const HtmlBlock = memo(({ html }: HtmlBlockProps) => {\n const sanitized = useMemo(() => DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }), [html]);\n\n return <div className='dx-feed-html' dangerouslySetInnerHTML={{ __html: sanitized }} />;\n});\n\nHtmlBlock.displayName = 'HtmlBlock';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\n\nimport {\n type XmlWidgetRegistry,\n type XmlWidgetState,\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n extendedMarkdown,\n xmlBlockDecoration,\n xmlFormatting,\n xmlTags,\n} from '@dxos/ui-editor';\n\nimport { highlights, highlightTheme } from './highlight';\n\nexport type ItemExtensionOptions = {\n registry?: XmlWidgetRegistry;\n editable?: boolean;\n themeMode?: 'light' | 'dark';\n setWidgets?: (widgets: XmlWidgetState[]) => void;\n};\n\n/**\n * The extension set one item's document is built from.\n *\n * Everything that does not depend on the item is **built once and shared**, and that is the point of\n * this module rather than a tidiness. `EditorView.theme()` mints a new `StyleModule` and a new\n * generated class on every call, so a set built per item injects one stylesheet per row and\n * invalidates the whole document's style each time it does. Measured at ~14ms per row against ~2ms\n * for the identical set shared (`baseline/mount`) — six times the cost of the first fill, for\n * stylesheets that are all the same.\n *\n * Extensions are descriptors, not instances: a `StateField` or `ViewPlugin` in a shared array is\n * still instantiated per view, so sharing changes nothing but the identity CodeMirror dedupes on.\n */\nexport const createBlockExtensions = ({\n registry,\n editable = false,\n themeMode = 'light',\n setWidgets,\n}: ItemExtensionOptions = {}): Extension[] => [\n ...sharedExtensions(registry, editable, themeMode),\n // The one part that cannot be shared: the callback that hands this item's widgets back to it.\n ...(registry ? [xmlTags({ registry, setWidgets: setWidgets ?? (() => {}), bookmarks: ['prompt'] })] : []),\n];\n\n/** Registries are compared by identity, so a feed's single registry is a single cache scope. */\nconst NO_REGISTRY = {};\n\nconst cache = new WeakMap<object, Map<string, Extension[]>>();\n\nconst sharedExtensions = (\n registry: XmlWidgetRegistry | undefined,\n editable: boolean,\n themeMode: 'light' | 'dark',\n): Extension[] => {\n const scope = registry ?? NO_REGISTRY;\n let byVariant = cache.get(scope);\n if (!byVariant) {\n byVariant = new Map();\n cache.set(scope, byVariant);\n }\n\n const key = `${editable}:${themeMode}`;\n let extensions = byVariant.get(key);\n if (!extensions) {\n extensions = build(registry, editable, themeMode);\n byVariant.set(key, extensions);\n }\n\n return extensions;\n};\n\nconst build = (registry: XmlWidgetRegistry | undefined, editable: boolean, themeMode: 'light' | 'dark'): Extension[] =>\n [\n createBasicExtensions({ readOnly: !editable, editable, lineWrapping: true }),\n createThemeExtensions({ themeMode }),\n // A registry changes how the document is *parsed*, not only how it is decorated: registered\n // tags have to survive as single blocks through the markdown parser before `xmlTags` can\n // replace them, and without that they render as the literal angle brackets they are.\n registry ? extendedMarkdown({ registry }) : createMarkdownExtensions(),\n registry && xmlFormatting({ skip: ['prompt'] }),\n decorateMarkdown(),\n // The tags are hidden but the prompt is NOT framed here: the frame is chrome's, which also\n // owns the rewind toolbar under it. Styling it in both places drew the border twice.\n registry?.prompt && xmlBlockDecoration({ tag: 'prompt', hideTags: true }),\n highlights,\n highlightTheme,\n ].filter(Boolean) as Extension[];\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { EditorState, type Extension } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\nimport React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useThemeContext } from '@dxos/react-ui';\nimport { type XmlWidgetRegistry, type XmlWidgetState } from '@dxos/ui-editor';\n\nimport { createBlockExtensions } from './extensions';\nimport { type HighlightRange, setHighlights } from './highlight';\nimport { useSelectionGroup } from './selection-group';\n\nexport type MarkdownBlockProps = {\n text: string;\n /**\n * When false the view mounts with `EditorView.editable.of(false)`, which drops `contenteditable`\n * from the DOM. Browsers refuse to extend one selection across two contenteditable hosts, so this\n * is what decides whether a drag can span adjacent items.\n */\n editable?: boolean;\n registry?: XmlWidgetRegistry;\n hits?: readonly HighlightRange[];\n /** Number of block widgets this item currently has mounted; 0 once it unmounts. */\n onWidgetsChange?: (count: number) => void;\n};\n\n/**\n * One message as its own markdown document.\n *\n * Each item owns a document, so streaming appends and per-message editing stay local — where a\n * single thread-wide document needs a cursor and a range table to know which message it is touching.\n */\nexport const MarkdownBlock = memo(({ text, editable = false, registry, hits, onWidgetsChange }: MarkdownBlockProps) => {\n const { themeMode } = useThemeContext();\n const [view, setView] = useState<EditorView | null>(null);\n // React widgets render in portals into hosts the extension places in the document, so the item has\n // to own them: a widget's tree belongs to the React root that rendered the item, not to CodeMirror.\n const [widgets, setWidgets] = useState<XmlWidgetState[]>([]);\n const rootRef = useRef<HTMLDivElement>(null);\n\n // Read through a ref so the group never lands in the extension deps: rebuilding extensions\n // reconstructs the view, which would discard the selection the reader just made.\n const selectionGroup = useSelectionGroup();\n const selectionGroupRef = useRef(selectionGroup);\n selectionGroupRef.current = selectionGroup;\n\n const extensions = useMemo<Extension[]>(\n () => [\n ...createBlockExtensions({ registry, editable, themeMode, setWidgets }),\n EditorView.updateListener.of((update) => {\n if (update.selectionSet && !update.state.selection.main.empty) {\n selectionGroupRef.current.claim(update.view);\n }\n }),\n ],\n [editable, themeMode, registry],\n );\n\n // Deliberately NOT `useTextEditor`, which builds the view in a passive effect — i.e. after paint.\n // The virtualizer measures each row as it mounts, so a row whose document does not exist yet\n // measures at chrome height and then grows, shifting every row below it. Constructing the view in\n // the layout phase gives the row its real height before the first paint, so it is measured once.\n const initialTextRef = useRef(text);\n useLayoutEffect(() => {\n const parent = rootRef.current;\n if (!parent) {\n return;\n }\n\n const instance = new EditorView({\n parent,\n state: EditorState.create({ doc: initialTextRef.current, extensions }),\n });\n const unregister = selectionGroupRef.current.register(instance);\n setView(instance);\n\n return () => {\n unregister();\n instance.destroy();\n setView(null);\n };\n }, [extensions]);\n\n // The document belongs to the model, not the view: a message whose text changes is reconciled\n // rather than remounted, so the virtualizer's measurement for this row survives the update.\n const currentRef = useRef(text);\n useEffect(() => {\n if (!view || currentRef.current === text) {\n return;\n }\n\n const previous = currentRef.current;\n currentRef.current = text;\n initialTextRef.current = text;\n // A streaming tail only ever extends: dispatch the delta so CodeMirror keeps scroll position and\n // decorations instead of rebuilding the document on every frame.\n view.dispatch(\n text.startsWith(previous)\n ? { changes: { from: previous.length, insert: text.slice(previous.length) } }\n : { changes: { from: 0, to: view.state.doc.length, insert: text } },\n );\n }, [view, text]);\n\n // Nothing is dispatched to clear highlights that were never set: a transaction makes CodeMirror\n // re-measure the document, and a viewport of rows mounting is a viewport of forced layouts for a\n // search that is not running.\n const highlighted = useRef(false);\n useEffect(() => {\n if (!view || (!hits?.length && !highlighted.current)) {\n return;\n }\n\n highlighted.current = Boolean(hits?.length);\n view.dispatch({ effects: setHighlights.of(hits ?? []) });\n }, [view, hits]);\n\n // Reported rather than derived: the widgets are portals into the item's own document, so nothing\n // above the item can count them without reaching into the DOM.\n useEffect(() => {\n onWidgetsChange?.(widgets.length);\n return () => onWidgetsChange?.(0);\n }, [widgets.length, onWidgetsChange]);\n\n // A block widget is measured once, when CodeMirror mounts it. Its content is React rendered into a\n // portal, so anything that changes height afterwards — a disclosure opening, an image loading —\n // leaves the editor holding a stale height and drawing the lines below it over the widget.\n //\n // Observing the portal roots and asking for a re-measure is what keeps the document's geometry\n // honest; the row's own height then follows through the virtualizer's observer.\n useEffect(() => {\n if (!view || !widgets.length) {\n return;\n }\n\n const observer = new ResizeObserver(() => view.requestMeasure());\n for (const { root } of widgets) {\n observer.observe(root);\n }\n\n return () => observer.disconnect();\n }, [view, widgets]);\n\n return (\n <>\n <div ref={rootRef} />\n {widgets.map(({ Component, root, id, props }) => (\n <div key={id}>{createPortal(<Component view={view ?? undefined} {...props} />, root)}</div>\n ))}\n </>\n );\n});\n\nMarkdownBlock.displayName = 'MarkdownBlock';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type ContentBlock, type Message } from '@dxos/types';\n\n/**\n * What an item renders. The engine is renderer-agnostic: a message may resolve to a markdown\n * document, to raw HTML (email bodies, which are not markdown and cannot be a CodeMirror document),\n * or to a caller-supplied component.\n */\nexport type ItemContent =\n | { kind: 'markdown'; text: string }\n | { kind: 'html'; html: string }\n | { kind: 'custom'; key: string; data?: unknown };\n\n/** Resolves a message to the content its item renders. One per scenario (chat, email, …). */\nexport type MessageRenderer = (message: Message.Message) => ItemContent;\n\n/**\n * Default renderer: an `text/html` block wins (email), otherwise every block is flattened to\n * markdown. Block kinds the engine does not know become fenced JSON so nothing renders as blank.\n */\nexport const defaultRenderer: MessageRenderer = (message) => createContent(message, false);\n\n/**\n * Assistant chat: as {@link defaultRenderer}, plus the reader's own turns marked with `<prompt>`.\n *\n * Separate because the tag is only meaningful where a widget registry knows it — a feed rendered\n * without one would show the reader their own angle brackets, which is what a human chat, a\n * transcript and a comment list all are.\n */\nexport const chatRenderer: MessageRenderer = (message) => createContent(message, true);\n\nconst createContent = (message: Message.Message, prompts: boolean): ItemContent => {\n const textBlocks = message.blocks.filter((block): block is ContentBlock.Text => block._tag === 'text');\n const html = textBlocks.find((block) => block.mimeType === 'text/html');\n if (html) {\n return { kind: 'html', html: html.text };\n }\n\n return {\n kind: 'markdown',\n text: message.blocks\n .map((block) => blockToMarkdown(message, block, prompts))\n .filter(Boolean)\n .join('\\n\\n'),\n };\n};\n\n/**\n * One block as markdown, with anything that is not prose emitted as an XML tag for a widget to\n * render. A message is the concatenation of these, which is what makes an item a document rather\n * than a component tree: the blocks are content, and the chrome around them belongs to the host.\n */\nconst blockToMarkdown = (message: Message.Message, block: ContentBlock.Any, prompts: boolean): string => {\n switch (block._tag) {\n case 'text':\n // A prompt is marked rather than styled: the reader's own words are content, and the widget\n // that frames them is the host's business.\n return prompts && message.sender.role === 'user' ? tag('prompt', block.text, block) : block.text.trim();\n case 'reasoning':\n return tag('reasoning', block.reasoningText ?? block.redactedText ?? '', block);\n case 'status':\n return tag('status', block.statusText, block);\n case 'summary':\n return tag('summary', block.content, block);\n case 'suggestion':\n return tag('suggestion', block.text, block);\n case 'select':\n return block.options.length\n ? `<select>${block.options.map((option) => `<option>${escapeXml(option)}</option>`).join('')}</select>`\n : '';\n case 'toolCall':\n // Name and status ride on the tag rather than in widget state kept beside the document: an\n // item is rebuilt from its message alone, so anything the widget needs has to survive there.\n return `<toolCall id=\"${escapeXml(block.toolCallId)}\" name=\"${escapeXml(block.name)}\"${block.pending ? ' pending=\"true\"' : ''} />`;\n case 'toolResult':\n return tag('toolResult', String(block.result ?? block.error ?? ''), block, `for=\"${escapeXml(block.name)}\"`);\n default:\n // Nothing renders blank: an unknown block is shown as what it is.\n return tag('json', JSON.stringify(block), block);\n }\n};\n\n/**\n * An XML tag holding block content.\n *\n * A pending block is emitted **unclosed**, which is the streaming contract: the item reconciles by\n * appending, so a closing tag written before the content is complete would have to be rewritten on\n * every chunk — and rewriting the document is what discards decorations and scroll position.\n *\n * Paragraph breaks inside the content are collapsed, since a blank line would end the enclosing\n * markdown block and leave the rest of the tag parsing as prose.\n */\nconst tag = (name: string, content: string, block: ContentBlock.Any, attributes?: string): string => {\n const text = escapeXml(content.replace(/\\n\\n+/g, ' ').trim());\n if (!text.length && !block.pending) {\n return '';\n }\n\n const open = `<${name}${attributes ? ` ${attributes}` : ''}>`;\n return block.pending ? `${open}${text}` : `${open}${text}</${name}>`;\n};\n\nconst escapeXml = (raw: string): string => raw.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n/** Plain text of a message, used by search and copy so both read the same projection. */\nexport const messageText = (message: Message.Message, renderer: MessageRenderer): string => {\n const content = renderer(message);\n switch (content.kind) {\n case 'markdown':\n return content.text;\n case 'html':\n return stripHtml(content.html);\n case 'custom':\n return '';\n }\n};\n\n/** Crude tag strip — the engine only needs searchable/copyable text, not a parse tree. */\nconst stripHtml = (html: string): string =>\n html\n .replace(/<[^>]*>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n//\n// Search\n//\n\nexport type SearchHit = {\n messageId: string;\n /** Index into the message list, so the virtualizer can scroll to it without a second lookup. */\n index: number;\n /** Offset into the message's rendered text. */\n offset: number;\n length: number;\n};\n\n/**\n * Search the model rather than the DOM.\n *\n * Virtualization means most messages are not mounted, and a `viewType` filter can hide blocks that\n * are still in the model — neither is reachable by a per-view CodeMirror search, so thread-wide find\n * has to run over the projection.\n */\nexport const searchFeed = (\n messages: readonly Message.Message[],\n renderer: MessageRenderer,\n query: string,\n): SearchHit[] => {\n const hits: SearchHit[] = [];\n if (!query.length) {\n return hits;\n }\n\n const needle = query.toLowerCase();\n messages.forEach((message, index) => {\n const text = messageText(message, renderer).toLowerCase();\n let offset = text.indexOf(needle);\n while (offset !== -1) {\n hits.push({ messageId: message.id, index, offset, length: query.length });\n offset = text.indexOf(needle, offset + needle.length);\n }\n });\n\n return hits;\n};\n\n//\n// Selection\n//\n\n/** A point in the feed: a message plus an offset into its rendered text. */\nexport type FeedAnchor = { messageId: string; offset: number };\n\nexport type FeedRange = { from: FeedAnchor; to: FeedAnchor };\n\n/**\n * Text between two anchors, spanning message boundaries.\n *\n * Native selection cannot span separate editor instances, and unmounted messages are not in the DOM\n * at all, so cross-message copy is reconstructed from the model.\n */\nexport const sliceFeed = (\n messages: readonly Message.Message[],\n renderer: MessageRenderer,\n range: FeedRange,\n): string => {\n const fromIndex = messages.findIndex((message) => message.id === range.from.messageId);\n const toIndex = messages.findIndex((message) => message.id === range.to.messageId);\n if (fromIndex === -1 || toIndex === -1) {\n return '';\n }\n\n // Normalize so a backwards drag (focus before anchor) yields the same text as a forwards one.\n const [start, end] =\n fromIndex < toIndex || (fromIndex === toIndex && range.from.offset <= range.to.offset)\n ? [range.from, range.to]\n : [range.to, range.from];\n\n const [startIndex, endIndex] = fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];\n\n const parts: string[] = [];\n for (let index = startIndex; index <= endIndex; index++) {\n const text = messageText(messages[index], renderer);\n const from = index === startIndex ? start.offset : 0;\n const to = index === endIndex ? end.offset : text.length;\n parts.push(text.slice(from, to));\n }\n\n return parts.join('\\n\\n');\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Atom from 'effect/unstable/reactivity/Atom';\nimport * as Registry from 'effect/unstable/reactivity/AtomRegistry';\n\n/**\n * What one change to the model actually was — told, never inferred.\n *\n * The virtualizer's anchor survives a prepend only if it knows the prepend happened (SPEC F-7.1);\n * the engine used to reconstruct that by scanning for the previous first id, which is the model's\n * knowledge recovered from its absence. Here the model says so. The identity scan still exists in\n * exactly one place — `replace`, the adapter for hosts that hand over a whole new array — because\n * that is the one caller that genuinely does not know.\n */\nexport type ListChange = {\n prepended?: number;\n appended?: number;\n /** Ids whose content changed in place — a streaming tail, an edit. */\n updated?: readonly string[];\n};\n\nexport type ListModelOptions<T> = {\n items?: readonly T[];\n /** Identity of an item. Measurements, selection and decorations are all keyed by this. */\n getId: (item: T) => string;\n};\n\n/**\n * The low-level reactive collection the virtualizer binds to (SPEC: ListModel).\n *\n * Two faces on one state, deliberately: a plain synchronous face (`count`, `at`, `subscribe`) that\n * the virtualizer consumes without dragging an atom registry into every story and test, and an\n * atom face (`rowsAtom`) for hosts that live in atom-land — derivations (stops, decorations,\n * readouts) hang off the atom rather than becoming parallel props. Both are the same array; the\n * subscribe face is not a fallback but the binding's contract.\n */\nexport class ListModel<T> {\n #items: T[];\n readonly #getId: (item: T) => string;\n readonly #listeners = new Set<(change: ListChange) => void>();\n\n /** The rows as an atom, for derivations. Registry-managed by whoever consumes it. */\n readonly rowsAtom: Atom.Writable<readonly T[]>;\n readonly #registry: Registry.AtomRegistry;\n\n constructor({ items = [], getId }: ListModelOptions<T>) {\n this.#items = [...items];\n this.#getId = getId;\n this.rowsAtom = Atom.make<readonly T[]>(this.#items);\n // A model owns its registry so the atom face works without a provider; a host embedded in a\n // wider registry can still read `rowsAtom` through its own.\n this.#registry = Registry.make();\n }\n\n get count(): number {\n return this.#items.length;\n }\n\n get items(): readonly T[] {\n return this.#items;\n }\n\n getId = (index: number): string => {\n const item = this.#items[index];\n return item === undefined ? `missing-${index}` : this.#getId(item);\n };\n\n at = (index: number): T | undefined => this.#items[index];\n\n indexOf(id: string): number {\n return this.#items.findIndex((item) => this.#getId(item) === id);\n }\n\n subscribe(listener: (change: ListChange) => void): () => void {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n append(items: readonly T[]): void {\n if (!items.length) {\n return;\n }\n\n this.#items = [...this.#items, ...items];\n this.#publish({ appended: items.length });\n }\n\n prepend(items: readonly T[]): void {\n if (!items.length) {\n return;\n }\n\n this.#items = [...items, ...this.#items];\n this.#publish({ prepended: items.length });\n }\n\n /** An item changed in place — same identity, new content. */\n update(id: string): void {\n this.#publish({ updated: [id] });\n }\n\n /**\n * Replace one item with a successor of the same identity — the streaming case for *frozen*\n * items. An ECHO object mutates and `update` announces it; a schema-made message is frozen, so\n * the chunk arrives as a fresh value under the old id. Same event either way: `updated`.\n */\n patch(id: string, next: T): void {\n const index = this.#items.findIndex((item) => this.#getId(item) === id);\n if (index < 0 || this.#getId(next) !== id) {\n return;\n }\n\n this.#items = [...this.#items.slice(0, index), next, ...this.#items.slice(index + 1)];\n this.#publish({ updated: [id] });\n }\n\n /**\n * The adapter for hosts that hand over a whole new array (a React prop, a query result).\n *\n * The one place the prepend inference survives, because this is the one caller that genuinely\n * does not know what changed: the old first item is found in the new array, and everything before\n * it was prepended. The virtualizer itself is always told (SPEC F-7.1).\n */\n replace(items: readonly T[]): void {\n const previous = this.#items;\n const previousFirst = previous.length ? this.#getId(previous[0]) : undefined;\n this.#items = [...items];\n\n let prepended = 0;\n if (previousFirst !== undefined && items.length > previous.length) {\n const grew = items.length - previous.length;\n for (let index = 1; index <= grew; index++) {\n if (items[index] !== undefined && this.#getId(items[index]) === previousFirst) {\n prepended = index;\n break;\n }\n }\n }\n\n const appended = prepended ? 0 : Math.max(0, items.length - previous.length);\n this.#publish({ prepended: prepended || undefined, appended: appended || undefined });\n }\n\n #publish(change: ListChange): void {\n this.#registry.set(this.rowsAtom, this.#items);\n for (const listener of this.#listeners) {\n listener(change);\n }\n }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Message } from '@dxos/types';\n\nimport { type MessageRenderer, type SearchHit, defaultRenderer, messageText, searchFeed } from './feed-model';\nimport { ListModel } from './list-model';\n\n/**\n * One position navigation can land on (SPEC: Stop).\n *\n * Produced by a pluggable policy over the model and consumed identically by every driver —\n * toolbar, arrow keys, outliner, minimap — which is what keeps the rails and the keymap from\n * growing parallel wiring.\n */\nexport type Stop = {\n index: number;\n id: string;\n label?: string;\n};\n\n/** The built-in policies; a host may hand a function instead. */\nexport type StopsPolicy = 'message' | 'prompt' | ((message: Message.Message, index: number) => boolean);\n\nexport type FeedModelOptions = {\n messages?: readonly Message.Message[];\n stops?: StopsPolicy;\n /** Paged history: consumed when the window reaches the start edge. Paging is the model's business. */\n loadBefore?: () => Promise<readonly Message.Message[]>;\n /** The forward twin, for feeds that are windows into something longer. */\n loadAfter?: () => Promise<readonly Message.Message[]>;\n};\n\n/**\n * The native case, not an add-on (SPEC: FeedModel): a ListModel of messages carrying the feed's\n * own semantics — the stops policy, the streaming tail, and iteration. The list consumes this and\n * nothing else; `hits`, `isAnchor` and `streamingId` all left the props API and live here or in\n * the decoration provider.\n *\n * ECHO/queue bindings adapt to this outside the package; `fromMessages` adapts the plain case.\n */\nexport class FeedModel extends ListModel<Message.Message> {\n #stops: StopsPolicy;\n #streamingId: string | undefined;\n #loading = false;\n readonly #loadBefore?: () => Promise<readonly Message.Message[]>;\n readonly #loadAfter?: () => Promise<readonly Message.Message[]>;\n\n constructor({ messages = [], stops = 'message', loadBefore, loadAfter }: FeedModelOptions = {}) {\n super({ items: messages, getId: (message) => message.id });\n this.#stops = stops;\n this.#loadBefore = loadBefore;\n this.#loadAfter = loadAfter;\n }\n\n get messages(): readonly Message.Message[] {\n return this.items;\n }\n\n /** The tail message reconciling by delta; its item must not remount per chunk. */\n get streamingId(): string | undefined {\n return this.#streamingId;\n }\n\n setStreaming(id: string | undefined): void {\n const previous = this.#streamingId;\n this.#streamingId = id;\n // The item that stopped (or started) streaming re-renders; the rest of the feed does not care.\n for (const changed of [previous, id]) {\n if (changed) {\n this.update(changed);\n }\n }\n }\n\n /** A chunk arrived for the streaming tail: same identity, longer content. */\n stream(id: string): void {\n this.update(id);\n }\n\n setStops(stops: StopsPolicy): void {\n this.#stops = stops;\n }\n\n /**\n * The positions navigation can land on, under the current policy.\n *\n * Recomputed on demand rather than cached: a policy is a predicate over the model, and the model\n * knows when it changed — callers that want reactivity derive from `rowsAtom`.\n */\n stops(): Stop[] {\n const predicate =\n this.#stops === 'message'\n ? () => true\n : this.#stops === 'prompt'\n ? (message: Message.Message) => message.sender.role === 'user'\n : this.#stops;\n\n const stops: Stop[] = [];\n this.messages.forEach((message, index) => {\n if (predicate(message, index)) {\n stops.push({ index, id: message.id });\n }\n });\n\n return stops;\n }\n\n /**\n * The window reached the start: ask the source for history.\n *\n * The model owns iteration (SPEC F-7.2) — the pages arrive as a prepend, which the virtualizer\n * is told about, so nothing on screen moves (F-1.1). Serialized, because edges are reached once\n * per frame while the reader sits on them.\n */\n async more(edge: 'start' | 'end'): Promise<boolean> {\n const load = edge === 'start' ? this.#loadBefore : this.#loadAfter;\n if (!load || this.#loading) {\n return false;\n }\n\n this.#loading = true;\n try {\n const page = await load();\n if (!page.length) {\n return false;\n }\n\n if (edge === 'start') {\n this.prepend(page);\n } else {\n this.append(page);\n }\n\n return true;\n } finally {\n this.#loading = false;\n }\n }\n\n /** Model-level search, since virtualization hides most items from any DOM search. */\n search(query: string, renderer: MessageRenderer = defaultRenderer): SearchHit[] {\n return searchFeed(this.messages, renderer, query);\n }\n\n /** The rendered text of one message, for previews and rails. */\n textOf(id: string, renderer: MessageRenderer = defaultRenderer): string | undefined {\n const index = this.indexOf(id);\n const message = this.at(index);\n return message && messageText(message, renderer);\n }\n}\n\n/** The plain case: a feed over an array the host already has. */\nexport const fromMessages = (messages: readonly Message.Message[], options: Omit<FeedModelOptions, 'messages'> = {}) =>\n new FeedModel({ messages, ...options });\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useEffect, useMemo } from 'react';\n\nimport { type Message } from '@dxos/types';\n\nimport { FeedModel, type FeedModelOptions } from './feed';\nimport { ListModel } from './list-model';\n\n/**\n * A model over an array the host re-creates per render — the React-prop case.\n *\n * The model is stable; the array is folded into it with `replace`, which is where the prepend\n * inference lives (and the only place — SPEC F-7.1). In an effect rather than during render: the\n * model publishes synchronously to its subscribers, and a subscriber invalidating itself while the\n * host is still rendering is a cross-component update React rejects. The window it opens is one\n * frame, and the window's own subscription closes it.\n */\nexport const useListModel = <T>(items: readonly T[], getId: (item: T) => string): ListModel<T> => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const model = useMemo(() => new ListModel({ items, getId }), []);\n useEffect(() => {\n if (model.items !== items) {\n model.replace(items);\n }\n }, [model, items]);\n\n return model;\n};\n\n/**\n * The feed-shaped twin, for a host that re-creates its message array per render.\n *\n * Streaming through this adapter is a `replace` whose length does not change; the model still\n * publishes, so the window still re-renders, and the streaming item reconciles by document delta\n * exactly as it would under a told update.\n */\nexport const useFeedModel = (\n messages: readonly Message.Message[],\n options: Omit<FeedModelOptions, 'messages'> = {},\n): FeedModel => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const model = useMemo(() => new FeedModel({ messages, ...options }), []);\n useEffect(() => {\n if (model.messages !== messages) {\n model.replace(messages);\n }\n }, [model, messages]);\n\n return model;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * Where the rows go, in one number each.\n *\n * Scalars only — `start`, `extent`, `viewport` — never `top` or `height`, so the axis is the DOM\n * binding's business and never gets baked in here (§9). No DOM, no React, no CodeMirror: everything\n * this decides can be decided from arithmetic, and everything it cannot decide is a measurement\n * somebody hands it.\n *\n * The model is one authoritative fact and derivations from it: an **anchor**, identified by message\n * id, whose absolute position in content space is known. Every other row's position is that position\n * plus or minus the extents in between. Prepending rows does not move the anchor, so it does not move\n * anything at or after it — which is the property §7 needs and the reason this is not a prefix sum\n * from index 0.\n */\nexport type Extents = {\n /**\n * Extent along the scroll axis of the row at `index`, when nothing has been measured for it.\n */\n of: (index: number) => number;\n /**\n * Never revised by measurement.\n *\n * The layout constrains the extent — a horizontal feed of declared-width items, a list of\n * fixed-height rows — so offsets are an exact prefix sum and there is nothing to correct. It does\n * **not** mean unchecked: the binding still measures in dev and reports a mismatch (§8).\n */\n exact?: boolean;\n};\n\nexport type PlacementOptions = {\n count: number;\n /** Identity of the row at `index`. Extents are stored against this, so a prepend cannot shift them. */\n getId: (index: number) => string;\n extents: Extents;\n /** Extent of the visible region along the scroll axis. */\n viewport: number;\n /** Rows to mount beyond the visible region, each side. */\n overscan?: number;\n /**\n * Empty extent after the last row, so the reader can scroll it up to the start of the viewport.\n *\n * A number the host supplies, not a mode the list has. On the old design this was a flag that\n * special-cased the tail everywhere it was consulted, and it could not be made stable; here it is\n * added to the sizer and nothing else knows about it (§7).\n */\n reserve?: number;\n};\n\n/** The mounted range and where its parent goes. */\nexport type Layout = {\n /** First and last mounted row, inclusive — the visible rows plus overscan. */\n first: number;\n last: number;\n /**\n * First and last row the reader can actually see.\n *\n * Reported separately because it is what a readout means by \"where am I\": the mounted range\n * includes rows deliberately kept off screen, and naming one of those would be describing the\n * overscan rather than the reader.\n */\n visible: { first: number; last: number };\n /** Absolute position of the first mounted row: what the parent is translated by (§7). */\n offset: number;\n /** Total extent of the scrollable content, which is what the thumb is computed from. */\n sizerExtent: number;\n};\n\n/** What reaching an edge revealed about the estimates, when it revealed anything. */\nexport type EdgeDrift = {\n edge: 'start' | 'end';\n /** How far out the estimate was. Positive means the region was shorter than assumed. */\n delta: number;\n};\n\nconst DEFAULT_OVERSCAN = 8;\n\n/**\n *\n */\nexport class Placement {\n #count: number;\n #getId: (index: number) => string;\n #extents: Extents;\n #viewport: number;\n #overscan: number;\n #reserve: number;\n\n /** Measured extents by message id, so that reordering the model cannot invalidate them. */\n readonly #measured = new Map<string, number>();\n\n /** The one authoritative position: this row starts exactly here. */\n #anchor: { id: string; index: number; start: number };\n\n #scroll = 0;\n\n constructor({ count, getId, extents, viewport, overscan = DEFAULT_OVERSCAN, reserve = 0 }: PlacementOptions) {\n this.#count = count;\n this.#getId = getId;\n this.#extents = extents;\n this.#viewport = viewport;\n this.#overscan = overscan;\n this.#reserve = reserve;\n this.#anchor = { id: count ? getId(0) : '', index: 0, start: 0 };\n }\n\n get scroll(): number {\n return this.#scroll;\n }\n\n get count(): number {\n return this.#count;\n }\n\n /** The viewport is the binding's to know: it is the one thing here that comes from the DOM. */\n setViewport(viewport: number): void {\n this.#viewport = viewport;\n }\n\n /**\n * The host's answer about extents can change — a row is edited, a panel opens, a story flips a\n * fixture — and it is a *function*, so keeping the one handed over at construction quietly pins\n * the layout to whatever was true then. Measured extents survive: they are keyed by id and this\n * only replaces what the host would say about a row nobody has measured.\n */\n setExtents(extents: Extents): void {\n this.#extents = extents;\n }\n\n setReserve(reserve: number): void {\n this.#reserve = reserve;\n }\n\n /**\n * The host's identity function, which is a closure over the host's own list.\n *\n * Appending a message gives a *new* `getId`, and a placement holding the one it was constructed\n * with resolves the new row's index to an id nobody will ever measure. The binding then stores\n * that row's measurement under the id the DOM carries and reads it back under the stale one, so\n * the row is measured, found to disagree, measured again — every commit, for ever. It presents as\n * a render loop rather than as a wrong size, which is what made `bridge/Tail` look like a tail\n * defect (§8: measurement is only a correction if it can be read back).\n */\n setGetId(getId: (index: number) => string): void {\n this.#getId = getId;\n }\n\n get anchor(): Readonly<{ id: string; index: number; start: number }> {\n return this.#anchor;\n }\n\n /** Extent of one row: what was measured, or what the host says. */\n extentOf(index: number): number {\n const measured = this.#measured.get(this.#getId(index));\n return measured ?? this.#extents.of(index);\n }\n\n /**\n * Absolute position of a row, derived from the anchor rather than summed from the start.\n *\n * Rows between here and the anchor are the only ones whose extents matter (§5): everything before\n * the window is described by the anchor's position, not by adding up guesses about it.\n */\n positionOf(index: number): number {\n let position = this.#anchor.start;\n if (index >= this.#anchor.index) {\n for (let row = this.#anchor.index; row < index; row++) {\n position += this.extentOf(row);\n }\n } else {\n for (let row = index; row < this.#anchor.index; row++) {\n position -= this.extentOf(row);\n }\n }\n\n return position;\n }\n\n /** The reader moved. Nothing is corrected here — only the mounted range changes. */\n scrollTo(offset: number): void {\n this.#scroll = offset;\n this.#reanchor();\n }\n\n /**\n * A row turned out to be a different size than assumed.\n *\n * Rows at or after the anchor push the ones after them and leave the anchor alone. A row *before*\n * the anchor moves the rows between it and the anchor, which is correct and unavoidable: the\n * anchor is the thing being held still, and it is still being held.\n */\n measure(id: string, extent: number): void {\n if (this.#extents.exact) {\n return;\n }\n\n this.#measured.set(id, extent);\n }\n\n /** The model changed length. `prepended` rows arrived before the anchor and shift its index. */\n setCount(count: number, { prepended = 0 }: { prepended?: number } = {}): void {\n this.#count = count;\n if (prepended) {\n this.#anchor = { ...this.#anchor, index: this.#anchor.index + prepended };\n }\n }\n\n /**\n * Move to a row that may be nowhere near the window.\n *\n * The only operation that positions from estimates rather than from measurement, because there is\n * no measured path to somewhere the reader has never been. A jump is a discontinuity the reader\n * asked for, so being approximately right is what it can be.\n */\n jumpTo(index: number, align: 'start' | 'end' = 'start'): void {\n const clamped = Math.max(0, Math.min(index, this.#count - 1));\n let start = 0;\n for (let row = 0; row < clamped; row++) {\n start += this.extentOf(row);\n }\n\n this.#anchor = { id: this.#getId(clamped), index: clamped, start };\n // The last row's end *is* the content's end, whatever the estimates in between say. Summing to\n // it instead lands wherever the guesses add up to, which for a chat's \"go to the bottom\" is a\n // tail that is nearly at the bottom — the one place approximately right is wrong.\n const end =\n clamped === this.#count - 1 ? this.layout().sizerExtent - this.#reserve : start + this.extentOf(clamped);\n this.#scroll = align === 'end' ? end - this.#viewport : start;\n this.#scroll = Math.max(0, this.#scroll);\n this.#reanchor();\n }\n\n /**\n * Where the reader has to be for `index` to sit at the start (or end) of the viewport.\n *\n * Reports; does not move. `jumpTo` re-anchors as well, which is right for a discontinuity and\n * wrong for a glide: the anchor would arrive at the destination while the element was still\n * travelling, so the reader would watch the rows they were scrolling *through* unmount and the\n * journey happen over nothing.\n */\n offsetOf(index: number, align: 'start' | 'end' = 'start'): number {\n const clamped = Math.max(0, Math.min(index, this.#count - 1));\n const start = this.positionOf(clamped);\n return Math.max(0, align === 'end' ? start + this.extentOf(clamped) - this.#viewport : start);\n }\n\n /**\n * Where the reader has to be for the last row to rest against the end of the viewport.\n *\n * Derived from the anchor, like every other position, and deliberately **not** from a sum over the\n * model: a follow asks this again on every commit, and each answer mounts rows whose measurement\n * replaces the estimates that sum is over — so a sum-based end moves every time it is consulted,\n * and consulting it is what moves it. That is not slow convergence, it is a feedback loop, and in\n * React it terminates by exceeding the update limit rather than by settling (`bridge/Tail`).\n *\n * Anchor-relative, the answer only changes as the rows *between the anchor and the last row* are\n * measured — each of them once — so a follow settles in a couple of commits and rows the reader\n * has already passed cannot disturb it.\n */\n endOffset(): number {\n if (!this.#count) {\n return 0;\n }\n\n const last = this.#count - 1;\n return Math.max(0, this.positionOf(last) + this.extentOf(last) - this.#viewport);\n }\n\n /** The mounted range, the parent's offset, and the extent the thumb is computed from. */\n layout(): Layout {\n if (!this.#count) {\n return { first: 0, last: -1, visible: { first: 0, last: -1 }, offset: 0, sizerExtent: 0 };\n }\n\n const { first, last, visible } = this.#range();\n const offset = this.positionOf(first);\n let windowExtent = 0;\n for (let row = first; row <= last; row++) {\n windowExtent += this.extentOf(row);\n }\n\n let after = 0;\n for (let row = last + 1; row < this.#count; row++) {\n after += this.extentOf(row);\n }\n\n return { first, last, visible, offset, sizerExtent: offset + windowExtent + after + this.#reserve };\n }\n\n /**\n * Repay the start edge's drift: shift every position so row 0 starts at exactly zero, and move\n * the scroll by the same amount so nothing the reader sees moves.\n *\n * The first row starting at zero is the one absolute fact of content space, and estimates being\n * replaced above the anchor break it: a feed opened at its tail whose rows measure taller than\n * assumed pushes row 0 *negative*, where no scroll can reach it — the reader arrives at the top\n * and the first messages simply are not there. This is the single correction that must touch the\n * scroll (§7's rule is about corrections and readers sharing a channel; here both the content and\n * the offset move by the same delta in the same commit, so the reader sees nothing at all).\n * Returns the shift for the binding to apply to the element.\n */\n rebaseStart(): number {\n if (!this.#count) {\n return 0;\n }\n\n const shift = -this.positionOf(0);\n if (shift === 0) {\n return 0;\n }\n\n this.#anchor = { ...this.#anchor, start: this.#anchor.start + shift };\n this.#scroll = Math.max(0, this.#scroll + shift);\n return shift;\n }\n\n /**\n * What an edge revealed, if the window has reached one.\n *\n * The edges are where estimate meets ground truth: the first row starts at zero and the last one\n * ends at the content's end, by definition, so a computed position that says otherwise is the\n * measure of how wrong the estimates were. Reported rather than silently applied — absorbing it\n * into the anchor would move what the reader is looking at, and at an edge they are looking at\n * exactly the thing that would move.\n */\n drift(): EdgeDrift | undefined {\n if (!this.#count) {\n return undefined;\n }\n\n const { first, last } = this.#range();\n if (first === 0 && this.positionOf(0) !== 0) {\n return { edge: 'start', delta: this.positionOf(0) };\n }\n\n if (last === this.#count - 1) {\n const end = this.positionOf(last) + this.extentOf(last);\n // Against the content's end, not the document's: reserved space is somewhere the reader may go\n // and nowhere any row is, so counting it would report a drift the size of the reserve for ever.\n const { sizerExtent } = this.layout();\n const content = sizerExtent - this.#reserve;\n if (end !== content) {\n return { edge: 'end', delta: content - end };\n }\n }\n\n return undefined;\n }\n\n /** Visible rows, plus overscan, clamped to the model. */\n #range(): { first: number; last: number; visible: { first: number; last: number } } {\n let first = this.#anchor.index;\n // Walk out from the anchor rather than searching from zero: the anchor is the only position\n // known exactly, so it is the only sound place to start.\n while (first > 0 && this.positionOf(first) > this.#scroll) {\n first--;\n }\n while (first < this.#count - 1 && this.positionOf(first) + this.extentOf(first) <= this.#scroll) {\n first++;\n }\n\n let last = first;\n let filled = this.positionOf(first) + this.extentOf(first);\n while (last < this.#count - 1 && filled < this.#scroll + this.#viewport) {\n last++;\n filled += this.extentOf(last);\n }\n\n return {\n first: Math.max(0, first - this.#overscan),\n last: Math.min(this.#count - 1, last + this.#overscan),\n visible: { first, last },\n };\n }\n\n /**\n * The anchor is the first row the reader can see — always, not merely when the old one leaves\n * the window.\n *\n * The anchor is the row held still while others are measured, so it must be the reader's\n * reference point. Clamping the old anchor into the mounted range let it drift to the window's\n * *far* edge after an upward scroll — below everything on screen — and a widget toggled open\n * above it then pushed the whole viewport up by its own growth: the engine held a row the reader\n * could not see still, at the expense of every row they could. Re-anchoring preserves the new\n * anchor's computed position exactly, so the move itself shifts nothing.\n */\n #reanchor(): void {\n if (!this.#count) {\n return;\n }\n\n const { visible } = this.#range();\n if (this.#anchor.index === visible.first) {\n return;\n }\n\n const index = visible.first;\n this.#anchor = { id: this.#getId(index), index, start: this.positionOf(index) };\n }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, {\n type ReactNode,\n type Ref,\n useCallback,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { type ThemedClassName } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport { useFollow } from '../hooks/useFollow';\nimport { type EdgeDrift, type Extents, type Layout, Placement } from './placement';\n\n/**\n * What the window needs from a model, structurally — `ListModel`/`FeedModel` satisfy it, and the\n * virtualizer stays importable without the model layer. The contract is SPEC F-7.1: a change says\n * what it was (`prepended`), so the anchor is told rather than left to infer it from identities.\n */\nexport type WindowModel = {\n readonly count: number;\n getId: (index: number) => string;\n subscribe: (\n listener: (change: { prepended?: number; appended?: number; updated?: readonly string[] }) => void,\n ) => () => void;\n};\n\n/**\n * The DOM shape, and nothing else.\n *\n * Three elements: a **sizer** that holds no rows and exists only to give the thumb something to\n * measure, a **window** holding the mounted rows in normal flow, and the scroller around them. The\n * window is absolutely positioned and moved by a transform, so a correction changes one number and\n * never touches `scrollTop` — which is what stops corrections and the reader sharing a channel (§7).\n *\n * Rows are unpositioned. A row that changes extent reflows the ones after it, in the browser, in the\n * same frame; the alternative was re-placing every row below it ourselves on every frame of the\n * change — measured at 177 re-placements for one disclosure opening (§6).\n *\n * Knows nothing about messages, markdown or CodeMirror. What it renders is whatever `children`\n * returns, which is what lets `placement/*` drive it with content that cannot lie about its size.\n */\nexport type WindowAxis = 'block' | 'inline';\n\n/**\n * What a toolbar or a readout needs, and nothing more.\n *\n * Published rather than rendered: chrome belongs to the host (§2), so the window says where it is\n * and the host decides what to draw about it.\n */\nexport type WindowState = {\n /** The row at the top of the viewport — what \"where am I\" means to a reader. */\n index: number;\n visible: { first: number; last: number };\n /** The mounted range, which includes the overscan the reader cannot see. */\n mounted: { first: number; last: number };\n count: number;\n /**\n * The same thing in pixels, which is what a map of the list has to draw.\n *\n * `window` is the mounted parent's span in content space — where the rows that exist actually are —\n * and `total` is what the thumb is scaled against. A map drawn from indices instead would be a map\n * of the model rather than of the layout, and it is the layout that goes wrong.\n */\n geometry: { scroll: number; viewport: number; total: number; window: { start: number; extent: number } };\n};\n\nexport type WindowController = {\n /**\n * `behavior: 'smooth'` glides, and only over content that is mounted — a glide across rows that do\n * not exist is a journey over a blank screen, so a far target is taken instantly (§7).\n */\n scrollToIndex: (index: number, align?: 'start' | 'end', behavior?: ScrollBehavior) => void;\n};\n\nexport type WindowProps = ThemedClassName<{\n model: WindowModel;\n extents: Extents;\n /** Which way the list runs. The principles hold either way; only this mapping differs (§9). */\n axis?: WindowAxis;\n overscan?: number;\n /** Empty extent after the last row, so it can be scrolled to the top of the viewport (§7). */\n reserve?: number;\n /**\n * Keep the last row against the end of the viewport as content arrives.\n *\n * Consumed by the trivial `Window` binding, which composes `useFollow`; `useWindow` itself knows\n * nothing about following — the intent, the withdrawal and the glide are the aspect's (SPEC §Aspects).\n */\n sticky?: boolean;\n /** What an edge revealed about the estimates, if it revealed anything. */\n onEdge?: (drift: EdgeDrift) => void;\n /** A row whose declared extent was not the extent it rendered at. `exact` means do not correct — not do not check (§8). */\n onMismatch?: (mismatch: { index: number; id: string; declared: number; actual: number }) => void;\n onChange?: (state: WindowState) => void;\n controllerRef?: Ref<WindowController>;\n children: (index: number, id: string) => ReactNode;\n}>;\n\n/** Extent of the mounted rows, which is the parent's own size in content space. */\nconst windowExtentOf = (placement: Placement, first: number, last: number): number => {\n let extent = 0;\n for (let index = first; index <= last; index++) {\n extent += placement.extentOf(index);\n }\n\n return extent;\n};\n\n/**\n * Everything the shape needs, with no opinion about who owns the scroll element.\n *\n * Split out because the DOM below is only the *simplest* binding of it. A host that already has a\n * scroll container — `MessageList` scrolls inside `ScrollArea`, which owns the overlay thumbs and\n * the padding tokens — cannot nest a second one, and would otherwise have to reimplement the\n * placement to keep its own chrome. It hands its element over instead.\n */\nexport type UseWindowOptions = Omit<WindowProps, 'classNames' | 'children'> & {\n /** The element being scrolled, when the host owns it. `Window` passes its own. */\n scrollerRef: React.RefObject<HTMLElement | null>;\n};\n\nexport type UseWindowResult = {\n placement: Placement;\n layout: Layout;\n /** Ref for the element holding the mounted rows, which is what gets measured. */\n windowRef: React.RefObject<HTMLDivElement | null>;\n /** Extent of the whole document along the axis, reserve included: what the thumb is scaled to. */\n sizerExtent: number;\n /** Absolute position of the first mounted row, which the parent is translated by. */\n offset: number;\n first: number;\n last: number;\n};\n\nexport const useWindow = ({\n scrollerRef,\n model,\n extents,\n axis = 'block',\n overscan,\n reserve = 0,\n onEdge,\n onMismatch,\n onChange,\n controllerRef,\n}: UseWindowOptions): UseWindowResult => {\n const windowRef = useRef<HTMLDivElement>(null);\n const [, render] = useState(0);\n const invalidate = useCallback(() => render((value) => value + 1), []);\n\n const count = model.count;\n const getId = model.getId;\n\n // Held across renders, and told about the model rather than rebuilt from it: the anchor is state,\n // and rebuilding would discard the one position that is known exactly.\n const placement = useMemo(\n () => new Placement({ count, getId, extents, viewport: 0, overscan }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n\n // A mismatch is news the first time and noise afterwards. Without this the report is emitted on\n // every commit, and a host that renders the report re-renders on it — which is an infinite loop,\n // found by `placement/Drift` on its first run.\n const reported = useRef(new Set<string>());\n\n // Told before anything consults the model: `getId` is a closure over the host's list, so the one\n // captured at construction names rows that no longer exist there.\n placement.setGetId(getId);\n\n // Which end the model grew at, told by the model (SPEC F-7.1). The engine used to reconstruct\n // this by scanning for the previous first id — the model's own knowledge, recovered from its\n // absence; the scan survives only in `ListModel.replace`, the adapter for hosts that hand over a\n // whole new array and genuinely do not know what changed. The model publishes synchronously, so\n // a prepend reaches the anchor in the same tick it happens; the render-time guard below covers\n // the one gap — a *new* model instance arriving mid-render, before the effect re-subscribes.\n useEffect(() => {\n const unsubscribe = model.subscribe((change) => {\n placement.setCount(model.count, { prepended: change.prepended ?? 0 });\n invalidate();\n });\n\n placement.setCount(model.count, {});\n invalidate();\n return unsubscribe;\n }, [model, placement, invalidate]);\n\n if (placement.count !== model.count) {\n placement.setCount(model.count, {});\n }\n\n // Navigation, not correction. The rule that corrections never touch `scrollTop` (§7) is about the\n // list moving itself; a reader asking to be somewhere is the one case where moving the scroll is\n // the whole point.\n useImperativeHandle(\n controllerRef,\n () => ({\n scrollToIndex: (index, align, behavior = 'auto') => {\n const scroller = scrollerRef.current;\n const { first, last } = placement.layout();\n // A glide only over rows that are already there. Beyond them the offset is a sum of\n // estimates, so the travel would cross content that does not exist and land somewhere the\n // measurement then corrects — a step the reader watched go to the wrong place.\n if (behavior === 'smooth' && scroller && index >= first && index <= last) {\n const target = placement.offsetOf(index, align);\n // Reported, not applied: the element animates and its scroll events drive the placement,\n // one frame at a time, so the mounted window stays under the reader for the whole journey.\n scroller.scrollTo(axis === 'block' ? { top: target, behavior } : { left: target, behavior });\n return;\n }\n\n placement.jumpTo(index, align);\n if (scroller) {\n if (axis === 'block') {\n scroller.scrollTop = placement.scroll;\n } else {\n scroller.scrollLeft = placement.scroll;\n }\n }\n\n invalidate();\n },\n }),\n [placement, axis, invalidate],\n );\n\n // Told on every render, not captured once: `extents` is a function the host may replace, and a\n // placement holding the first one it was given keeps a layout that was true at mount.\n placement.setExtents(extents);\n placement.setReserve(reserve);\n\n const { first, last, visible, offset, sizerExtent } = placement.layout();\n\n // Declared before the follow, so the follow's first run has a viewport to compute an end against:\n // effects run in order, and an end measured against a viewport of zero is the whole document.\n useLayoutEffect(() => {\n const scroller = scrollerRef.current;\n if (!scroller) {\n return;\n }\n\n const read = () => (axis === 'block' ? scroller.clientHeight : scroller.clientWidth);\n placement.setViewport(read());\n const observer = new ResizeObserver(() => {\n placement.setViewport(read());\n invalidate();\n });\n\n observer.observe(scroller);\n const onScroll = () => {\n placement.scrollTo(axis === 'block' ? scroller.scrollTop : scroller.scrollLeft);\n invalidate();\n };\n\n scroller.addEventListener('scroll', onScroll, { passive: true });\n invalidate();\n return () => {\n observer.disconnect();\n scroller.removeEventListener('scroll', onScroll);\n };\n }, [placement, axis, invalidate]);\n\n // Measured after every commit, so a row that grows later — a font, a portaled widget, an image —\n // is not a special case but the same case arriving again (§8).\n useLayoutEffect(() => {\n const parent = windowRef.current;\n if (!parent) {\n return;\n }\n\n let changed = false;\n for (const element of parent.children) {\n const row = element as HTMLElement;\n const index = Number(row.dataset.index);\n const id = row.dataset.objectId!;\n const actual = Math.round(axis === 'block' ? row.offsetHeight : row.offsetWidth);\n const declared = placement.extentOf(index);\n if (extents.exact) {\n const key = `${id}:${declared}:${actual}`;\n if (actual !== declared && !reported.current.has(key)) {\n reported.current.add(key);\n onMismatch?.({ index, id, declared, actual });\n }\n continue;\n }\n\n if (actual && actual !== declared) {\n placement.measure(id, actual);\n changed = true;\n }\n }\n\n // The start edge's drift is repaid, not merely reported: row 0 at a negative position is a top\n // of the document no scroll can reach. Content and offset move by the same delta in the same\n // commit, so the mounted rows hold their place on screen while the coordinate system comes\n // true. Forward shifts never read as the reader (the follow only weighs backwards gestures).\n const shift = placement.rebaseStart();\n if (shift !== 0) {\n const scroller = scrollerRef.current;\n if (scroller) {\n if (axis === 'block') {\n scroller.scrollTop = placement.scroll;\n } else {\n scroller.scrollLeft = placement.scroll;\n }\n }\n\n changed = true;\n }\n\n // Deduped for the same reason as a mismatch: an edge is news the first time it is reached, and\n // a host that renders the report would otherwise re-render on it for ever.\n const drift = placement.drift();\n const key = drift && `${drift.edge}:${drift.delta}`;\n if (drift && key && !reported.current.has(key)) {\n reported.current.add(key);\n onEdge?.(drift);\n }\n\n if (changed) {\n invalidate();\n }\n });\n\n const announced = useRef<string>('');\n const state = `${visible.first}:${visible.last}:${first}:${last}:${count}`;\n if (onChange && announced.current !== state) {\n announced.current = state;\n // In render rather than an effect, so a readout never lags the frame it describes by one.\n const scroller = scrollerRef.current;\n const viewport = (axis === 'block' ? scroller?.clientHeight : scroller?.clientWidth) ?? 0;\n const extent = windowExtentOf(placement, first, last);\n queueMicrotask(() =>\n onChange({\n index: visible.first,\n visible,\n mounted: { first, last },\n count,\n geometry: { scroll: placement.scroll, viewport, total: sizerExtent, window: { start: offset, extent } },\n }),\n );\n }\n\n return {\n placement,\n layout: { first, last, visible, offset, sizerExtent },\n windowRef,\n sizerExtent,\n offset,\n first,\n last,\n };\n};\n\n/**\n * The DOM shape, and nothing else.\n *\n * Three elements: a **sizer** that holds no rows and exists only to give the thumb something to\n * measure, a **window** holding the mounted rows in normal flow, and the scroller around them.\n */\nexport const Window = ({ classNames, children, controllerRef, ...options }: WindowProps) => {\n const scrollerRef = useRef<HTMLDivElement>(null);\n const inner = useRef<WindowController>(null);\n const { placement, windowRef, offset, sizerExtent, first, last } = useWindow({\n ...options,\n scrollerRef,\n controllerRef: inner,\n });\n const axis = options.axis ?? 'block';\n const main = axis === 'block' ? 'height' : 'width';\n const { getId } = options.model;\n\n // The binding composes the aspect; the hook knows nothing about following. Graduates with the\n // virtualizer — nothing in it is about messages.\n const follow = useFollow({\n scrollerRef,\n placement,\n extent: sizerExtent,\n count: options.model.count,\n axis,\n reserve: options.reserve,\n enabled: options.sticky,\n });\n\n // The exposed controller answers the follow before it moves (see FollowHandle.onNavigate).\n useImperativeHandle(\n controllerRef,\n () => ({\n scrollToIndex: (index, align, behavior) => {\n follow.onNavigate(index);\n inner.current?.scrollToIndex(index, align, behavior);\n },\n }),\n [follow],\n );\n\n const rows = [];\n for (let index = first; index <= last; index++) {\n rows.push(\n <div key={getId(index)} data-index={index} data-object-id={getId(index)}>\n {children(index, getId(index))}\n </div>,\n );\n }\n\n return (\n <div\n ref={scrollerRef}\n className={mx('relative', axis === 'block' ? 'overflow-y-auto' : 'overflow-x-auto', classNames)}\n // Off deliberately: the browser adjusting the scroll as well would be a second party anchoring\n // the same thing, and the defect this design exists to remove is exactly that (§6).\n style={{ overflowAnchor: 'none' }}\n data-testid='window.scroller'\n >\n <div style={{ [main]: sizerExtent }} data-testid='window.sizer' />\n <div\n ref={windowRef}\n className={mx('absolute top-0 left-0 flex', axis === 'block' ? 'flex-col w-full' : 'flex-row h-full')}\n style={{ transform: axis === 'block' ? `translateY(${offset}px)` : `translateX(${offset}px)` }}\n data-testid='window.window'\n >\n {rows}\n </div>\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/** Movement below this is sub-pixel rounding, not a row changing place. */\nconst EPSILON = 1;\n\nexport type PositionStats = {\n /** Rows whose offset changed after they were laid out — every one of these is a visible flicker. */\n shifts: number;\n /** Windows where offsets did not increase with index; a row was placed over its neighbour. */\n breaks: number;\n /** The most recent shift, for reading in the console while scrolling. */\n last?: PositionShift;\n};\n\nexport type PositionShift = { index: number; from: number; to: number; delta: number };\n\n/** Recent shifts kept for inspection — enough to see whether one row type moves or all of them. */\nconst TRACE_LIMIT = 40;\n\n/** `key` is the virtualizer's item key — the message id, via `getItemKey`. */\nexport type PositionKey = number | string | bigint;\n\nexport type PositionEntry = { key: PositionKey; index: number; start: number };\n\n/** Movement of the viewport itself between samples, which every row is expected to follow. */\nexport type PositionFrame = { scrollOffset: number };\n\nconst EMPTY: PositionStats = { shifts: 0, breaks: 0 };\n\n/**\n * Records where each row was placed, and reports when a row moves.\n *\n * A virtualized list is only still if a row's offset, once laid out, does not change: rows above the\n * reader are measured as they mount, and any correction there pushes everything below it. Scrolling\n * **up** is where this shows, because that is the direction in which unmeasured rows enter — going\n * down, the rows above have already been measured.\n *\n * Positions are recorded **relative to the viewport** (`start - scrollOffset`), not as absolute\n * document offsets, because those two answer different questions. Measuring a row for the first time\n * re-lays every row below it, and the virtualizer compensates by moving `scrollTop` the same\n * distance — the offsets all change and the reader sees nothing. What a reader sees is a row landing\n * somewhere else on screen, which is what this counts.\n *\n * Two invariants:\n *\n * 1. a row keyed by message id holds its place on screen for as long as it stays mounted;\n * 2. positions within the mounted window increase strictly with index.\n *\n * The count is what a reader means by \"flicker\". Deliberately kept as a count rather than a log:\n * a scroll produces thousands of samples, and the number is the signal.\n */\n/**\n * Watches for jumps in what was actually painted, once per animation frame.\n *\n * The counting above is done per React render, which cannot separate a row moving from the sampling\n * happening at a different moment than the layout. This reads the scroll and every row's box in the\n * **same** frame and compares each row's travel against the scroll's: a row that keeps its place in\n * the document travels exactly as far as the scroll did, so anything else is the reader seeing a\n * jump. It is the in-page form of measuring a screen recording frame by frame, and it is what a\n * developer should have running while working on the list.\n */\nexport const useJumpDetector = (viewport: HTMLElement | null, enabled = true) => {\n const [jumps, setJumps] = useState({ count: 0, worst: 0 });\n\n useEffect(() => {\n if (!viewport || !enabled) {\n return;\n }\n\n let handle = 0;\n let previous = new Map<string, number>();\n let previousScroll = viewport.scrollTop;\n let count = 0;\n let worst = 0;\n\n const tick = () => {\n const scroll = viewport.scrollTop;\n const travelled = previousScroll - scroll;\n const current = new Map<string, number>();\n for (const row of viewport.querySelectorAll<HTMLElement>('[data-index]')) {\n const top = row.getBoundingClientRect().top;\n const index = row.dataset.index!;\n current.set(index, top);\n const before = previous.get(index);\n if (before !== undefined) {\n const residual = Math.abs(top - (before + travelled));\n if (residual > EPSILON) {\n count++;\n worst = Math.max(worst, Math.round(residual));\n }\n }\n }\n\n previous = current;\n previousScroll = scroll;\n handle = requestAnimationFrame(tick);\n };\n\n handle = requestAnimationFrame(tick);\n const timer = setInterval(() => setJumps({ count, worst }), 250);\n return () => {\n cancelAnimationFrame(handle);\n clearInterval(timer);\n };\n }, [viewport, enabled]);\n\n return jumps;\n};\n\nexport const usePositionLog = () => {\n const positions = useRef(new Map<PositionKey, number>());\n const trace = useRef<PositionShift[]>([]);\n const [stats, setStats] = useState<PositionStats>(EMPTY);\n const pending = useRef<PositionStats>(EMPTY);\n\n const reset = useCallback(() => {\n positions.current.clear();\n trace.current = [];\n pending.current = EMPTY;\n setStats(EMPTY);\n }, []);\n\n const record = useCallback((entries: readonly PositionEntry[], { scrollOffset }: PositionFrame) => {\n let { shifts, breaks, last } = pending.current;\n\n let previousStart = -Infinity;\n for (const { key, index, start: absolute } of entries) {\n const start = absolute - scrollOffset;\n const recorded = positions.current.get(key);\n if (recorded !== undefined && Math.abs(recorded - start) > EPSILON) {\n shifts++;\n last = { index, from: Math.round(recorded), to: Math.round(start), delta: Math.round(start - recorded) };\n trace.current.push(last);\n if (trace.current.length > TRACE_LIMIT) {\n trace.current.shift();\n }\n }\n\n positions.current.set(key, start);\n if (start < previousStart) {\n breaks++;\n }\n previousStart = start;\n }\n\n // Rows that are no longer mounted are forgotten: their next measurement is a fresh layout, and\n // holding the old offset would report every remount as a shift.\n const mounted = new Set(entries.map(({ key }) => key));\n for (const key of positions.current.keys()) {\n if (!mounted.has(key)) {\n positions.current.delete(key);\n }\n }\n\n const next = { shifts, breaks, last };\n pending.current = next;\n setStats((previous) =>\n previous.shifts === next.shifts && previous.breaks === next.breaks ? previous : { ...next },\n );\n }, []);\n\n return { ...stats, trace, record, reset };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport React, {\n type ComponentType,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { Column, ScrollArea, type ScrollAreaRootProps, composable, composableProps, setRef } from '@dxos/react-ui';\nimport { type Message } from '@dxos/types';\nimport { type XmlWidgetRegistry } from '@dxos/ui-editor';\n\nimport { type FeedNavigation, useDecorations, useFeedNavigation, useFollow, useItemSelectionValue } from '../../hooks';\nimport { type FeedModel, type ItemContent, type MessageRenderer, defaultRenderer } from '../../model';\nimport { type WindowController, type WindowState, useWindow } from '../../virtualizer';\nimport {\n type HighlightRange,\n HtmlBlock,\n MarkdownBlock,\n SelectionGroupContext,\n WidgetScopeProvider,\n WidgetStateProvider,\n createSelectionGroup,\n createWidgetStateStore,\n} from '../Block';\nimport { useJumpDetector, usePositionLog } from './position-log';\n\n//\n// Context\n//\n\nconst MESSAGE_LIST_NAME = 'MessageList';\n\n/** Per-message chrome (avatar, timestamp, fork/rewind/reply controls), supplied by the host. */\nexport type MessageChromeProps = PropsWithChildren<{\n message: Message.Message;\n index: number;\n selected: boolean;\n onSelect: (id: string, additive: boolean) => void;\n}>;\n\nexport type MessageRange = { startIndex: number; endIndex: number };\n\ntype MessageListContextValue = {\n /** The feed's model: messages, identity, stops, streaming, iteration. The one source (SPEC F-7). */\n model: FeedModel;\n renderer: MessageRenderer;\n registry?: XmlWidgetRegistry;\n Chrome: ComponentType<MessageChromeProps>;\n Custom?: ComponentType<{ content: ItemContent & { kind: 'custom' }; message: Message.Message }>;\n debug?: boolean;\n /** The mounted window; `undefined` until the viewport has measured. */\n range?: MessageRange;\n /** Index the reader is on: moved by the arrow keys and by any navigation, tracked while scrolling. */\n currentIndex: number;\n /** The one seam every navigation driver calls: toolbar, arrows, outline, minimap (SPEC F-3.2). */\n navigation: FeedNavigation;\n /** Rows mounted right now: the window the reader is paying for. */\n mountedRows: number;\n /** Block widgets mounted across every mounted item — what the visible window actually costs. */\n mountedWidgets: number;\n reportWidgets: (id: string, count: number) => void;\n /** Rows that moved on screen against the scroll, counted per animation frame. */\n jumps: { count: number; worst: number };\n /** Rows that moved after being laid out, and windows whose offsets were out of order. */\n shifts: number;\n breaks: number;\n resetShifts: () => void;\n /** The element holding the mounted rows, which is what gets measured. */\n windowRef: React.RefObject<HTMLDivElement | null>;\n /** Absolute position of the first mounted row: what that element is translated by (§7). */\n offset: number;\n /** Extent of the whole document, reserved space included — what the thumb is scaled against. */\n sizerExtent: number;\n first: number;\n last: number;\n setViewport: (viewport: HTMLElement | null) => void;\n scrollToIndex: (index: number, options?: ScrollToOptions) => void;\n scrollToBottom: (options?: ScrollToOptions) => void;\n};\n\nexport type ScrollToOptions = {\n align?: 'start' | 'center' | 'end';\n behavior?: 'auto' | 'smooth';\n};\n\nconst [MessageListProvider, useMessageListContext] = createContext<MessageListContextValue>(MESSAGE_LIST_NAME);\n\n/**\n * The list's state and scroll controls, for parts that live outside the viewport — a toolbar's\n * find-next, a statusbar's range readout.\n */\nexport const useMessageList = (consumerName = 'useMessageList') => {\n const {\n range,\n currentIndex,\n navigation,\n mountedRows,\n mountedWidgets,\n jumps,\n shifts,\n breaks,\n resetShifts,\n model,\n scrollToIndex,\n scrollToBottom,\n } = useMessageListContext(consumerName);\n return {\n range,\n currentIndex,\n navigation,\n mountedRows,\n mountedWidgets,\n jumps,\n shifts,\n breaks,\n resetShifts,\n model,\n count: model.count,\n scrollToIndex,\n scrollToBottom,\n };\n};\n\n//\n// Root\n//\n\nexport type MessageListRootProps = PropsWithChildren<{\n /**\n * The feed's model (SPEC F-7): messages and identity, the stops policy, the streaming tail, and\n * iteration (`loadBefore`/`loadAfter`, consumed when the window reaches an edge). `fromMessages`\n * adapts a plain array; ECHO/queue bindings adapt outside this package.\n *\n * What is NOT here, deliberately: search hits (decorations — `DecorationsProvider` + an item's\n * `useDecorations`), selection (`ItemSelectionProvider` + `useItemSelection`), `streamingId` and\n * `isAnchor` (model concerns). The list neither knows nor routes cross-cutting per-item data.\n */\n model: FeedModel;\n renderer?: MessageRenderer;\n registry?: XmlWidgetRegistry;\n /**\n * Chrome wrapper; receives the item as `children`. Defaults to a bare frame.\n * Chrome must be layout-stable: a control that changes a row's height on hover or focus\n * re-triggers measurement, and a pointer travelling down the list mid-scroll then shifts every\n * row below it. Toggle such affordances with opacity, or take them out of flow.\n */\n Chrome?: ComponentType<MessageChromeProps>;\n /**\n * Renders a `custom` item. The engine's own kinds are documents — markdown in CodeMirror, HTML —\n * and both build their content asynchronously enough to be measured wrong once. A host component\n * whose height is settled at first paint is the control case: if a feed of those still moves, the\n * fault is the list; if it does not, the fault is in what the item is building.\n */\n Custom?: ComponentType<{ content: ItemContent & { kind: 'custom' }; message: Message.Message }>;\n /**\n * Outline each item and the blocks inside it, so what the layout is measuring is visible.\n */\n debug?: boolean;\n /**\n * Estimated row height before measurement. A function is worth supplying whenever the feed's\n * rows differ widely — a one-line prompt beside a long answer — because a single number is wrong\n * for both.\n */\n estimateSize?: number | ((message: Message.Message, index: number) => number);\n /**\n * Pin to the bottom as messages arrive, and as a streaming message grows (chat behaviour).\n */\n stickyBottom?: boolean;\n /**\n * Reserve empty space below the last row, so that any message — including the last — can be\n * brought to the top of the viewport. A number in the sizer, not a mode the list has (§7).\n */\n scrollPastEnd?: boolean;\n overscan?: number;\n onRangeChange?: (range: MessageRange) => void;\n}>;\n\nconst DefaultChrome = ({ children }: MessageChromeProps) => <>{children}</>;\n\n/** Row height assumed before anything has been measured. */\nconst DEFAULT_ESTIMATE = 120;\n\n/**\n * Headless root of a feed: owns the placement, the selection group and the model-to-item mapping.\n * Renders no DOM of its own, so a toolbar or statusbar that reads its state can sit outside the\n * scroll container — the scrolling part is `MessageList.Viewport`.\n */\nconst MessageListRoot = ({\n children,\n model,\n renderer = defaultRenderer,\n registry,\n Chrome = DefaultChrome,\n Custom,\n debug,\n estimateSize = DEFAULT_ESTIMATE,\n stickyBottom = false,\n scrollPastEnd = false,\n overscan = 8,\n onRangeChange,\n}: MessageListRootProps) => {\n // Owned here so it outlives every row: a widget's state must survive its row being destroyed.\n const widgetStore = useMemo(createWidgetStateStore, []);\n\n const [viewport, setViewportState] = useState<HTMLElement | null>(null);\n // The same element as a ref, because the placement is bound to an element rather than to a render.\n const scrollerRef = useRef<HTMLElement | null>(null);\n const setViewport = useCallback((element: HTMLElement | null) => {\n scrollerRef.current = element;\n setViewportState(element);\n }, []);\n\n const [range, setRange] = useState<MessageRange | undefined>(undefined);\n const [mounted, setMounted] = useState(0);\n\n // One number standing in for the caller's estimate wherever a single value is needed, since a\n // per-row estimator answers about a row and not about the feed.\n const nominalSize = typeof estimateSize === 'number' ? estimateSize : DEFAULT_ESTIMATE;\n\n const [currentIndex, setCurrentIndex] = useState(0);\n const currentIndexRef = useRef(0);\n const setCurrent = useCallback((index: number) => {\n currentIndexRef.current = index;\n setCurrentIndex(index);\n }, []);\n\n // Widget census, kept per item so an item that unmounts takes its own widgets out of the total.\n // Batched into state rather than counted on every report: a scroll mounts and unmounts several\n // items per frame, and a readout is not worth a render each.\n const widgetCounts = useRef(new Map<string, number>());\n const [mountedWidgets, setMountedWidgets] = useState(0);\n const reportWidgets = useCallback((id: string, count: number) => {\n if (count) {\n widgetCounts.current.set(id, count);\n } else {\n widgetCounts.current.delete(id);\n }\n\n setMountedWidgets([...widgetCounts.current.values()].reduce((total, value) => total + value, 0));\n }, []);\n\n // What the host says, and nothing else.\n //\n // There is no running average here and no re-base built on one. Both existed because the old\n // engine could only revise the rows below the last one it had measured, so a feed opened at its\n // tail kept a document sized by the opening guess and had to be rebuilt from the top to correct\n // it — which moved every offset in the list at once. A measurement is kept against the row's id\n // and consulted from the anchor, so a wrong estimate is wrong only for rows nobody has seen (§8).\n const extents = useMemo(\n () => ({\n of: (index: number) => {\n const message = model.at(index);\n return typeof estimateSize === 'function' && message ? estimateSize(message, index) : nominalSize;\n },\n }),\n [model, estimateSize, nominalSize],\n );\n\n // The viewport less a nominal row: enough for the reader to bring the last row to the top.\n //\n // Measured from the element, never read back out of the layout. It is an *input* to the sizer, so\n // anything derived from the sizer would oscillate — and deliberately not the last row's measured\n // size, which the layout also feeds.\n const reserve = useMemo(\n () => (scrollPastEnd && viewport && model.count ? Math.max(0, viewport.clientHeight - nominalSize) : 0),\n [scrollPastEnd, viewport, model.count, nominalSize],\n );\n\n const controller = useRef<WindowController>(null);\n const onWindowChange = useCallback(\n (state: WindowState) => {\n const next = state.count ? { startIndex: state.visible.first, endIndex: state.visible.last } : undefined;\n setRange(next);\n setMounted(state.mounted.last - state.mounted.first + 1);\n if (next) {\n onRangeChange?.(next);\n // The cursor is the row at the top of the viewport, derived from the scroll and never set\n // alongside it. That is what makes a press always move: the row containing the offset is the\n // one the stop above begins strictly higher than, and the stop below strictly lower.\n setCurrent(next.startIndex);\n\n // Iteration is the model's (SPEC F-7.2): the window reaching an edge asks it for more, the\n // page arrives as a prepend the anchor is told about, and nothing on screen moves. The model\n // serializes and drains, so asking every commit costs nothing once the source is done.\n if (next.startIndex === 0) {\n void model.more('start');\n }\n if (next.endIndex >= model.count - 1) {\n void model.more('end');\n }\n }\n },\n [onRangeChange, setCurrent, model],\n );\n\n // The model *is* the window's model: FeedModel satisfies the structural contract, so there is no\n // adapter and no second copy of the truth.\n const { placement, windowRef, offset, sizerExtent, first, last } = useWindow({\n scrollerRef,\n model,\n extents,\n overscan,\n reserve,\n onChange: onWindowChange,\n controllerRef: controller,\n });\n\n // The follow is an aspect the host composes, not part of the window (SPEC §Aspects) — the intent,\n // its withdrawal by a backwards scroll, and the glide all live there.\n const follow = useFollow({\n scrollerRef,\n placement,\n extent: sizerExtent,\n count: model.count,\n reserve,\n enabled: stickyBottom,\n });\n\n // What a reader would call flicker: a row moving on screen by more than the scroll moved, sampled\n // once per frame so the reading is of what was painted. Only while debugging — it reads every\n // mounted row's box every frame.\n const jumps = useJumpDetector(viewport, debug);\n\n // Where each row was placed, and whether it stayed there.\n const { shifts, breaks, trace: shiftTrace, record: recordPositions, reset: resetShifts } = usePositionLog();\n useEffect(() => {\n const rows = [];\n for (let index = first; index <= last; index++) {\n rows.push({ key: model.getId(index), index, start: placement.positionOf(index) });\n }\n\n // The offset the layout was computed against, not the element's current `scrollTop`: reading the\n // DOM here samples a scroll that has moved on since, and every row then looks displaced by\n // exactly one frame of travel.\n recordPositions(rows, { scrollOffset: placement.scroll });\n }, [first, last, offset, sizerExtent, model, placement, recordPositions]);\n\n const scrollToIndex = useCallback(\n (index: number, { align = 'start', behavior = 'auto' }: ScrollToOptions = {}) => {\n // Before the write, not via the scroll event it raises: asking to be somewhere answers \"do\n // you want the tail?\", and the correction effect must not get a word in first.\n follow.onNavigate(index);\n controller.current?.scrollToIndex(index, align === 'center' ? 'start' : align, behavior);\n },\n [follow],\n );\n\n const scrollToBottom = useCallback(\n (options?: ScrollToOptions) => {\n if (model.count) {\n scrollToIndex(model.count - 1, { align: 'end', ...options });\n }\n },\n [scrollToIndex, model],\n );\n\n useEffect(() => {\n if (viewport) {\n // Why the list is where it is, readable from the console as `$0.__feed`.\n (viewport as any).__feed = {\n placement,\n get layout() {\n return placement.layout();\n },\n get shifted() {\n return { shifts, breaks, recent: shiftTrace.current };\n },\n };\n }\n }, [viewport, placement, shifts, breaks, shiftTrace]);\n\n // The one seam every driver calls (SPEC F-3.2): the toolbar's buttons, the arrows below, and the\n // rails all step through this, over the stops the model's policy names (SPEC F-3.1).\n const navigation = useFeedNavigation({\n controller,\n stops: () => model.stops(),\n current: () => currentIndexRef.current,\n count: () => model.count,\n scrollPastEnd,\n // The follow scrolls only while content arrives AND the reader is pinned to the bottom;\n // navigating anywhere but the last row un-pins, before the jump's own scroll event fires.\n onNavigate: follow.onNavigate,\n });\n\n // Arrow keys move by message, not by line: a plain arrow steps the cursor to the adjacent message\n // and Cmd/Ctrl + Arrow jumps to the first or last.\n //\n // Bound imperatively to the viewport element (rather than as a React prop) because\n // `ScrollArea.Viewport` narrows its props to the slottable set, and wrapping it in a focusable div\n // would insert a box into the height chain.\n useEffect(() => {\n if (!viewport) {\n return;\n }\n\n // The items are non-editable, so nothing inside the list takes focus on its own; the viewport\n // has to be focusable for the keymap to have somewhere to land.\n viewport.tabIndex = 0;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.altKey || event.shiftKey) {\n return;\n }\n\n const delta = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;\n if (!delta) {\n return;\n }\n\n event.preventDefault();\n if (event.metaKey || event.ctrlKey) {\n if (delta > 0) {\n navigation.last();\n } else {\n navigation.first();\n }\n return;\n }\n\n navigation.step(delta);\n };\n\n viewport.addEventListener('keydown', onKeyDown);\n return () => viewport.removeEventListener('keydown', onKeyDown);\n }, [viewport, navigation]);\n\n // The feed has one selection even though it has many editors; the group is what enforces that.\n const selectionGroup = useMemo(createSelectionGroup, []);\n\n return (\n <SelectionGroupContext.Provider value={selectionGroup}>\n <WidgetStateProvider store={widgetStore}>\n <MessageListProvider\n model={model}\n renderer={renderer}\n registry={registry}\n Chrome={Chrome}\n Custom={Custom}\n debug={debug}\n range={range}\n currentIndex={currentIndex}\n navigation={navigation}\n mountedRows={mounted}\n mountedWidgets={mountedWidgets}\n reportWidgets={reportWidgets}\n jumps={jumps}\n shifts={shifts}\n breaks={breaks}\n resetShifts={resetShifts}\n windowRef={windowRef}\n offset={offset}\n sizerExtent={sizerExtent}\n first={first}\n last={last}\n setViewport={setViewport}\n scrollToIndex={scrollToIndex}\n scrollToBottom={scrollToBottom}\n >\n {children}\n </MessageListProvider>\n </WidgetStateProvider>\n </SelectionGroupContext.Provider>\n );\n};\n\nMessageListRoot.displayName = 'MessageList.Root';\n\n//\n// Viewport\n//\n\nconst MESSAGE_LIST_VIEWPORT_NAME = 'MessageList.Viewport';\n\ntype MessageListViewportExtra = Pick<\n ScrollAreaRootProps,\n 'autoHide' | 'centered' | 'native' | 'padding' | 'scrollbars' | 'thin'\n> & {\n /**\n * Gutter size for the three-track layout each row is laid out on (`gutter | content | gutter`).\n *\n * The tracks live inside the row rather than on the container, because the rows are laid out by\n * the browser inside one placed parent and cannot be items of an outer grid. Chrome that wants the\n * gutters — a fork control, an avatar, a resolve toggle — opts in with `Column.Row`; everything\n * else sits in the centre track.\n */\n gutter?: 'sm' | 'md' | 'lg';\n};\n\n/**\n * The scroll container and the mounted window of rows.\n *\n * Scrolling belongs to `ScrollArea`, which owns the overlay thumbs and the padding tokens; the\n * placement needs only the element being scrolled, which `ScrollArea.Viewport` publishes.\n */\nconst MessageListViewport = composable<HTMLDivElement, MessageListViewportExtra>(\n ({ autoHide, centered, native, padding, scrollbars, thin, gutter, ...props }, forwardedRef) => {\n const { model, Chrome, windowRef, offset, sizerExtent, first, last, setViewport } =\n useMessageListContext(MESSAGE_LIST_VIEWPORT_NAME);\n // The value once, per-row state derived: hooks do not run in loops, and the row loop below is\n // one. Item-shaped chrome uses `useItemSelection(id)` instead.\n const { selectedIds, onSelect } = useItemSelectionValue();\n\n const handleViewportRef = useCallback(\n (element: HTMLDivElement | null) => {\n setViewport(element);\n setRef(forwardedRef, element);\n },\n [setViewport, forwardedRef],\n );\n\n const rows = [];\n for (let index = first; index <= last; index++) {\n const message = model.at(index);\n if (!message) {\n continue;\n }\n\n rows.push(\n // Unpositioned, and that is the point. A row that changes extent reflows the ones after it,\n // in the browser, in the same frame; placing each row ourselves meant re-placing every row\n // below it on every frame of the change — 177 re-placements for one disclosure opening (§6).\n <div key={message.id} data-index={index} data-object-id={message.id}>\n <Column.Root gutter={gutter}>\n <Column.Center>\n <Chrome\n message={message}\n index={index}\n selected={selectedIds?.has(message.id) ?? false}\n onSelect={(id, additive) => onSelect?.(id, additive)}\n >\n <MessageListItem message={message} />\n </Chrome>\n </Column.Center>\n </Column.Root>\n </div>,\n );\n }\n\n return (\n <ScrollArea.Root\n {...composableProps(props)}\n orientation='vertical'\n autoHide={autoHide}\n centered={centered}\n native={native}\n padding={padding}\n scrollbars={scrollbars}\n thin={thin}\n >\n <ScrollArea.Viewport\n data-testid='feed.viewport'\n // Off deliberately: the browser adjusting the scroll as well would be a second party\n // anchoring the same thing, and the defect this design exists to remove is exactly that.\n style={{ position: 'relative', overflowAnchor: 'none' }}\n ref={handleViewportRef}\n >\n {/* Holds no rows: it exists only to give the thumb something to measure. */}\n <div style={{ height: sizerExtent }} data-testid='feed.sizer' />\n <div\n ref={windowRef}\n className='absolute top-0 left-0 flex flex-col w-full'\n style={{ transform: `translateY(${offset}px)` }}\n data-testid='feed.window'\n >\n {rows}\n </div>\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n );\n },\n);\n\nMessageListViewport.displayName = MESSAGE_LIST_VIEWPORT_NAME;\n\n//\n// Item\n//\n\nconst MESSAGE_LIST_ITEM_NAME = 'MessageList.Item';\n\ntype MessageListItemExtra = {\n message: Message.Message;\n};\n\n/**\n * One message, rendered by the kind its renderer resolves. Exposed so a host can render a message\n * outside the scrolling window — a pinned message, a preview — through the same path.\n */\nconst MessageListItem = composable<HTMLDivElement, MessageListItemExtra>(({ message, ...props }, forwardedRef) => {\n const { renderer, registry, Custom, debug, reportWidgets } = useMessageListContext(MESSAGE_LIST_ITEM_NAME);\n const content = renderer(message);\n // The item asks for its own cross-cutting data by id (SPEC §Aspects); the list never routed it.\n const decorations = useDecorations(message.id);\n const hits = useMemo<HighlightRange[] | undefined>(\n () =>\n decorations.length\n ? decorations.map(({ range }) => [range.offset, range.offset + range.length] as HighlightRange)\n : undefined,\n [decorations],\n );\n const handleWidgetsChange = useCallback(\n (count: number) => reportWidgets(message.id, count),\n [reportWidgets, message.id],\n );\n\n return (\n // The outlines are the item and the block-level children of its document — which is exactly what\n // the virtualizer measures and what a widget's late paint changes.\n <div\n {...composableProps(props, {\n classNames: debug\n ? 'outline outline-1 outline-dashed outline-primary-500/50 [&_.cm-content>*]:outline [&_.cm-content>*]:outline-1 [&_.cm-content>*]:outline-dashed [&_.cm-content>*]:outline-neutral-500/40'\n : undefined,\n })}\n ref={forwardedRef}\n >\n {content.kind === 'markdown' && (\n <WidgetScopeProvider scope={message.id}>\n <MarkdownBlock text={content.text} registry={registry} hits={hits} onWidgetsChange={handleWidgetsChange} />\n </WidgetScopeProvider>\n )}\n {content.kind === 'html' && <HtmlBlock html={content.html} />}\n {content.kind === 'custom' && Custom && <Custom content={content} message={message} />}\n </div>\n );\n});\n\nMessageListItem.displayName = MESSAGE_LIST_ITEM_NAME;\n\n//\n// MessageList\n//\n\nexport const MessageList = {\n Root: MessageListRoot,\n Viewport: MessageListViewport,\n Item: MessageListItem,\n};\n\nexport type { MessageListItemExtra as MessageListItemProps, MessageListViewportExtra as MessageListViewportProps };\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport { type ThemedClassName } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type WindowState } from '../../virtualizer';\n\n/** 2rem, matching the outline rail it usually sits opposite. */\nconst DEFAULT_WIDTH = 32;\n\n/**\n * The whole list at a glance: what exists, what is mounted, and what the reader can see.\n *\n * Drawn in **content space**, not in indices. The three quantities a virtualized list can get wrong\n * are where the mounted rows are, how big the reader's slice of them is, and whether the total the\n * thumb is scaled against is honest — and none of those are visible from a row count. Here they are\n * three bars on one track, so a mounted range that has drifted away from the viewport, or a total\n * that jumps while nothing scrolls, is a picture rather than a number to be noticed.\n *\n * Debug only. It reads the state the window publishes and draws it (§2); it does not participate.\n */\nexport type MinimapProps = ThemedClassName<{\n state?: WindowState;\n /** Width of the rail, in px. @default 32 (2rem) */\n width?: number;\n /**\n * Jump to where the reader pointed, as a fraction of the whole list.\n *\n * A fraction and not an index: the map is drawn in content space, so what a click names is a\n * position, and turning that into a row is the host's business — it has the count.\n */\n onSelect?: (fraction: number) => void;\n}>;\n\nexport const Minimap = ({ classNames, state, width = DEFAULT_WIDTH, onSelect }: MinimapProps) => {\n const total = state?.geometry.total ?? 0;\n const scale = (value: number) => (total > 0 ? `${Math.max(0, Math.min(100, (value / total) * 100))}%` : '0%');\n\n return (\n <div\n className={mx('relative h-full bg-input-surface overflow-hidden', onSelect && 'cursor-pointer', classNames)}\n style={{ width }}\n title={total ? `${Math.round(total)}px over ${state?.count} rows` : 'empty'}\n data-testid='minimap'\n onClick={\n onSelect &&\n ((event) => {\n const box = event.currentTarget.getBoundingClientRect();\n onSelect(Math.max(0, Math.min(1, (event.clientY - box.top) / box.height)));\n })\n }\n >\n {state && total > 0 && (\n <>\n {/* Mounted rows: where content actually exists. Should always contain the viewport — if it\n does not, the reader is looking at rows nobody has rendered. */}\n <div\n className='absolute inset-x-0 bg-accent-fill/30'\n style={{ top: scale(state.geometry.window.start), height: scale(state.geometry.window.extent) }}\n data-testid='minimap.mounted'\n />\n {/* Tenths, so a glance says roughly where in the list a bar sits without reading a number. */}\n {Array.from({ length: 9 }, (_, step) => (\n <div\n key={step}\n className='absolute inset-x-0 border-t border-separator/40'\n style={{ top: `${((step + 1) / 10) * 100}%` }}\n />\n ))}\n {/* The viewport, which is the reader. */}\n <div\n className='absolute inset-x-0 border-y border-accent-bg bg-accent-fill/70'\n style={{ top: scale(state.geometry.scroll), height: scale(state.geometry.viewport) }}\n data-testid='minimap.viewport'\n />\n </>\n )}\n </div>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, {\n type KeyboardEvent as ReactKeyboardEvent,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { Popover, type ThemedClassName } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\n// Rest tick width (px) and the wave radius (in rows) over which the hover extension falls off. The\n// peak is the rail's own width: a tick at full extension spans it.\nconst REST_WIDTH = 8;\n/** 2rem, matching the debug minimap it usually sits opposite. */\nconst DEFAULT_WIDTH = 32;\nconst WAVE_SPREAD = 2;\n\n/** Height of one tick, and so the pitch the rail thins its markers to. */\nconst TICK_SIZE = 8;\n\nconst intersects = (range: { from: number; to: number }, visible: { from: number; to: number }): boolean =>\n range.from < visible.to && range.to > visible.from;\n\n/**\n * A single anchor marker within the mapped document.\n * `range` is expressed in the document's own position space (e.g. CodeMirror offsets).\n */\nexport type OutlineMarker = {\n id: string;\n title: string;\n description?: string;\n range: { from: number; to: number };\n};\n\nexport type OutlineProps = ThemedClassName<{\n markers: OutlineMarker[];\n /** Currently-visible document range; markers intersecting it render brighter (\"active\"). */\n visibleRange?: { from: number; to: number };\n /**\n * Height of one tick, in px.\n *\n * Fixed rather than shared out over the rail's height: ticks that stretch to fill make a short\n * document's markers enormous and a long one's hairline, so the same rail reads as a different\n * control depending on what is in it. A fixed pitch means the rail thins to what fits and every\n * tick looks the same. @default 8\n */\n tickSize?: number;\n /** Width of the rail, in px, which is also how far a tick extends at the peak of the wave. @default 32 (2rem) */\n width?: number;\n onSelect?: (marker: OutlineMarker, index: number) => void;\n /**\n * Step one item, in whatever the host counts items in.\n *\n * Not one tick: the rail thins its markers to what fits, so the next tick can be ten items away,\n * and a reader pressing an arrow means \"the next one\" rather than \"the next thing I can see\". The\n * host steps it with the same mechanism its toolbar uses, which is why this is a callback rather\n * than something the rail decides.\n */\n onNavigate?: (delta: number) => void;\n}>;\n\n/**\n * A vertical rail of horizontal ticks, each representing an anchor marker in a\n * scrollable document. The rows tile the full height, so hovering anywhere in the rail activates\n * the nearest tick: it (and its neighbours, with a distance falloff) extends rightward in a wave.\n * A single popover — anchored to the tick the reader is at — shows the\n * marker's title/description. Markers whose range intersects `visibleRange` render at full\n * opacity; the rest are dimmed.\n */\n/** A rendered tick: the marker it points at, and the span of the document it stands for. */\ntype Row = { marker: OutlineMarker; index: number; span: { from: number; to: number } };\n\n/**\n * Evenly spreads `markers` over at most `capacity` rows, keeping the first and last.\n *\n * Each rendered tick then stands for the span up to the next one, so a thinned rail still covers\n * the whole document — a position between two ticks belongs to the one above it, rather than to\n * nothing.\n */\nconst thin = (markers: OutlineMarker[], capacity: number): Row[] => {\n const step = capacity > 1 ? (markers.length - 1) / (capacity - 1) : 0;\n const indexes =\n markers.length <= capacity\n ? markers.map((_, index) => index)\n : Array.from({ length: capacity }, (_, row) => Math.round(row * step));\n\n return indexes.map((index, row) => ({\n marker: markers[index],\n index,\n span: { from: markers[index].range.from, to: markers[indexes[row + 1]]?.range.from ?? markers[index].range.to },\n }));\n};\n\nexport const Outline = ({\n classNames,\n markers,\n visibleRange,\n tickSize = TICK_SIZE,\n width = DEFAULT_WIDTH,\n onSelect,\n onNavigate,\n}: OutlineProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const rowsRef = useRef<Array<HTMLButtonElement | null>>([]);\n // Two ways to be *at* a tick, kept apart: the pointer is over one, or the rail has focus and the\n // reader has navigated to one. Conflating them means leaving with the mouse cannot dismiss the\n // card — the keyboard position immediately puts it back.\n const [pointer, setPointer] = useState<number | null>(null);\n const [navigated, setNavigated] = useState<number | null>(null);\n // The rail is bounded by whatever contains it. Measured rather than assumed, because the same\n // component is used both in a sized grid cell and floating over a document, where nothing\n // constrains it and its natural height is the right one.\n const [available, setAvailable] = useState(0);\n useEffect(() => {\n const parent = containerRef.current?.parentElement;\n if (!parent) {\n return;\n }\n\n const observer = new ResizeObserver(([entry]) => setAvailable(entry.contentRect.height));\n observer.observe(parent);\n setAvailable(parent.getBoundingClientRect().height);\n return () => observer.disconnect();\n }, []);\n\n const natural = markers.length * tickSize;\n // Below two rows the measurement is a transient (an unlaid-out parent), not a real constraint.\n const bounded = available >= tickSize * 2;\n const rows = useMemo(\n () => thin(markers, bounded ? Math.min(markers.length, Math.floor(available / tickSize)) : markers.length),\n [markers, bounded, available, tickSize],\n );\n\n // Rest is full opacity when there is no visible range to compare against. Compared against the\n // row's span rather than its marker, so a thinned rail still lights the tick the reader is under.\n const isActive = useCallback(\n (row: Row) => (visibleRange ? intersects(row.span, visibleRange) : true),\n [visibleRange],\n );\n\n // Not \"has focus\" — \"is being driven by the keyboard\". A click focuses a tick too, and gating on\n // focus left the card up after the pointer had gone: the reader clicked, moved away, and the\n // keyboard position they never asked for kept it open.\n const [keyboard, setKeyboard] = useState(false);\n\n // Whichever the reader is actually using. The pointer is the more direct statement, so it wins by\n // default — but not once the arrows are in play: clicking a tick and then pressing an arrow leaves\n // the pointer resting where it was clicked, and a pointer that always wins pins the card to a tick\n // the reader has already navigated away from. Pointing again takes it back (`onPointerEnter`).\n const shown = keyboard ? (navigated ?? pointer) : (pointer ?? navigated);\n\n // A backstop while a card is up: the pointer arriving anywhere outside the rail clears it.\n //\n // The tick's own leave is enough for a plain hover, and is not enough after a click — the popover\n // that opens brings focus guards that sit over the page, and the tick never sees the pointer go.\n // Verified with a real pointer: hovering away cleared it, clicking and then hovering away did not.\n // Asking the document is the one question no overlay can intercept.\n useEffect(() => {\n if (pointer == null) {\n return;\n }\n\n const rail = containerRef.current;\n const owner = rail?.ownerDocument;\n if (!rail || !owner) {\n return;\n }\n\n const onOver = (event: PointerEvent) => {\n if (!rail.contains(event.target as Node | null)) {\n setPointer(null);\n }\n };\n\n owner.addEventListener('pointerover', onOver);\n return () => owner.removeEventListener('pointerover', onOver);\n }, [pointer]);\n\n // Offset of the shown tick, read from the tick itself: the rail centres its ticks, so the position\n // is not `index * tickSize` and asking the element is the only answer that stays right.\n const [anchorOffset, setAnchorOffset] = useState(0);\n useLayoutEffect(() => {\n const row = shown == null ? null : rowsRef.current[shown];\n if (row) {\n setAnchorOffset(row.offsetTop + row.offsetHeight / 2);\n }\n }, [shown]);\n\n // Wave falloff: a Gaussian (normal-distribution) bell centred on the tick shown — a rounded\n // peak with inflected shoulders that flatten toward rest, rather than a dome. `WAVE_SPREAD` is\n // the standard deviation (in rows).\n const widthFor = useCallback(\n (index: number) => {\n if (shown == null) {\n return REST_WIDTH;\n }\n\n const distance = index - shown;\n const falloff = Math.exp(-(distance * distance) / (2 * WAVE_SPREAD * WAVE_SPREAD));\n return REST_WIDTH + (width - REST_WIDTH) * falloff;\n },\n [shown, width],\n );\n\n // Arrow keys walk the rail. The ticks are buttons, so tab reaches the rail and the arrows then\n // move within it — the roving behaviour a vertical list of controls is expected to have. The step\n // while the keyboard is still on a tick; and clamped to `rows`, since thinning can leave the ref\n // array longer than the rail it now renders.\n const handleKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const delta = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;\n if (!delta) {\n return;\n }\n\n // Navigation wins where the host offers it: a focused tick is a place in the document, so the\n // arrows move through the document rather than through the rail's own ticks — which are\n // thinned, and so are not one-to-one with anything the reader is counting.\n if (onNavigate) {\n event.preventDefault();\n setKeyboard(true);\n onNavigate(delta);\n return;\n }\n\n // Otherwise they walk the rail, the roving behaviour a vertical list of controls is expected\n // to have. Taken from the focused element rather than from the shown tick, and clamped to\n // `rows`, since thinning can leave the ref array longer than the rail it now renders.\n const current = rowsRef.current.findIndex((element) => element === event.target);\n if (current < 0) {\n return;\n }\n\n const next = Math.min(Math.max(current + delta, 0), rows.length - 1);\n event.preventDefault();\n rowsRef.current[next]?.focus();\n },\n [rows.length, onNavigate],\n );\n\n // While the rail has focus, the shown tick follows where the reader has got to.\n //\n // Navigation steps items and the rail shows ticks, and the two are not one-to-one — so after an\n // arrow press the popover would otherwise describe the tick the pointer last touched rather than\n // the place the reader is now. Derived from the visible range, so it tracks the list however the\n // reader moved: the arrows, the toolbar, or the scrollbar.\n const visibleFrom = visibleRange?.from;\n useEffect(() => {\n if (!keyboard || visibleFrom == null) {\n setNavigated(null);\n return;\n }\n\n const index = rows.findIndex((row) => visibleFrom < row.span.to);\n setNavigated(index < 0 ? rows.length - 1 : index);\n }, [keyboard, visibleFrom, rows]);\n\n const hoveredMarker = shown == null ? undefined : rows[shown]?.marker;\n\n return (\n <Popover.Root open={hoveredMarker != null}>\n <div\n role='navigation'\n className={mx('relative flex flex-col justify-center overflow-hidden', classNames)}\n style={{\n width: `${width}px`,\n height: bounded ? '100%' : `${natural}px`,\n }}\n // Published so a test can ask *why* a card is up — the pointer being over a tick, or the\n // keyboard having navigated to one — rather than only that it is (§12).\n data-pointer={pointer ?? ''}\n data-navigated={navigated ?? ''}\n // And which of the two is actually being shown, since that is the invariant — the reader\n // sees one card, not two states (§12).\n data-shown={shown ?? ''}\n // A pointer anywhere in the rail ends the keyboard's claim on what is shown: the reader has\n // gone back to pointing, and the two should not both assert a position.\n onPointerDown={() => setKeyboard(false)}\n onBlurCapture={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n setKeyboard(false);\n }\n }}\n onKeyDown={handleKeyDown}\n ref={containerRef}\n >\n {/* Ticks are a fixed height, so a document with fewer markers than the rail can hold does not\n fill it — and they are centred rather than stacked at the top, which reads as the rail\n having been cut short. Centring is the rail's own business; where the *rail* sits is its\n container's. */}\n {rows.map((row, index) => {\n const marker = row.marker;\n const active = isActive(row);\n const tick = (\n <button\n key={marker.id}\n type='button'\n aria-label={marker.title}\n // Full-width row; flexes to fill a bounded rail height (min height keeps it hoverable\n // when the rail is unbounded), so any point in the rail maps to a tick. The focus\n // ring is suppressed because focus already shows as the tick extending and\n // darkening — an outline around an invisible full-width row reads as a stray box.\n // `flex-1` with `min-h-0`: the rows share whatever height the rail has, so N markers\n // spread over the full range rather than stacking at their natural pitch and leaving\n // the rest blank. Thinning above guarantees they never need less than the pitch.\n className='flex items-center w-full shrink-0 cursor-pointer outline-none'\n style={{ height: tickSize }}\n ref={(element) => {\n rowsRef.current[index] = element;\n }}\n onPointerEnter={() => {\n setPointer(index);\n // The reader has gone back to pointing, so the keyboard's claim on what is shown\n // ends here rather than at a blur that a mouse never causes.\n setKeyboard(false);\n }}\n // Cleared on the tick that set it, rather than on the rail. A rail-level leave is\n // synthesised from over/out pairs and was observed not firing at all — the card\n // stayed up after the pointer had gone, in the browser and in a test. Moving between\n // ticks does not flicker: the leave and the next enter land in one batch.\n onPointerLeave={() => setPointer((current) => (current === index ? null : current))}\n onFocus={() => keyboard && setNavigated(index)}\n onClick={() => onSelect?.(marker, row.index)}\n >\n <div\n className={mx(\n 'h-[3px] rounded-full transition-all duration-200 ease-out',\n shown === index ? 'bg-neutral-800 dark:bg-neutral-200' : 'bg-neutral-400 dark:bg-neutral-600',\n active ? 'opacity-100' : 'opacity-30',\n )}\n style={{ width: widthFor(index) }}\n />\n </button>\n );\n\n return tick;\n })}\n\n {/* A separate anchor, and the ticks are never wrapped.\n Wrapping the shown tick in `Popover.Anchor` changes the element type at that position, so\n React unmounts and remounts that button — and a destroyed element never receives\n `pointerleave`, which is why the card would not dismiss when the pointer left. Verified\n with a real pointer: `data-pointer` stayed on the tick after the pointer had gone.\n A zero-height anchor moved to the shown tick's offset keeps every tick stable; the\n popover is keyed to the marker so it still re-measures when the anchor moves. */}\n <Popover.Anchor asChild>\n <div className='absolute left-0' style={{ top: anchorOffset, width, height: 0 }} />\n </Popover.Anchor>\n </div>\n {hoveredMarker && (\n <Popover.Content\n // Keyed to the tick: the popover measures its anchor when it mounts, and moving the anchor\n // to a different element does not make it measure again — the card stayed put while the\n // pointer walked the rail, drifting further from the tick with every step. Remounting per\n // tick is what makes it re-measure.\n key={hoveredMarker.id}\n side='right'\n align='center'\n // Pinned to the anchor point rather than flipped into view, so it tracks the tick.\n avoidCollisions={false}\n onOpenAutoFocus={(event) => event.preventDefault()}\n >\n <Popover.Viewport>\n <div className='px-2 py-1 max-w-[24rem] w-[24rem]'>\n <p className='truncate font-medium'>{hoveredMarker.title}</p>\n {hoveredMarker.description && (\n <p className='mt-1 text-sm text-description line-clamp-3'>{hoveredMarker.description}</p>\n )}\n </div>\n </Popover.Viewport>\n <Popover.Arrow />\n </Popover.Content>\n )}\n </Popover.Root>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { A as ItemSelectionProvider, C as useWidgetState, D as SelectionGroupContext, E as setHighlights, F as useFeedNavigation, I as DecorationsProvider, L as useDecorations, M as useItemSelection, N as useItemSelectionValue, O as createSelectionGroup, P as useFollow, R as ScrollFollower, S as createWidgetStateStore, T as highlights, _ as sliceFeed, a as useJumpDetector, b as WidgetScopeProvider, c as useFeedModel, d as fromMessages, f as ListModel, g as searchFeed, h as messageText, i as useMessageList, j as isAdditive, k as useSelectionGroup, l as useListModel, m as defaultRenderer, n as Minimap, o as usePositionLog, p as chatRenderer, r as MessageList, t as Outline, u as FeedModel, v as MarkdownBlock, w as highlightTheme, x as WidgetStateProvider, y as HtmlBlock, z as stepVelocity } from "./chunk-src.mjs";
2
+ export { DecorationsProvider, FeedModel, HtmlBlock, ItemSelectionProvider, ListModel, MarkdownBlock, MessageList, Minimap, Outline, ScrollFollower, SelectionGroupContext, WidgetScopeProvider, WidgetStateProvider, chatRenderer, createSelectionGroup, createWidgetStateStore, defaultRenderer, fromMessages, highlightTheme, highlights, isAdditive, messageText, searchFeed, setHighlights, sliceFeed, stepVelocity, useDecorations, useFeedModel, useFeedNavigation, useFollow, useItemSelection, useItemSelectionValue, useJumpDetector, useListModel, useMessageList, usePositionLog, useSelectionGroup, useWidgetState };