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
|
- Graph view in context panel — lists files with inbound/outbound
|
||||||
link counts from `pql search --connections` (T-039).
|
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
|
### Changed
|
||||||
|
|
||||||
- Workspace renders Claude as the always-visible primary surface
|
- 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/kernel/kernel.dart';
|
||||||
import 'package:clide_app/widgets/widgets.dart';
|
import 'package:clide_app/widgets/widgets.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@@ -47,18 +49,10 @@ class WelcomeView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_StartAction(
|
_StartAction(
|
||||||
label: i.string('open-project',
|
label: i.string('open-project', namespace: _ns, placeholder: 'Open project…'),
|
||||||
namespace: _ns, placeholder: 'Open project…'),
|
hint: i.string('open-project.hint', namespace: _ns, placeholder: 'Pick a git repository'),
|
||||||
hint: i.string('open-project.hint',
|
icon: PhosphorIcons.folder,
|
||||||
namespace: _ns,
|
onTap: () => _openProject(context),
|
||||||
placeholder: 'Pick a git repository'),
|
|
||||||
icon: const FolderIcon(),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
_StartAction(
|
|
||||||
label: 'New Claude session…',
|
|
||||||
icon: const TerminalIcon(),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -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 {
|
class _StartAction extends StatefulWidget {
|
||||||
|
|||||||
@@ -33,11 +33,20 @@ class ProjectManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
_current = Directory(root);
|
_current = Directory(root);
|
||||||
await _settings.setProjectDir(_current);
|
await _settings.setProjectDir(_current);
|
||||||
|
await _settings.set<String>('app.lastProject', root);
|
||||||
_events.emit(ProjectOpened(path: root));
|
_events.emit(ProjectOpened(path: root));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return true;
|
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 {
|
Future<void> close() async {
|
||||||
if (_current == null) return;
|
if (_current == null) return;
|
||||||
_current = null;
|
_current = null;
|
||||||
|
|||||||
+1
-1
@@ -83,7 +83,7 @@ Future<void> main() async {
|
|||||||
await services.extensions.activateAll();
|
await services.extensions.activateAll();
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
final opened = await services.project.open(Directory.current.path);
|
final opened = await services.project.openLast();
|
||||||
if (opened) {
|
if (opened) {
|
||||||
services.panels.activateTab(Slots.workspace, 'claude.primary');
|
services.panels.activateTab(Slots.workspace, 'claude.primary');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user