shared ToolCheck service, welcome status, logo doubled
ToolCheck kernel service checks ptyc/pql/tmux/git availability once at boot, shared by status bar and welcome screen. Welcome status line shows "application ok" in green or lists missing tools in amber. Logo doubled to 144px. DialogHost moved inside Expanded to fix blank workspace. Welcome maxWidth 850px, path overflow fixed with Flexible. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-8
@@ -87,12 +87,12 @@ class _RootShellState extends State<_RootShell> {
|
||||
color: tokens.globalBackground,
|
||||
child: ClideResizeBorder(
|
||||
windowControls: widget.services.window,
|
||||
child: DialogHost(
|
||||
router: widget.services.dialog,
|
||||
child: Column(
|
||||
children: [
|
||||
_HatBar(kernel: widget.services),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
_HatBar(kernel: widget.services),
|
||||
Expanded(
|
||||
child: DialogHost(
|
||||
router: widget.services.dialog,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: RootLayout()),
|
||||
@@ -101,8 +101,8 @@ class _RootShellState extends State<_RootShell> {
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,82 +1,45 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ToolStatusItem extends StatefulWidget {
|
||||
class ToolStatusItem extends StatelessWidget {
|
||||
const ToolStatusItem({super.key});
|
||||
|
||||
@override
|
||||
State<ToolStatusItem> createState() => _ToolStatusItemState();
|
||||
}
|
||||
|
||||
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 cwd = Directory.current.path;
|
||||
final ptycResult = _exists('$cwd/native/linux-x64/ptyc') || _exists('$cwd/ptyc/bin/ptyc') || await _which('ptyc');
|
||||
final pqlResult = await _which('pql');
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_ptycOk = ptycResult;
|
||||
_pqlOk = pqlResult;
|
||||
_checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
bool _exists(String path) => File(path).existsSync();
|
||||
|
||||
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;
|
||||
if (!_checked) return const SizedBox.shrink();
|
||||
|
||||
if (_ptycOk && _pqlOk) {
|
||||
return _chip('ok', tokens.statusSuccess, tokens);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!_ptycOk) _chip('ptyc not found', tokens.statusWarning, tokens),
|
||||
if (!_ptycOk && !_pqlOk) const SizedBox(width: 10),
|
||||
if (!_pqlOk) _chip('pql not found', tokens.statusWarning, tokens),
|
||||
],
|
||||
return ListenableBuilder(
|
||||
listenable: kernel.toolCheck,
|
||||
builder: (ctx, _) {
|
||||
final tc = kernel.toolCheck;
|
||||
if (!tc.checked) return const SizedBox.shrink();
|
||||
if (tc.allOk) {
|
||||
return _chip('ok', tokens.statusSuccess, tokens);
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (var i = 0; i < tc.errors.length; i++) ...[
|
||||
if (i > 0) const SizedBox(width: 10),
|
||||
_chip(tc.errors[i], tokens.statusWarning, tokens),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _chip(String label, Color color, SurfaceTokens tokens) {
|
||||
return Semantics(
|
||||
label: label,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(label, fontSize: clideFontCaption, color: color),
|
||||
],
|
||||
),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(label, fontSize: clideFontCaption, color: color),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ class _Header extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 72,
|
||||
height: 72,
|
||||
width: 144,
|
||||
height: 144,
|
||||
child: ScalableImageWidget.fromSISource(
|
||||
si: ScalableImageSource.fromSvg(rootBundle, 'assets/logo/logo.svg'),
|
||||
),
|
||||
@@ -272,21 +272,21 @@ class _StatusLine extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeName = kernel.theme.currentName;
|
||||
return ListenableBuilder(
|
||||
listenable: kernel.ipc,
|
||||
listenable: kernel.toolCheck,
|
||||
builder: (ctx, _) {
|
||||
final connected = kernel.ipc.isConnected;
|
||||
final themeName = kernel.theme.currentName;
|
||||
final tc = kernel.toolCheck;
|
||||
return Row(
|
||||
children: [
|
||||
ClideText('clide 2.0.0-dev', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||
ClideText(' · ', muted: true, fontSize: 12),
|
||||
ClideText(
|
||||
connected ? 'daemon connected' : 'daemon disconnected',
|
||||
fontSize: 12,
|
||||
fontFamily: clideMonoFamily,
|
||||
color: connected ? tokens.statusSuccess : tokens.statusError,
|
||||
),
|
||||
if (!tc.checked)
|
||||
ClideText('checking…', muted: true, fontSize: 12, fontFamily: clideMonoFamily)
|
||||
else if (tc.allOk)
|
||||
ClideText('application ok', fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusSuccess)
|
||||
else
|
||||
ClideText(tc.errors.join(' · '), fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusWarning),
|
||||
ClideText(' · ', muted: true, fontSize: 12),
|
||||
ClideText('theme: ', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||
ClideText(themeName, fontSize: 12, fontFamily: clideMonoFamily, color: tokens.globalFocus),
|
||||
|
||||
@@ -46,4 +46,5 @@ export 'src/theme/palette.dart';
|
||||
export 'src/theme/resolver.dart';
|
||||
export 'src/theme/semantic.dart';
|
||||
export 'src/theme/tokens.dart';
|
||||
export 'src/tool_check.dart';
|
||||
export 'src/window_controls.dart';
|
||||
|
||||
@@ -25,6 +25,7 @@ import 'package:clide/kernel/src/secrets.dart';
|
||||
import 'package:clide/kernel/src/settings.dart';
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/kernel/src/theme/loader.dart';
|
||||
import 'package:clide/kernel/src/tool_check.dart';
|
||||
import 'package:clide/kernel/src/tray.dart';
|
||||
import 'package:clide/kernel/src/window_controls.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -57,6 +58,7 @@ class KernelServices {
|
||||
required this.project,
|
||||
required this.extensions,
|
||||
required this.window,
|
||||
required this.toolCheck,
|
||||
});
|
||||
|
||||
final Logger log;
|
||||
@@ -82,6 +84,7 @@ class KernelServices {
|
||||
final ProjectManager project;
|
||||
final ExtensionManager extensions;
|
||||
final WindowControls window;
|
||||
final ToolCheck toolCheck;
|
||||
|
||||
static Future<KernelServices> boot({
|
||||
required Directory appDir,
|
||||
@@ -128,6 +131,7 @@ class KernelServices {
|
||||
final net = NetworkStatus();
|
||||
final focus = FocusTracker();
|
||||
final window = WindowControls();
|
||||
final toolCheck = ToolCheck();
|
||||
final project = ProjectManager(
|
||||
log: log,
|
||||
events: events,
|
||||
@@ -192,6 +196,7 @@ class KernelServices {
|
||||
project: project,
|
||||
extensions: extensions,
|
||||
window: window,
|
||||
toolCheck: toolCheck,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ToolCheck extends ChangeNotifier {
|
||||
bool ptycOk = false;
|
||||
bool pqlOk = false;
|
||||
bool tmuxOk = false;
|
||||
bool gitOk = false;
|
||||
bool checked = false;
|
||||
|
||||
bool get allOk => ptycOk && pqlOk && tmuxOk && gitOk;
|
||||
|
||||
List<String> get errors => [
|
||||
if (!ptycOk) 'ptyc not found',
|
||||
if (!pqlOk) 'pql not found',
|
||||
if (!tmuxOk) 'tmux not found',
|
||||
if (!gitOk) 'git not found',
|
||||
];
|
||||
|
||||
Future<void> check() async {
|
||||
final cwd = Directory.current.path;
|
||||
ptycOk = File('$cwd/native/linux-x64/ptyc').existsSync() ||
|
||||
File('$cwd/ptyc/bin/ptyc').existsSync() ||
|
||||
await _which('ptyc');
|
||||
pqlOk = await _which('pql');
|
||||
tmuxOk = await _which('tmux');
|
||||
gitOk = await _which('git');
|
||||
checked = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
static Future<bool> _which(String name) async {
|
||||
try {
|
||||
final r = await Process.run('which', [name]);
|
||||
return r.exitCode == 0;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/app.dart';
|
||||
import 'package:clide/builtin/canvas/canvas.dart';
|
||||
import 'package:clide/builtin/claude/claude.dart';
|
||||
@@ -107,6 +109,7 @@ Future<void> main() async {
|
||||
await services.extensions.activateAll();
|
||||
|
||||
if (!kIsWeb) {
|
||||
unawaited(services.toolCheck.check());
|
||||
await services.project.loadRecents();
|
||||
var opened = await services.project.openLast();
|
||||
if (!opened) {
|
||||
|
||||
Reference in New Issue
Block a user