Background
TrafficGraphView (Sources/Spook/Views/TrafficGraphView.swift) renders a simple line+fill graph of total download/upload traffic. GraphCanvas draws the graph using SwiftUI Path geometry. There are no axis labels, no interactive elements, and the data does not refresh while the panel stays open.
Current Limitations
- No Y-axis labels: The graph normalises to
maxValue but never displays it. Users see a line but can't read the scale.
- No X-axis time labels: The time range (1h / 24h / 7d) is selected but no timestamps or time markers appear on the graph.
- Stale data: The graph loads once on
.task and again when the range picker changes. It does not auto-refresh during an open session — live network activity accumulates in the database but is not reflected.
- Confusing legend: The legend shows
"Total: ↓X ↑Y" using lastSample.cumulativeIn/Out, but GraphCanvas plots per-sample bytesIn/Out. The word "Total" and the cumulative/per-period distinction is unclear.
- No hover interaction: There is no way to inspect the value at a specific point in the graph.
What to Fix
Required
1. Y-axis peak label
Inside GraphCanvas (or overlaid on TrafficGraphView), show the maxValue formatted with ByteFormatter.format() at the top-left of the graph canvas. Optionally, show a midpoint label (maxValue / 2) at the horizontal grid line midpoint.
2. X-axis time labels
Below GraphCanvas, add a small HStack with the start and end timestamps of the visible range:
- For
.hour: "1h ago" on the left, "now" on the right.
- For
.day: "24h ago" on the left, "now" on the right.
- For
.week: "7d ago" on the left, "now" on the right.
Use .font(.system(size: 9)) and .foregroundColor(.secondary) to keep them subtle.
3. Clarify the legend
Replace "Total:" with a label that reflects the selected range:
Text("↓ \(ByteFormatter.format(lastSample.cumulativeIn)) ↑ \(ByteFormatter.format(lastSample.cumulativeOut))")
Or label it "Last \(selectedRange.rawValue):" to make the period explicit.
4. Auto-refresh while the panel is open
Add a repeating timer to reload graph data. Use SwiftUI's onReceive with a Timer.publish:
.onReceive(Timer.publish(every: 60, on: .main, in: .common).autoconnect()) { _ in
Task { await loadSamples() }
}
This should be added to the TrafficGraphView.body.
Optional
5. Hover tooltip
Use onContinuousHover (macOS 13+) on GraphCanvas to detect cursor position. Map the X coordinate back to the nearest TrafficSample and display a small overlay showing the timestamp and byte value at that point.
Acceptance Criteria
Relevant Files
Sources/Spook/Views/TrafficGraphView.swift — TrafficGraphView, GraphCanvas, LegendItem
Sources/Spook/Services/HistoryStore.swift — getHourlySamples(hours:)
Sources/Spook/Utilities/Formatters.swift — ByteFormatter
Background
TrafficGraphView(Sources/Spook/Views/TrafficGraphView.swift) renders a simple line+fill graph of total download/upload traffic.GraphCanvasdraws the graph using SwiftUIPathgeometry. There are no axis labels, no interactive elements, and the data does not refresh while the panel stays open.Current Limitations
maxValuebut never displays it. Users see a line but can't read the scale..taskand again when the range picker changes. It does not auto-refresh during an open session — live network activity accumulates in the database but is not reflected."Total: ↓X ↑Y"usinglastSample.cumulativeIn/Out, butGraphCanvasplots per-samplebytesIn/Out. The word "Total" and the cumulative/per-period distinction is unclear.What to Fix
Required
1. Y-axis peak label
Inside
GraphCanvas(or overlaid onTrafficGraphView), show themaxValueformatted withByteFormatter.format()at the top-left of the graph canvas. Optionally, show a midpoint label (maxValue / 2) at the horizontal grid line midpoint.2. X-axis time labels
Below
GraphCanvas, add a smallHStackwith the start and end timestamps of the visible range:.hour:"1h ago"on the left,"now"on the right..day:"24h ago"on the left,"now"on the right..week:"7d ago"on the left,"now"on the right.Use
.font(.system(size: 9))and.foregroundColor(.secondary)to keep them subtle.3. Clarify the legend
Replace
"Total:"with a label that reflects the selected range:Or label it
"Last \(selectedRange.rawValue):"to make the period explicit.4. Auto-refresh while the panel is open
Add a repeating timer to reload graph data. Use SwiftUI's
onReceivewith aTimer.publish:This should be added to the
TrafficGraphView.body.Optional
5. Hover tooltip
Use
onContinuousHover(macOS 13+) onGraphCanvasto detect cursor position. Map the X coordinate back to the nearestTrafficSampleand display a small overlay showing the timestamp and byte value at that point.Acceptance Criteria
ByteFormatter.format().Relevant Files
Sources/Spook/Views/TrafficGraphView.swift—TrafficGraphView,GraphCanvas,LegendItemSources/Spook/Services/HistoryStore.swift—getHourlySamples(hours:)Sources/Spook/Utilities/Formatters.swift—ByteFormatter