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,
|
color: tokens.globalBackground,
|
||||||
child: ClideResizeBorder(
|
child: ClideResizeBorder(
|
||||||
windowControls: widget.services.window,
|
windowControls: widget.services.window,
|
||||||
child: DialogHost(
|
child: Column(
|
||||||
router: widget.services.dialog,
|
children: [
|
||||||
child: Column(
|
_HatBar(kernel: widget.services),
|
||||||
children: [
|
Expanded(
|
||||||
_HatBar(kernel: widget.services),
|
child: DialogHost(
|
||||||
Expanded(
|
router: widget.services.dialog,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
const Positioned.fill(child: RootLayout()),
|
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/kernel/kernel.dart';
|
||||||
import 'package:clide/widgets/widgets.dart';
|
import 'package:clide/widgets/widgets.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class ToolStatusItem extends StatefulWidget {
|
class ToolStatusItem extends StatelessWidget {
|
||||||
const ToolStatusItem({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final kernel = ClideKernel.of(context);
|
||||||
final tokens = ClideTheme.of(context).surface;
|
final tokens = ClideTheme.of(context).surface;
|
||||||
if (!_checked) return const SizedBox.shrink();
|
return ListenableBuilder(
|
||||||
|
listenable: kernel.toolCheck,
|
||||||
if (_ptycOk && _pqlOk) {
|
builder: (ctx, _) {
|
||||||
return _chip('ok', tokens.statusSuccess, tokens);
|
final tc = kernel.toolCheck;
|
||||||
}
|
if (!tc.checked) return const SizedBox.shrink();
|
||||||
|
if (tc.allOk) {
|
||||||
return Row(
|
return _chip('ok', tokens.statusSuccess, tokens);
|
||||||
mainAxisSize: MainAxisSize.min,
|
}
|
||||||
children: [
|
return Row(
|
||||||
if (!_ptycOk) _chip('ptyc not found', tokens.statusWarning, tokens),
|
mainAxisSize: MainAxisSize.min,
|
||||||
if (!_ptycOk && !_pqlOk) const SizedBox(width: 10),
|
children: [
|
||||||
if (!_pqlOk) _chip('pql not found', tokens.statusWarning, tokens),
|
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) {
|
Widget _chip(String label, Color color, SurfaceTokens tokens) {
|
||||||
return Semantics(
|
return Padding(
|
||||||
label: label,
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
child: Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Row(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
|
||||||
children: [
|
const SizedBox(width: 6),
|
||||||
Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
|
ClideText(label, fontSize: clideFontCaption, color: color),
|
||||||
const SizedBox(width: 6),
|
],
|
||||||
ClideText(label, fontSize: clideFontCaption, color: color),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ class _Header extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 72,
|
width: 144,
|
||||||
height: 72,
|
height: 144,
|
||||||
child: ScalableImageWidget.fromSISource(
|
child: ScalableImageWidget.fromSISource(
|
||||||
si: ScalableImageSource.fromSvg(rootBundle, 'assets/logo/logo.svg'),
|
si: ScalableImageSource.fromSvg(rootBundle, 'assets/logo/logo.svg'),
|
||||||
),
|
),
|
||||||
@@ -272,21 +272,21 @@ class _StatusLine extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final themeName = kernel.theme.currentName;
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: kernel.ipc,
|
listenable: kernel.toolCheck,
|
||||||
builder: (ctx, _) {
|
builder: (ctx, _) {
|
||||||
final connected = kernel.ipc.isConnected;
|
final tc = kernel.toolCheck;
|
||||||
final themeName = kernel.theme.currentName;
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
ClideText('clide 2.0.0-dev', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
ClideText('clide 2.0.0-dev', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||||
ClideText(' · ', muted: true, fontSize: 12),
|
ClideText(' · ', muted: true, fontSize: 12),
|
||||||
ClideText(
|
if (!tc.checked)
|
||||||
connected ? 'daemon connected' : 'daemon disconnected',
|
ClideText('checking…', muted: true, fontSize: 12, fontFamily: clideMonoFamily)
|
||||||
fontSize: 12,
|
else if (tc.allOk)
|
||||||
fontFamily: clideMonoFamily,
|
ClideText('application ok', fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusSuccess)
|
||||||
color: connected ? tokens.statusSuccess : tokens.statusError,
|
else
|
||||||
),
|
ClideText(tc.errors.join(' · '), fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusWarning),
|
||||||
ClideText(' · ', muted: true, fontSize: 12),
|
ClideText(' · ', muted: true, fontSize: 12),
|
||||||
ClideText('theme: ', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
ClideText('theme: ', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||||
ClideText(themeName, fontSize: 12, fontFamily: clideMonoFamily, color: tokens.globalFocus),
|
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/resolver.dart';
|
||||||
export 'src/theme/semantic.dart';
|
export 'src/theme/semantic.dart';
|
||||||
export 'src/theme/tokens.dart';
|
export 'src/theme/tokens.dart';
|
||||||
|
export 'src/tool_check.dart';
|
||||||
export 'src/window_controls.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/settings.dart';
|
||||||
import 'package:clide/kernel/src/theme/controller.dart';
|
import 'package:clide/kernel/src/theme/controller.dart';
|
||||||
import 'package:clide/kernel/src/theme/loader.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/tray.dart';
|
||||||
import 'package:clide/kernel/src/window_controls.dart';
|
import 'package:clide/kernel/src/window_controls.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@@ -57,6 +58,7 @@ class KernelServices {
|
|||||||
required this.project,
|
required this.project,
|
||||||
required this.extensions,
|
required this.extensions,
|
||||||
required this.window,
|
required this.window,
|
||||||
|
required this.toolCheck,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Logger log;
|
final Logger log;
|
||||||
@@ -82,6 +84,7 @@ class KernelServices {
|
|||||||
final ProjectManager project;
|
final ProjectManager project;
|
||||||
final ExtensionManager extensions;
|
final ExtensionManager extensions;
|
||||||
final WindowControls window;
|
final WindowControls window;
|
||||||
|
final ToolCheck toolCheck;
|
||||||
|
|
||||||
static Future<KernelServices> boot({
|
static Future<KernelServices> boot({
|
||||||
required Directory appDir,
|
required Directory appDir,
|
||||||
@@ -128,6 +131,7 @@ class KernelServices {
|
|||||||
final net = NetworkStatus();
|
final net = NetworkStatus();
|
||||||
final focus = FocusTracker();
|
final focus = FocusTracker();
|
||||||
final window = WindowControls();
|
final window = WindowControls();
|
||||||
|
final toolCheck = ToolCheck();
|
||||||
final project = ProjectManager(
|
final project = ProjectManager(
|
||||||
log: log,
|
log: log,
|
||||||
events: events,
|
events: events,
|
||||||
@@ -192,6 +196,7 @@ class KernelServices {
|
|||||||
project: project,
|
project: project,
|
||||||
extensions: extensions,
|
extensions: extensions,
|
||||||
window: window,
|
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/app.dart';
|
||||||
import 'package:clide/builtin/canvas/canvas.dart';
|
import 'package:clide/builtin/canvas/canvas.dart';
|
||||||
import 'package:clide/builtin/claude/claude.dart';
|
import 'package:clide/builtin/claude/claude.dart';
|
||||||
@@ -107,6 +109,7 @@ Future<void> main() async {
|
|||||||
await services.extensions.activateAll();
|
await services.extensions.activateAll();
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
|
unawaited(services.toolCheck.check());
|
||||||
await services.project.loadRecents();
|
await services.project.loadRecents();
|
||||||
var opened = await services.project.openLast();
|
var opened = await services.project.openLast();
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
|
|||||||
Reference in New Issue
Block a user