The catalog
The stored-series grid — one rollup row per venue, one row per series, six sortable columns, a search box, and in the app a checkbox column with a bulk-action bar.
The Stored tab's grid is crates/vike-data-manager/src/view.rs, and its header states the shape:
a dense, sortable, venue-grouped grid over the tree crates/vike-data-manager/src/model.rs's
build_tree produces, searchable by venue or symbol, with a click-to-select row. This page is
what you see and what each control does; Coverage takes the
Coverage and Partial columns on their own, because they carry the most information.
Rows
The grid is a flat table, not a tree. The header records the density rewrite that replaced a
per-symbol collapsing tree — "chunky, awkward at hundreds/thousands of symbols" — with one thin
rollup header row per venue (about 22 px) followed by one row of about 26 px per (symbol, series)
pair. The venue header carries the venue name and its rollup, in the shape venue_rollup_label's
doc gives: 430 series · 2.1B rows · 18.4 GB.
Each series row is one kind, or kind/interval for a bar series (kind_label), under its
symbol. What counts as a symbol is decided in the model: SymbolNode's doc says the label is the
symbol for a per-symbol series and the group name for a grouped one, and the node is keyed on the
pair (label, grouped) because a grouped series and a per-symbol series can share a name while being
genuinely different instruments — different directories, different manifests, and a family
mid-migration with history in both. Keying on the bare symbol would have collapsed every grouped
series in a venue into one blank node. The tree is sorted deterministically, so the initial render
is stable across refreshes.
Columns
header_row_cells renders six sortable headers: Symbol, Kind, Coverage, Rows,
Size, Updated. Clicking a header sorts ascending; clicking the active header flips the
direction (header_cell). Sorting is per venue group — each group is ordered independently under
its own header. SortColumn's default is Symbol ascending, chosen so that the first render matches
build_tree's own order, and cmp_flat_row breaks ties by symbol, then kind and interval, so rows
with equal keys still land in a deterministic order. The plain picker keeps its sort state in egui
temp memory keyed off the widget id, because stored_catalog_ui's signature is a two-mount
contract that cannot grow a parameter; the rich grid keeps it in GridState instead.
The Coverage cell is a thin span bar; hovering it shows coverage_label's text — for example
1234 rows, 2024-01-01 → 2024-03-01, or no data for an empty series rather than an epoch-derived
nonsense range — rendered identically in both mounts. The rich grid adds a seventh, unsortable
Partial column after Coverage: a glyph rather than text so it stays narrow, with the count and
the missing kinds in its tooltip. header_row_cells's doc explains why it has no sort — it is an
instrument-level flag, so every row of one instrument carries the same value and sorting would
only cluster instruments, which the grouping already does.
Search
The search box filters by venue or symbol only. matches_query is a case-insensitive substring
match against those two fields; kind and interval are deliberately not searched, and an empty
query matches everything.
Selection and the bulk bar
Two entry points render the same rows. stored_catalog_ui is the plain picker: a search box, the
sorted grid, and Some(StoredSelection) on the frame a row is clicked — venue, symbol, kind and
interval, which StoredSelection's doc calls enough for a caller to load it, whether that means
opening a chart or feeding a slice picker. The Studio uses this one.
stored_catalog_grid is the app-only rich grid. Its doc lists what it adds: a leading checkbox
column that toggles state.selected, a header "select all (filtered)" checkbox, and a bulk-action
bar shown once anything is selected. The selection is a BTreeSet<SeriesKey> — the same four
fields as a StoredSelection, kept as a distinct type because a selection set's element and a
one-shot click result are different roles. bulk_action_bar shows an "N selected" count and three
buttons: Backfill, Update, Delete. BulkAction's doc is explicit that the grid only
reports which button was clicked; the app owns what each does. Backfill and Update go to
Backfill from the GUI. Delete is confirm-gated in the app,
driven by either a single row open or the bulk action, and the bar accepts a delete_disabled
reason that grays the button with hover text — passed when the grid is showing a remote store,
since delete is a local-store operation.
Delete removes data on disk
The app's delete path is the only destructive action on the Stored tab; the Symbols tab carries its
own Delete, which removes a DataSet from storage/datasets.json (Store::delete) and touches no
series. The Stored delete is confirm-gated, and the bulk variant snapshots the selection at the
moment Delete was clicked, so a selection change while the modal is open cannot silently widen or
narrow what gets deleted
(ToolView::stored_confirm_bulk_delete).
The views sidebar
Beside the rich grid, views_sidebar is a thin selector over ViewFilter: All, one entry per
venue with its series count, the smart views Has gaps and Stale, and a Watchlists group
built from names the app passes in — which, per stored_tool_content's doc, are the DataSet names
from the Symbols tab. Clicking an entry sets the active view; the grid re-filters on the next
frame through filter_tree. ViewFilter's doc is candid about two placeholders: Watchlist and
AssetClass are carried in the type but the sidebar only receives watchlist names, not their
symbol membership, and the display tree has no asset-class tag, so both currently behave like
All. Has gaps and Stale are real, and are described on Coverage.
What is tested
The header states the discipline: everything above the actual paint calls — formatters, the stale
and gap predicates, flatten and sort, the coverage label — is pure and unit-tested; the two egui
render functions, it says, are not. That second half has been overtaken by the tests: view.rs's
own test module renders stored_catalog_grid in real headless egui frames and reads the text it
paints back out (the Partial column's tests), and drives a real pointer press-and-release over
bulk_action_bar's button rects to prove a click resolves to a BulkAction. stored_catalog_ui,
the plain picker, is the one with no render test. model.rs pins its own grouping, including a
grouped series showing under its group name rather than a blank node.
Data Manager
The tool window that shows what the history store holds — one shared catalog view mounted by both the desktop app and the Studio, and the sub-tabs around it.
Coverage
The span bar and its shared scale, the stale tint, gap cut-outs from the file index, the cross-kind Partial column, and the two smart views that filter on them.