Collections and data views

Copy Markdown View Source

GPUI provides variable-height and uniform source-backed collection primitives. Stable identities and explicit bounds let native displays virtualize layout without moving application data policy out of Elixir.

Virtualized collections

virtual_collection/1 is the variable-height primitive for transcripts, activity feeds, and other heterogeneous collections. The complete logical collection stays in the renderer-independent snapshot, while the native display measures and renders the visible region with bounded pixel overdraw.

<UI.virtual_collection
  id="transcript"
  label="Conversation"
  alignment="bottom"
  follow="tail"
  follow_request={assigns.follow_request}
  overdraw={320}
  phx-range="messages_visible"
  class="h-[560px]"
>
  {Enum.map(assigns.messages, fn message ->
    UI.virtual_item(%{
      id: message.id,
      revision: message.revision,
      children: [render_message(message)]
    })
  end)}
</UI.virtual_collection>

alignment="bottom" starts short collections at the lower edge. follow="tail" tracks appended or growing tail content until the user scrolls away; increment follow_request to explicitly return to the tail. To reveal a retained item, set reveal and increment reveal_request. The truthful placement strategies for the variable-height primitive are nearest and top.

Every item ID is stable, unique, non-empty, and at most 128 bytes. Increment an item's revision when retained content may change measured height. Appends, prepends, removals, and revision changes preserve GPUI's measured-height cache for unaffected IDs. Collections are bounded to 100,000 items and overdraw to 4,096 pixels.

phx-range emits deduplicated visible %{first: first, last: last} ranges with an exclusive last. Unlike the uniform source-backed primitive below, variable collections currently require all logical item nodes in each snapshot: unknown offscreen heights have no truthful placeholder geometry. Remote displays receive this same complete serializable contract.

virtual_list/1 presents source-backed uniform collections through GPUI's native uniform-list layout. Every row has a stable ID and the same declared height; only the visible range is constructed and laid out natively.

<UI.virtual_list
  id="processes"
  label="BEAM processes"
  selected={assigns.selected_pid}
  reveal={assigns.selected_pid}
  reveal_strategy="nearest"
  item_height={48}
  phx-change="process_selected"
  class="h-[480px]"
>
  {Enum.map(assigns.processes, fn process ->
    UI.virtual_list_item(%{
      id: process.pid,
      children: [process.label]
    })
  end)}
</UI.virtual_list>

selected is controlled and changes emit the selected item ID. reveal requests programmatic scrolling with nearest, top, center, or bottom placement. Up/Down, Home/End, Enter, and Space operate from one listbox tab stop and skip disabled rows. Pointer selection focuses the list, and native accessibility exposes listbox and option roles with an active descendant.

The list itself must have a fixed or maximum height. Rows that differ from item_height violate the uniform-list contract. Filtering and sorting may replace or reorder children while scroll state remains attached to the list's stable ID.

Source-backed lists

Rendering is not the only cost of a large collection: putting every child—or the full source model—into view assigns also serializes it into every local or remote snapshot. Source-backed lists keep the full model in a supervised source process and put only the current loaded slice in assigns.

<UI.virtual_list
  id="records"
  label="Records"
  total_count={assigns.total_count}
  offset={assigns.loaded_offset}
  overscan={8}
  selected={assigns.selected_id}
  selected_index={assigns.selected_index}
  reveal={assigns.selected_id}
  reveal_index={assigns.selected_index}
  item_height={40}
  phx-change="record_selected"
  phx-range="records_range"
  class="h-[480px]"
>
  {Enum.map(assigns.loaded_rows, &row/1)}
</UI.virtual_list>

phx-range emits %{first: first, last: last} with an exclusive last index. The requested range already includes overscan. The source responds by loading a contiguous slice, setting offset to first, and replacing loaded_rows. selected_index and reveal_index preserve controlled selection and distant reveal while the selected row is unloaded. Placeholder rows retain the correct scroll geometry until the requested slice arrives. Arrow navigation uses the loaded overscan; Home and End activate only when the corresponding endpoint is loaded. Applications can perform global jumps by changing the controlled selection and reveal index.

Use GPUI.Test.range/5 to request ranges without a native display. Native range events are coalesced per render cycle and use the same protocol over remote displays.

Source-backed data tables

data_table/1 adds fixed headers, horizontal scrolling, explicit column widths, numeric alignment, sorting events, and grid accessibility to the uniform source-backed collection contract. Column definitions precede loaded rows, and every row contains one child per column:

<UI.data_table
  id="processes"
  label="BEAM processes"
  total_count={assigns.total_count}
  offset={assigns.loaded_offset}
  selected={assigns.selected_id}
  selected_index={assigns.selected_index}
  selected_column={assigns.selected_column}
  reveal={assigns.selected_id}
  reveal_index={assigns.selected_index}
  sort_column={assigns.sort_column}
  sort_direction={assigns.sort_direction}
  phx-change="process_selected"
  phx-cell-change="cell_selected"
  phx-sort="process_sorted"
  phx-range="process_range"
  class="h-[480px]"
>
  <UI.table_column id="pid" label="Process" width={140} />
  <UI.table_column id="memory" label="Memory" width={120} align="right" sortable={true} />
  {Enum.map(assigns.loaded_rows, fn row ->
    UI.table_row(%{id: row.id, children: [row.pid, row.memory]})
  end)}
</UI.data_table>

Widths are display pixels and each column is bounded from 40 to 2,000 pixels. Sortable headers emit their stable column ID through phx-sort; controlled sort_column and sort_direction (ascending or descending) expose the current order. Row selection emits the row ID. Cell selection and Left/Right navigation emit [row_id, column_id] through phx-cell-change, while Up/Down, Home/End, Enter, and Space follow the source-backed row rules.

The native accessibility tree uses grid, row, column-header, and grid-cell roles with one-based row and column indexes and total grid dimensions. Use GPUI.Test.table_sort/4, GPUI.Test.table_cell_select/5, and GPUI.Test.range/5 for deterministic tests. Full row models remain in the source process; snapshots contain only column definitions and the loaded row slice.

Accessible trees

tree/1 and tree_item/1 apply the same uniform-height, source-backed range, selection, and reveal contracts to hierarchical collections. The source emits a flattened visible slice; each item declares its hierarchy and accessibility metadata:

<UI.tree
  id="files"
  label="Repository files"
  total_count={assigns.total_count}
  offset={assigns.loaded_offset}
  selected={assigns.selected_id}
  selected_index={assigns.selected_index}
  reveal={assigns.selected_id}
  reveal_index={assigns.selected_index}
  phx-change="file_selected"
  phx-toggle="directory_toggled"
  phx-range="file_range"
  class="h-[480px]"
>
  {Enum.map(assigns.loaded_entries, fn entry ->
    UI.tree_item(%{
      id: entry.id,
      parent_id: entry.parent_id,
      level: entry.level,
      branch: entry.branch?,
      expanded: entry.expanded?,
      position: entry.position,
      set_size: entry.set_size,
      children: [entry.label]
    })
  end)}
</UI.tree>

phx-change emits the selected item ID. phx-toggle requests controlled branch expansion or collapse; the application updates its source model and visible slice. Left collapses an expanded branch or selects its loaded parent. Right expands a collapsed branch or selects its first loaded, enabled child. Up and Down skip disabled items, while Home and End retain the source-backed endpoint rules. Native accessibility exposes tree and tree-item roles plus level, expanded, selected, position-in-set, and set-size metadata.

Use GPUI.Test.change/4, GPUI.Test.select/4, and GPUI.Test.range/5 for deterministic tree tests. All three event paths are forwarded unchanged by remote displays.

Source-backed code and diff viewers

code_viewer/1 specializes the uniform source-backed collection contract for monospaced source text and unified diffs. Lines do not wrap. max_columns provides stable horizontal geometry even when the longest line is not in the loaded slice, while tab_width controls deterministic tab expansion.

<UI.code_viewer
  id="preview"
  label="File preview"
  mode="diff"
  total_count={assigns.total_count}
  offset={assigns.loaded_offset}
  selected={assigns.selected_id}
  selected_index={assigns.selected_index}
  reveal={assigns.selected_id}
  reveal_index={assigns.selected_index}
  max_columns={assigns.max_columns}
  tab_width={4}
  phx-change="line_selected"
  phx-range="preview_range"
  phx-copy="line_copied"
  class="h-[480px]"
>
  {Enum.map(assigns.loaded_lines, fn line ->
    UI.code_line(%{
      id: line.id,
      number: line.number,
      text: line.text,
      kind: line.kind
    })
  end)}
</UI.code_viewer>

Modes are plain and diff. Diff line kinds are addition, deletion, context, and hunk; plain viewers can additionally use debug, info, warning, and error for theme-aware semantic log presentation. Line numbers are optional. Up/Down, Page Up/Page Down, Home/End, Enter, and Space use one accessible listbox tab stop and preserve the source-backed endpoint rules. Pointer selection emits the stable line ID.

Ctrl/Cmd+C writes the selected loaded line to the clipboard on the display machine. This is intentionally display-local for remote sessions. If phx-copy is set, the viewer emits an acknowledgement after requesting the platform write. Tests use GPUI.Test.select/4, GPUI.Test.range/5, and GPUI.Test.copy_selected_line/3; deterministic helpers do not access the host clipboard.