Views

The view layer is ten pure-presentation primitives. Seven content views — Table, KeyValue, JsonTree, Jsonl, Image, Video, Text — plus three chrome pieces (PreviewHeader, PreviewSubBar, StatusView) that frame whichever body the host plugs in. None of them fetch — they take parsed data and display props, so the host owns URL signing, caching, and parsing.

For end-to-end previewing (parse + render), see the loaders page for the parsers, or the FilePreview page for ready-made loader + view containers.

Usage

Every preview view is a pure visualization primitive. The host owns URL signing, caching, and parsing; the view takes already-parsed data and display props and renders pixels. None of them fetch.

FilePreview.tsxtsx
import {
  PreviewHeader,
  PreviewSubBar,
  TableView,
  StatusView,
} from '@dreamlake/viz'

export function FilePreview({ file, parsed, status }) {
  if (status === 'loading') return <StatusView kind="loading" />
  if (status === 'error')   return <StatusView kind="error" message={parsed.error} />

  return (
    <div className="flex flex-col h-full">
      <PreviewHeader
        name={file.name}
        ext={file.ext}
        path={file.path}
        size={file.size}
        modified={file.modified}
      />
      <TableView cols={parsed.cols} rows={parsed.rows} totalRows={parsed.totalRows} truncated />
    </div>
  )
}

The split is the load-bearing rule: views never own data. That keeps the same component reusable across apps with very different fetch stories — signed-URL S3, IndexedDB cache, server-side parse — without forking the rendering.

Pair the right view with each file shape:

File shapeView
CSV, Parquet, MCAP channelTableView
Single-record metadataKeyValueView
JSON documentJsonTreeView
JSONL streamJsonlView
Image (PNG / JPG / WebP / …)ImageView
Video (MP4 / WebM / …)VideoView
Plain text / Markdown / codeTextView
Empty / loading / error / too-large / unsupportedStatusView

Header & sub-bar

Two thin strips that frame every preview body. PreviewHeader is the top row — extension icon · directory · filename · size · modified. PreviewSubBar is the line below — left slot carries a summary, right slot carries a status dot or an inline action. Both render at fixed heights so the body underneath doesn't reflow when files change.

PreviewHeader

datasets/2026-05/runs/
trajectories.parquet
4.6 MB2026-05-09 14:22

The header always renders two lines so the height is stable across files. For a bucket-root file (no parent directory), the path line falls back to / rather than collapsing. size is raw bytes and is formatted internally via fmtSize.

PreviewSubBar

12 cols · 5,000 rowsshowing first 100 rows

The slot model keeps the bar policy-free — TableView paints col/row counts in the left slot and a truncation dot in the right; TextView paints line count + dirty marker on the left and edit / save buttons on the right. Anything that fits the mono-10.5px register works.

Tabular data

TableView

The generic table that every tabular file shape (CSV, Parquet, MCAP channels) renders through. Pass columns + parsed row tuples; cells are formatted by type — booleans pick up the fn / tag code-token hues, numbers tabular-align, nulls render at 40 % opacity.

5 cols · 5,000 rowsshowing first 7 rows
#
idint64
episodestring
stepint32
rewardfloat32
donebool
1
1
ep-0001
0
0
false
2
2
ep-0001
1
0.12
false
3
3
ep-0001
2
0.34
false
4
4
ep-0001
3
0.87
true
5
5
ep-0002
0
0
false
6
6
ep-0002
1
null
false
7
7
ep-0002
2
0.51
true

When the host has paged the data (read the first N rows out of a much larger file), pass totalRows plus truncated. The sub-bar then paints the accent dot and "showing first N rows" caption on the right edge. If every column carries a type, the header grows a second mono line for type labels; columns without a type collapse to a single line.

Set hideSubBar when embedding the table inside another preview pane that already owns the status bar — for example, an MCAP channels table sitting under a metadata KeyValueView.

KeyValueView

Vertical metadata table for flat string-keyed maps. Used for file headers, MCAP metadata blocks, EXIF, schema dumps — anywhere you want keys right-aligned next to their values in a stable grid.

formatApache Parquet v2.9
compressionZSTD (level 3)
rows5,000
row groups4
created bypyarrow 15.0.2
schema shaa31b…f902

The key column is width: 1 plus whitespace-nowrap — it auto-sizes to the longest key, which keeps the value column from wandering as you switch files.

JSON & JSONL

JsonTreeView

Foldable tree for one JSON document. Levels deeper than defaultOpenDepth start collapsed; click anywhere on a row to toggle. Scalars are colored by JSON type from the dreamlake code-token palette — keywords blue, strings amber, numbers purple — so the rendering matches inline JSON elsewhere.

{
"run": "rl/2026-05-09/seed-42"
"config": {
"algo": "ppo"
"horizon": 2048
"gamma": 0.995
"optimizer": { 3 keys }
"env": { 3 keys }
}
"metrics": {
"reward_mean": 0.873
"reward_std": 0.214
"success_rate": 0.91
"episodes": 1280
}
"tags": [
"rl"
"panda"
"reach"
"curriculum"
]
"finished": true
"note": null
}

Tune defaultOpenDepth for the data: pass 1 for flat configs (only the root opens), 3 or 4 for deeply nested traces where the user probably wants the leaves visible.

JsonlView

One record per line, gutter on the left, inline JSON on the right. Visual style intentionally tracks JsonTreeView so the two read as a pair when a host pane offers both modes for the same file.

1
2
3
4
5
6
{"t": 0, "kind": "reset", "env": 0, "obs_hash": "a3f1"}
{"t": 0.02, "kind": "step", "env": 0, "action": [0.12, -0.03, 0.04], "r": 0, "done": false}
{"t": 0.04, "kind": "step", "env": 0, "action": [0.18, 0.01, 0.05], "r": 0.12, "done": false}
{"t": 0.06, "kind": "step", "env": 0, "action": [0.21, -0.02, 0.07], "r": 0.34, "done": false}
{"t": 0.08, "kind": "step", "env": 0, "action": [0.1, 0.04, 0.02], "r": 0.87, "done": true}
{"t": 0.1, "kind": "reset", "env": 0, "obs_hash": "c0e7"}

The view assumes the host has already split the file on newlines and JSON-parsed each line — pass records as the resulting array. Long lines are clipped with an ellipsis at the row level; if you need full records, drop into the tree view for the row instead.

Media

ImageView

<img> wrapped in the shadowed card every preview pane uses. The onLoad callback fires once the browser has decoded the image and passes the natural "W×H" string back — most hosts thread that into the header or sub-bar so users see the source resolution.

Sample gradient
resolution ·

The wrapper caps at max-w-[560px] and centers — large originals are scaled down to fit. The image element keeps its natural aspect ratio, so tall portraits and wide panoramas both lay out cleanly without extra props.

VideoView

<video controls> on a 16:9 canvas. Browsers stream the file in chunks via Range requests as the user scrubs, so previewing a multi-GB recording never has to download up-front. onLoadedMetadata surfaces duration and resolution once the demuxer has the header.

metadata · —

The demo above streams a small sample clip from a public CDN; in a real host you pass the same signed URL your data layer hands out. Container formats the <video> element can demux are browser-dependent — MP4 (H.264 + AAC) is the safe default.

Because the unsafe cases fail silently — a black frame and no exception — VideoView also watches for them and calls onError with a classified VideoErrorInfo: MediaErrors, and the codec-unsupported case where metadata parses but the picture is 0×0. If neither lands within stallMs it overlays an advisory banner without unmounting the player. See the composed docs for the rendered failure states.

Text

TextView is the plain-text viewer / editor. Read-only when onSave is omitted — the sub-bar shows a "read-only" tag and the body renders as a <pre>. Pass an onSave and an Edit button appears on the right of the sub-bar; click it to swap the body for a <textarea>.

8 lines · MD
1
2
3
4
5
6
7
8
# notes.md
- Verify the value-function head still trains on the new env.
- Re-run sweep at gamma in {0.99, 0.995, 0.999}.
- File regressions land in the parquet under datasets/2026-05/.

> The reward shaping change is responsible for the ~7% bump on
> success-rate. Roll forward.

Keyboard:

  • Cmd/Ctrl-S saves while editing. The button is disabled until the buffer is dirty, and shows a "saving…" label while the promise is in flight.
  • Esc discards local edits and exits edit mode.

onSave returns a Promise<void>. Resolve to mark the new text as the baseline; reject to surface the error message inline in the sub-bar. The view doesn't know what "save" means — wire it to a PUT, a Yjs awareness message, an IndexedDB write, whatever fits the host.

When the text prop changes underneath you (the parent switched files or another tab saved over this one), the view snaps local edits back to the new baseline during render — the React 19 prop-derivation idiom, not a useEffect. That keeps the visible state in sync with the source of truth without a frame of stale display.

Status states

StatusView covers the five non-content states a preview pane can be in: nothing selected, loading, error, file-too-large-to-preview, and unsupported-format. One component because they share the same "icon-on-tile + headline + detail" layout — only the tone changes.

Select a file to preview.

Switch on kind:

  • empty — nothing selected. The neutral state at first paint.
  • loading — show while the parse is in flight. label overrides the default "loading…" caption when the host wants to be specific (e.g. "reading parquet…").
  • error — render a parse / fetch error. Pass the message via message; long errors wrap inside the centered card.
  • too-large — file exceeds the host's preview cap. Pass sizeBytes (the actual size) and capBytes (the cap) so the built-in copy can name both. Use hint to swap in a different message — e.g. a download link or a per-format escape hatch.
  • unsupported — file extension the host has no view for. Pass ext so the message names the format.

The component fills its container — drop it into the same body slot the content views would have occupied, no special wrapper needed.

Props reference

PreviewHeader

PropTypeDefaultDescription
namestringFilename rendered as the bold second line. Required.
extstringExtension, used to pick the leading icon (png, mp4, parquet, json, …).
pathstringFull file path. The parent directory is shown above the filename; falls back to / when the file sits at the root.
sizenumberRaw bytes. Formatted internally via fmtSize.
modifiedDate | stringLast-modified timestamp. Dates are formatted as YYYY-MM-DD HH:mm; strings pass through.

PreviewSubBar

PropTypeDefaultDescription
leftReactNodeLeft slot. Convention: summary text (col / row counts, line count, parse status). Required.
rightReactNodeRight slot. Convention: status dot or inline action buttons. Required.

TableView

PropTypeDefaultDescription
colsTableColumn[]Column definitions ({ name, type? }). If any column has a type, the header grows a second mono line to show it.
rowsunknown[][]Row tuples — one inner array per row, values in column order. Cells are formatted by JS type (boolean, number, null, string).
totalRowsnumber | nullTotal rows in the full file. Painted on the left of the sub-bar; falls back to rows.length if omitted.
shownRowsnumberrows.lengthHow many rows are actually rendered — used by the "showing first N" caption.
truncatedbooleanfalseWhen true, the sub-bar shows an accent dot + truncation caption on the right.
subInfoLeftReactNodeOverride the default left slot (col / row counts).
subInfoRightReactNodeOverride the default right slot (truncation caption).
hideSubBarbooleanfalseHide the sub-bar entirely. Use when the table is embedded under a pane that already owns one.

KeyValueView

PropTypeDefaultDescription
itemsArray<[string, string]>Ordered list of [key, value] pairs. Order is preserved — pre-sort on the host side if you want alphabetical.

JsonTreeView

PropTypeDefaultDescription
valueunknownAlready-parsed JSON value (object, array, or scalar). Required.
defaultOpenDepthnumber2Depth below which nodes start collapsed. 0 = everything collapsed; Infinity = everything open.

JsonlView

PropTypeDefaultDescription
recordsunknown[]Array of already-parsed records, one per JSONL line. Long records are clipped at the row level.

ImageView

PropTypeDefaultDescription
srcstringImage URL (signed S3, blob, data URI, …). Required.
altstring''Alt text. Empty by default since previews are typically content the user just selected.
onLoad(resolution: string) => voidFires once the browser has decoded the image. The argument is "naturalWidth×naturalHeight".

VideoView

PropTypeDefaultDescription
srcstringVideo URL. Browsers fetch chunks via Range requests as the user scrubs — no full download. Required.
extstringExtension (mp4, mov, …). Display-only: it words the error copy accurately (".mov files" rather than "this file").
onLoadedMetadata(info: { duration: number; resolution: string }) => voidFires once the demuxer has the header. duration is in seconds; resolution is "W×H". Suppressed when the picture came back 0×0 — that fires onError instead.
onError(info: VideoErrorInfo) => voidFires once per failure, with { kind, code, title, message, hint }. Covers both MediaErrors and the silent no-video-track case.
stallMsnumber15000How long to wait with no metadata and no error before overlaying an advisory "still loading" banner. 0 disables it.

TextView

PropTypeDefaultDescription
textstringThe text to render. Changing this prop snaps any pending local edits back to the new value. Required.
extstringExtension shown in the sub-bar (e.g. md, yaml). Display-only, no syntax highlighting yet.
onSave(text: string) => Promise<void>When supplied, the sub-bar grows an Edit button. The promise drives the saving state; reject to surface the error inline. Omit for a read-only viewer.

StatusView

PropTypeDefaultDescription
kind'empty' | 'loading' | 'error' | 'too-large' | 'unsupported'Which state to render. Required.
labelstring'loading…'kind="loading": the caption. kind="error": replaces the Preview failed headline, for callers that know what failed.
messagestring'Preview failed'Error detail. Only used for kind="error".
extstringExtension shown in the unsupported message. Only used for kind="unsupported".
sizeBytesnumber0Actual file size. Only used for kind="too-large".
capBytesnumber0Preview cap. Only used for kind="too-large".
hintReactNodekind="error": a remediation line under the message. kind="too-large": replaces the default cap copy entirely — e.g. a download link.