From 864a1062a64f4df927019ef6a39db79a8e272c28 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 3 May 2026 21:52:26 +0200 Subject: [PATCH] remove dead code and fix analyzer warnings Unused _resolvePtycPath (bin/clide.dart), _TrafficDot (app.dart and clide_column_hat.dart), stale @override on _spawned field (claude_pane.dart), duplicate import and unused local in test_app.dart, unnecessary non-null assertions in project.dart. Co-Authored-By: Claude --- .pql/pql-plan.json | 2 +- bin/clide.dart | 38 ++---- lib/app.dart | 73 +++++------- lib/builtin/claude/src/claude_pane.dart | 19 ++- lib/kernel/src/project.dart | 4 +- lib/test_app.dart | 152 ++++++++++++------------ lib/widgets/src/clide_column_hat.dart | 27 +---- 7 files changed, 130 insertions(+), 185 deletions(-) diff --git a/.pql/pql-plan.json b/.pql/pql-plan.json index b2205d20..8648d23d 100644 --- a/.pql/pql-plan.json +++ b/.pql/pql-plan.json @@ -1,5 +1,5 @@ { - "exported_at": "2026-05-03T19:52:13Z", + "exported_at": "2026-05-03T19:52:26Z", "decisions": [ { "id": "D-1", diff --git a/bin/clide.dart b/bin/clide.dart index 0a575561..42c8d428 100644 --- a/bin/clide.dart +++ b/bin/clide.dart @@ -242,10 +242,7 @@ Future _runCliArgs( // Responses come back on the same socket. Events may be interleaved // (the daemon broadcasts), so we skip events until we see the // response whose id matches our request. - final lines = socket - .cast>() - .transform(utf8.decoder) - .transform(const LineSplitter()); + final lines = socket.cast>().transform(utf8.decoder).transform(const LineSplitter()); try { await for (final line in lines) { @@ -346,11 +343,14 @@ Future _runGit(List args) async { final rest = args.sublist(1); final setUpstream = rest.contains('-u'); final positional = rest.where((a) => a != '-u').toList(); - await _runCliArgs('git.push', { - if (setUpstream) 'setUpstream': true, - if (positional.isNotEmpty) 'remote': positional[0], - if (positional.length > 1) 'branch': positional[1], - }, exitOnOk: true); + await _runCliArgs( + 'git.push', + { + if (setUpstream) 'setUpstream': true, + if (positional.isNotEmpty) 'remote': positional[0], + if (positional.length > 1) 'branch': positional[1], + }, + exitOnOk: true); default: _die('unknown git verb: ${args.first}'); } @@ -390,13 +390,11 @@ Future _runTail(List args) async { void quit() { unawaited(socket.close()); } + ProcessSignal.sigint.watch().listen((_) => quit()); ProcessSignal.sigterm.watch().listen((_) => quit()); - final lines = socket - .cast>() - .transform(utf8.decoder) - .transform(const LineSplitter()); + final lines = socket.cast>().transform(utf8.decoder).transform(const LineSplitter()); try { await for (final line in lines) { @@ -428,20 +426,6 @@ void _emitError({ stderr.writeln(jsonEncode(err.toJson())); } -String _resolvePtycPath() { - final self = Platform.resolvedExecutable; - final binDir = File(self).parent.path; - final candidates = [ - '$binDir/../ptyc/bin/ptyc', - '$binDir/ptyc', - 'ptyc', - ]; - for (final c in candidates) { - if (File(c).existsSync()) return c; - } - return 'ptyc'; -} - Never _die(String msg) { _emitError( code: IpcExitCode.userError, diff --git a/lib/app.dart b/lib/app.dart index bae9dae5..5d06b663 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -92,30 +92,30 @@ class _RootShellState extends State<_RootShell> { autofocus: true, onKeyEvent: _onKey, child: ColoredBox( - color: tokens.globalBackground, - child: ClideResizeBorder( - windowControls: widget.services.window, - child: Column( - children: [ - _HatBar(kernel: widget.services), - Expanded( - child: DialogHost( - router: widget.services.dialog, - child: Stack( - children: [ - const Positioned.fill(child: RootLayout()), - const ClidePalette(), - const Positioned.fill(child: _WelcomeOverlay()), - ], + color: tokens.globalBackground, + child: ClideResizeBorder( + windowControls: widget.services.window, + child: Column( + children: [ + _HatBar(kernel: widget.services), + Expanded( + child: DialogHost( + router: widget.services.dialog, + child: Stack( + children: [ + const Positioned.fill(child: RootLayout()), + const ClidePalette(), + const Positioned.fill(child: _WelcomeOverlay()), + ], + ), ), ), - ), - ], + ], + ), ), ), ), ), - ), ); } @@ -308,23 +308,6 @@ class _RightHatContent extends StatelessWidget { } } -class _TrafficDot extends StatelessWidget { - const _TrafficDot({required this.color, required this.onTap}); - final Color color; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return ClideTappable( - onTap: onTap, - builder: (context, hovered, _) => Container( - width: 12, height: 12, - decoration: BoxDecoration(color: hovered ? color : color.withAlpha(0xCC), shape: BoxShape.circle), - ), - ); - } -} - class _WinBtn extends StatelessWidget { const _WinBtn({required this.icon, required this.onTap, required this.tokens, this.isClose = false}); final ClideIconPainter icon; @@ -338,7 +321,8 @@ class _WinBtn extends StatelessWidget { return ClideTappable( onTap: onTap, builder: (context, hovered, _) => Container( - width: 36, height: hatHeight, + width: 36, + height: hatHeight, color: hovered ? hoverBg : null, alignment: Alignment.center, child: ClideIcon(icon, size: 14, color: hovered && isClose ? const Color(0xFFFFFFFF) : tokens.chromeForeground), @@ -435,9 +419,9 @@ class _ProjectSwitcherDropdownState extends State<_ProjectSwitcherDropdown> { widget.kernel.panels.activateTab(Slots.workspace, 'claude.primary'); } else { widget.kernel.dialog.show((ctx, dismiss) => _NotARepoDialog( - path: picked, - onDismiss: () => dismiss(), - )); + path: picked, + onDismiss: () => dismiss(), + )); } } return; @@ -515,8 +499,7 @@ class _ProjectSwitcherDropdownState extends State<_ProjectSwitcherDropdown> { children: [ _ActionRow(label: 'Open Local Project', shortcut: Platform.isMacOS ? '⌘O' : 'Ctrl+O', tokens: tokens, onTap: _openFolder), _ActionRow(label: 'New Window', shortcut: Platform.isMacOS ? '⌘⇧N' : 'Ctrl+Shift+N', tokens: tokens, onTap: _newWindow), - if (widget.kernel.project.isOpen) - _ActionRow(label: 'Close Project', shortcut: '', tokens: tokens, onTap: _closeWorkspace), + if (widget.kernel.project.isOpen) _ActionRow(label: 'Close Project', shortcut: '', tokens: tokens, onTap: _closeWorkspace), ], ), ), @@ -589,8 +572,7 @@ class _ActionRow extends StatelessWidget { child: Row( children: [ Expanded(child: ClideText(label, fontSize: 14)), - if (shortcut != null && shortcut!.isNotEmpty) - ClideText(shortcut!, fontSize: 12, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + if (shortcut != null && shortcut!.isNotEmpty) ClideText(shortcut!, fontSize: 12, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ], ), ), @@ -629,7 +611,10 @@ class _OpenFolderDialogState extends State<_OpenFolderDialog> { Future _submit() async { final path = _controller.text.trim(); if (path.isEmpty) return; - setState(() { _loading = true; _error = null; }); + setState(() { + _loading = true; + _error = null; + }); try { await widget.onOpen(path); } catch (_) { diff --git a/lib/builtin/claude/src/claude_pane.dart b/lib/builtin/claude/src/claude_pane.dart index 0bd84edc..0aa6fb79 100644 --- a/lib/builtin/claude/src/claude_pane.dart +++ b/lib/builtin/claude/src/claude_pane.dart @@ -29,8 +29,7 @@ class ClaudePane extends StatefulWidget { this.isPrimary = true, this.secondaryIndex, this.showChrome = true, - }) : assert(isPrimary || secondaryIndex != null, - 'secondary panes need an index'); + }) : assert(isPrimary || secondaryIndex != null, 'secondary panes need an index'); final bool isPrimary; final bool showChrome; @@ -52,9 +51,9 @@ class _ClaudePaneState extends State { String? _error; String _statusLine = 'attaching…'; - @override bool _spawned = false; + @override void initState() { super.initState(); _terminal = Terminal(maxLines: _maxLines); @@ -120,9 +119,7 @@ class _ClaudePaneState extends State { repoRoot = (rootResp.data['path'] as String?) ?? repoRoot; } - _sessionName = widget.isPrimary - ? primarySessionName(repoRoot) - : secondarySessionName(repoRoot, widget.secondaryIndex!); + _sessionName = widget.isPrimary ? primarySessionName(repoRoot) : secondarySessionName(repoRoot, widget.secondaryIndex!); // tmux-wrapped session for persistence (D-041). // -x/-y set the initial window size; without them tmux defaults @@ -135,8 +132,10 @@ class _ClaudePaneState extends State { '-A', '-s', _sessionName!, - '-x', '$cols', - '-y', '$rows', + '-x', + '$cols', + '-y', + '$rows', ]; print('[spawn] cols=${_terminal.viewWidth} rows=${_terminal.viewHeight}'); var resp = await ipc.request('pane.spawn', args: { @@ -264,9 +263,7 @@ class _ClaudePaneState extends State { @override Widget build(BuildContext context) { - final title = widget.isPrimary - ? 'claude — primary' - : 'claude — secondary ${widget.secondaryIndex}'; + final title = widget.isPrimary ? 'claude — primary' : 'claude — secondary ${widget.secondaryIndex}'; final body = _error != null ? Padding( padding: const EdgeInsets.all(16), diff --git a/lib/kernel/src/project.dart b/lib/kernel/src/project.dart index 732082f7..10da5742 100644 --- a/lib/kernel/src/project.dart +++ b/lib/kernel/src/project.dart @@ -96,7 +96,7 @@ class ProjectManager extends ChangeNotifier { // Tell the backend isolate to (re)initialize services for this workspace. if (_onProjectOpen != null) { - await _onProjectOpen!(root); + await _onProjectOpen(root); } await _settings.setProjectDir(_current); @@ -135,7 +135,7 @@ class ProjectManager extends ChangeNotifier { // the merged UI thread). Falls back to direct Process.run for tests // and the CLI binary. if (_onValidateProject != null) { - return _onValidateProject!(path); + return _onValidateProject(path); } try { final r = await Process.run(_toolchain.git, ['rev-parse', '--show-toplevel'], workingDirectory: path, environment: _toolchain.gitEnv); diff --git a/lib/test_app.dart b/lib/test_app.dart index f5e34b62..802aaf37 100644 --- a/lib/test_app.dart +++ b/lib/test_app.dart @@ -40,7 +40,6 @@ import 'src/panes/registry.dart'; import 'src/pty/session.dart'; import 'kernel/src/toolchain.dart'; import 'src/daemon/dispatcher.dart'; -import 'src/ipc/envelope.dart'; import 'src/pty/env.dart' show expandedPath; const _timeout = Duration(seconds: 30); @@ -98,11 +97,11 @@ class _ClideTestAppState extends State { print('[testmode] === done ($passed passed, $failed failed, ${_results.length} total) ==='); print('[testmode:json] ${jsonEncode({ - 'passed': passed, - 'failed': failed, - 'total': _results.length, - 'failures': failedNames, - })}'); + 'passed': passed, + 'failed': failed, + 'total': _results.length, + 'failures': failedNames, + })}'); setState(() => _done = true); await Future.delayed(const Duration(seconds: 2)); @@ -165,16 +164,13 @@ class _ClideTestAppState extends State { }); await _testAsync('git rev-parse (project.open sim)', () async { - final r = await Process.run(tc.git, ['rev-parse', '--show-toplevel'], - workingDirectory: workDir, environment: tc.gitEnv); + final r = await Process.run(tc.git, ['rev-parse', '--show-toplevel'], workingDirectory: workDir, environment: tc.gitEnv); return 'exit=${r.exitCode} ${(r.stdout as String).trim()}'; }); await _testAsync('sequential git calls', () async { - final r1 = await Process.run(tc.git, ['rev-parse', '--show-toplevel'], - workingDirectory: workDir, environment: tc.gitEnv); - final r2 = await Process.run(tc.git, ['rev-parse', '--abbrev-ref', 'HEAD'], - workingDirectory: workDir, environment: tc.gitEnv); + final r1 = await Process.run(tc.git, ['rev-parse', '--show-toplevel'], workingDirectory: workDir, environment: tc.gitEnv); + final r2 = await Process.run(tc.git, ['rev-parse', '--abbrev-ref', 'HEAD'], workingDirectory: workDir, environment: tc.gitEnv); return 'root=${(r1.stdout as String).trim()} branch=${(r2.stdout as String).trim()}'; }); @@ -182,8 +178,7 @@ class _ClideTestAppState extends State { final paths = await compute(resolveToolchainPaths, workDir); final tc2 = Toolchain(); tc2.applyResolved(paths); - final r = await Process.run(tc2.git, ['rev-parse', '--show-toplevel'], - workingDirectory: workDir, environment: tc2.gitEnv); + final r = await Process.run(tc2.git, ['rev-parse', '--show-toplevel'], workingDirectory: workDir, environment: tc2.gitEnv); return 'exit=${r.exitCode} ${(r.stdout as String).trim()}'; }); @@ -317,7 +312,9 @@ class _ClideTestAppState extends State { } // Cleanup - try { await appDir.delete(recursive: true); } catch (_) {} + try { + await appDir.delete(recursive: true); + } catch (_) {} } catch (e) { _addResult('ext:boot', false, '$e'); } @@ -335,7 +332,6 @@ class _ClideTestAppState extends State { final dispatcher = DaemonDispatcher(); final bus = DaemonBus(); final eventSink = _TestEventSink(bus); - final workDir2 = Directory(workDir); final paneRegistry = PaneRegistry(events: eventSink); registerPaneCommands(dispatcher, paneRegistry); final ipc = InProcessClient(log: Logger(), events: bus, dispatcher: dispatcher); @@ -382,8 +378,7 @@ class _ClideTestAppState extends State { final parent = sv[0]; final child = sv[1]; pkg_ffi.calloc.free(sv); - final proc = await Process.start('/tmp/checkfd', [], - environment: {...Platform.environment, 'PTYC_SOCK_FD': '$child'}); + final proc = await Process.start('/tmp/checkfd', [], environment: {...Platform.environment, 'PTYC_SOCK_FD': '$child'}); final stderr = await proc.stderr.transform(utf8.decoder).join(); final exit = await proc.exitCode; libc.close(parent); @@ -422,60 +417,60 @@ class _ClideTestAppState extends State { }); if (!Platform.isMacOS) { - // Additional direct PtySession tests (Linux only — no merged thread). + // Additional direct PtySession tests (Linux only — no merged thread). - // Test 1: spawn /bin/echo via PtySession, read output - await _testAsync('pty spawn echo', () async { - final session = await PtySession.spawn( - argv: ['/bin/echo', 'CLIDE_PTY_TEST_OK'], - cwd: workDir, - ptycPath: tc.ptyc, - ); - final bytes = []; - final done = Completer(); - session.output.listen(bytes.addAll, onDone: () => done.complete()); - await done.future.timeout(const Duration(seconds: 5)); - await session.close(); - final output = utf8.decode(bytes, allowMalformed: true); - final ok = output.contains('CLIDE_PTY_TEST_OK'); - return ok ? 'output contains marker' : 'marker not found in ${output.length} bytes'; - }); + // Test 1: spawn /bin/echo via PtySession, read output + await _testAsync('pty spawn echo', () async { + final session = await PtySession.spawn( + argv: ['/bin/echo', 'CLIDE_PTY_TEST_OK'], + cwd: workDir, + ptycPath: tc.ptyc, + ); + final bytes = []; + final done = Completer(); + session.output.listen(bytes.addAll, onDone: () => done.complete()); + await done.future.timeout(const Duration(seconds: 5)); + await session.close(); + final output = utf8.decode(bytes, allowMalformed: true); + final ok = output.contains('CLIDE_PTY_TEST_OK'); + return ok ? 'output contains marker' : 'marker not found in ${output.length} bytes'; + }); - // Test 2: spawn shell, write a command, verify output - await _testAsync('pty spawn shell', () async { - final session = await PtySession.spawn( - argv: [tc.shell, '-c', 'echo CLIDE_SHELL_TEST'], - cwd: workDir, - ptycPath: tc.ptyc, - ); - final bytes = []; - final done = Completer(); - session.output.listen(bytes.addAll, onDone: () => done.complete()); - await done.future.timeout(const Duration(seconds: 5)); - await session.close(); - final output = utf8.decode(bytes, allowMalformed: true); - final ok = output.contains('CLIDE_SHELL_TEST'); - return ok ? 'shell output contains marker' : 'marker not found in ${output.length} bytes'; - }); + // Test 2: spawn shell, write a command, verify output + await _testAsync('pty spawn shell', () async { + final session = await PtySession.spawn( + argv: [tc.shell, '-c', 'echo CLIDE_SHELL_TEST'], + cwd: workDir, + ptycPath: tc.ptyc, + ); + final bytes = []; + final done = Completer(); + session.output.listen(bytes.addAll, onDone: () => done.complete()); + await done.future.timeout(const Duration(seconds: 5)); + await session.close(); + final output = utf8.decode(bytes, allowMalformed: true); + final ok = output.contains('CLIDE_SHELL_TEST'); + return ok ? 'shell output contains marker' : 'marker not found in ${output.length} bytes'; + }); - // Test 3: spawn interactive shell, write to stdin, verify file creation - await _testAsync('pty write to child', () async { - final marker = '/tmp/clide-pty-test-${DateTime.now().millisecondsSinceEpoch}'; - final session = await PtySession.spawn( - argv: [tc.shell], - cwd: workDir, - ptycPath: tc.ptyc, - ); - session.write(utf8.encode('touch $marker && exit\n')); - final bytes = []; - final done = Completer(); - session.output.listen(bytes.addAll, onDone: () => done.complete()); - await done.future.timeout(const Duration(seconds: 5)); - await session.close(); - final fileCreated = File(marker).existsSync(); - if (fileCreated) File(marker).deleteSync(); - return fileCreated ? 'file created + cleaned up' : 'file not created'; - }); + // Test 3: spawn interactive shell, write to stdin, verify file creation + await _testAsync('pty write to child', () async { + final marker = '/tmp/clide-pty-test-${DateTime.now().millisecondsSinceEpoch}'; + final session = await PtySession.spawn( + argv: [tc.shell], + cwd: workDir, + ptycPath: tc.ptyc, + ); + session.write(utf8.encode('touch $marker && exit\n')); + final bytes = []; + final done = Completer(); + session.output.listen(bytes.addAll, onDone: () => done.complete()); + await done.future.timeout(const Duration(seconds: 5)); + await session.close(); + final fileCreated = File(marker).existsSync(); + if (fileCreated) File(marker).deleteSync(); + return fileCreated ? 'file created + cleaned up' : 'file not created'; + }); } // end !Platform.isMacOS print('[testmode]'); @@ -513,8 +508,7 @@ class _ClideTestAppState extends State { Future _testExec(String label, String bin, List args, String workDir, {Map? env}) async { try { - final r = await Process.run(bin, args, workingDirectory: workDir, environment: env) - .timeout(const Duration(seconds: 5)); + final r = await Process.run(bin, args, workingDirectory: workDir, environment: env).timeout(const Duration(seconds: 5)); final stdout = (r.stdout as String).trim(); final stderr = (r.stderr as String).trim(); final firstLine = stdout.isNotEmpty ? stdout.split('\n').first : (stderr.isNotEmpty ? stderr.split('\n').first : '(empty)'); @@ -545,7 +539,8 @@ class _ClideTestAppState extends State { children: [ const Text('ClideTestApp', style: TextStyle(color: Color(0xFFCDD6F4), fontSize: 20, fontWeight: FontWeight.bold, decoration: TextDecoration.none)), const SizedBox(height: 4), - Text(_done ? 'Done — exiting' : 'Running tests...', style: const TextStyle(color: Color(0xFF6C7086), fontSize: 13, decoration: TextDecoration.none)), + Text(_done ? 'Done — exiting' : 'Running tests...', + style: const TextStyle(color: Color(0xFF6C7086), fontSize: 13, decoration: TextDecoration.none)), const SizedBox(height: 16), Expanded( child: ListView.builder( @@ -556,10 +551,17 @@ class _ClideTestAppState extends State { padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ - Text(r.ok ? '●' : '●', style: TextStyle(color: r.ok ? const Color(0xFFA6E3A1) : const Color(0xFFF38BA8), fontSize: 12, decoration: TextDecoration.none)), + Text(r.ok ? '●' : '●', + style: TextStyle(color: r.ok ? const Color(0xFFA6E3A1) : const Color(0xFFF38BA8), fontSize: 12, decoration: TextDecoration.none)), const SizedBox(width: 8), - SizedBox(width: 220, child: Text(r.name, style: const TextStyle(color: Color(0xFFCDD6F4), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none))), - Expanded(child: Text(r.output, style: const TextStyle(color: Color(0xFF9399B2), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none), overflow: TextOverflow.ellipsis)), + SizedBox( + width: 220, + child: Text(r.name, + style: const TextStyle(color: Color(0xFFCDD6F4), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none))), + Expanded( + child: Text(r.output, + style: const TextStyle(color: Color(0xFF9399B2), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none), + overflow: TextOverflow.ellipsis)), ], ), ); diff --git a/lib/widgets/src/clide_column_hat.dart b/lib/widgets/src/clide_column_hat.dart index 5f6017ce..3feb1912 100644 --- a/lib/widgets/src/clide_column_hat.dart +++ b/lib/widgets/src/clide_column_hat.dart @@ -21,14 +21,12 @@ class ColumnHat extends StatelessWidget { final String? projectLabel; final String? branchLabel; - factory ColumnHat.left({required WindowControls windowControls}) => - ColumnHat._(position: HatPosition.left, windowControls: windowControls); + factory ColumnHat.left({required WindowControls windowControls}) => ColumnHat._(position: HatPosition.left, windowControls: windowControls); factory ColumnHat.center({required WindowControls windowControls, String? project, String? branch}) => ColumnHat._(position: HatPosition.center, windowControls: windowControls, projectLabel: project, branchLabel: branch); - factory ColumnHat.right({required WindowControls windowControls}) => - ColumnHat._(position: HatPosition.right, windowControls: windowControls); + factory ColumnHat.right({required WindowControls windowControls}) => ColumnHat._(position: HatPosition.right, windowControls: windowControls); @override Widget build(BuildContext context) { @@ -101,27 +99,6 @@ class _RightContent extends StatelessWidget { } } -class _TrafficDot extends StatelessWidget { - const _TrafficDot({required this.color, required this.onTap}); - final Color color; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return ClideTappable( - onTap: onTap, - builder: (context, hovered, _) => Container( - width: 12, - height: 12, - decoration: BoxDecoration( - color: hovered ? color : color.withAlpha(0xCC), - shape: BoxShape.circle, - ), - ), - ); - } -} - class _WinButton extends StatelessWidget { const _WinButton({required this.icon, required this.onTap, required this.tokens, this.isClose = false}); final ClideIconPainter icon;