make sidebar filter boxes CLI-addressable via the MessageBus (T-270)

The sidebar/dock filter fields (the shared ClideFilterBox) had no CLI
peer — a one-way, UI-only affordance that broke D-6 parity. Add the
drive+observe verb `clide ui filter <address> [<text>]`, routed entirely
through the kernel MessageBus pub/sub so a box reacts to a published
message identically whether the trigger was a UI keystroke or the CLI —
keeping extensions first-class (no dispatcher→widget wiring).

- ClideFilterBox gains an `address`; when set it listens on `filter.set`
  for its address and republishes its value on `filter.state`. Null
  address keeps the box a kernel-free UI widget.
- FilterStateCache (new kernel service) caches the latest `filter.state`
  per address — the bus has no retention, so this backs the observe-half.
- ui.filter: with text → publishes `filter.set` (drive); without →
  reads the cache (observe). Honest toolError when there is no live UI.
- Address every box: decisions/tickets/files/git/output/problems panes,
  the four search boxes, and the pql search/query/markdown inputs.

Addresses are the ids from `clide pane list` (e.g. decisions.panel).
settings.json: allow the `clide` CLI + relevant skills.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 12:44:40 +02:00
co-authored by Claude Opus 4.8
parent f813fd4d5a
commit 826395481c
22 changed files with 511 additions and 20 deletions
+12 -4
View File
@@ -105,7 +105,7 @@ class _SearchPanelViewState extends State<SearchPanelView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClideFilterBox(hint: 'Search', onChanged: c.run, onSubmitted: c.run),
ClideFilterBox(address: 'search.findInFiles', hint: 'Search', onChanged: c.run, onSubmitted: c.run),
const SizedBox(height: 6),
Row(
children: [
@@ -137,7 +137,9 @@ class _SearchPanelViewState extends State<SearchPanelView> {
const SizedBox(height: 6),
Row(
children: [
Expanded(child: ClideFilterBox(hint: 'Replace', icon: null, debounce: Duration.zero, onChanged: c.setReplacement)),
Expanded(
child: ClideFilterBox(
address: 'search.findInFiles.replace', hint: 'Replace', icon: null, debounce: Duration.zero, onChanged: c.setReplacement)),
const SizedBox(width: 6),
_ReplaceAllButton(
enabled: c.replacement.isNotEmpty && c.matchCount > 0,
@@ -147,9 +149,15 @@ class _SearchPanelViewState extends State<SearchPanelView> {
],
),
const SizedBox(height: 6),
ClideFilterBox(hint: 'files to include (e.g. *.dart)', icon: null, debounce: Duration.zero, onChanged: (v) => c.include = v),
ClideFilterBox(
address: 'search.findInFiles.include',
hint: 'files to include (e.g. *.dart)',
icon: null,
debounce: Duration.zero,
onChanged: (v) => c.include = v),
const SizedBox(height: 4),
ClideFilterBox(hint: 'files to exclude', icon: null, debounce: Duration.zero, onChanged: (v) => c.exclude = v),
ClideFilterBox(
address: 'search.findInFiles.exclude', hint: 'files to exclude', icon: null, debounce: Duration.zero, onChanged: (v) => c.exclude = v),
],
),
),