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_engine or local_store, directly or through another member (rule 6, make arch);
  • no WebView, HtmlElementView or iframe in lib/, harness/ or test/ (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 VizHostOptions with the form factor, the scope (VizScope.instance() or VizScope.space(id)), the period of the queries, allowTvMode (desktop app and console), the display names, the time zone of the person and, only when telemetry.dashboard_url is set, onOpenGrafana.
  • Use vizViewsFor(options) for the tabs or navigation entries (never TV mode on mobile or without allowTvMode) and buildVizView(kind, ...) for the view; open TV mode as TvMode(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).