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.
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 shape | View |
|---|---|
| CSV, Parquet, MCAP channel | TableView |
| Single-record metadata | KeyValueView |
| JSON document | JsonTreeView |
| JSONL stream | JsonlView |
| Image (PNG / JPG / WebP / …) | ImageView |
| Video (MP4 / WebM / …) | VideoView |
| Plain text / Markdown / code | TextView |
| Empty / loading / error / too-large / unsupported | StatusView |
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
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
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.
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.
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.
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.
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.
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.
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>.
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.
Switch on kind:
empty— nothing selected. The neutral state at first paint.loading— show while the parse is in flight.labeloverrides the default "loading…" caption when the host wants to be specific (e.g."reading parquet…").error— render a parse / fetch error. Pass the message viamessage; long errors wrap inside the centered card.too-large— file exceeds the host's preview cap. PasssizeBytes(the actual size) andcapBytes(the cap) so the built-in copy can name both. Usehintto 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. Passextso 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
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Filename rendered as the bold second line. Required. |
ext | string | — | Extension, used to pick the leading icon (png, mp4, parquet, json, …). |
path | string | — | Full file path. The parent directory is shown above the filename; falls back to / when the file sits at the root. |
size | number | — | Raw bytes. Formatted internally via fmtSize. |
modified | Date | string | — | Last-modified timestamp. Dates are formatted as YYYY-MM-DD HH:mm; strings pass through. |
PreviewSubBar
| Prop | Type | Default | Description |
|---|---|---|---|
left | ReactNode | — | Left slot. Convention: summary text (col / row counts, line count, parse status). Required. |
right | ReactNode | — | Right slot. Convention: status dot or inline action buttons. Required. |
TableView
| Prop | Type | Default | Description |
|---|---|---|---|
cols | TableColumn[] | — | Column definitions ({ name, type? }). If any column has a type, the header grows a second mono line to show it. |
rows | unknown[][] | — | Row tuples — one inner array per row, values in column order. Cells are formatted by JS type (boolean, number, null, string). |
totalRows | number | null | — | Total rows in the full file. Painted on the left of the sub-bar; falls back to rows.length if omitted. |
shownRows | number | rows.length | How many rows are actually rendered — used by the "showing first N" caption. |
truncated | boolean | false | When true, the sub-bar shows an accent dot + truncation caption on the right. |
subInfoLeft | ReactNode | — | Override the default left slot (col / row counts). |
subInfoRight | ReactNode | — | Override the default right slot (truncation caption). |
hideSubBar | boolean | false | Hide the sub-bar entirely. Use when the table is embedded under a pane that already owns one. |
KeyValueView
| Prop | Type | Default | Description |
|---|---|---|---|
items | Array<[string, string]> | — | Ordered list of [key, value] pairs. Order is preserved — pre-sort on the host side if you want alphabetical. |
JsonTreeView
| Prop | Type | Default | Description |
|---|---|---|---|
value | unknown | — | Already-parsed JSON value (object, array, or scalar). Required. |
defaultOpenDepth | number | 2 | Depth below which nodes start collapsed. 0 = everything collapsed; Infinity = everything open. |
JsonlView
| Prop | Type | Default | Description |
|---|---|---|---|
records | unknown[] | — | Array of already-parsed records, one per JSONL line. Long records are clipped at the row level. |
ImageView
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URL (signed S3, blob, data URI, …). Required. |
alt | string | '' | Alt text. Empty by default since previews are typically content the user just selected. |
onLoad | (resolution: string) => void | — | Fires once the browser has decoded the image. The argument is "naturalWidth×naturalHeight". |
VideoView
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Video URL. Browsers fetch chunks via Range requests as the user scrubs — no full download. Required. |
ext | string | — | Extension (mp4, mov, …). Display-only: it words the error copy accurately (".mov files" rather than "this file"). |
onLoadedMetadata | (info: { duration: number; resolution: string }) => void | — | Fires 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) => void | — | Fires once per failure, with { kind, code, title, message, hint }. Covers both MediaErrors and the silent no-video-track case. |
stallMs | number | 15000 | How long to wait with no metadata and no error before overlaying an advisory "still loading" banner. 0 disables it. |
TextView
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | — | The text to render. Changing this prop snaps any pending local edits back to the new value. Required. |
ext | string | — | Extension 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
| Prop | Type | Default | Description |
|---|---|---|---|
kind | 'empty' | 'loading' | 'error' | 'too-large' | 'unsupported' | — | Which state to render. Required. |
label | string | 'loading…' | kind="loading": the caption. kind="error": replaces the Preview failed headline, for callers that know what failed. |
message | string | 'Preview failed' | Error detail. Only used for kind="error". |
ext | string | — | Extension shown in the unsupported message. Only used for kind="unsupported". |
sizeBytes | number | 0 | Actual file size. Only used for kind="too-large". |
capBytes | number | 0 | Preview cap. Only used for kind="too-large". |
hint | ReactNode | — | kind="error": a remediation line under the message. kind="too-large": replaces the default cap copy entirely — e.g. a download link. |