D
DreamLake

Data

Tracks

A track is a topic — a single stream of time-stamped data produced by one source. "Left wrist camera", "arm joint positions", "IMU on the base", "operator narration" — each is a track.

A vuer-m3u track is defined by five load-bearing fields:

interface Track {
  id: string          // stable identifier (usually a UUID or short slug)
  name: string        // human-readable label
  path: string        // topic path — "robot/joint_positions"
  dtype: DtypeId      // data-type identifier — "joint_angles"
  src: string         // m3u8 URL pointing to the chunks
}

The fields, in detail

id

Stable, unique identifier — typically a server-assigned UUID. Used as a React key and for referring back to the track from UI state (hidden flags, selection, etc.). Don't encode semantic information here; use path and dtype for that.

name

Human-readable label for tree / UI display. Often the last segment of path, but can differ (e.g. path: "robot/arm/qpos", name: "Arm joint positions (left)").

path

Topic identity + hierarchy key. /-separated; segments form a tree.

"robot/joint_positions"
"robot/arm/qpos"
"robot/arm/effort"
"cameras/wrist"
"cameras/scene"
"events"

The timeline synthesizes groups from path prefixes automatically — no parentId wiring. See Timeline Authoring for how prefix-based tree derivation works.

On the server (dreamlake-py), path is the primary organizing axis:

episode.track("robot/joint_positions", dtype="joint_angles")
episode.track("cameras/wrist", dtype="video")

dtype

Data-type identifier — the bridge between "what the data is" and "how it's drawn." Must match a registered DtypeSpec. dtype contracts:

  • Chunk format — what file extension the m3u8 references (jsonl, vtt, ts, …)
  • JSONL line shape{ts, data: number[]}, {ts, te, label, ...}, etc.
  • Unit / range defaults — merged into lane props

The server does NOT enforce the dtype contract. If your chunks don't match the schema, the view breaks — that's on the author.

vuer-m3u ships 13 built-in dtypes. Register custom ones with registerDtype().

src

m3u8 playlist URL. Standard HLS; chunk format inferred from file extension. vuer-m3u parses, fetches, decodes, prefetches, and caches — see M3U8 Transport.

Server Track vs Client TrackRow

Server-side records (see the dreamlake-py SDK) carry the core identity fields. The client extends this into a TrackRow that adds purely presentational overrides — these never travel back to the server:

// Client-side — used in TimelineConfig.tracks
interface TrackRow {
  // Identity fields (1:1 with server Track)
  id: string
  path: string
  dtype: DtypeId
  src?: string          // OR data — mutually exclusive
  data?: unknown[]
 
  // Presentation overrides (client-only)
  name?: string         // tree-label override — default is last segment of path
  visible?: boolean
  height?: number
  color?: string
  icon?: string
  props?: Record<string, unknown>    // merged on top of dtype.defaults
}

name, color, icon, height, visible, props are all per-visualization overrides — they don't belong on the server.

Inline data

src points at an m3u8; data supplies inline chunks directly. Exactly one must be provided — runtime validated.

// src-based — fetches + decodes at playhead time
{ id: 'a', path: 'robot/arm', dtype: 'joint_angles', src: '/arm.m3u8' }
 
// Inline — for small datasets (annotations, markers, test fixtures)
{ id: 'e', path: 'events', dtype: 'marker_event',
  data: [
    { ts: 1.0, label: 'start' },
    { ts: 5.0, label: 'grasp', color: 'green' },
    { ts: 9.5, label: 'release' },
  ] }

Inline data renders synchronously at mount; src-backed tracks lazy-load as the playhead reaches each segment.

Path conventions

  • Use / as the separator. Empty segments ("a//b" or leading/trailing /) are rejected at validation.
  • Prefer lower_snake_case leaves — "sensors/imu_base" not "sensors/IMU_Base".
  • Keep paths stable — they're topic identifiers, not display labels. Rename via name if the display name needs to change.
  • Depth is unbounded: "a/b/c/d/e" works; the tree just shows more levels.

Path-based grouping

TimelineConfig.groups maps path prefixes to per-group presentation overrides:

{
  groups: {
    'cameras':   { name: 'Cameras',     color: 'green', icon: 'video' },
    'robot':     { name: 'Robot state', color: 'blue',  icon: 'robot' },
    'robot/arm': { name: 'Arm',         icon: 'arm' },
  },
  tracks: [
    { id: 'cam',  path: 'cameras/wrist',  dtype: 'video',        src: '...' },
    { id: 'qpos', path: 'robot/arm/qpos', dtype: 'joint_angles', src: '...' },
  ],
}

Prefixes without an entry still appear in the tree — they just use defaults (path leaf as label, expanded, no icon).

See Timeline Authoring for the full config contract.

Where tracks live in the system

┌── SERVER (dreamlake-py + BSS) ─────────────────────────┐
│                                                        │
│   Track { id, name, path, dtype, src, metadata? }      │
│   stored in Postgres + S3                              │
│                                                        │
│   path     → primary topic identifier                  │
│   dtype    → schema convention (not enforced)          │
│                                                        │
└──────────────────────┬─────────────────────────────────┘

                       │  API (GET /episodes/:id/tracks)

┌── CLIENT (vuer-m3u) ───────────────────────────────────┐
│                                                        │
│   TrackRow = Track + { name?, color?, icon?, height?,  │
│                        visible?, props? }              │
│                                                        │
│   TimelineConfig {                                     │
│     container,                                         │
│     tracks: TrackRow[],    ← 1:1 with server tracks    │
│     groups?: Record<prefix, GroupConfig>
│   }                                                    │
│                                                        │
└────────────────────────────────────────────────────────┘

The app composes TimelineConfig from server track records. Most apps just pass the server response through unchanged; some layer app-specific styling (color, icon) on top.

One config, two containers

The same TimelineConfig drives both visualization surfaces:

<TrackerContainer  config={config} views={defaultTrackerViews}  />  {/* instant state */}
<TimelineContainer config={config} views={defaultTimelineViews} />  {/* trend timeline */}

Both containers:

  • accept the identical config: TimelineConfig prop
  • resolve clock the same way (explicit prop → outer <ClockProvider> → new internal one)
  • dispatch each track through a views map keyed on its dtype

Swap containers to switch presentations. Wrap in a shared <ClockProvider> to keep play/seek in sync across both. The track doesn't care which container renders it — that's the point of keeping dtype (data identity) separate from view (presentation).

Next

  • Dtypes — the schema registry each track references via its dtype
  • M3U8 Transport — what src points at and how the library fetches chunks
  • Views — instant-state rendering (<TrackerContainer> + standalone views)
  • Timeline Authoring — trend rendering (<TimelineContainer> + lanes)