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
+36
View File
@@ -0,0 +1,36 @@
import 'dart:async';
import 'message_bus.dart';
/// Caches the latest filter value per address, fed by `filter.state`
/// messages on the [MessageBus] (T-270, D-6 parity).
///
/// Sidebar filter boxes are addressable from the CLI: `clide ui filter`
/// with an address and text publishes a `filter.set` message the box
/// reacts to (the drive-half). The observe-half — the same verb with no
/// text — needs to read the box's *current* value back, but the bus is
/// a plain broadcast stream with no retention. Each box republishes its
/// value on the `filter.state` channel whenever it changes; this cache
/// listens once and remembers the latest per address, giving the observe
/// verb something to read.
///
/// Pure Dart — no Flutter import — so it serialises through the kernel and
/// stays usable from `dart test`.
class FilterStateCache {
FilterStateCache({required MessageBus messages}) {
_sub = messages.subscribe(channel: 'filter.state').listen((m) {
_values[m.publisher] = m.data['query'] as String? ?? '';
});
}
final Map<String, String> _values = {};
StreamSubscription<Message>? _sub;
/// The last reported filter value for [address], or null if no box at
/// that address has reported yet.
String? get(String address) => _values[address];
void dispose() {
_sub?.cancel();
}
}