vike
Desktop appData Manager

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.

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.

On this page