Spotlight phases 2-4: stage, grouped results, surtitles and in-pill levels #30

Closed
opened 2026-09-27 08:08:50 +08:00 by Breadway · 8 comments
Owner

Remaining phases of BAR-26, built on the phase 1 pill (BAR-27, reviewed PASS). Spec: ~/Projects/bos-ui-demos/proposed/spotlight-stage.html.

Phase 2, stage: full-screen overlay opened from the pill (click on the time, breadbar --pill/stage command). Dimmed scrim with the light cone, search field first, recent app tiles (keyboard selectable while the query is empty), three-zone floor (controls: volume + output picker, now playing with transport, brightness, Wi-Fi/Bluetooth/DND toggles; centre: large time, status sentence, widget cards; notifications with per-item dismiss and clear all), power row with confirm on restart/off. Esc clears the query then closes; clicking the scrim closes.

Phase 3, results: grouped open windows (Hyprland clients, switch to), apps, commands with >, calculator with =; arrow keys and Enter.

Phase 4, surtitles and levels: notifications show as a one-line surtitle under the pill (respecting DND) instead of corner toasts under this theme; volume/brightness changes draw inside the pill instead of the OSD.

Also from the phase 1 review: hot-zone input region must not swallow clicks while the pill is shown; stage open state feeds auto-hide.

Remaining phases of BAR-26, built on the phase 1 pill (BAR-27, reviewed PASS). Spec: ~/Projects/bos-ui-demos/proposed/spotlight-stage.html. Phase 2, stage: full-screen overlay opened from the pill (click on the time, breadbar --pill/stage command). Dimmed scrim with the light cone, search field first, recent app tiles (keyboard selectable while the query is empty), three-zone floor (controls: volume + output picker, now playing with transport, brightness, Wi-Fi/Bluetooth/DND toggles; centre: large time, status sentence, widget cards; notifications with per-item dismiss and clear all), power row with confirm on restart/off. Esc clears the query then closes; clicking the scrim closes. Phase 3, results: grouped open windows (Hyprland clients, switch to), apps, commands with >, calculator with =; arrow keys and Enter. Phase 4, surtitles and levels: notifications show as a one-line surtitle under the pill (respecting DND) instead of corner toasts under this theme; volume/brightness changes draw inside the pill instead of the OSD. Also from the phase 1 review: hot-zone input region must not swallow clicks while the pill is shown; stage open state feeds auto-hide.
Author
Owner

Phases 2-4 complete on feature/spotlight-pill (breadbar 5c25838, bread-ecosystem dbfd36a). Also lands the phase-1 review's carried-over must-fix.

Must-fix: the tucked hot-zone's input region is now a real 2-4px strip at the top edge (empty while shown), not the full 420x14 footprint; visually confirmed via a screenshot-only debug overlay (pill-tucked-input). pill_autohide::State gained an open term so the pill stays down while the stage is open.

Phase 2/3 (stage): full-screen Overlay surface, breadbar --stage toggle|show|hide, routed to the focused monitor like OpenLauncher. Search-first with recent tiles, a three-zone floor (controls/centre/notifications), grouped results (open windows/apps/commands/calc) with keyboard nav, and a power row with a real confirm-armed state machine.

Phase 4: surtitle captions and in-pill levels, gated by new [notifications].style/[osd].style theme options (default to today's behaviour everywhere else).

Also fixed along the way: the active workspace chip and "WS current" number were still @accent (should be the mockup's flat --light, now @fg); the unread dot's @yellow is now the same @accent brass the new surtitle/stage usage needs. A real bug in the screenshot path: opening the stage/surtitle windows synchronously during init() raced dispatch()'s own connect_map registration and hung every capture; fixed by deferring the open to a post-dispatch idle callback.

breadbar: 85 tests pass (21 new), clippy clean, no new fmt diffs in touched files. bread-theme: 119 tests pass, clippy clean, golden snapshots re-blessed (non-spotlight diffs confirmed pure-inert additions before blessing).

One incident during testing: an early debug run of breadbar --screenshot stage-idle was launched directly instead of through bread-capture's isolated compositor, briefly touching the live Wayland session (a few seconds, no lasting effect observed, no crash). Caught immediately, not repeated; full details in the report going to the user now.

Known gaps: a satellite (non-primary) instance's stage shows an empty notification history rather than the real one (no cross-instance store bridge); the in-pill level display's percentage label can clip on a very narrow pill; narrow-screen floor stacking is a manual toggle, not yet driven by real monitor width. Leaving this in doing per instructions.

Phases 2-4 complete on feature/spotlight-pill (breadbar 5c25838, bread-ecosystem dbfd36a). Also lands the phase-1 review's carried-over must-fix. Must-fix: the tucked hot-zone's input region is now a real 2-4px strip at the top edge (empty while shown), not the full 420x14 footprint; visually confirmed via a screenshot-only debug overlay (pill-tucked-input). pill_autohide::State gained an `open` term so the pill stays down while the stage is open. Phase 2/3 (stage): full-screen Overlay surface, breadbar --stage toggle|show|hide, routed to the focused monitor like OpenLauncher. Search-first with recent tiles, a three-zone floor (controls/centre/notifications), grouped results (open windows/apps/commands/calc) with keyboard nav, and a power row with a real confirm-armed state machine. Phase 4: surtitle captions and in-pill levels, gated by new [notifications].style/[osd].style theme options (default to today's behaviour everywhere else). Also fixed along the way: the active workspace chip and "WS current" number were still @accent (should be the mockup's flat --light, now @fg); the unread dot's @yellow is now the same @accent brass the new surtitle/stage usage needs. A real bug in the screenshot path: opening the stage/surtitle windows synchronously during init() raced dispatch()'s own connect_map registration and hung every capture; fixed by deferring the open to a post-dispatch idle callback. breadbar: 85 tests pass (21 new), clippy clean, no new fmt diffs in touched files. bread-theme: 119 tests pass, clippy clean, golden snapshots re-blessed (non-spotlight diffs confirmed pure-inert additions before blessing). One incident during testing: an early debug run of `breadbar --screenshot stage-idle` was launched directly instead of through bread-capture's isolated compositor, briefly touching the live Wayland session (a few seconds, no lasting effect observed, no crash). Caught immediately, not repeated; full details in the report going to the user now. Known gaps: a satellite (non-primary) instance's stage shows an empty notification history rather than the real one (no cross-instance store bridge); the in-pill level display's percentage label can clip on a very narrow pill; narrow-screen floor stacking is a manual toggle, not yet driven by real monitor width. Leaving this in doing per instructions.
Author
Owner

Review of phases 2-4 (breadbar 5c25838, bread-ecosystem dbfd36a): PASS after fixes. The fixes are on the same branches: breadbar 4dff21a..e9e8157 and bread-ecosystem e8f92d5. Not pushed.

Blocking issues found and fixed

  1. Screenshot mode stole live D-Bus names (4dff21a). This bug predates phase 2. bread-capture isolates Wayland but not D-Bus, and zbus requests names with AllowReplacement|ReplaceExisting by default. So any non-notification capture took org.freedesktop.Notifications and org.kde.StatusNotifierWatcher from the live bar, and the live bar never got them back. This is the likely mechanism behind the earlier incident. The notification server now only runs for real runs or the sample notification views, and the tray watcher is skipped under --screenshot. I verified that the live owner PID was unchanged after every capture batch.
  2. Every click closed the stage (c3f468e). The column overlay set can-target=false. In GTK this makes all descendants unpickable, so it does not behave like CSS pointer-events. I proved this with a pick() test in an isolated headless sway. The column is now targetable, and the overlay's click gesture closes only when pick() lands on the scrim or an empty layout container.
  3. Enter never launched, and Esc died after any click (c3f468e). The key controller was bubble-phase on the GtkEntry, which swallows Return (the capsule launcher documents the same bug). It is now a capture-phase controller on the window. Esc works from any focus; arrows and Enter act only while the search field has focus.
  4. Power confirm could be satisfied by a double-click or a held key's autorepeat (c3f468e). A second press within 400 ms of the previous press is now ignored. There are tests for double-click and for 6 s of 30 Hz autorepeat never running the action. The expiry timer is now generation-guarded.
  5. In-pill level was clipped (98236ed plus the CSS in e8f92d5). GtkOverlay does not measure overlay children by default, so only "VOL" and a stub bar showed. The overlay is measured now, and the content fades out instead of being covered by an opaque patch.

Also fixed

  • Hot-zone strip kept live while the pill is shown (35df999). Emptying it left a dead gap above the pill, which made the pill flicker while the pointer moved along the top edge. The 3 px strip never overlaps the pill, which sits 10 px down.
  • The pill's time now opens the stage (e1b1b85). This was a BAR-30 requirement that was never wired up. It is gated to auto-hide themes.
  • The open state is cleared if the compositor unmaps the stage, for example on an output unplug (e9e8157).
  • Result rows now carry their target (window address, desktop id or command id) and are activated by it. Before, a lookup by title could focus the wrong one of two windows with the same title.
  • Narrow stacking is now automatic, based on the pinned output width being under 860 px, with the centre zone first. The column scrolls, so the power row stays reachable.
  • The act badge and sub label now swap with visibility instead of opacity (d6023f6).
  • CSS fixes (e8f92d5):
    • Stock button fills were removed from tiles, rows and power buttons.
    • The entry's blue focus ring is gone, and its text is 22 px.
    • The Hanken UI face now applies to running text. The new font_ui_css variable is named per node, because the shared * rule defeats inheritance.
    • Blue borders were removed from the sliders and the level bar.
    • The level bar is 120 px.
    • Goldens were re-blessed. The non-spotlight diffs touch only the inert stage, pill-level and surtitle rules.

Non-blocking, left open

  • Toggling the stage while it is open on another monitor opens a second stage instead of closing the first.
  • Satellite stages have an empty history, and the DND chip there does nothing (separate Rc).
  • breadbar --stage opens the stage on any theme. The doc comment says it is a no-op.
  • Missing mockup elements:
    • centre-zone widget cards (this was not listed as a deviation)
    • the footer key-hint row
    • notification timestamps
    • the hover-only dismiss button
    • the now-playing art tile
  • The "Clear all" label is still in the display face.
  • The pill sits under the scrim, while the mockup draws it above.
  • Under the surtitle style, critical notifications get only the 3.8 s caption.
  • In-pill levels show only on the primary's pill.
  • Real pollers overwrite the stage's sample media, battery and SSID data in captures.
  • 5c25838 carries a lot of rustfmt churn in unrelated code.

Verification

  • breadbar: cargo test passes (88), clippy --all-targets is clean.
  • bread-ecosystem: workspace tests pass, clippy is clean.
  • load_sorted_entries timed at 2.4 to 5 ms (129 entries, release). hyprctl clients runs async with a 2 s timeout.
  • Captured all spotlight views in isolation. I also captured bar, notification, osd-volume and control-panel under glass-workbench, liquid-motion, daylight and loaf, and pixel-diffed them against a time-aligned phase-1 baseline build. The only differences are live CPU/temperature readings; there are no regressions.
  • Clicks and keys cannot be driven in bread-capture, so the input fixes still need a short live smoke test.
Review of phases 2-4 (breadbar 5c25838, bread-ecosystem dbfd36a): PASS after fixes. The fixes are on the same branches: breadbar 4dff21a..e9e8157 and bread-ecosystem e8f92d5. Not pushed. ## Blocking issues found and fixed 1. **Screenshot mode stole live D-Bus names** (4dff21a). This bug predates phase 2. bread-capture isolates Wayland but not D-Bus, and zbus requests names with AllowReplacement|ReplaceExisting by default. So any non-notification capture took org.freedesktop.Notifications and org.kde.StatusNotifierWatcher from the live bar, and the live bar never got them back. This is the likely mechanism behind the earlier incident. The notification server now only runs for real runs or the sample notification views, and the tray watcher is skipped under --screenshot. I verified that the live owner PID was unchanged after every capture batch. 2. **Every click closed the stage** (c3f468e). The column overlay set can-target=false. In GTK this makes all descendants unpickable, so it does not behave like CSS pointer-events. I proved this with a pick() test in an isolated headless sway. The column is now targetable, and the overlay's click gesture closes only when pick() lands on the scrim or an empty layout container. 3. **Enter never launched, and Esc died after any click** (c3f468e). The key controller was bubble-phase on the GtkEntry, which swallows Return (the capsule launcher documents the same bug). It is now a capture-phase controller on the window. Esc works from any focus; arrows and Enter act only while the search field has focus. 4. **Power confirm could be satisfied by a double-click or a held key's autorepeat** (c3f468e). A second press within 400 ms of the previous press is now ignored. There are tests for double-click and for 6 s of 30 Hz autorepeat never running the action. The expiry timer is now generation-guarded. 5. **In-pill level was clipped** (98236ed plus the CSS in e8f92d5). GtkOverlay does not measure overlay children by default, so only "VOL" and a stub bar showed. The overlay is measured now, and the content fades out instead of being covered by an opaque patch. ## Also fixed - Hot-zone strip kept live while the pill is shown (35df999). Emptying it left a dead gap above the pill, which made the pill flicker while the pointer moved along the top edge. The 3 px strip never overlaps the pill, which sits 10 px down. - The pill's time now opens the stage (e1b1b85). This was a BAR-30 requirement that was never wired up. It is gated to auto-hide themes. - The open state is cleared if the compositor unmaps the stage, for example on an output unplug (e9e8157). - Result rows now carry their target (window address, desktop id or command id) and are activated by it. Before, a lookup by title could focus the wrong one of two windows with the same title. - Narrow stacking is now automatic, based on the pinned output width being under 860 px, with the centre zone first. The column scrolls, so the power row stays reachable. - The act badge and sub label now swap with visibility instead of opacity (d6023f6). - CSS fixes (e8f92d5): - Stock button fills were removed from tiles, rows and power buttons. - The entry's blue focus ring is gone, and its text is 22 px. - The Hanken UI face now applies to running text. The new `font_ui_css` variable is named per node, because the shared `*` rule defeats inheritance. - Blue borders were removed from the sliders and the level bar. - The level bar is 120 px. - Goldens were re-blessed. The non-spotlight diffs touch only the inert stage, pill-level and surtitle rules. ## Non-blocking, left open - Toggling the stage while it is open on another monitor opens a second stage instead of closing the first. - Satellite stages have an empty history, and the DND chip there does nothing (separate Rc). - `breadbar --stage` opens the stage on any theme. The doc comment says it is a no-op. - Missing mockup elements: - centre-zone widget cards (this was not listed as a deviation) - the footer key-hint row - notification timestamps - the hover-only dismiss button - the now-playing art tile - The "Clear all" label is still in the display face. - The pill sits under the scrim, while the mockup draws it above. - Under the surtitle style, critical notifications get only the 3.8 s caption. - In-pill levels show only on the primary's pill. - Real pollers overwrite the stage's sample media, battery and SSID data in captures. - 5c25838 carries a lot of rustfmt churn in unrelated code. ## Verification - breadbar: cargo test passes (88), clippy --all-targets is clean. - bread-ecosystem: workspace tests pass, clippy is clean. - load_sorted_entries timed at 2.4 to 5 ms (129 entries, release). hyprctl clients runs async with a 2 s timeout. - Captured all spotlight views in isolation. I also captured bar, notification, osd-volume and control-panel under glass-workbench, liquid-motion, daylight and loaf, and pixel-diffed them against a time-aligned phase-1 baseline build. The only differences are live CPU/temperature readings; there are no regressions. - Clicks and keys cannot be driven in bread-capture, so the input fixes still need a short live smoke test.
Author
Owner

Live crash found and hotfixed (commit 91a3f35 on fix/spotlight-satellite-drop, installed 2026-09-28). A dock replug dropped a satellite bar, but its pill hot zone, stage, surtitle and panel windows stayed alive; the orphaned hot zone then sent PillHoverEnter to the shut-down relm4 runtime, input() panicked inside a GTK handler, and the process aborted (three times: 21:54, 00:19, 01:26). Fix: App::shutdown destroys those windows, and GTK-callback/timer sends go through a non-panicking post() helper. BAR-31's parity work is asked to cherry-pick it.

Live crash found and hotfixed (commit 91a3f35 on fix/spotlight-satellite-drop, installed 2026-09-28). A dock replug dropped a satellite bar, but its pill hot zone, stage, surtitle and panel windows stayed alive; the orphaned hot zone then sent PillHoverEnter to the shut-down relm4 runtime, input() panicked inside a GTK handler, and the process aborted (three times: 21:54, 00:19, 01:26). Fix: App::shutdown destroys those windows, and GTK-callback/timer sends go through a non-panicking post() helper. BAR-31's parity work is asked to cherry-pick it.
Author
Owner

No Forgejo PR or remote work branch is available for review. The issue comments point to local-only feature/spotlight-pill worktrees (breadbar fc078d4, bread-ecosystem b249b33), both ahead of origin/main, but neither branch exists on Forgejo and there is no open PR. I cannot publish or push a branch as reviewer. Please publish the existing branch(es) and link the PRs; leaving BAR-30 in review.

No Forgejo PR or remote work branch is available for review. The issue comments point to local-only `feature/spotlight-pill` worktrees (breadbar `fc078d4`, bread-ecosystem `b249b33`), both ahead of `origin/main`, but neither branch exists on Forgejo and there is no open PR. I cannot publish or push a branch as reviewer. Please publish the existing branch(es) and link the PRs; leaving BAR-30 in review.
Author
Owner

Dependency PR: Breadway/bread-ecosystem#45 adds the bread-theme schema, styles, and bread-capture changes required by this spotlight work. Merge it before the breadbar PR, which will follow on feature/spotlight-pill. Headless Sway captures passed for pill shown/tucked, stage idle/results, critical surtitle, and in-pill levels.

Dependency PR: https://git.breadway.dev/Breadway/bread-ecosystem/pulls/45 adds the bread-theme schema, styles, and bread-capture changes required by this spotlight work. Merge it before the breadbar PR, which will follow on feature/spotlight-pill. Headless Sway captures passed for pill shown/tucked, stage idle/results, critical surtitle, and in-pill levels.
Author
Owner

Breadbar implementation PR: #37. Dependency: merge bread-ecosystem PR #45 first, which provides the theme API and CSS. Headless Sway smoke captures passed for the shown/tucked pill, stage idle/results, critical surtitle, and in-pill level.

Breadbar implementation PR: https://git.breadway.dev/Breadway/breadbar/pulls/37. Dependency: merge bread-ecosystem PR #45 first, which provides the theme API and CSS. Headless Sway smoke captures passed for the shown/tucked pill, stage idle/results, critical surtitle, and in-pill level.
Author
Owner

PR #37 check / check (push) is currently failing. The implementation depends on bread-ecosystem PR #45, which has passed CI but is not merged yet. I am moving this card to TODO as requested for REVIEW items with failing CI. Recheck the breadbar CI after #45 merges; then send it back through review.

PR #37 check / check (push) is currently failing. The implementation depends on bread-ecosystem PR #45, which has passed CI but is not merged yet. I am moving this card to TODO as requested for REVIEW items with failing CI. Recheck the breadbar CI after #45 merges; then send it back through review.
Author
Owner

PR #37 now passes fresh check / check (push) on 0fd9f05 (5m17s). The merged bread-ecosystem #45 dependency is resolved through the existing PR-check scripts/dev-deps.sh workflow, which records bread-ecosystem d9ab179 in the generated dev-deps lock; Cargo.toml and the committed release-tag Cargo.lock remain unchanged. Local clippy, build, and all 120 tests pass against that same dependency commit. Ready for independent review: #37

PR #37 now passes fresh `check / check (push)` on 0fd9f05 (5m17s). The merged bread-ecosystem #45 dependency is resolved through the existing PR-check `scripts/dev-deps.sh` workflow, which records bread-ecosystem d9ab179 in the generated dev-deps lock; Cargo.toml and the committed release-tag Cargo.lock remain unchanged. Local clippy, build, and all 120 tests pass against that same dependency commit. Ready for independent review: https://git.breadway.dev/Breadway/breadbar/pulls/37
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Breadway/breadbar#30
No description provided.