Commit 4bdbdc7
fix(brainbar): scope results overlay to Dashboard + dismiss on tab-switch + wire click-outside
Closes 3 new P0s from the post-03d66b8a live test:
1. **Click-outside didn't dismiss** — root cause: commit 03d66b8 added
the `isSearchOverlayDismissed` @published flag to `QuickCaptureViewModel`
but `BrainBarCommandBarResultsOverlayGate.shouldShow` never actually
read it. The flag was being set but shouldShow returned true anyway,
so the overlay stayed visible. The earlier
`.onTapGesture { viewModel.dismissSearchOverlay() }` on the tab-content
Group was firing correctly — the dismissed flag just wasn't gating
anything. Now `shouldShow` checks
`!isSearchOverlayDismissed` and the overlay hides.
2. **Results overlay persisted across tab switches** — the overlay was
attached to the Group that swaps dashboard/injections/graph content,
so it floated above whichever tab was active. Fix: the overlay now
takes an `isOnActiveTab: Bool` prop; `BrainBarWindowRootView` passes
`selectedTab == .dashboard`. `shouldShow` short-circuits false when
off-tab. Belt-and-suspenders: `.onChange(of: selectedTab)` on the
root view explicitly calls `dismissSearchOverlay()` when the user
leaves Dashboard, so any future re-architecture of the overlay host
still clears stale state.
3. **Tap-catcher is now built into the overlay itself** as a full-area
`Color.clear` + `.onTapGesture` UNDER the results card (ZStack),
instead of an `.onTapGesture` attached to the underlying Group.
Previously, clicks on dashboard metric cards could be absorbed by
their `.background(RoundedRectangle(...))` shapes before reaching
the Group's handler; the new design puts a guaranteed-transparent
tap layer behind the card so clicks anywhere outside the card
reach `dismissSearchOverlay()`. The tap-catcher only exists while
the overlay is visible, so normal tab-content interactions are
unaffected at rest.
Also migrates the animation definitions from the parent to the gate —
the ZStack's `.animation(value: shouldShow)` is the single trigger,
replacing the parent-level `.animation(value: ...)` chain that had to
guess which @published property was changing.
Suspected root cause for user's "input stuck after tab-switch" report:
the overlay was holding a visible `Color.clear` tap-shape over the
entire tab region even after navigating to Graph/Injections, which
could confuse AppKit's first-responder dispatch when the user clicked
back into the NSTextField. With the overlay now properly scoped to
Dashboard + dismissing on switch, the tap-catcher no longer lingers,
and the NSTextField owns its own hit region in the header cleanly.
Tests
- `swift test --filter BrainBar` → 270 tests, 0 failures (unchanged).
The existing `testHandleInputChangeClearsOverlayDismissedFlag` and
`testDismissSearchOverlayHidesResultsWithoutClearingInput` now
actually exercise a gate that honours the flag.
Visual verification
- `/tmp/brainbar-v5-search-dashboard.png` — typing "brainlayer" on
Dashboard shows the results dropdown.
- `/tmp/brainbar-v5-click-outside.png` — click below the results card
dismisses the overlay; "brainlayer" remains in the input so the
next keystroke re-shows.
Deferred to commit 5 (as planned): async-off-main submitSearch, teal
hysteresis on hero accent, sparkline dot pixel-snap centering. Deferred
to commit 6: expand-in-context detail drawer (Atomic pattern) +
enrichment metrics redesign.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 03d66b8 commit 4bdbdc7
2 files changed
Lines changed: 37 additions & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
295 | 295 | | |
296 | 296 | | |
297 | 297 | | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
298 | 302 | | |
299 | 303 | | |
300 | 304 | | |
301 | | - | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
302 | 309 | | |
303 | 310 | | |
304 | 311 | | |
305 | 312 | | |
306 | 313 | | |
307 | 314 | | |
| 315 | + | |
308 | 316 | | |
309 | 317 | | |
310 | | - | |
311 | | - | |
312 | | - | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
313 | 335 | | |
| 336 | + | |
314 | 337 | | |
315 | 338 | | |
316 | 339 | | |
317 | | - | |
| 340 | + | |
| 341 | + | |
318 | 342 | | |
319 | 343 | | |
320 | 344 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
54 | 49 | | |
55 | 50 | | |
56 | 51 | | |
| |||
0 commit comments