replace daemon indicator with ptyc/pql tool status

The status bar now shows whether ptyc and pql are findable on PATH
(green = found, amber = missing) instead of the obsolete daemon
connection state. Per D-056 there is no daemon to connect to.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 00:43:18 +02:00
co-authored by Claude Opus 4.6
parent f2c20fe2d0
commit 3df0a3f026
3 changed files with 85 additions and 91 deletions
+9 -22
View File
@@ -1,33 +1,20 @@
import 'package:clide/builtin/ipc_status/src/status_item.dart';
import 'package:clide/extension/extension.dart';
import 'package:clide/kernel/kernel.dart';
class IpcStatusExtension extends ClideExtension {
@override
String get id => 'builtin.ipc-status';
@override
String get title => 'Daemon connection';
String get title => 'Tool status';
@override
String get version => '0.1.0';
DaemonClient? _ipc;
String get version => '0.2.0';
@override
Future<void> activate(ClideExtensionContext ctx) async {
_ipc = ctx.ipc;
}
@override
List<ContributionPoint> get contributions {
final ipc = _ipc;
if (ipc == null) return const [];
return [
StatusItemContribution(
id: 'ipc-status.indicator',
priority: 100, // right-side
listenable: ipc,
build: (_) => IpcStatusItem(ipc: ipc),
),
];
}
List<ContributionPoint> get contributions => [
StatusItemContribution(
id: 'ipc-status.indicator',
priority: 100,
build: (_) => const ToolStatusItem(),
),
];
}
+71 -40
View File
@@ -1,53 +1,84 @@
import 'dart:io';
import 'package:clide/kernel/kernel.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter/widgets.dart';
class IpcStatusItem extends StatelessWidget {
const IpcStatusItem({super.key, required this.ipc});
class ToolStatusItem extends StatefulWidget {
const ToolStatusItem({super.key});
final DaemonClient ipc;
@override
State<ToolStatusItem> createState() => _ToolStatusItemState();
}
static const _ns = 'builtin.ipc-status';
class _ToolStatusItemState extends State<ToolStatusItem> {
bool _ptycOk = false;
bool _pqlOk = false;
bool _checked = false;
@override
void initState() {
super.initState();
_check();
}
Future<void> _check() async {
final ptycResult = await _which('ptyc');
final pqlResult = await _which('pql');
if (!mounted) return;
setState(() {
_ptycOk = ptycResult;
_pqlOk = pqlResult;
_checked = true;
});
}
Future<bool> _which(String name) async {
try {
final r = await Process.run('which', [name]);
return r.exitCode == 0;
} catch (_) {
return false;
}
}
@override
Widget build(BuildContext context) {
final kernel = ClideKernel.of(context);
final tokens = ClideTheme.of(context).surface;
return ListenableBuilder(
listenable: Listenable.merge([ipc, kernel.i18n]),
builder: (ctx, _) {
final connected = ipc.isConnected;
final color = connected ? tokens.statusSuccess : tokens.statusError;
final i = kernel.i18n;
final label = connected
? i.string('connected', namespace: _ns, placeholder: 'connected')
: i.string('disconnected',
namespace: _ns, placeholder: 'disconnected');
final hint = connected
? i.string('connected.hint',
namespace: _ns,
placeholder: 'clide daemon is reachable over the local socket')
: i.string('disconnected.hint',
namespace: _ns,
placeholder:
'clide daemon is not running — start it with `clide --daemon`');
return Semantics(
label: label,
hint: hint,
liveRegion: true,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ClideIcon(const PlugIcon(), size: 12, color: color),
const SizedBox(width: 6),
ClideText(label, fontSize: clideFontCaption, color: color),
],
),
),
);
},
if (!_checked) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_Indicator(label: 'ptyc', ok: _ptycOk, tokens: tokens),
const SizedBox(width: 10),
_Indicator(label: 'pql', ok: _pqlOk, tokens: tokens),
],
),
);
}
}
class _Indicator extends StatelessWidget {
const _Indicator({required this.label, required this.ok, required this.tokens});
final String label;
final bool ok;
final SurfaceTokens tokens;
@override
Widget build(BuildContext context) {
final color = ok ? tokens.statusSuccess : tokens.statusWarning;
return Semantics(
label: '$label ${ok ? "available" : "not found"}',
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ClideIcon(PhosphorIcons.circlesFour, size: 10, color: color),
const SizedBox(width: 4),
ClideText(label, fontSize: clideFontCaption, color: color, fontFamily: clideMonoFamily),
],
),
);
}
}
+5 -29
View File
@@ -1,9 +1,7 @@
import 'dart:ui';
import 'package:clide/builtin/ipc_status/ipc_status.dart';
import 'package:clide/builtin/ipc_status/src/status_item.dart';
import 'package:clide/extension/extension.dart';
import 'package:clide/kernel/kernel.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
@@ -14,18 +12,7 @@ void main() {
late KernelFixture f;
setUp(() async {
f = await KernelFixture.create(
i18nCatalogs: {
'builtin.ipc-status': {
const Locale('en', 'US'): const {
'connected': {'translation': 'connected'},
'connected.hint': {'translation': 'daemon reachable'},
'disconnected': {'translation': 'disconnected'},
'disconnected.hint': {'translation': 'daemon down'},
},
},
},
);
f = await KernelFixture.create();
});
tearDown(() async => f.dispose());
@@ -41,21 +28,10 @@ void main() {
expect(items.first.priority, 100);
});
testWidgets('renders "disconnected" label until connected', (tester) async {
await tester.pumpWidget(
harness(f, IpcStatusItem(ipc: f.services.ipc)),
);
expect(find.text('disconnected'), findsOneWidget);
});
testWidgets('flips to "connected" when the client reports connected',
(tester) async {
await tester.pumpWidget(
harness(f, IpcStatusItem(ipc: f.services.ipc)),
);
f.ipc.setConnected(true);
testWidgets('renders without crashing', (tester) async {
await tester.pumpWidget(harness(f, const ToolStatusItem()));
await tester.pumpAndSettle();
expect(find.text('connected'), findsOneWidget);
expect(tester.takeException(), isNull);
});
});
}