Skip to content

Dashboard scrolling stutters: hover-selection storm and per-frame blur #288

Description

@thefourcraft

Scrolling the Dashboard quickly stutters, most visibly when the pointer passes over the usage charts.

Root causes

  1. Hover-selection storm. Both charts in Zerm/Views/Metrics/UsageTrendCharts.swift bind .chartXSelection(value: $selectedDate). On macOS this tracks the pointer, so content scrolling under a stationary cursor rewrites selectedDate on every event. Each write re-evaluates the whole view body and rebuilds both Charts.
  2. O(n^2) inside the mark builders. selectedBucket is a computed property doing buckets.min { ... } (O(n)) and it is referenced inside the Chart(buckets) { bucket in ... } closures, so it runs once per data element. ratePoints, peakBucket, lastRateBucket and xDomain are likewise recomputed on every body pass.
  3. Per-frame compositing. .thinMaterial on six MetricCards, both chart cards and the footer capsule, plus a .shadow(radius: 30) on the hero — each an offscreen blur pass on every scroll frame.
  4. Non-lazy data table. UsageDataTable lays out every row in a plain VStack inside the outer ScrollView; on All Time that is one row per active day.
  5. Reload churn. MetricsContent.reload() re-fetches the entire UsageDay store on the main actor for every .usageStatsUpdated post and every range switch, even though the range window is applied in memory afterwards.

Fix

  • Precompute everything derived from buckets into an Equatable series value built once, and look up the nearest bucket by index instead of min(by:).
  • Snap chart selection to a bucket and write state only when the selected bucket actually changes, so pointer movement inside one bar costs nothing.
  • Hoist selection checks out of the mark builders so the marks are selection-invariant.
  • Replace card materials with a solid adaptive fill plus hairline border, and shrink the hero shadow.
  • LazyVStack for the data table.
  • Cache the fetched days, re-bucket in memory on range change, and coalesce .usageStatsUpdated reloads.

No behaviour or number on screen changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions