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 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 21:52:26 +02:00
co-authored by Claude
parent 943ab966d2
commit 864a1062a6
7 changed files with 130 additions and 185 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"exported_at": "2026-05-03T19:52:13Z",
"exported_at": "2026-05-03T19:52:26Z",
"decisions": [
{
"id": "D-1",
+11 -27
View File
@@ -242,10 +242,7 @@ Future<void> _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<List<int>>()
.transform(utf8.decoder)
.transform(const LineSplitter());
final lines = socket.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter());
try {
await for (final line in lines) {
@@ -346,11 +343,14 @@ Future<void> _runGit(List<String> 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<void> _runTail(List<String> args) async {
void quit() {
unawaited(socket.close());
}
ProcessSignal.sigint.watch().listen((_) => quit());
ProcessSignal.sigterm.watch().listen((_) => quit());
final lines = socket
.cast<List<int>>()
.transform(utf8.decoder)
.transform(const LineSplitter());
final lines = socket.cast<List<int>>().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,
+29 -44
View File
@@ -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<void> _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 (_) {
+8 -11
View File
@@ -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<ClaudePane> {
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<ClaudePane> {
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<ClaudePane> {
'-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<ClaudePane> {
@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),
+2 -2
View File
@@ -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);
+77 -75
View File
@@ -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<ClideTestApp> {
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<void>.delayed(const Duration(seconds: 2));
@@ -165,16 +164,13 @@ class _ClideTestAppState extends State<ClideTestApp> {
});
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<ClideTestApp> {
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<ClideTestApp> {
}
// 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<ClideTestApp> {
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<ClideTestApp> {
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<ClideTestApp> {
});
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 = <int>[];
final done = Completer<void>();
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 = <int>[];
final done = Completer<void>();
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 = <int>[];
final done = Completer<void>();
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 = <int>[];
final done = Completer<void>();
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 = <int>[];
final done = Completer<void>();
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 = <int>[];
final done = Completer<void>();
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<ClideTestApp> {
Future<void> _testExec(String label, String bin, List<String> args, String workDir, {Map<String, String>? 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<ClideTestApp> {
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<ClideTestApp> {
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)),
],
),
);
+2 -25
View File
@@ -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;