Files
clide/lib/builtin/problems/src/problems_view.dart
T
jpmschweitzerandClaude Opus 4.6 46329700d5 dissolve app/ into repo root (D-056)
Single Flutter package at the repo root. All code, tests, assets,
and platform directories moved from app/ to root. Package renamed
from clide_app to clide — all imports rewritten. Merged pubspec
combines core (ffi) and app (flutter, yaml, xterm) dependencies.
Makefile simplified: no APP_PRESENT conditionals, no cd, no daemon
lifecycle. 317 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 00:37:20 +02:00

161 lines
4.8 KiB
Dart

/// Sidebar panel showing project diagnostics from pql.
library;
import 'dart:async';
import 'package:clide/kernel/kernel.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter/widgets.dart';
import 'problems_controller.dart';
class ProblemsView extends StatefulWidget {
const ProblemsView({super.key});
@override
State<ProblemsView> createState() => _ProblemsViewState();
}
class _ProblemsViewState extends State<ProblemsView> {
ProblemsController? _controller;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_controller != null) return;
final kernel = ClideKernel.of(context);
_controller = ProblemsController(ipc: kernel.ipc);
unawaited(_controller!.refresh());
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final c = _controller;
if (c == null) return const SizedBox.shrink();
return ListenableBuilder(
listenable: c,
builder: (context, _) {
final tokens = ClideTheme.of(context).surface;
return Semantics(
label: 'problems panel',
container: true,
explicitChildNodes: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
child: Row(
children: [
Expanded(
child: ClideText(
'Problems (${c.problems.length})',
fontSize: clideFontCaption,
color: tokens.sidebarForeground,
),
),
Semantics(
button: true,
label: 'refresh problems',
child: GestureDetector(
onTap: () => unawaited(c.refresh()),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: ClideText(
'Refresh',
fontSize: clideFontCaption,
color: tokens.sidebarForeground,
),
),
),
),
],
),
),
if (c.loading && c.problems.isEmpty)
const Padding(
padding: EdgeInsets.all(12),
child: ClideText('Scanning…', muted: true),
),
if (!c.loading && c.problems.isEmpty)
const Padding(
padding: EdgeInsets.all(12),
child: ClideText(
'No problems found.',
muted: true,
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
for (final p in c.problems) _ProblemRow(problem: p),
],
),
),
),
],
),
);
},
);
}
}
class _ProblemRow extends StatelessWidget {
const _ProblemRow({required this.problem});
final Problem problem;
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
ClideText(
problem.source,
fontSize: clideFontMono,
color: tokens.statusWarning,
fontFamily: clideMonoFamily,
),
const SizedBox(width: 6),
Expanded(
child: ClideText(
problem.message,
color: tokens.sidebarForeground,
maxLines: 2,
),
),
],
),
if (problem.hint != null)
Padding(
padding: const EdgeInsets.only(left: 44, top: 2),
child: ClideText(
problem.hint!,
fontSize: clideFontMono,
muted: true,
fontFamily: clideMonoFamily,
),
),
],
),
);
}
}