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>
This commit is contained in:
2026-04-23 00:37:20 +02:00
co-authored by Claude Opus 4.6
parent a526c5b9b7
commit 46329700d5
394 changed files with 978 additions and 1090 deletions
+183
View File
@@ -0,0 +1,183 @@
import 'package:clide/kernel/src/theme/controller.dart';
import 'package:flutter/widgets.dart';
import 'clide_divider.dart';
import 'clide_icon.dart';
import 'clide_text.dart';
import 'icons/x.dart';
import 'typography.dart';
/// Shared chrome for any pane that sits in a tab or split: a title
/// strip at the top, an optional close button, and the pane body
/// underneath. `ClidePtyView`, diff views, canvas tabs, graph tabs —
/// everything with a "pane header" surface reuses this.
class ClidePaneChrome extends StatelessWidget {
const ClidePaneChrome({
super.key,
required this.title,
required this.child,
this.subtitle,
this.leading,
this.onClose,
this.trailing,
});
/// Primary label in the header — typically the pane kind + an
/// abbreviated path / session name (`terminal — ~/clide`).
final String title;
/// Optional secondary line (cwd hint, session id, status).
final String? subtitle;
/// Icon or badge drawn before the title.
final Widget? leading;
/// Main pane content.
final Widget child;
/// If provided, renders an `x` close button on the right. Primary
/// Claude panes deliberately pass `null` so the user can't hide the
/// primary (D-041).
final VoidCallback? onClose;
/// Extra trailing widgets (status indicator, menu button, etc.).
/// Drawn before the close button when both are present.
final List<Widget>? trailing;
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
return ColoredBox(
color: tokens.panelBackground,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_Header(
title: title,
subtitle: subtitle,
leading: leading,
onClose: onClose,
trailing: trailing,
),
const ClideDivider(),
Expanded(child: child),
],
),
);
}
}
class _CloseButton extends StatefulWidget {
const _CloseButton({required this.onPressed});
final VoidCallback onPressed;
@override
State<_CloseButton> createState() => _CloseButtonState();
}
class _CloseButtonState extends State<_CloseButton> {
bool _hover = false;
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
return Semantics(
button: true,
label: 'Close pane',
onTap: widget.onPressed,
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.onPressed,
child: Container(
width: 20,
height: 20,
alignment: Alignment.center,
decoration: BoxDecoration(
color: _hover ? tokens.tabCloseHover : null,
),
child: ClideIcon(
const CloseIcon(),
size: 10,
color: tokens.panelHeaderForeground,
),
),
),
),
);
}
}
class _Header extends StatelessWidget {
const _Header({
required this.title,
required this.subtitle,
required this.leading,
required this.onClose,
required this.trailing,
});
final String title;
final String? subtitle;
final Widget? leading;
final VoidCallback? onClose;
final List<Widget>? trailing;
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
return Semantics(
container: true,
explicitChildNodes: true,
label: 'pane header: $title',
child: ColoredBox(
color: tokens.panelHeader,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
child: Row(
children: [
if (leading != null) ...[leading!, const SizedBox(width: 6)],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
ClideText(
title,
fontSize: clideFontCaption,
color: tokens.panelHeaderForeground,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (subtitle != null)
ClideText(
subtitle!,
fontSize: clideFontCaption,
muted: true,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
if (trailing != null) ...trailing!.map(
(w) => Padding(
padding: const EdgeInsets.only(left: 6),
child: w,
),
),
if (onClose != null)
Padding(
padding: const EdgeInsets.only(left: 6),
child: _CloseButton(onPressed: onClose!),
),
],
),
),
),
);
}
}