# orca.ui.viz

> A module of morca's API.

Two views `orca ui` draws in the browser: a run's Dista timeline, and the pipelines over time on the home page. Both are plain scripts in the page (nothing is fetched from elsewhere) over JSON the UI serves, and both load that JSON only when they're looked at, so the pages stay as fast as they were.

**A run's timeline** (`timeline_section`): the run's `timeline.json`
(Chrome's trace format, as dista writes it) drawn on a canvas, a track per
process (the driver or head first, then each worker) and a lane per slot
its spans overlap on, `X` and `B`/`E` events alike. Spans are coloured by
their function's name; "inputs" (a task waiting for what it reads) are thin
bars under them. A wheel zooms around the pointer, a drag zooms to what it
covers, and Reset zoom goes back; spans narrower than a pixel are drawn as
one pixel per lane, so a few hundred thousand events stay quick. Hovering
says the span's name, process and slot, start, duration and arguments. The
file is fetched when the section scrolls into view; Perfetto and the raw
file are linked beside it.

**The pipelines over time** (`gantt_tabs`, `gantt_panel`, `gantt_json`):
"List" and "Timeline" tabs over the home page's table, the choice kept in
`localStorage`; the list is the default and is all there is without
scripts. The timeline is a row per pipeline in the order of their datasets
(a pipeline that reads what another writes comes after it, indented by its
depth), its runs over the last hour, 6 hours, day or week as bars coloured
by status, fill runs thinner, a line at now, and a thin connector from a
run to each run its commits started (`orca.sensed` joined to
`orca.commits`). Datasets between pipelines are drawn at the left. Bars
narrower than two pixels are merged, and the JSON holds the newest
`MAX_RUNS` runs of the window.

```
GET /api/gantt?window=24h      # 1h, 6h, 24h or 7d
```

## Functions

### `window_seconds`

```mojo
def window_seconds(window: String) -> Int
```

The seconds of a window `/api/gantt` takes: `1h`, `6h`, `24h` (the default, and what anything else means) or `7d`.

### `gantt_json`

```mojo
def gantt_json(mut db: State, window: String) -> String
```

`/api/gantt`: the pipelines in dataset order, with their runs in the window and the runs each started.

### `gantt_tabs`

```mojo
def gantt_tabs(mut h: Html)
```

The tabs over the home page's pipelines, hidden until the script shows them, then the list's panel opening.

### `gantt_panel`

```mojo
def gantt_panel(mut h: Html)
```

Closes the list's panel, then the timeline's, empty until it's shown, and its script.

### `timeline_section`

```mojo
def timeline_section(mut h: Html, run: Int)
```

The run's timeline viewer, which fetches `/runs/<run>/timeline.json` once it's in view, with Perfetto and the file linked beside it.

## Aliases

### `MAX_RUNS`

```mojo
comptime MAX_RUNS = 20000
```

Runs `/api/gantt` answers at most: the newest of the window.

### `MAX_TRIGGERS`

```mojo
comptime MAX_TRIGGERS = 5000
```

Connectors between runs `/api/gantt` answers at most.

### `VIZ_STYLE`

```mojo
comptime VIZ_STYLE = "\n/* The views drawn in the browser (orca.ui.viz). */\n.vtabs { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: center;\n  justify-content: space-between; margin: 1.25rem 0 -0.5rem; }\n.vtabs [role=tablist], .vtabs__win { display: inline-flex; border: 1px solid var(--border);\n  border-radius: var(--radius-sm); overflow: hidden; background: var(--surface); }\n.vtabs__tab, .vtabs__w { font: inherit; font-size: 0.8125rem; color: var(--muted); background: none;\n  border: 0; padding: 0.3rem 0.8rem; cursor: pointer; }\n.vtabs__tab + .vtabs__tab, .vtabs__w + .vtabs__w { border-left: 1px solid var(--border); }\n.vtabs__tab[aria-selected=true], .vtabs__w[aria-pressed=true] { color: var(--text); font-weight: 600;\n  background: var(--surface-2); box-shadow: inset 0 -2px 0 var(--ember); }\n.vtabs__tab:focus-visible, .vtabs__w:focus-visible { outline: 2px solid var(--lake); outline-offset: -2px; }\n.vtabs__w { font-family: var(--mono); font-size: 0.75rem; }\n.gantt__wrap, .tlv__wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; position: relative; }\n.gantt__msg, .tlv__msg { padding: 0.8rem 1.1rem; margin: 0; }\n.gantt svg { display: block; font-family: var(--mono); }\n.gantt__row { fill: transparent; }\n.gantt__row--alt { fill: color-mix(in srgb, var(--text) 2.5%, transparent); }\n.gantt__name { font-size: 12px; fill: var(--text); }\na:hover .gantt__name { fill: var(--lake); text-decoration: underline; }\n.gantt__sub { font-size: 10px; fill: var(--faint); }\n.gantt__chip { fill: none; stroke: var(--muted); stroke-dasharray: 3 2; }\n.gantt__chiptext { font-size: 9.5px; fill: var(--muted); }\n.gantt__tick { stroke: var(--border); }\n.gantt__time { font-size: 10px; fill: var(--faint); }\n.gantt__edge { fill: none; stroke: var(--border-strong); stroke-width: 1.2; }\n.gantt__link { fill: none; stroke: var(--muted); stroke-width: 1; opacity: 0.7; }\n.gantt__now { stroke: var(--ember-soft); stroke-width: 1.5; }\n.gantt__nowtext { font-size: 10px; fill: var(--ember-soft); }\n.gantt a:hover rect { opacity: 0.8; }\n.g--succeeded { fill: var(--lake); background: var(--lake); }\n.g--running { fill: var(--live); background: var(--live); }\n.g--retrying { fill: var(--warn); background: var(--warn); }\n.g--failed { fill: var(--ember); background: var(--ember); }\n.g--queued { fill: color-mix(in srgb, var(--warn) 35%, transparent); stroke: var(--warn);\n  stroke-dasharray: 2 2; background: color-mix(in srgb, var(--warn) 35%, transparent); }\n.g--cancelled { fill: var(--border-strong); background: var(--border-strong); }\n.g--many { stroke: var(--text); stroke-width: 0.6; stroke-opacity: 0.35; }\n.gantt__legend { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; padding: 0.6rem 1.1rem;\n  border-top: 1px solid var(--border); font-size: 0.75rem; color: var(--muted); }\n.gantt__legend span { display: inline-flex; align-items: center; gap: 0.35rem; }\n.gantt__key { display: inline-block; width: 1.1rem; height: 0.6rem; border-radius: 2px; }\n.gantt__key--fill { height: 0.25rem; }\n.gantt__key--now { width: 2px; height: 0.9rem; background: var(--ember-soft); }\n.gantt__key--link { width: 0.8rem; height: 0.55rem; border-left: 1px solid var(--muted);\n  border-bottom: 1px solid var(--muted); border-radius: 0; }\n.tlv__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.9rem;\n  padding: 0.6rem 1.1rem; border-bottom: 1px solid var(--border); font-size: 0.8125rem; }\n.tlv__info { font-size: 0.75rem; margin-left: auto; }\n.tlv__canvas { display: block; cursor: crosshair; touch-action: pan-x pan-y; }\n.tlv__help { font-size: 0.75rem; padding: 0.4rem 1.1rem 0.7rem; margin: 0; }\n.tlv__tip, .gantt__tip { position: absolute; z-index: 2; pointer-events: none; max-width: 22rem;\n  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);\n  box-shadow: var(--shadow); padding: 0.45rem 0.6rem; font-size: 0.75rem; line-height: 1.4; }\n.tlv__tip b { display: block; font-family: var(--mono); font-weight: 600; word-break: break-all; }\n.tlv__tip dl { display: grid; grid-template-columns: auto 1fr; gap: 0 0.6rem; margin: 0.25rem 0 0; }\n.tlv__tip dt { color: var(--faint); }\n.tlv__tip dd { margin: 0; font-family: var(--mono); word-break: break-all; }\n"
```

The views' rules, served after `orca.ui.style.STYLE` in `/static/orca.css`.
