bothy_viz
Visualizations of the statistics (docs/spec/25-visualization.md 7): view models, the ports
LiveFrameSource and StatsQuerySource, the basic forms, the six views with their table views,
the fixtures and the harness.
Kind: Flutter package, a member of the Dart workspace at the repository root
(resolution: workspace, publish_to: none).
Dependencies
Allowed members (docs/spec/15-client-architecture.md, 3.1; tools/arch-check/rules.yaml):
design_system, api_client, domain (arrows of the diagram), and through them l10n, which the
package also names directly for its texts. Every other member is out of reach; make arch fails on
a dependency or an import of it. Further rules:
- never depends on
sync_engineorlocal_store, directly or through another member (rule 6,make arch); - no WebView,
HtmlElementViewor iframe inlib/,harness/ortest/(test/no_webview_test.dart, the package part of VZ-T16); - no native source (rule 4); no charting library: every form is a
CustomPainter.
Layout
| Path | Content |
|---|---|
lib/bothy_viz.dart |
the public library; every public symbol has a doc comment |
lib/src/model/ |
view models from the generated types (LiveTopologyModel, BackendModel, DeliveryModel, ExecutiveModel, TimelineModel, RunCardModel, LaneModel, SeriesModel, TrafficLightModel, MetricTileModel, BarModel); a missing cost member is "not shown", never zero |
lib/src/sources/ |
LiveFrameSource with the base LiveFrameSourceBase (stale after viz.stale_after on a VizClock, error events), StatsQuerySource, and FixtureLiveFrameSource, FixtureStatsQuerySource, FakeVizClock for tests and the harness |
lib/src/marks/ |
LaneMark, RunCardMark, AreaMark, BarMark, TrafficLightMark, MetricTileMark; colors only from color.viz.*, the run state and the state roles; texts only from l10n |
lib/src/views/ |
the six views, VizViewKind with the registry vizViewRegistry, VizHostOptions, the shared frame (VizViewScaffold: switch "As table", notes, link "Open in Grafana") |
lib/src/views/table/ |
VizTable and one table builder per view, with the same values as the view |
lib/src/views/timeline/ |
TimelinePlayback: play, pause, jump and speed on a VizClock |
lib/src/run_graph/ |
the run graph and the control room card (M4-39; 09 5.6, R-VZ-14): RunGraphModel.fromRun, the pure derivation from the generated Run; RunGraphView, the trace view on a shared time axis with the list form "As list", arrow keys and rate-limited announcements (RunGraphAnnouncer); RunControlCard with RunControlCardModel.from over the generated RunSummary and, while live, the Run; runSummaryOf; RunGraphTexts |
lib/src/slo/ |
slo_catalog.g.dart (generated by tools/observability/sli_bounds.py) and slo_catalog.dart (display names) |
lib/src/fixtures/ |
VizFixtures: decodes a fixture with the generated serializers |
fixtures/ |
JSON fixtures per schema and state (README); also assets, so the harness reads them from the bundle |
harness/ |
the harness app bothy_viz_harness with every mark and view, and the performance test of the live topology (README) |
test/ |
unit, widget, semantics and golden tests (test/goldens/ci/ on every host, test/goldens/macos/ only on macOS) |
Run graph and control room card
Not views of level a (25 1, R-VZ-14): they show run content under run.read and read no
stats.*. The host derives RunGraphModel.fromRun(run, now, agentName: name) again on every
applied patch (linear in the steps) and passes it to RunGraphView with
ui.livebox_visible_steps, a11y.live_announce_min_interval and its clock; the view needs a
bounded height. The card takes RunControlCardModel.from(summary, document: run, ...): with the
document it is live (current tool, open subagents, workflows and jobs, stall), without it the
summary with "live details paused". Goldens: test/run_graph/ci/ and test/run_graph/macos/.
The phone (M5-14, 01 5.3 M12) uses the same view in its touch layout: layout takes
RunGraphLayout.narrow (each bar on one shared track below its label) or RunGraphLayout.wide
(beside it); null picks by width with runGraphLayoutFor, narrow below DsLayout.overlayWidth.
touch: true lets a tap on a row with children close or open it. The derivation is the same in
both layouts. RunControlCard(expand: true) is the phone's card: full width, Graph and Stop
stacked at full width.
Views
Every view takes the same four arguments, XxxView({live, queries, options, onOpenThread})
(docs/DECISIONS.md D-M6-12-1), and knows no transport: the apps and the console plug in their
sources (M6-16).
| View | Reads | Notes |
|---|---|---|
LiveTopologyView (6.1) |
LiveFrameSource (without one: getStatsLive once) |
lanes per space and channel, run cards, rates of the last minute, stale and "More runs than shown"; a card tap calls onOpenThread |
BackendView (6.2) |
getStatsBackends, the agents of every live frame |
configured and reported mode with "Reported: ...", supported modes, switches, I2 and I3 denials, costs per mode, rate-limit state |
DeliveryView (6.3) |
LiveFrameSource, getStatsTimeline for the history |
instance scope only; in a space scope "Only for the owner" without calling a source |
ExecutiveView (6.4) |
getUsageStats, getRunStats, getErrorStats, getCostStats, getStatsSlo |
forbidden of the traffic light shows "Only for the owner", of the costs leaves them out; ExecutiveTileGroups is reused by A7 |
TimelineView (6.5) |
getStatsTimeline once |
plays locally with TimelinePlayback at viz.timeline_speeds; the playhead starts at the end |
TvMode (6.6) |
the views it rotates | overlay, dark, comfortable, one font step larger, no controls, viz.tv_rotation_interval, costs only with viz.tv_show_costs, any key or click ends it |
How a host embeds them:
- Build
VizHostOptionswith the form factor, the scope (VizScope.instance()orVizScope.space(id)), the period of the queries,allowTvMode(desktop app and console), the display names, the time zone of the person and, only whentelemetry.dashboard_urlis set,onOpenGrafana. - Use
vizViewsFor(options)for the tabs or navigation entries (never TV mode on mobile or withoutallowTvMode) andbuildVizView(kind, ...)for the view; open TV mode asTvMode(views: [...], ..., onExit: ...)over the screen. - The views are columns without their own scroll view; place them in a scrollable area.
- Errors of a source (
forbidden,not_found,feature_disabled,rate_limited) show their text, and a live view stops its source until the host starts it again.
Run from the repository root: make lint test coverage arch COMPONENTS=apps. Regenerate the
goldens with flutter test --update-goldens test/goldens in this folder. The API pages come from
make docs (Dart API reference).
Libraries
- bothy_viz
- Visualizations of the statistics: view models, ports, basic forms, fixtures and harness support (docs/spec/25-visualization.md 7).