Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 215 additions & 57 deletions brain-bar/Sources/BrainBar/BrainBarWindowRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ private struct BrainBarDashboardView: View {
@State private var writePulseRevision = 0
@State private var enrichmentPulseRevision = 0
@State private var detailsExpanded = false
@State private var signalCoverageExpanded = false
@State private var vectorSignalDetailExpanded = false

private var flowSummary: DashboardFlowSummary {
DashboardFlowSummary.derive(daemon: collector.daemon, stats: collector.stats)
Expand All @@ -336,7 +338,6 @@ private struct BrainBarDashboardView: View {
overviewCard(layout: layout)
freshnessLine
pipelinePanel(layout: layout)
signalCoveragePanel(layout: layout)
diagnostics(layout: layout)
}
.padding(layout.outerPadding)
Expand Down Expand Up @@ -489,12 +490,17 @@ private struct BrainBarDashboardView: View {

if layout.chartColumns == 2 {
HStack(alignment: .top, spacing: layout.gridSpacing) {
writesCard(layout: layout)
VStack(alignment: .leading, spacing: 12) {
writesCard(layout: layout)
signalCoveragePanel(layout: layout)
}
.frame(maxWidth: .infinity, alignment: .topLeading)
enrichmentsCard(layout: layout)
}
} else {
VStack(spacing: layout.gridSpacing) {
writesCard(layout: layout)
signalCoveragePanel(layout: layout)
enrichmentsCard(layout: layout)
}
}
Expand Down Expand Up @@ -549,7 +555,9 @@ private struct BrainBarDashboardView: View {
private func signalCoveragePanel(layout: BrainBarDashboardLayout) -> some View {
BrainBarSignalCoveragePanel(
stats: collector.stats,
compact: layout.compactCards
compact: layout.compactCards,
isExpanded: $signalCoverageExpanded,
isVectorDetailExpanded: $vectorSignalDetailExpanded
)
}

Expand Down Expand Up @@ -692,75 +700,123 @@ private struct BrainBarTrigramProgress: View {
private struct BrainBarSignalCoveragePanel: View {
let stats: BrainDatabase.DashboardStats
let compact: Bool
@Binding var isExpanded: Bool
@Binding var isVectorDetailExpanded: Bool

private var signals: [BrainBarSignalCoverage] {
[
let green = BrainBarStateTheme.active.theme.swiftUIColor
let amber = BrainBarStateTheme.degraded.theme.swiftUIColor

return [
BrainBarSignalCoverage(
name: "Vector",
indexedCount: stats.vectorIndexedChunkCount,
totalCount: stats.signalEligibleChunkCount,
backlogCount: stats.vectorBacklogCount,
coveragePercent: stats.vectorCoveragePercent,
accentColor: .brainBarAccent
accentColor: amber,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive signal colors from current coverage

When the dashboard is connected to a DB where Vector is fully caught up, this still renders Vector as degraded, while the nearby FTS5/Trigram entries stay green even if their backlog grows. Since the percentages and backlog counts already come from DashboardStats, the accent should be based on each signal’s current coverage/backlog rather than frozen snapshot colors; otherwise refreshed stats can show healthy-looking or degraded-looking retrieval signals incorrectly.

Useful? React with 👍 / 👎.

showsDetail: true
),
BrainBarSignalCoverage(
name: "FTS",
name: "FTS5",
indexedCount: stats.ftsIndexedChunkCount,
totalCount: stats.signalEligibleChunkCount,
backlogCount: stats.ftsBacklogCount,
coveragePercent: stats.ftsCoveragePercent,
accentColor: .brainBarAccentBright
accentColor: green,
showsDetail: false
),
BrainBarSignalCoverage(
name: "Trigram",
indexedCount: stats.trigramIndexedChunkCount,
totalCount: stats.signalEligibleChunkCount,
backlogCount: stats.trigramBacklogCount,
coveragePercent: stats.trigramCoveragePercent,
accentColor: .brainBarAccentViolet
accentColor: green,
showsDetail: false
),
]
}

var body: some View {
VStack(alignment: .leading, spacing: compact ? 12 : 16) {
ViewThatFits(in: .horizontal) {
HStack(alignment: .firstTextBaseline, spacing: 12) {
BrainBarSectionLabel("Signal Coverage")
Text("Coverage is counted per retrieval signal; backlogs stay separate.")
.font(.system(size: 11, weight: .medium))
.foregroundStyle(.secondary)
.lineLimit(1)
Spacer(minLength: 0)
VStack(alignment: .leading, spacing: compact ? 10 : 12) {
disclosureButton

if isExpanded {
signalBars
.transition(.opacity.combined(with: .move(edge: .top)))

if isVectorDetailExpanded, let vector = signals.first(where: { $0.showsDetail }) {
BrainBarVectorSignalDetail(signal: vector, compact: compact)
.transition(.opacity.combined(with: .move(edge: .top)))
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}

VStack(alignment: .leading, spacing: 6) {
BrainBarSectionLabel("Signal Coverage")
Text("Coverage is counted per retrieval signal; backlogs stay separate.")
.font(.system(size: 11, weight: .medium))
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
private var disclosureButton: some View {
Button {
withAnimation(.easeInOut(duration: 0.18)) {
isExpanded.toggle()
if !isExpanded {
isVectorDetailExpanded = false
}
}
} label: {
HStack(spacing: 7) {
Image(systemName: isExpanded ? "chevron.down" : "chevron.right")
.font(.system(size: 9, weight: .bold))
.frame(width: 12, height: 12)

if compact {
VStack(spacing: 10) {
ForEach(signals) { signal in
BrainBarSignalCoverageRow(signal: signal, compact: true)
}
Text("see under the hood")
.font(.system(size: 11, weight: .semibold))
.foregroundStyle(Color.brainBarTextSecondary)

Spacer(minLength: 0)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.help("Show retrieval signal coverage")
}

@ViewBuilder
private var signalBars: some View {
if compact {
VStack(spacing: 8) {
ForEach(signals) { signal in
signalRow(for: signal)
}
} else {
HStack(alignment: .top, spacing: 12) {
ForEach(signals) { signal in
BrainBarSignalCoverageRow(signal: signal, compact: false)
}
}
} else {
HStack(alignment: .top, spacing: 10) {
ForEach(signals) { signal in
signalRow(for: signal)
}
}
}
.padding(compact ? 18 : 22)
.background(
BrainBarGlassPanel(cornerRadius: BrainBarDesignTokens.Radius.lg, tint: .brainBarAccentBright)
)
}

@ViewBuilder
private func signalRow(for signal: BrainBarSignalCoverage) -> some View {
if signal.showsDetail {
Button {
withAnimation(.easeInOut(duration: 0.18)) {
isVectorDetailExpanded.toggle()
}
} label: {
BrainBarSignalCoverageRow(
signal: signal,
compact: compact,
isSelected: isVectorDetailExpanded
)
}
.buttonStyle(.plain)
.help("Show Vector backlog details")
} else {
BrainBarSignalCoverageRow(signal: signal, compact: compact, isSelected: false)
}
}
}

Expand All @@ -771,53 +827,155 @@ private struct BrainBarSignalCoverage: Identifiable {
let backlogCount: Int
let coveragePercent: Double
let accentColor: Color
let showsDetail: Bool

var id: String { name }

var countText: String {
"\(indexedCount)/\(totalCount)"
}

var percentText: String {
String(format: "%.0f%%", coveragePercent)
}

var clampedCoveragePercent: Double {
min(max(coveragePercent, 0), 100)
}

var backlogText: String {
"backlog \(backlogCount)"
NumberFormatter.localizedString(from: NSNumber(value: backlogCount), number: .decimal)
}
}

private struct BrainBarSignalCoverageRow: View {
let signal: BrainBarSignalCoverage
let compact: Bool
let isSelected: Bool

var body: some View {
VStack(alignment: .leading, spacing: compact ? 8 : 10) {
VStack(alignment: .leading, spacing: compact ? 7 : 8) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(signal.name)
.font(.system(size: compact ? 13 : 14, weight: .semibold))
.font(.system(size: compact ? 12 : 13, weight: .semibold))
.foregroundStyle(Color.brainBarTextPrimary)
Spacer(minLength: 8)
Text(signal.percentText)
.font(.system(size: compact ? 16 : 18, weight: .bold, design: .rounded))
.font(.system(size: compact ? 13 : 15, weight: .bold, design: .rounded))
.foregroundStyle(signal.accentColor)
.monospacedDigit()
}

ProgressView(value: min(max(signal.coveragePercent, 0), 100), total: 100)
.tint(signal.accentColor)
GeometryReader { proxy in
ZStack(alignment: .leading) {
Capsule()
.fill(signal.accentColor.opacity(0.16))
Capsule()
.fill(
LinearGradient(
colors: [signal.accentColor.opacity(0.68), signal.accentColor],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: proxy.size.width * signal.clampedCoveragePercent / 100)
}
}
.frame(height: 6)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, compact ? 10 : 11)
.padding(.horizontal, compact ? 11 : 12)
.background(BrainBarDashboardCardStyle(emphasized: isSelected))
.overlay {
RoundedRectangle(cornerRadius: BrainBarDesignTokens.Radius.md, style: .continuous)
.stroke(signal.accentColor.opacity(isSelected ? 0.48 : 0.2), lineWidth: isSelected ? 1.2 : 1)
}
}
}

private struct BrainBarVectorSignalDetail: View {
let signal: BrainBarSignalCoverage
let compact: Bool

HStack(spacing: 10) {
BrainBarLaneMetric(label: "Indexed", value: signal.countText)
BrainBarLaneMetric(label: "Backlog", value: "\(signal.backlogCount)")
Spacer(minLength: 0)
// TODO: Replace these rollout estimates with measured vector drain fields
// once DashboardStats carries vector indexing history.
private let estimatedDrainRatePerHour = 3_300
private let estimatedETAHours = 13
Comment on lines +897 to +900

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compute vector drain and ETA from live stats

When vectorBacklogCount differs from the rollout snapshot, including when it reaches zero or stops draining, L2 still displays ~3,300/hr and ~13h, so users see an ETA unrelated to the current database state. The detail should be derived from measured stats/backlog or hidden until those fields exist; otherwise BrainBar can report a caught-up or stalled vector index as actively draining with a fixed ETA.

Useful? React with 👍 / 👎.

Comment on lines +897 to +900

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider tracking the TODO for real drain metrics.

The hardcoded values (estimatedDrainRatePerHour = 3_300, estimatedETAHours = 13) and the static "falling" trend label (line 946) are acknowledged as scaffolding. Once DashboardStats carries vector indexing history, these should be replaced with computed values.

Would you like me to open an issue to track adding vector drain rate and ETA calculation to DashboardStats?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@brain-bar/Sources/BrainBar/BrainBarWindowRootView.swift` around lines 897 -
900, The hardcoded values estimatedDrainRatePerHour and estimatedETAHours in
BrainBarWindowRootView are acknowledged as temporary scaffolding in the TODO
comment. Create a formal issue or task in your project tracker to document the
technical debt of replacing these hardcoded estimates with computed values
derived from DashboardStats once it carries vector indexing history.
Alternatively, enhance the TODO comment to include specific acceptance criteria
such as what fields need to be added to DashboardStats and how the drain rate
and ETA should be calculated from actual vector indexing metrics.


var body: some View {
Group {
if compact {
VStack(alignment: .leading, spacing: 12) {
metrics
trend
}
} else {
HStack(alignment: .center, spacing: 18) {
metrics
Spacer(minLength: 8)
trend
}
}
}
.padding(.vertical, compact ? 12 : 14)
.padding(.horizontal, compact ? 12 : 16)
.background(
RoundedRectangle(cornerRadius: BrainBarDesignTokens.Radius.md, style: .continuous)
.fill(signal.accentColor.opacity(0.08))
.overlay(
RoundedRectangle(cornerRadius: BrainBarDesignTokens.Radius.md, style: .continuous)
.stroke(signal.accentColor.opacity(0.28), lineWidth: 1)
)
)
}

private var metrics: some View {
ViewThatFits(in: .horizontal) {
HStack(spacing: compact ? 14 : 22) {
BrainBarSignalDetailMetric(label: "drain", value: "~\(formatted(estimatedDrainRatePerHour))/hr", tint: signal.accentColor)
BrainBarSignalDetailMetric(label: "ETA", value: "~\(estimatedETAHours)h", tint: signal.accentColor)
BrainBarSignalDetailMetric(label: "backlog", value: signal.backlogText, tint: Color.brainBarTextPrimary)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vector ETA ignores live backlog

Medium Severity

BrainBarVectorSignalDetail shows live backlog from DashboardStats beside fixed ~3,300/hr drain and ~13h ETA. As stats refresh, backlog can be zero or far from the demo backlog while drain and ETA never change, so the L2 panel contradicts itself.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 163c4bb. Configure here.

}

Text(signal.backlogText)
.font(.system(size: 11, weight: .medium))
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 10) {
BrainBarSignalDetailMetric(label: "drain", value: "~\(formatted(estimatedDrainRatePerHour))/hr", tint: signal.accentColor)
BrainBarSignalDetailMetric(label: "ETA", value: "~\(estimatedETAHours)h", tint: signal.accentColor)
BrainBarSignalDetailMetric(label: "backlog", value: signal.backlogText, tint: Color.brainBarTextPrimary)
}
}
}

private var trend: some View {
Label("falling", systemImage: "arrow.down.right")
.font(.system(size: 11, weight: .bold))
.foregroundStyle(signal.accentColor)
.padding(.vertical, 5)
.padding(.horizontal, 8)
.background(Capsule().fill(signal.accentColor.opacity(0.12)))
.overlay(Capsule().stroke(signal.accentColor.opacity(0.32), lineWidth: 1))
.help("Vector backlog trend")
}

private func formatted(_ value: Int) -> String {
NumberFormatter.localizedString(from: NSNumber(value: value), number: .decimal)
}
}

private struct BrainBarSignalDetailMetric: View {
let label: String
let value: String
let tint: Color

var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(value)
.font(.system(size: 16, weight: .bold, design: .rounded))
.foregroundStyle(tint)
.monospacedDigit()
.lineLimit(1)

Text(label)
.font(.system(size: 9, weight: .semibold))
.foregroundStyle(Color.brainBarTextMuted)
.textCase(.uppercase)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(compact ? 12 : 14)
.background(BrainBarDashboardCardStyle())
}
}

Expand Down
8 changes: 6 additions & 2 deletions brain-bar/Tests/BrainBarTests/DashboardTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,13 @@ final class DashboardTests: XCTestCase {
let source = try brainBarSourceFile("Sources/BrainBar/BrainBarWindowRootView.swift")

XCTAssertTrue(source.contains("BrainBarSignalCoveragePanel"))
XCTAssertTrue(source.contains("Signal Coverage"))
XCTAssertTrue(source.contains("see under the hood"))
XCTAssertTrue(source.contains("@State private var signalCoverageExpanded"))
XCTAssertTrue(source.contains("@State private var vectorSignalDetailExpanded"))
XCTAssertFalse(source.contains("BrainBarSectionLabel(\"Signal Coverage\")"))
XCTAssertFalse(source.contains("Coverage is counted per retrieval signal"))
XCTAssertTrue(source.contains("Vector"))
XCTAssertTrue(source.contains("FTS"))
XCTAssertTrue(source.contains("FTS5"))
XCTAssertTrue(source.contains("Trigram"))
XCTAssertTrue(source.contains("signalEligibleChunkCount"))
XCTAssertTrue(source.contains("vectorBacklogCount"))
Expand Down
Loading