persist last-opened project and wire welcome open dialog
ProjectManager.open() now saves app.lastProject to user settings. Boot calls openLast() instead of opening cwd — first launch shows the welcome screen, subsequent launches restore the last project directly into Claude. The welcome "Open project" button shows a modal path-entry dialog that validates against git and activates the Claude tab on success. Removed the dead "New Claude session" button since secondary sessions are spawned via command palette. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,11 @@ heading, and (b) bumping `project.yaml` `version:` in the same commit.
|
||||
- Graph view in context panel — lists files with inbound/outbound
|
||||
link counts from `pql search --connections` (T-039).
|
||||
|
||||
- Last-opened project persisted to user settings and restored on
|
||||
boot. First launch shows the welcome screen with a path-entry
|
||||
dialog; subsequent launches re-open the last project directly
|
||||
into Claude.
|
||||
|
||||
### Changed
|
||||
|
||||
- Workspace renders Claude as the always-visible primary surface
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide_app/kernel/kernel.dart';
|
||||
import 'package:clide_app/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -47,18 +49,10 @@ class WelcomeView extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_StartAction(
|
||||
label: i.string('open-project',
|
||||
namespace: _ns, placeholder: 'Open project…'),
|
||||
hint: i.string('open-project.hint',
|
||||
namespace: _ns,
|
||||
placeholder: 'Pick a git repository'),
|
||||
icon: const FolderIcon(),
|
||||
onTap: () {},
|
||||
),
|
||||
_StartAction(
|
||||
label: 'New Claude session…',
|
||||
icon: const TerminalIcon(),
|
||||
onTap: () {},
|
||||
label: i.string('open-project', namespace: _ns, placeholder: 'Open project…'),
|
||||
hint: i.string('open-project.hint', namespace: _ns, placeholder: 'Pick a git repository'),
|
||||
icon: PhosphorIcons.folder,
|
||||
onTap: () => _openProject(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -67,6 +61,119 @@ class WelcomeView extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openProject(BuildContext context) {
|
||||
final kernel = ClideKernel.of(context);
|
||||
kernel.dialog.show<String>((ctx, dismiss) {
|
||||
return _OpenProjectDialog(
|
||||
onOpen: (path) async {
|
||||
final ok = await kernel.project.open(path);
|
||||
if (ok) {
|
||||
kernel.panels.activateTab(Slots.workspace, 'claude.primary');
|
||||
dismiss(path);
|
||||
}
|
||||
},
|
||||
onCancel: () => dismiss(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _OpenProjectDialog extends StatefulWidget {
|
||||
const _OpenProjectDialog({required this.onOpen, required this.onCancel});
|
||||
final Future<void> Function(String path) onOpen;
|
||||
final VoidCallback onCancel;
|
||||
|
||||
@override
|
||||
State<_OpenProjectDialog> createState() => _OpenProjectDialogState();
|
||||
}
|
||||
|
||||
class _OpenProjectDialogState extends State<_OpenProjectDialog> {
|
||||
final _controller = TextEditingController();
|
||||
final _focus = FocusNode();
|
||||
String? _error;
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focus.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
final path = _controller.text.trim();
|
||||
if (path.isEmpty) return;
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
await widget.onOpen(path);
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _error = 'Not a git repository');
|
||||
}
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return Container(
|
||||
width: 420,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: tokens.modalSurfaceBackground,
|
||||
border: Border.all(color: tokens.modalSurfaceBorder),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ClideText('Open project', fontSize: 16, fontWeight: FontWeight.w600),
|
||||
const SizedBox(height: 4),
|
||||
const ClideText('Enter the path to a git repository.', muted: true, fontSize: 13),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: tokens.panelBackground,
|
||||
border: Border.all(color: tokens.globalBorder),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: EditableText(
|
||||
controller: _controller,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(color: tokens.globalForeground, fontSize: 14, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback),
|
||||
cursorColor: tokens.globalForeground,
|
||||
backgroundCursorColor: tokens.globalTextMuted,
|
||||
onSubmitted: (_) => unawaited(_submit()),
|
||||
),
|
||||
),
|
||||
if (_error != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
ClideText(_error!, color: tokens.statusError, fontSize: 12),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ClideButton(label: 'Cancel', onPressed: widget.onCancel),
|
||||
const SizedBox(width: 8),
|
||||
ClideButton(label: _loading ? 'Opening…' : 'Open', onPressed: _loading ? null : _submit),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StartAction extends StatefulWidget {
|
||||
|
||||
@@ -33,11 +33,20 @@ class ProjectManager extends ChangeNotifier {
|
||||
}
|
||||
_current = Directory(root);
|
||||
await _settings.setProjectDir(_current);
|
||||
await _settings.set<String>('app.lastProject', root);
|
||||
_events.emit(ProjectOpened(path: root));
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> openLast() async {
|
||||
final last = _settings.get<String>('app.lastProject');
|
||||
if (last == null || last.isEmpty) return false;
|
||||
final dir = Directory(last);
|
||||
if (!await dir.exists()) return false;
|
||||
return open(last);
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
if (_current == null) return;
|
||||
_current = null;
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ Future<void> main() async {
|
||||
await services.extensions.activateAll();
|
||||
|
||||
if (!kIsWeb) {
|
||||
final opened = await services.project.open(Directory.current.path);
|
||||
final opened = await services.project.openLast();
|
||||
if (opened) {
|
||||
services.panels.activateTab(Slots.workspace, 'claude.primary');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user