An app may only publish inside its own bread.<app_id>.* namespace, so
breadbox (app id 'box') cannot emit bread.command.box.open — bread-client
refuses it and the redirect did nothing but log a warning. The capsule now
also subscribes to bread.box.open_requested, which is what breadbox emits.
bread.command.box.open is kept as well: that is the addressed-to-an-app
command form an external trigger (the bread CLI, a keybind, another app)
would legitimately send, so the capsule opens whether it was asked directly
or told by breadbox.
Completes the spotlight capsule (theme 04), THEME_SYSTEM_PLAN.md §7:
- Keyboard open (item A): a new launcher_command module subscribes to
bread.command.box.open — only when the active theme's launcher is
Embedded — and focuses launcher_entry on receipt, which the existing
EventControllerFocus::connect_enter already turns into an open_fn()
call, same path a mouse click into the entry takes. breadbox's own
keybind-triggered launch now redirects to this same event under an
embedded theme instead of stacking its overlay window on top (see the
breadbox commit).
- Click-away scrim (item B): open_fn/close_fn now show/hide panel.rs's
dismiss surface via the new show_capsule_dismiss/hide_dismiss, and
panels.set_on_dismiss(close_fn) wires a click-away into the capsule's
own close path. The scrim's clickable region starts at a fixed offset
(capsule row height + the drawer's own maximum content height,
DRAWER_MAX_HEIGHT_PX) rather than the drawer's live height, so it can
never geometrically overlap a rendered result row regardless of
breadbar-dismiss's overlay layer always rendering above the bar's own.
- Query modes (item C): connect_changed now runs bread_launcher::parse_query
against the entry text and, when the parsed prefix is listed in
[launcher].modes, swaps launcher_results.scroller for a new mode_list
populated by populate_mode_list (calc result / filtered commands / an
"open this URL" prompt). key_ctrl routes Up/Down/Return at whichever
list is active; a `>`/`.` row's action runs through the new ModeAction
enum (RunShell for a command's own fixed exec string, OpenUrl straight
to xdg-open) so arbitrary typed text is never passed through a shell.
- Sections (item D): ResultsList::new's new `sections` bool is threaded
from [launcher].sections.
- Search-state geometry (item E): root's own width now spring-animates
between [launcher].width and .search_width (animate_capsule_width, same
set_size_request-driven technique animate_drawer_height already uses),
and a `.searching` class on root switches border-radius to
.search_radius via a new CSS rule/transition in theme.rs.
Also fixes a real bug this surfaced: drawer_target_height was measuring
drawer_box itself, whose size_request is continuously overwritten by
animate_drawer_height's own tick callback — GTK clamps a widget's
measure() up to at least its own explicit size request, so switching from
the (tall) app list to a one-row calc result measured the PREVIOUS
frame's forced height instead of the new content's actual size. It now
sums each visible child's own natural height directly, which also fixes
the same latent issue for narrowing app-search results.
screenshot.rs gains two new views (capsule-sections, capsule-calc) to
exercise D and C's calc path.