surface kernel notifications as toasts (T-382)
The Notifications service had zero widget consumers — anything pushed through ctx.notify (cli_install's dogfood warnings, install results) accumulated in a list nothing rendered. The service now takes the kernel MessageBus and publishes each notification to the toast channel with mapped severity, so the existing ToastOverlay renders them; the active list stays for API compatibility. Chose routing over building a notifications tray nobody asked for. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4009,3 +4009,16 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCC6AR37VTF4SY8DR99JHC', 'status', 'in_progress', 'done', NULL, '2026-06-11 23:16:28', '2026-06-11 23:16:28', '2026-06-11 23:16:28', NULL, '2fe291251d1a4789c8d2b75a2c32396f', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCJEYHC91PMVNVWVHBR2RG', 'status', 'backlog', 'in_progress', NULL, '2026-06-11 23:16:53', '2026-06-11 23:16:53', '2026-06-11 23:16:53', NULL, 'e6c0cb3a2fce6e257e054f5b3e209814', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCJEYHC91PMVNVWVHBR2RG', 'status', 'in_progress', 'done', NULL, '2026-06-11 23:18:22', '2026-06-11 23:18:22', '2026-06-11 23:18:22', NULL, '22809d0fac7e1aa116e682c18dae7455', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'status', 'backlog', 'in_progress', NULL, '2026-06-11 23:18:47', '2026-06-11 23:18:47', '2026-06-11 23:18:47', NULL, '2ebff93f1c7328538d0075a2a5c34cca', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'status', 'in_progress', 'done', NULL, '2026-06-11 23:20:52', '2026-06-11 23:20:52', '2026-06-11 23:20:52', NULL, '6a025da791052ffa97803f6ce8afa2f1', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'description', 'lib/kernel/src/notify.dart has zero widget consumers — anything pushed through the Notifications service (e.g. cli_install''s dogfood warnings) accumulates in a list no surface renders. ToastService exists right next to it and does render.
|
||||
|
||||
Fix options (pick one, note it on this ticket): (a) route notify-level messages through ToastService with severity styling; (b) add a notifications tray/indicator surface; (c) delete the service and migrate callers to toasts. Option (a) or (c) is likely right for current scale — avoid building a tray nobody asked for.
|
||||
|
||||
Acceptance: a notification posted by cli_install is visibly surfaced in the UI (test via whichever surface is chosen); no silent sink remains.', 'lib/kernel/src/notify.dart has zero widget consumers — anything pushed through the Notifications service (e.g. cli_install''s dogfood warnings) accumulates in a list no surface renders. ToastService exists right next to it and does render.
|
||||
|
||||
Fix options (pick one, note it on this ticket): (a) route notify-level messages through ToastService with severity styling; (b) add a notifications tray/indicator surface; (c) delete the service and migrate callers to toasts. Option (a) or (c) is likely right for current scale — avoid building a tray nobody asked for.
|
||||
|
||||
Acceptance: a notification posted by cli_install is visibly surfaced in the UI (test via whichever surface is chosen); no silent sink remains.
|
||||
|
||||
Resolved with option (a): Notifications now takes the kernel MessageBus and publishes every notification to the toast channel (severity-mapped, ''title — message''); the in-memory active list stays for API compatibility. No tray built.', NULL, '2026-06-11 23:20:59', '2026-06-11 23:20:59', '2026-06-11 23:20:59', NULL, 'a34021fbc6ce201bc9e8f5954129b7cd', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -4149,3 +4149,20 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
|
||||
Fix: emit a newline span for hard breaks; render images as at least an alt-text placeholder chip (full image rendering can be a follow-up — note the existing feedback that live-pane images go through clide image show).
|
||||
|
||||
Acceptance: golden/widget test for hard-break line splitting; image node renders alt text; no regression in existing markdown goldens.', 'done', 'medium', NULL, NULL, NULL, '2026-06-11 21:59:13', '2026-06-11 23:18:22', NULL, '5ec0ffc2676c2f31476249301ae2869b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'bug', '06FBHBGHNEQTAEPGNJKN42C1E8', 'Notifications service renders nowhere — warnings vanish into an unrendered list', 'lib/kernel/src/notify.dart has zero widget consumers — anything pushed through the Notifications service (e.g. cli_install''s dogfood warnings) accumulates in a list no surface renders. ToastService exists right next to it and does render.
|
||||
|
||||
Fix options (pick one, note it on this ticket): (a) route notify-level messages through ToastService with severity styling; (b) add a notifications tray/indicator surface; (c) delete the service and migrate callers to toasts. Option (a) or (c) is likely right for current scale — avoid building a tray nobody asked for.
|
||||
|
||||
Acceptance: a notification posted by cli_install is visibly surfaced in the UI (test via whichever surface is chosen); no silent sink remains.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-11 21:59:55', '2026-06-11 23:18:47', NULL, '20850287af7c135adc76d53f200f5209', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'bug', '06FBHBGHNEQTAEPGNJKN42C1E8', 'Notifications service renders nowhere — warnings vanish into an unrendered list', 'lib/kernel/src/notify.dart has zero widget consumers — anything pushed through the Notifications service (e.g. cli_install''s dogfood warnings) accumulates in a list no surface renders. ToastService exists right next to it and does render.
|
||||
|
||||
Fix options (pick one, note it on this ticket): (a) route notify-level messages through ToastService with severity styling; (b) add a notifications tray/indicator surface; (c) delete the service and migrate callers to toasts. Option (a) or (c) is likely right for current scale — avoid building a tray nobody asked for.
|
||||
|
||||
Acceptance: a notification posted by cli_install is visibly surfaced in the UI (test via whichever surface is chosen); no silent sink remains.', 'done', 'medium', NULL, NULL, NULL, '2026-06-11 21:59:55', '2026-06-11 23:20:52', NULL, 'fb8aa4e3ae8a67be11cb051975e3804c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'bug', '06FBHBGHNEQTAEPGNJKN42C1E8', 'Notifications service renders nowhere — warnings vanish into an unrendered list', 'lib/kernel/src/notify.dart has zero widget consumers — anything pushed through the Notifications service (e.g. cli_install''s dogfood warnings) accumulates in a list no surface renders. ToastService exists right next to it and does render.
|
||||
|
||||
Fix options (pick one, note it on this ticket): (a) route notify-level messages through ToastService with severity styling; (b) add a notifications tray/indicator surface; (c) delete the service and migrate callers to toasts. Option (a) or (c) is likely right for current scale — avoid building a tray nobody asked for.
|
||||
|
||||
Acceptance: a notification posted by cli_install is visibly surfaced in the UI (test via whichever surface is chosen); no silent sink remains.
|
||||
|
||||
Resolved with option (a): Notifications now takes the kernel MessageBus and publishes every notification to the toast channel (severity-mapped, ''title — message''); the in-memory active list stays for API compatibility. No tray built.', 'done', 'medium', NULL, NULL, NULL, '2026-06-11 21:59:55', '2026-06-11 23:20:59', NULL, 'e98115731bb2c593bd17ae945a79d260', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -120,6 +120,11 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
together and images vanished; breaks now emit a newline and images render
|
||||
an italic `[image: alt]` placeholder. (T-379)
|
||||
|
||||
- **Extension notifications actually appear on screen.** Messages pushed
|
||||
through the kernel Notifications service (e.g. the CLI-install dogfood
|
||||
warnings) accumulated in a list no surface rendered; they now raise
|
||||
toasts with matching severity. (T-382)
|
||||
|
||||
- **Accepting ExitPlanMode now leaves plan mode in the conversation panel.**
|
||||
Approving Claude's plan (the ExitPlanMode tool) transitioned the underlying
|
||||
session out of plan mode, but clide's tracked permission mode didn't follow,
|
||||
|
||||
@@ -166,7 +166,7 @@ class KernelServices {
|
||||
final readerNav = ReaderNavRegistry(messages);
|
||||
final clipboard = ClideClipboard();
|
||||
final files = FileServices(events);
|
||||
final notify = Notifications();
|
||||
final notify = Notifications(messages: messages);
|
||||
final dialog = DialogRouter();
|
||||
final tray = TrayRegistry();
|
||||
final secrets = SecretsVault();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/kernel/src/events/message_bus.dart';
|
||||
import 'package:clide/kernel/src/toast.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum NotificationLevel { info, warning, error, success }
|
||||
@@ -18,6 +20,14 @@ class ClideNotification {
|
||||
}
|
||||
|
||||
class Notifications extends ChangeNotifier {
|
||||
Notifications({MessageBus? messages}) : _messages = messages;
|
||||
|
||||
/// When wired (the facade passes the kernel bus), every notification is
|
||||
/// also published to the toast channel so it actually renders — the
|
||||
/// in-memory list had zero widget consumers and messages vanished
|
||||
/// silently (T-382).
|
||||
final MessageBus? _messages;
|
||||
|
||||
final List<ClideNotification> _active = [];
|
||||
final Map<String, Timer> _timers = {};
|
||||
int _seq = 0;
|
||||
@@ -41,6 +51,21 @@ class Notifications extends ChangeNotifier {
|
||||
final n = ClideNotification(id: id, level: level, message: message, title: title, duration: duration ?? const Duration(seconds: 4));
|
||||
_active.add(n);
|
||||
_timers[id] = Timer(n.duration, () => dismiss(id));
|
||||
final bus = _messages;
|
||||
if (bus != null) {
|
||||
publishToast(
|
||||
bus,
|
||||
'kernel.notify',
|
||||
title == null ? message : '$title — $message',
|
||||
severity: switch (level) {
|
||||
NotificationLevel.info => ToastSeverity.info,
|
||||
NotificationLevel.warning => ToastSeverity.warning,
|
||||
NotificationLevel.error => ToastSeverity.error,
|
||||
NotificationLevel.success => ToastSeverity.success,
|
||||
},
|
||||
duration: duration,
|
||||
);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -272,5 +272,36 @@ void main() {
|
||||
expect(calls, 1);
|
||||
n.dispose();
|
||||
});
|
||||
|
||||
// T-382: the in-memory list had no widget consumer — notifications
|
||||
// vanished silently. Wired to the bus, each one now raises a toast.
|
||||
test('a bus-wired notification surfaces as a rendered toast (T-382)', () async {
|
||||
final messages = MessageBus();
|
||||
final toasts = ToastService(messages: messages);
|
||||
addTearDown(toasts.dispose);
|
||||
final n = Notifications(messages: messages);
|
||||
addTearDown(n.dispose);
|
||||
|
||||
n.warn('clide CLI not on PATH', title: 'dogfood');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(toasts.entries, hasLength(1));
|
||||
expect(toasts.entries.single.message, 'dogfood — clide CLI not on PATH');
|
||||
expect(toasts.entries.single.severity, ToastSeverity.warning);
|
||||
});
|
||||
|
||||
test('error and success levels map to their toast severities (T-382)', () async {
|
||||
final messages = MessageBus();
|
||||
final toasts = ToastService(messages: messages);
|
||||
addTearDown(toasts.dispose);
|
||||
final n = Notifications(messages: messages);
|
||||
addTearDown(n.dispose);
|
||||
|
||||
n.error('boom');
|
||||
n.success('done');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(toasts.entries.map((e) => e.severity), [ToastSeverity.error, ToastSeverity.success]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user