feat(brainbar): show graph + injections panels with degradation resilience#315
Conversation
…ience Etan-mandate 2026-05-22: "IMPROVE BRAINBAR AND MAKE IT SHOW GRAPH AND INJESTIONS ALREADY, WITHOUT DEGRATION!" + addendum: "AUTO MERGE AFTER /pr-loop, not sloppy." PR #314 wired the runtime database + InjectionStore so the "Warming memory…" placeholder cleared and BrainBarGraphTab + BrainBarInjectionTab get non-nil dependencies. But two failure modes remained: 1. When KG queries failed (typically transient ReadOnly / busy / locked from writer-pidfile contention with the Python enrich-supervisor + drain — PR #309), `KGViewModel.loadGraph` silently set nodes=[]/ edges=[] and returned false. The UI had no way to distinguish "empty graph" from "queries failing," so the canvas blanked. 2. Same pattern in `InjectionStore.refresh`: failures only `NSLog`'d and the panel held stale or empty events with no user-visible signal. This change adds a small `DegradationState` enum (`.healthy` / `.degraded(reason:)`) published on both view-models. `loadGraph` retries once on a 200ms backoff before reporting degraded, then keeps last-known- good nodes/edges so the user sees prior data rather than a blank canvas. `InjectionStore.refresh` flips state on failure and clears it on the next successful poll cycle. A small `DegradationBadge` (orange capsule with an exclamation-triangle icon) overlays the top-right of each tab when its state is degraded, with the underlying error in the SwiftUI .help tooltip. Per Etan's mandate: degraded ≠ hidden. Tests cover the enum surface, KG load-failure → degraded transition, KG success → healthy transition, and InjectionStore initial-healthy state. 387/387 tests pass, swift build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with 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.
Inline comments:
In `@brain-bar/Tests/BrainBarTests/DegradationStateTests.swift`:
- Around line 45-60: Add a test that verifies a degraded→healthy recovery by
first forcing a failing load to set KGViewModel.degradationState to .degraded
(e.g., make vm.loadGraph() fail or simulate DB error), then fix the underlying
data/state (insert the two entities and relation as in
testLoadGraphSuccessKeepsDegradationStateHealthy) and call vm.loadGraph() again;
assert the second call returns true and vm.degradationState is .healthy and
nodes/edges are populated. Reference KGViewModel, loadGraph(), degradationState,
nodes, and edges when locating where to add the test.
- Line 5: The KGDegradationStateTests test class (and the other two test classes
flagged by SwiftLint) lack an explicit deinit which triggers the required_deinit
lint rule; add a simple explicit deinit { } to each test class (for example in
KGDegradationStateTests) to satisfy the rule—locate the class declarations
(e.g., class KGDegradationStateTests: XCTestCase) and add an empty deinitializer
method in each class body.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7fd1448d-1bcf-4492-b515-88b12a1e0896
📒 Files selected for processing (6)
brain-bar/Sources/BrainBar/BrainBarWindowRootView.swiftbrain-bar/Sources/BrainBar/DegradationState.swiftbrain-bar/Sources/BrainBar/InjectionStore.swiftbrain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swiftbrain-bar/Sources/BrainBarDaemon/DegradationState.swiftbrain-bar/Tests/BrainBarTests/DegradationStateTests.swift
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Cursor Bugbot
- GitHub Check: test (3.13)
- GitHub Check: test (3.11)
- GitHub Check: test (3.12)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-03-18T00:12:08.774Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In Swift files under brain-bar/Sources/BrainBar, enforce that when a critical dependency like the database is nil due to startup ordering (socket before DB), any tool handler that accesses the database must throw an explicit error (e.g., ToolError.noDatabase) instead of returning a default/empty value. Do not allow silent defaults (e.g., guard let db else { return ... }). Flag patterns that silently return defaults when db is nil, as this masks startup timing issues. This guidance applies broadly to similar Swift files in the BrainBar module, not just this one location.
Applied to files:
brain-bar/Sources/BrainBar/DegradationState.swiftbrain-bar/Sources/BrainBar/InjectionStore.swiftbrain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swiftbrain-bar/Sources/BrainBar/BrainBarWindowRootView.swift
📚 Learning: 2026-03-29T18:45:40.988Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 133
File: brain-bar/Sources/BrainBar/BrainDatabase.swift:0-0
Timestamp: 2026-03-29T18:45:40.988Z
Learning: In the BrainBar module’s Swift database layer (notably BrainDatabase.swift), ensure that the `search()` function’s `unreadOnly=true` path orders results by the delivery frontier cursor so the watermark `maxRowID` stays contiguous. Specifically, when `unreadOnly` is enabled, the query must include `ORDER BY c.rowid ASC` (e.g., via `let orderByClause = unreadOnly ? "c.rowid ASC" : "f.rank"`). Do not replace the unread-only ordering with relevance-based sorting (e.g., `f.rank`) unconditionally or for the unread-only path, as it can introduce gaps in the watermark and incorrectly mark unseen rows as delivered. Flag any future change to the `ORDER BY` clause in this function that makes relevance sorting apply to the unread-only case.
Applied to files:
brain-bar/Sources/BrainBar/DegradationState.swiftbrain-bar/Sources/BrainBar/InjectionStore.swiftbrain-bar/Sources/BrainBar/BrainBarWindowRootView.swift
🪛 SwiftLint (0.63.2)
brain-bar/Tests/BrainBarTests/DegradationStateTests.swift
[Warning] 5-5: Classes should have an explicit deinit method
(required_deinit)
[Warning] 64-64: Classes should have an explicit deinit method
(required_deinit)
[Warning] 92-92: Classes should have an explicit deinit method
(required_deinit)
🔇 Additional comments (5)
brain-bar/Sources/BrainBar/DegradationState.swift (1)
15-27: LGTM!brain-bar/Sources/BrainBar/InjectionStore.swift (1)
21-21: LGTM!Also applies to: 106-115
brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift (1)
12-12: LGTM!Also applies to: 38-68
brain-bar/Sources/BrainBar/BrainBarWindowRootView.swift (1)
753-763: LGTM!Also applies to: 775-785, 794-811
brain-bar/Sources/BrainBarDaemon/DegradationState.swift (1)
1-1: ⚡ Quick winConfirm symlink forwarder is valid (no build-failure risk)
brain-bar/Sources/BrainBarDaemon/DegradationState.swiftis committed as a real git symlink (mode120000) and is a symlink on disk pointing to../BrainBar/DegradationState.swift, so it won’t be treated as plain text Swift source.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c25dc7602c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch { | ||
| lastError = error | ||
| if attempt < attemptLimit { | ||
| try? await Task.sleep(nanoseconds: 200_000_000) |
There was a problem hiding this comment.
Honor task cancellation before retrying graph load
If the graph view disappears while loadGraph() is running, SwiftUI cancels the .task, but the retry path swallows CancellationError via try? await Task.sleep(...) and still performs the second fetchGraphRows attempt. That means canceled loads keep reading SQLite and can still update degradation state/logs after the UI has gone away, increasing unnecessary DB contention in exactly the lock-sensitive path this change targets. Exit early on cancellation before sleeping/retrying.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@coderabbitai review |
|
@codex review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with 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.
Inline comments:
In `@brain-bar/Tests/BrainBarTests/InjectionStoreTests.swift`:
- Around line 152-160: The test helper function listInjectionEvents currently
returns an empty array when eventResults is empty, hiding extra calls; change
the guard in listInjectionEvents to fail fast by throwing a clear test-time
error (e.g., a custom UnexpectedCall/test assertion or call preconditionFailure)
when eventResults is exhausted instead of returning []; update any test
scaffolding that expects this behavior and reference listInjectionEvents and the
eventResults buffer when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3ca7001e-a58f-4b95-ac71-84211eb70746
📒 Files selected for processing (2)
brain-bar/Sources/BrainBar/InjectionStore.swiftbrain-bar/Tests/BrainBarTests/InjectionStoreTests.swift
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Cursor Bugbot
- GitHub Check: test (3.13)
- GitHub Check: test (3.12)
- GitHub Check: test (3.11)
- GitHub Check: Macroscope - Correctness Check
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-03-18T00:12:08.774Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In Swift files under brain-bar/Sources/BrainBar, enforce that when a critical dependency like the database is nil due to startup ordering (socket before DB), any tool handler that accesses the database must throw an explicit error (e.g., ToolError.noDatabase) instead of returning a default/empty value. Do not allow silent defaults (e.g., guard let db else { return ... }). Flag patterns that silently return defaults when db is nil, as this masks startup timing issues. This guidance applies broadly to similar Swift files in the BrainBar module, not just this one location.
Applied to files:
brain-bar/Sources/BrainBar/InjectionStore.swift
📚 Learning: 2026-03-29T18:45:40.988Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 133
File: brain-bar/Sources/BrainBar/BrainDatabase.swift:0-0
Timestamp: 2026-03-29T18:45:40.988Z
Learning: In the BrainBar module’s Swift database layer (notably BrainDatabase.swift), ensure that the `search()` function’s `unreadOnly=true` path orders results by the delivery frontier cursor so the watermark `maxRowID` stays contiguous. Specifically, when `unreadOnly` is enabled, the query must include `ORDER BY c.rowid ASC` (e.g., via `let orderByClause = unreadOnly ? "c.rowid ASC" : "f.rank"`). Do not replace the unread-only ordering with relevance-based sorting (e.g., `f.rank`) unconditionally or for the unread-only path, as it can introduce gaps in the watermark and incorrectly mark unseen rows as delivered. Flag any future change to the `ORDER BY` clause in this function that makes relevance sorting apply to the unread-only case.
Applied to files:
brain-bar/Sources/BrainBar/InjectionStore.swift
🪛 SwiftLint (0.63.2)
brain-bar/Tests/BrainBarTests/InjectionStoreTests.swift
[Warning] 136-136: Classes should have an explicit deinit method
(required_deinit)
🔇 Additional comments (2)
brain-bar/Sources/BrainBar/InjectionStore.swift (1)
4-24: LGTM!Also applies to: 42-73, 109-109, 130-130, 137-173
brain-bar/Tests/BrainBarTests/InjectionStoreTests.swift (1)
88-129: LGTM!Also applies to: 132-150, 162-171
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@coderabbitai review |
|
@codex review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
| if let id { | ||
| guard let database else { return } | ||
| if let lookup = try? database.lookupEntity(query: nodeById(id)?.name ?? "") { | ||
| selectedEntity = EntityCard(lookupPayload: lookup) |
There was a problem hiding this comment.
🟡 Medium KnowledgeGraph/KGViewModel.swift:166
In selectNode(id:), when database.lookupEntity returns nil (entity not found), selectedEntity retains its previous value from an earlier selection. This leaves the UI in a mismatched state: selectedNodeId points to the newly tapped node, but selectedEntity displays the card of a previously selected, different entity. Consider updating selectedEntity = nil when the lookup fails.
if let lookup = try? database.lookupEntity(query: nodeById(id)?.name ?? "") {
- selectedEntity = EntityCard(lookupPayload: lookup)
+ selectedEntity = lookup.map { EntityCard(lookupPayload: $0) }
}🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift around lines 166-169:
In `selectNode(id:)`, when `database.lookupEntity` returns `nil` (entity not found), `selectedEntity` retains its previous value from an earlier selection. This leaves the UI in a mismatched state: `selectedNodeId` points to the newly tapped node, but `selectedEntity` displays the card of a previously selected, different entity. Consider updating `selectedEntity = nil` when the lookup fails.
Evidence trail:
brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift lines 164-177 at REVIEWED_COMMIT. Line 165 sets selectedNodeId = id unconditionally. Line 168's `if let lookup = try? database.lookupEntity(...)` only updates selectedEntity on success (line 169). On failure (nil return or throw), selectedEntity is not cleared, while selectedNodeId and selectedEntityChunks (line 171) reflect the new node.
|
Codex Review: Didn't find any major issues. Chef's kiss. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with 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.
Inline comments:
In `@brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift`:
- Around line 39-42: The initializer init(graphReader:) currently sets database
= nil causing selectNode(_:) and openConversation(_:) to silently no-op; change
these code paths to fail fast and surface errors: in init(graphReader:)
document/mark that database is missing (or make it failable), and inside
selectNode and openConversation add a guard that throws or calls the existing
error/reporting callback (or asserts) when database is nil instead of returning
quietly; reference the database property, init(graphReader:), selectNode(_:),
openConversation(_:), and KnowledgeGraphReading to locate where to add the guard
and error propagation so callers receive an explicit error when the DB
dependency is absent.
In `@brain-bar/Tests/BrainBarTests/DegradationStateTests.swift`:
- Around line 170-205: Add a brief comment above ScriptedKnowledgeGraphReader
explaining why `@unchecked` Sendable is safe: note that
ScriptedKnowledgeGraphReader mutates results and activeRelations and thus is not
intrinsically Sendable, but it is only instantiated and used within `@MainActor`
test contexts (never passed across actors or threads), so the unchecked
conformance is safe; reference the class name ScriptedKnowledgeGraphReader and
the mutable properties results and activeRelations, and mention the methods
fetchKGEntities and fetchKGRelations as the only access points to make the
intended confinement explicit.
- Around line 64-110: The test
testLoadGraphUntilSuccessfulRecoversAfterInitialDegradation asserts the wrong
retry count: loadGraphUntilSuccessful calls the trailing sleep closure after
each failed loadGraph() attempt, so with two failures then a success the retry
counter retrySleeps should be 2; update the assertion
XCTAssertEqual(retrySleeps, 1) to XCTAssertEqual(retrySleeps, 2) (reference
symbols: testLoadGraphUntilSuccessfulRecoversAfterInitialDegradation,
KGViewModel.loadGraphUntilSuccessful, retrySleeps, XCTAssertEqual).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1d57794c-2a9e-4a16-ba8d-cbad6c2877e5
📒 Files selected for processing (5)
brain-bar/Sources/BrainBar/DegradationState.swiftbrain-bar/Sources/BrainBar/KnowledgeGraph/KGCanvasView.swiftbrain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swiftbrain-bar/Tests/BrainBarTests/DegradationStateTests.swiftbrain-bar/Tests/BrainBarTests/InjectionStoreTests.swift
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-03-18T00:12:08.774Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In Swift files under brain-bar/Sources/BrainBar, enforce that when a critical dependency like the database is nil due to startup ordering (socket before DB), any tool handler that accesses the database must throw an explicit error (e.g., ToolError.noDatabase) instead of returning a default/empty value. Do not allow silent defaults (e.g., guard let db else { return ... }). Flag patterns that silently return defaults when db is nil, as this masks startup timing issues. This guidance applies broadly to similar Swift files in the BrainBar module, not just this one location.
Applied to files:
brain-bar/Sources/BrainBar/DegradationState.swiftbrain-bar/Sources/BrainBar/KnowledgeGraph/KGCanvasView.swiftbrain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift
📚 Learning: 2026-03-29T18:45:40.988Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 133
File: brain-bar/Sources/BrainBar/BrainDatabase.swift:0-0
Timestamp: 2026-03-29T18:45:40.988Z
Learning: In the BrainBar module’s Swift database layer (notably BrainDatabase.swift), ensure that the `search()` function’s `unreadOnly=true` path orders results by the delivery frontier cursor so the watermark `maxRowID` stays contiguous. Specifically, when `unreadOnly` is enabled, the query must include `ORDER BY c.rowid ASC` (e.g., via `let orderByClause = unreadOnly ? "c.rowid ASC" : "f.rank"`). Do not replace the unread-only ordering with relevance-based sorting (e.g., `f.rank`) unconditionally or for the unread-only path, as it can introduce gaps in the watermark and incorrectly mark unseen rows as delivered. Flag any future change to the `ORDER BY` clause in this function that makes relevance sorting apply to the unread-only case.
Applied to files:
brain-bar/Sources/BrainBar/DegradationState.swift
🔇 Additional comments (8)
brain-bar/Tests/BrainBarTests/InjectionStoreTests.swift (1)
134-134: LGTM!Also applies to: 138-138, 156-158
brain-bar/Sources/BrainBar/DegradationState.swift (1)
1-28: LGTM!brain-bar/Tests/BrainBarTests/DegradationStateTests.swift (4)
6-6: LGTM!Also applies to: 115-115, 145-145
25-62: LGTM!
113-141: LGTM!
143-168: LGTM!brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift (1)
3-8: LGTM!Also applies to: 46-125
brain-bar/Sources/BrainBar/KnowledgeGraph/KGCanvasView.swift (1)
59-63: LGTM!
| func testLoadGraphUntilSuccessfulRecoversAfterInitialDegradation() async { | ||
| // Regression guard: Cursor Bugbot PR #315 flagged that KGCanvasView | ||
| // only performed one load. A first failed read must be retried so the | ||
| // graph badge can clear after a later successful graph query. | ||
| let reader = ScriptedKnowledgeGraphReader(results: [ | ||
| .failure(ScriptedKnowledgeGraphError.fetchFailed), | ||
| .failure(ScriptedKnowledgeGraphError.fetchFailed), | ||
| .success(( | ||
| entities: [ | ||
| BrainDatabase.KGEntityRow( | ||
| id: "a", | ||
| name: "Alice", | ||
| entityType: "person", | ||
| description: nil, | ||
| importance: 7 | ||
| ), | ||
| BrainDatabase.KGEntityRow( | ||
| id: "b", | ||
| name: "BrainLayer", | ||
| entityType: "project", | ||
| description: nil, | ||
| importance: 8 | ||
| ), | ||
| ], | ||
| relations: [ | ||
| BrainDatabase.KGRelationRow( | ||
| id: "r1", | ||
| sourceId: "a", | ||
| targetId: "b", | ||
| relationType: "builds" | ||
| ), | ||
| ] | ||
| )), | ||
| ]) | ||
| let vm = KGViewModel(graphReader: reader) | ||
| var retrySleeps = 0 | ||
|
|
||
| let success = await vm.loadGraphUntilSuccessful(retryDelay: .milliseconds(1)) { _ in | ||
| retrySleeps += 1 | ||
| } | ||
|
|
||
| XCTAssertTrue(success) | ||
| XCTAssertEqual(vm.degradationState, .healthy) | ||
| XCTAssertEqual(vm.nodes.count, 2) | ||
| XCTAssertEqual(vm.edges.count, 1) | ||
| XCTAssertEqual(retrySleeps, 1) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify loadGraphUntilSuccessful retry and callback semantics
ast-grep --pattern $'func loadGraphUntilSuccessful($$$) async -> Bool {
$$$
}'Repository: EtanHey/brainlayer
Length of output: 44
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Find all references/definitions of loadGraphUntilSuccessful
rg -n --hidden --no-ignore-vcs "loadGraphUntilSuccessful" .
# 2) Show nearby implementation blocks for any hits (limit output per file)
# We'll just cat the relevant file sections after we identify them.Repository: EtanHey/brainlayer
Length of output: 449
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE_VM="brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift"
FILE_VIEW="brain-bar/Sources/BrainBar/KnowledgeGraph/KGCanvasView.swift"
echo "=== KGViewModel.swift (around loadGraphUntilSuccessful) ==="
nl -ba "$FILE_VM" | sed -n '70,170p'
echo
echo "=== KGCanvasView.swift (call site) ==="
nl -ba "$FILE_VIEW" | sed -n '40,110p'Repository: EtanHey/brainlayer
Length of output: 183
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE_VM="brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift"
FILE_VIEW="brain-bar/Sources/BrainBar/KnowledgeGraph/KGCanvasView.swift"
echo "=== KGViewModel.swift (around loadGraphUntilSuccessful) ==="
cat -n "$FILE_VM" | sed -n '70,170p'
echo
echo "=== KGCanvasView.swift (call site) ==="
cat -n "$FILE_VIEW" | sed -n '40,110p'Repository: EtanHey/brainlayer
Length of output: 7517
Fix retry count assertion in DegradationStateTests
loadGraphUntilSuccessful invokes the trailing closure (the sleep parameter) after every failed loadGraph() attempt. With 2 initial failures followed by success, retrySleeps should be 2, not 1 (update the XCTAssertEqual(retrySleeps, 1) accordingly).
🤖 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/Tests/BrainBarTests/DegradationStateTests.swift` around lines 64 -
110, The test testLoadGraphUntilSuccessfulRecoversAfterInitialDegradation
asserts the wrong retry count: loadGraphUntilSuccessful calls the trailing sleep
closure after each failed loadGraph() attempt, so with two failures then a
success the retry counter retrySleeps should be 2; update the assertion
XCTAssertEqual(retrySleeps, 1) to XCTAssertEqual(retrySleeps, 2) (reference
symbols: testLoadGraphUntilSuccessfulRecoversAfterInitialDegradation,
KGViewModel.loadGraphUntilSuccessful, retrySleeps, XCTAssertEqual).
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@coderabbitai review |
|
@codex review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@coderabbitai review |
|
@codex review |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 581bd2c. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 581bd2c233
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if await viewModel.loadGraphRepeatedly(onSuccessfulLoad: { | ||
| hasLoadedGraph = true | ||
| }) { | ||
| hasLoadedGraph = true | ||
| } |
There was a problem hiding this comment.
Stop graph polling when tab is not visible
KGCanvasView now starts an infinite loadGraphRepeatedly loop in .task, but BrainBarWindowRootView keeps previously activated tabs mounted and only hides them via opacity (brainBarTabVisibility). That means once a user visits Graph once, background graph reads continue every 30s even after switching away, creating ongoing SQLite read pressure and avoidable lock contention against writes. Gate this loop on active tab visibility or cancel it when the graph tab is hidden.
Useful? React with 👍 / 👎.
|
@coderabbitai review |
|
@codex review |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b06b74d907
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .task { | ||
| guard !hasLoadedGraph else { return } | ||
| if await viewModel.loadGraph() { | ||
| guard !hasStartedGraphPolling else { return } | ||
| hasStartedGraphPolling = true | ||
| defer { hasStartedGraphPolling = false } | ||
| if await viewModel.loadGraphRepeatedly(onSuccessfulLoad: { |
There was a problem hiding this comment.
Stop graph polling when graph view is not active
loadGraphRepeatedly is started from .task and loops until cancellation, but this view is kept mounted after first visit (the tab system hides inactive tabs with opacity rather than removing them). In that state, opening Graph once and switching away still leaves this loop running, so it continues periodic DB reads/retries in the background; with known SQLite lock contention, this can add avoidable read pressure and stale/degraded state churn even when the graph tab is not visible. Tie the polling lifecycle to actual tab visibility (or cancel on inactivity) instead of running unconditionally for the lifetime of the mounted view.
Useful? React with 👍 / 👎.

Summary
Etan TOP PRIORITY ("IMPROVE BRAINBAR AND MAKE IT SHOW GRAPH AND INJESTIONS ALREADY, WITHOUT DEGRATION!").
Commit
c25dc760(Etan-authored) adds graph + injections UI surfaces with degradation resilience — the BrainBar UI now shows the KG graph view + injections panel without getting stuck on partial/degraded daemon state.Diff
6 files changed, +229 / -13:
brain-bar/Sources/BrainBar/BrainBarWindowRootView.swift(+52/-1) — graph + injections panel wiringbrain-bar/Sources/BrainBar/DegradationState.swift(NEW, +28) — degradation state modelbrain-bar/Sources/BrainBar/InjectionStore.swift(+8) — degradation hooksbrain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift(+39/-3) — graph view-model degradation handlingbrain-bar/Sources/BrainBarDaemon/DegradationState.swift(+1) — daemon-side mirrorbrain-bar/Tests/BrainBarTests/DegradationStateTests.swift(NEW, +114) — regression test suiteTest plan
Provenance
Branch was prepared by brainlayerCodex worker spawned via cmux MCP earlier in this session. Codex pushed the commit before its surface was lost in a cmux topology shift; no PR was opened by codex. PR created here by orc gen-6 per A1 Row 1 (Etan-authored + autonomous-mode declared).
Related
docs.local/audits/2026-05-22-issue-1b-fts5-kg-investigation.md) — identified KG forward-only as the real "degradation without recovery" root cause; this PR addresses UI-side resilience, KG-ingest-side fix remains separate follow-upNote
Medium Risk
Touches live data-refresh logic for the knowledge graph and injection feed, adding retry/polling and new state transitions that could impact UI update frequency and error recovery behavior.
Overview
Adds a shared
DegradationStatemodel and a smallDegradationBadgeUI indicator, and wires the badge into both the Graph and Injections tabs so users see when data may be stale instead of the surface blanking.Makes the Graph tab resilient to transient SQLite read failures by retrying loads, continuously polling via
loadGraphRepeatedly, and keeping last-knownnodes/edgeswhile degraded; also stabilizes layout on refresh/resize by preserving node positions/velocities and reseeding based on the actual drawable canvas size (KGCanvasMetrics).Updates
InjectionStoreto publishdegradationStateand to only clear degraded status after a successfullistInjectionEventsread (not justdataVersion), with added test coverage for the new degradation and recovery behavior.Reviewed by Cursor Bugbot for commit b06b74d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Show degradation badges on graph and injection panels in BrainBar with recovery resilience
DegradationBadgeoverlay (orange pill with exclamation icon) to both the graph and injection tabs in BrainBar, shown when the respective view model or store reports a degraded state.DegradationStateenum (healthy/degraded(reason:)) shared across BrainBar and BrainBarDaemon via a symlink.InjectionStorenow publishesdegradationStateand stays degraded until an event query succeeds — a cleandataVersionread alone no longer clears the badge.KGViewModelretries graph loads once on failure, preserves existing node positions/velocities across reloads, and polls repeatedly vialoadGraphRepeatedly; node seeding uses the actual drawable canvas size viaKGCanvasMetrics.Macroscope summarized b06b74d.
Summary by CodeRabbit
Release Notes
New Features
Improvements