feat(log): live verbosity toggle — dock Level chip + clide log level (T-433)
The boot-time toggle (CLIDE_LOG / app.log.level) existed; this makes it adjustable at runtime, from the two surfaces that fit — D-6 parity — and NOT a Claude-composer slash command (log verbosity isn't a Claude-session concept). - Output dock Level chip: was a view-only filter; now also drives the kernel Logger.minLevel and persists app.log.level, so the choice is real (changes what's captured) and survives restart. Initialized from the logger's current level so it reflects a CLI change. A null callback keeps it a pure view filter (tests / no kernel). - `clide log level [<level>]` (lib/src/daemon/log_commands.dart): no arg reports the level + vocabulary; a valid level sets the live logger and persists; unknown → bad_arg (code 64), logger untouched. Wired in buildDispatcher with the kernel logger + settings (captured post-boot). Tested: the command (get/set/case-insensitive/bad-arg, live + persist) and the controller (initial level, callback fires, same-level no-op, filter-only when unwired). Coverage gate 95.05%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
/// merged health/toggle status-bar widget, and the `dock.toggle` command.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/builtin/output/src/dock_status_item.dart';
|
||||
import 'package:clide/builtin/output/src/output_view.dart';
|
||||
import 'package:clide/clide.dart';
|
||||
@@ -35,7 +37,20 @@ class OutputExtension extends ClideExtension {
|
||||
slot: Slots.dock,
|
||||
title: 'Output',
|
||||
priority: -100, // sort before Problems in the dock tab bar
|
||||
build: (ctx) => OutputView(ring: ClideKernel.of(ctx).logRing),
|
||||
build: (ctx) {
|
||||
// The Level chip is the live verbosity toggle (T-433): drive the kernel
|
||||
// logger + persist app.log.level so the choice survives restart and
|
||||
// matches the `clide log level` CLI (D-6 parity).
|
||||
final k = ClideKernel.of(ctx);
|
||||
return OutputView(
|
||||
ring: k.logRing,
|
||||
initialLevel: k.log.minLevel,
|
||||
onMinLevelChanged: (level) {
|
||||
k.log.minLevel = level;
|
||||
unawaited(k.settings.set('app.log.level', level.name));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
StatusItemContribution(
|
||||
id: 'output.dock-toggle',
|
||||
|
||||
@@ -10,15 +10,23 @@ import 'package:clide/kernel/src/log_ring.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class OutputController extends ChangeNotifier {
|
||||
OutputController(this.ring) {
|
||||
OutputController(this.ring, {LogLevel? initialLevel, this.onMinLevelChanged}) : minLevel = initialLevel ?? LogLevel.debug {
|
||||
_sub = ring.changes.listen((_) => notifyListeners());
|
||||
}
|
||||
|
||||
final LogRing ring;
|
||||
late final StreamSubscription<void> _sub;
|
||||
|
||||
/// Minimum level shown. Defaults to debug (trace is firehose-noise).
|
||||
LogLevel minLevel = LogLevel.debug;
|
||||
/// Invoked when the Level chip changes the level — the dock chip is the live
|
||||
/// dev/prod verbosity toggle (T-433), not just a view filter. The owner wires
|
||||
/// this to set the kernel `Logger.minLevel` and persist `app.log.level`. Null
|
||||
/// in tests / when no kernel is attached, leaving the chip a pure view filter.
|
||||
final void Function(LogLevel)? onMinLevelChanged;
|
||||
|
||||
/// Minimum level shown — initialized from the kernel logger's level so the
|
||||
/// chip reflects the real verbosity (which a `clide log level` CLI may have
|
||||
/// already set), defaulting to debug (trace is firehose-noise).
|
||||
LogLevel minLevel;
|
||||
|
||||
/// Source filter; null = all sources.
|
||||
String? source;
|
||||
@@ -29,6 +37,7 @@ class OutputController extends ChangeNotifier {
|
||||
void setMinLevel(LogLevel level) {
|
||||
if (minLevel == level) return;
|
||||
minLevel = level;
|
||||
onMinLevelChanged?.call(level);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,23 @@ import 'package:flutter/widgets.dart';
|
||||
import 'output_controller.dart';
|
||||
|
||||
class OutputView extends StatefulWidget {
|
||||
const OutputView({super.key, required this.ring});
|
||||
const OutputView({super.key, required this.ring, this.initialLevel, this.onMinLevelChanged});
|
||||
|
||||
/// The retained log buffer to render. The view owns a controller over it
|
||||
/// but never the ring itself (the app owns that).
|
||||
final LogRing ring;
|
||||
|
||||
/// Initial verbosity (the kernel logger's current level) + the sink that
|
||||
/// applies a chip change to the kernel + persists it (T-433).
|
||||
final LogLevel? initialLevel;
|
||||
final void Function(LogLevel)? onMinLevelChanged;
|
||||
|
||||
@override
|
||||
State<OutputView> createState() => _OutputViewState();
|
||||
}
|
||||
|
||||
class _OutputViewState extends State<OutputView> {
|
||||
late final OutputController _c = OutputController(widget.ring);
|
||||
late final OutputController _c = OutputController(widget.ring, initialLevel: widget.initialLevel, onMinLevelChanged: widget.onMinLevelChanged);
|
||||
final ScrollController _scroll = ScrollController();
|
||||
|
||||
/// Follow the tail until the user scrolls up; resumes when they return to
|
||||
|
||||
Reference in New Issue
Block a user