foldkit 0.33.6 → 0.34.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 (79) hide show
  1. package/README.md +19 -19
  2. package/dist/devtools/overlay.d.ts.map +1 -1
  3. package/dist/devtools/overlay.js +76 -33
  4. package/dist/html/index.d.ts +431 -5
  5. package/dist/html/index.d.ts.map +1 -1
  6. package/dist/html/index.js +1 -0
  7. package/dist/html/public.d.ts +1 -2
  8. package/dist/html/public.d.ts.map +1 -1
  9. package/dist/html/public.js +1 -2
  10. package/dist/task/dom.d.ts +6 -6
  11. package/dist/task/dom.js +6 -6
  12. package/dist/task/inert.d.ts +2 -2
  13. package/dist/task/inert.js +2 -2
  14. package/dist/task/scrollLock.d.ts +2 -2
  15. package/dist/task/scrollLock.js +2 -2
  16. package/dist/ui/checkbox/index.d.ts +5 -9
  17. package/dist/ui/checkbox/index.d.ts.map +1 -1
  18. package/dist/ui/checkbox/index.js +7 -10
  19. package/dist/ui/checkbox/public.d.ts +1 -1
  20. package/dist/ui/checkbox/public.d.ts.map +1 -1
  21. package/dist/ui/combobox/multi.d.ts +31 -10
  22. package/dist/ui/combobox/multi.d.ts.map +1 -1
  23. package/dist/ui/combobox/multi.js +1 -1
  24. package/dist/ui/combobox/public.d.ts +1 -1
  25. package/dist/ui/combobox/public.d.ts.map +1 -1
  26. package/dist/ui/combobox/shared.d.ts +53 -14
  27. package/dist/ui/combobox/shared.d.ts.map +1 -1
  28. package/dist/ui/combobox/shared.js +72 -28
  29. package/dist/ui/combobox/single.d.ts +31 -10
  30. package/dist/ui/combobox/single.d.ts.map +1 -1
  31. package/dist/ui/combobox/single.js +1 -1
  32. package/dist/ui/dialog/index.d.ts +14 -8
  33. package/dist/ui/dialog/index.d.ts.map +1 -1
  34. package/dist/ui/dialog/index.js +21 -12
  35. package/dist/ui/dialog/public.d.ts +1 -1
  36. package/dist/ui/dialog/public.d.ts.map +1 -1
  37. package/dist/ui/dialog/public.js +1 -1
  38. package/dist/ui/disclosure/index.d.ts +11 -8
  39. package/dist/ui/disclosure/index.d.ts.map +1 -1
  40. package/dist/ui/disclosure/index.js +20 -18
  41. package/dist/ui/disclosure/public.d.ts +1 -1
  42. package/dist/ui/disclosure/public.d.ts.map +1 -1
  43. package/dist/ui/listbox/multi.d.ts +34 -9
  44. package/dist/ui/listbox/multi.d.ts.map +1 -1
  45. package/dist/ui/listbox/multi.js +1 -1
  46. package/dist/ui/listbox/public.d.ts +1 -1
  47. package/dist/ui/listbox/public.d.ts.map +1 -1
  48. package/dist/ui/listbox/shared.d.ts +57 -13
  49. package/dist/ui/listbox/shared.d.ts.map +1 -1
  50. package/dist/ui/listbox/shared.js +77 -27
  51. package/dist/ui/listbox/single.d.ts +34 -9
  52. package/dist/ui/listbox/single.d.ts.map +1 -1
  53. package/dist/ui/listbox/single.js +1 -1
  54. package/dist/ui/menu/index.d.ts +39 -11
  55. package/dist/ui/menu/index.d.ts.map +1 -1
  56. package/dist/ui/menu/index.js +81 -32
  57. package/dist/ui/menu/public.d.ts +1 -1
  58. package/dist/ui/menu/public.d.ts.map +1 -1
  59. package/dist/ui/popover/index.d.ts +28 -12
  60. package/dist/ui/popover/index.d.ts.map +1 -1
  61. package/dist/ui/popover/index.js +50 -24
  62. package/dist/ui/popover/public.d.ts +1 -1
  63. package/dist/ui/popover/public.d.ts.map +1 -1
  64. package/dist/ui/radioGroup/index.d.ts +8 -7
  65. package/dist/ui/radioGroup/index.d.ts.map +1 -1
  66. package/dist/ui/radioGroup/index.js +12 -9
  67. package/dist/ui/radioGroup/public.d.ts +1 -1
  68. package/dist/ui/radioGroup/public.d.ts.map +1 -1
  69. package/dist/ui/switch/index.d.ts +5 -9
  70. package/dist/ui/switch/index.d.ts.map +1 -1
  71. package/dist/ui/switch/index.js +7 -10
  72. package/dist/ui/switch/public.d.ts +1 -1
  73. package/dist/ui/switch/public.d.ts.map +1 -1
  74. package/dist/ui/tabs/index.d.ts +13 -9
  75. package/dist/ui/tabs/index.d.ts.map +1 -1
  76. package/dist/ui/tabs/index.js +30 -17
  77. package/dist/ui/tabs/public.d.ts +1 -1
  78. package/dist/ui/tabs/public.d.ts.map +1 -1
  79. package/package.json +3 -3
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/devinjameson/foldkit/main/packages/website/public/logo-dark.svg">
4
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/devinjameson/foldkit/main/packages/website/public/logo.svg">
5
- <img src="https://raw.githubusercontent.com/devinjameson/foldkit/main/packages/website/public/logo.svg" alt="Foldkit" width="350">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/foldkit/foldkit/main/packages/website/public/logo-dark.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/foldkit/foldkit/main/packages/website/public/logo.svg">
5
+ <img src="https://raw.githubusercontent.com/foldkit/foldkit/main/packages/website/public/logo.svg" alt="Foldkit" width="350">
6
6
  </picture>
7
7
  </p>
8
8
 
@@ -13,12 +13,12 @@
13
13
  <h3 align="center">The frontend framework for correctness.</h3>
14
14
 
15
15
  <p align="center">
16
- <a href="https://foldkit.dev"><strong>Documentation</strong></a> · <a href="https://github.com/devinjameson/foldkit#examples"><strong>Examples</strong></a> · <a href="https://foldkit.dev/getting-started"><strong>Getting Started</strong></a>
16
+ <a href="https://foldkit.dev"><strong>Documentation</strong></a> · <a href="https://github.com/foldkit/foldkit#examples"><strong>Examples</strong></a> · <a href="https://foldkit.dev/getting-started"><strong>Getting Started</strong></a>
17
17
  </p>
18
18
 
19
19
  ---
20
20
 
21
- Foldkit is an [Elm Architecture](https://guide.elm-lang.org/architecture/) framework for TypeScript, powered by [Effect](https://effect.website/). One Model, one update function, one way to do things. No hooks, no local state, no hidden mutations.
21
+ Foldkit is a frontend framework for TypeScript, built on [Effect](https://effect.website/), using [The Elm Architecture](https://guide.elm-lang.org/architecture/). One Model, one update function, one way to do things. No hooks, no local state, no hidden mutations.
22
22
 
23
23
  > [!NOTE]
24
24
  > Foldkit is pre-1.0. The core API is stable, but breaking changes may occur in minor releases. See the [changelog](./CHANGELOG.md) for details.
@@ -129,7 +129,7 @@ const element = Runtime.makeElement({
129
129
  Runtime.run(element)
130
130
  ```
131
131
 
132
- Source: [examples/counter/src/main.ts](https://github.com/devinjameson/foldkit/blob/main/examples/counter/src/main.ts)
132
+ Source: [examples/counter/src/main.ts](https://github.com/foldkit/foldkit/blob/main/examples/counter/src/main.ts)
133
133
 
134
134
  ## What Ships With Foldkit
135
135
 
@@ -153,19 +153,19 @@ This is what makes Foldkit unusually AI-friendly. The same property that makes t
153
153
 
154
154
  ## Examples
155
155
 
156
- - **[Counter](https://github.com/devinjameson/foldkit/blob/main/examples/counter/src/main.ts)** — Increment/decrement with reset
157
- - **[Todo](https://github.com/devinjameson/foldkit/blob/main/examples/todo/src/main.ts)** — CRUD operations with localStorage persistence
158
- - **[Stopwatch](https://github.com/devinjameson/foldkit/blob/main/examples/stopwatch/src/main.ts)** — Timer with start/stop/reset
159
- - **[Error View](https://github.com/devinjameson/foldkit/blob/main/examples/error-view/src/main.ts)** — Custom error fallback UI
160
- - **[Form](https://github.com/devinjameson/foldkit/blob/main/examples/form/src/main.ts)** — Form validation with async email checking
161
- - **[Weather](https://github.com/devinjameson/foldkit/blob/main/examples/weather/src/main.ts)** — HTTP requests with async state handling
162
- - **[Routing](https://github.com/devinjameson/foldkit/blob/main/examples/routing/src/main.ts)** — URL routing with parser combinators
163
- - **[Query Sync](https://github.com/devinjameson/foldkit/blob/main/examples/query-sync/src/main.ts)** — URL query parameter sync with filtering and sorting
164
- - **[Snake](https://github.com/devinjameson/foldkit/blob/main/examples/snake/src/main.ts)** — Classic game built with Subscriptions
165
- - **[Auth](https://github.com/devinjameson/foldkit/blob/main/examples/auth/src/main.ts)** — Authentication flow with Submodels and OutMessage
166
- - **[Shopping Cart](https://github.com/devinjameson/foldkit/blob/main/examples/shopping-cart/src/main.ts)** — Nested models and complex state
167
- - **[WebSocket Chat](https://github.com/devinjameson/foldkit/blob/main/examples/websocket-chat/src/main.ts)** — Managed Resources with WebSocket integration
168
- - **[Typing Game](https://github.com/devinjameson/foldkit/tree/main/packages/typing-game)** — Multiplayer typing game with Effect RPC backend
156
+ - **[Counter](https://github.com/foldkit/foldkit/blob/main/examples/counter/src/main.ts)** — Increment/decrement with reset
157
+ - **[Todo](https://github.com/foldkit/foldkit/blob/main/examples/todo/src/main.ts)** — CRUD operations with localStorage persistence
158
+ - **[Stopwatch](https://github.com/foldkit/foldkit/blob/main/examples/stopwatch/src/main.ts)** — Timer with start/stop/reset
159
+ - **[Error View](https://github.com/foldkit/foldkit/blob/main/examples/error-view/src/main.ts)** — Custom error fallback UI
160
+ - **[Form](https://github.com/foldkit/foldkit/blob/main/examples/form/src/main.ts)** — Form validation with async email checking
161
+ - **[Weather](https://github.com/foldkit/foldkit/blob/main/examples/weather/src/main.ts)** — HTTP requests with async state handling
162
+ - **[Routing](https://github.com/foldkit/foldkit/blob/main/examples/routing/src/main.ts)** — URL routing with parser combinators
163
+ - **[Query Sync](https://github.com/foldkit/foldkit/blob/main/examples/query-sync/src/main.ts)** — URL query parameter sync with filtering and sorting
164
+ - **[Snake](https://github.com/foldkit/foldkit/blob/main/examples/snake/src/main.ts)** — Classic game built with Subscriptions
165
+ - **[Auth](https://github.com/foldkit/foldkit/blob/main/examples/auth/src/main.ts)** — Authentication flow with Submodels and OutMessage
166
+ - **[Shopping Cart](https://github.com/foldkit/foldkit/blob/main/examples/shopping-cart/src/main.ts)** — Nested models and complex state
167
+ - **[WebSocket Chat](https://github.com/foldkit/foldkit/blob/main/examples/websocket-chat/src/main.ts)** — Managed Resources with WebSocket integration
168
+ - **[Typing Game](https://github.com/foldkit/foldkit/tree/main/packages/typing-game)** — Multiplayer typing game with Effect RPC backend
169
169
 
170
170
  ## License
171
171
 
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/devtools/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,MAAM,EAIN,MAAM,EAQP,MAAM,QAAQ,CAAA;AAOf,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAMxE,OAAO,EAAE,KAAK,aAAa,EAA+B,MAAM,SAAS,CAAA;AA4wCzE,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,UAAU,gBAAgB,EAC1B,MAAM,YAAY,EAClB,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,sCA4ChC,CAAA"}
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/devtools/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,MAAM,EAIN,MAAM,EAQP,MAAM,QAAQ,CAAA;AAOf,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAMxE,OAAO,EAAE,KAAK,aAAa,EAA+B,MAAM,SAAS,CAAA;AA2yCzE,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,UAAU,gBAAgB,EAC1B,MAAM,YAAY,EAClB,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,sCA4ChC,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { Array as Array_, Effect, HashSet, Match as M, Number as Number_, Option, Predicate, Record, Schema as S, Stream, String as String_, SubscriptionRef, pipe, } from 'effect';
3
3
  import { OptionExt } from '../effectExtensions';
4
- import { html } from '../html';
4
+ import { createKeyedLazy, html } from '../html';
5
5
  import { m } from '../message';
6
6
  import { makeElement } from '../runtime/runtime';
7
7
  import { makeSubscriptions } from '../runtime/subscription';
@@ -352,6 +352,8 @@ const PANEL_POSITION_CLASS = {
352
352
  };
353
353
  const makeView = (position, mode, maybeBanner) => {
354
354
  const { div, header, span, ul, button, svg, path, keyed, Class, Style, OnClick, AriaHidden, Xmlns, Fill, ViewBox, StrokeWidth, Stroke, StrokeLinecap, StrokeLinejoin, D, } = html();
355
+ const lazyTreeNode = createKeyedLazy();
356
+ const lazyMessageRow = createKeyedLazy();
355
357
  // JSON TREE
356
358
  const leafValueView = (value) => M.value(value).pipe(M.when(Predicate.isNull, () => span([Class('json-null italic')], ['null'])), M.when(Predicate.isUndefined, () => span([Class('json-null italic')], ['undefined'])), M.when(Predicate.isString, stringValue => span([Class('json-string')], [`"${stringValue}"`])), M.when(Predicate.isNumber, numberValue => span([Class('json-number')], [String(numberValue)])), M.when(Predicate.isBoolean, booleanValue => span([Class('json-boolean')], [String(booleanValue)])), M.orElse(unknownValue => span([Class('json-null')], [String(unknownValue)])));
357
359
  const keyView = (key) => span([Class('json-key')], [`${key}:\u00a0`]);
@@ -375,72 +377,103 @@ const makeView = (position, mode, maybeBanner) => {
375
377
  const tagLabelView = (tag) => span([Class('json-tag')], [tag]);
376
378
  const diffDotView = span([Class('diff-dot')], []);
377
379
  const inlineDiffDotView = span([Class('diff-dot-inline')], []);
378
- const flattenTree = (value, treePath, expandedPaths, changedPaths, affectedPaths, depth, maybeKey, accumulator, indentRootChildren) => {
380
+ const flattenTree = ({ value, treePath, depth, key, ...shared }) => {
381
+ const { expandedPaths, changedPaths, affectedPaths, accumulator, indentRootChildren, } = shared;
379
382
  const isRoot = treePath === 'root';
380
383
  const nodeIsExpandable = isExpandable(value);
381
384
  const isExpanded = nodeIsExpandable && (isRoot || HashSet.has(expandedPaths, treePath));
382
- const maybeTag = pipe(value, Option.liftPredicate(isTagged), Option.map(({ _tag }) => _tag));
385
+ const tag = isTagged(value) ? value._tag : '';
383
386
  accumulator.push({
384
387
  value,
385
388
  treePath,
386
389
  depth,
387
- maybeKey,
390
+ key,
388
391
  isExpandable: nodeIsExpandable,
389
392
  isExpanded,
390
393
  isChanged: HashSet.has(changedPaths, treePath),
391
394
  isAffected: HashSet.has(affectedPaths, treePath),
392
- maybeTag,
395
+ tag,
393
396
  });
394
397
  if (!isExpanded) {
395
398
  return;
396
399
  }
397
400
  const childDepth = isRoot && !indentRootChildren ? depth : depth + 1;
398
401
  if (Array.isArray(value)) {
399
- value.forEach((item, arrayIndex) => flattenTree(item, `${treePath}.${arrayIndex}`, expandedPaths, changedPaths, affectedPaths, childDepth, Option.some(String(arrayIndex)), accumulator, indentRootChildren));
402
+ value.forEach((item, arrayIndex) => flattenTree({
403
+ ...shared,
404
+ value: item,
405
+ treePath: `${treePath}.${arrayIndex}`,
406
+ depth: childDepth,
407
+ key: String(arrayIndex),
408
+ }));
400
409
  }
401
410
  else if (Predicate.isReadonlyRecord(value)) {
402
- pipe(value, Record.toEntries, Array_.filter(([key]) => key !== '_tag'), Array_.forEach(([key, childValue]) => flattenTree(childValue, `${treePath}.${key}`, expandedPaths, changedPaths, affectedPaths, childDepth, Option.some(key), accumulator, indentRootChildren)));
411
+ pipe(value, Record.toEntries, Array_.filter(([entryKey]) => entryKey !== '_tag'), Array_.forEach(([entryKey, childValue]) => flattenTree({
412
+ ...shared,
413
+ value: childValue,
414
+ treePath: `${treePath}.${entryKey}`,
415
+ depth: childDepth,
416
+ key: entryKey,
417
+ })));
403
418
  }
404
419
  };
405
- const flatNodeView = (node) => {
406
- const indent = Style({ paddingLeft: `${node.depth * TREE_INDENT_PX}px` });
407
- const hasDiffDot = node.isChanged || node.isAffected;
408
- if (!node.isExpandable) {
420
+ const flatNodeView = (value, treePath, depth, key, nodeIsExpandable, isExpanded, isChanged, isAffected, tag) => {
421
+ const indent = Style({ paddingLeft: `${depth * TREE_INDENT_PX}px` });
422
+ const hasDiffDot = isChanged || isAffected;
423
+ if (!nodeIsExpandable) {
409
424
  return div([
410
- Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', node.isChanged && 'diff-changed')),
425
+ Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', isChanged && 'diff-changed')),
411
426
  indent,
412
427
  ], [
413
428
  ...(hasDiffDot ? [diffDotView] : []),
414
- ...Array_.getSomes([Option.map(node.maybeKey, keyView)]),
415
- leafValueView(node.value),
429
+ ...(String_.isNonEmpty(key) ? [keyView(key)] : []),
430
+ leafValueView(value),
416
431
  ]);
417
432
  }
418
- const isRoot = node.treePath === 'root';
419
- const preview = node.isExpanded
420
- ? Array.isArray(node.value)
421
- ? `(${node.value.length})`
433
+ const isRoot = treePath === 'root';
434
+ const preview = isExpanded
435
+ ? Array.isArray(value)
436
+ ? `(${value.length})`
422
437
  : ''
423
- : collapsedPreview(node.value);
438
+ : collapsedPreview(value);
424
439
  return div([
425
- Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', !isRoot && 'tree-row-expandable cursor-pointer', node.isChanged && 'diff-changed')),
440
+ Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', !isRoot && 'tree-row-expandable cursor-pointer', isChanged && 'diff-changed')),
426
441
  indent,
427
- ...(isRoot ? [] : [OnClick(ToggledTreeNode({ path: node.treePath }))]),
442
+ ...(isRoot ? [] : [OnClick(ToggledTreeNode({ path: treePath }))]),
428
443
  ], [
429
- ...(isRoot ? [] : [arrowView(node.isExpanded)]),
444
+ ...(isRoot ? [] : [arrowView(isExpanded)]),
430
445
  ...(!isRoot && hasDiffDot ? [diffDotView] : []),
431
- ...Array_.getSomes([
432
- Option.map(node.maybeKey, keyView),
433
- Option.map(node.maybeTag, tagLabelView),
434
- ]),
446
+ ...(String_.isNonEmpty(key) ? [keyView(key)] : []),
447
+ ...(String_.isNonEmpty(tag) ? [tagLabelView(tag)] : []),
435
448
  span([Class('json-preview')], [preview]),
436
449
  ]);
437
450
  };
438
451
  const treeView = (value, rootPath, expandedPaths, changedPaths, affectedPaths, maybeRootLabel, indentRootChildren) => {
439
452
  const nodes = [];
440
- flattenTree(value, rootPath, expandedPaths, changedPaths, affectedPaths, 0, maybeRootLabel, nodes, indentRootChildren);
453
+ flattenTree({
454
+ value,
455
+ treePath: rootPath,
456
+ expandedPaths,
457
+ changedPaths,
458
+ affectedPaths,
459
+ depth: 0,
460
+ key: Option.getOrElse(maybeRootLabel, () => ''),
461
+ accumulator: nodes,
462
+ indentRootChildren,
463
+ });
441
464
  return div([
442
465
  Class('inspector-tree flex-1 overflow-auto min-h-0 min-w-0 overscroll-none'),
443
- ], nodes.map(flatNodeView));
466
+ ], nodes.map(node => lazyTreeNode(node.treePath, flatNodeView, [
467
+ node.value,
468
+ node.treePath,
469
+ node.depth,
470
+ node.key,
471
+ node.isExpandable,
472
+ node.isExpanded,
473
+ node.isChanged,
474
+ node.isAffected,
475
+ node.tag,
476
+ ])));
444
477
  };
445
478
  const inspectedTimestamp = (model) => {
446
479
  const lastIndex = Array_.isEmptyReadonlyArray(model.entries)
@@ -490,12 +523,15 @@ const makeView = (position, mode, maybeBanner) => {
490
523
  model: model.inspectorTabs,
491
524
  toMessage: tabsMessage => GotInspectorTabsMessage({ message: tabsMessage }),
492
525
  tabs: INSPECTOR_TABS,
493
- className: 'flex flex-col flex-1 min-h-0',
494
- tabListClassName: 'flex border-b shrink-0',
526
+ tabListAriaLabel: 'Inspector tabs',
527
+ attributes: [Class('flex flex-col flex-1 min-h-0')],
528
+ tabListAttributes: [Class('flex border-b shrink-0')],
495
529
  tabToConfig: (tab, { isActive }) => ({
496
- buttonClassName: clsx('dt-tab-button cursor-pointer text-base font-mono px-3 py-1', isActive ? 'text-dt dt-tab-active' : 'text-dt-muted'),
530
+ buttonAttributes: [
531
+ Class(clsx('dt-tab-button cursor-pointer text-base font-mono px-3 py-1', isActive ? 'text-dt dt-tab-active' : 'text-dt-muted')),
532
+ ],
497
533
  buttonContent: span([], [tab]),
498
- panelClassName: 'flex flex-col flex-1 min-h-0 min-w-0',
534
+ panelAttributes: [Class('flex flex-col flex-1 min-h-0 min-w-0')],
499
535
  panelContent: Option.match(model.maybeInspectedModel, {
500
536
  onNone: () => emptyInspectorView,
501
537
  onSome: inspectedModel => inspectorTabContent(model, tab, inspectedModel),
@@ -608,7 +644,14 @@ const makeView = (position, mode, maybeBanner) => {
608
644
  const absoluteIndex = model.startIndex + arrayIndex;
609
645
  const isSelected = selectedIndex === absoluteIndex;
610
646
  const isPausedHere = model.isPaused && model.pausedAtIndex === absoluteIndex;
611
- return messageRowView(entry.tag, absoluteIndex, isSelected, isPausedHere, entry.timestamp - baseTimestamp, entry.isModelChanged);
647
+ return lazyMessageRow(String(absoluteIndex), messageRowView, [
648
+ entry.tag,
649
+ absoluteIndex,
650
+ isSelected,
651
+ isPausedHere,
652
+ entry.timestamp - baseTimestamp,
653
+ entry.isModelChanged,
654
+ ]);
612
655
  }), Array_.reverse);
613
656
  return ul([Class('message-list flex-1 overflow-y-auto min-h-0 overscroll-none')], [
614
657
  ...messageRows,