feat(welcome): New project flow + per-repo account roadblock (T-488)
The UI half of the new-project flow (story T-486). A "New project…" welcome action opens a dialog (location + name) that dispatches project.new (T-487), opens the result, and announces it on projectCreatedChannel. The Claude extension consumes that and shows the account roadblock — the embedded per-workspace picker + accounts list, so a fresh project gets bound to an account (or Default) right at birth. The two halves stay decoupled: the welcome builtin only publishes the event (no claude import); the claude builtin owns the account dialog. Only freshly- created projects announce, so existing opens are never prompted. Closes T-488. The non-repo "initialize as a project" path (T-489) is next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/// The per-repo account roadblock shown right after a new project is created
|
||||
/// (T-488, story T-486). Only a freshly-created project reaches here (the
|
||||
/// welcome dialog announces it on projectCreatedChannel) — existing opens never
|
||||
/// prompt. The new project is already the open workspace, so the embedded
|
||||
/// per-workspace picker binds it directly; the accounts list lets a first-run
|
||||
/// user add + sign in to an account before picking. No-Material (D-7).
|
||||
library;
|
||||
|
||||
import 'package:clide/builtin/claude/src/account_settings_control.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ClaudeAccountRoadblockDialog extends StatelessWidget {
|
||||
const ClaudeAccountRoadblockDialog({super.key, required this.projectName, required this.onClose});
|
||||
|
||||
final String projectName;
|
||||
final VoidCallback onClose;
|
||||
|
||||
KeyEventResult _onKey(FocusNode node, KeyEvent e) {
|
||||
if (e is KeyDownEvent && e.logicalKey == LogicalKeyboardKey.escape) {
|
||||
onClose();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ClideSettings.theme.of(context).surface;
|
||||
return Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: _onKey,
|
||||
child: Container(
|
||||
width: 560,
|
||||
constraints: const BoxConstraints(maxHeight: 540),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.modalSurfaceBackground,
|
||||
border: Border.all(color: theme.modalSurfaceBorder),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ClideText('Claude account for $projectName', fontSize: clideFontDialogTitle, fontWeight: FontWeight.w600, color: theme.globalForeground),
|
||||
const SizedBox(height: 4),
|
||||
ClideText(
|
||||
'Pick which Claude account this new project runs under, or keep the default system login. You can change it later in Settings or the pane badge.',
|
||||
muted: true,
|
||||
fontSize: clideFontMeta,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
ClideText('Account for this project', fontSize: clideFontMeta, muted: true),
|
||||
const SizedBox(height: 6),
|
||||
const Align(alignment: Alignment.centerLeft, child: ClaudeWorkspaceAccountControl()),
|
||||
const SizedBox(height: 18),
|
||||
ClideText('Accounts', fontSize: clideFontMeta, muted: true),
|
||||
const SizedBox(height: 6),
|
||||
const ClaudeAccountsListControl(),
|
||||
const SizedBox(height: 18),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: ClideButton(label: 'Continue', onPressed: onClose),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:clide/clide.dart';
|
||||
import 'package:clide/builtin/claude/src/account_registry.dart';
|
||||
import 'package:clide/builtin/claude/src/account_login_dialog.dart';
|
||||
import 'package:clide/builtin/claude/src/account_roadblock_dialog.dart';
|
||||
import 'package:clide/builtin/claude/src/account_settings_control.dart';
|
||||
import 'package:clide/builtin/claude/src/activity_cluster.dart' show foldLevelFromName, kActivityFoldLevelKey, nextFoldLevel;
|
||||
import 'package:clide/builtin/claude/src/claude_config.dart';
|
||||
@@ -20,6 +21,7 @@ import 'package:clide/builtin/claude/src/session_storage.dart';
|
||||
import 'package:clide/builtin/claude/src/ticket_pick_up.dart';
|
||||
import 'package:clide/builtin/claude/src/transcript_reader.dart' show ImageMessage;
|
||||
import 'package:clide/src/daemon/claude_account_commands.dart' show accountActionChannel;
|
||||
import 'package:clide/src/daemon/project_commands.dart' show projectCreatedChannel;
|
||||
import 'package:clide/src/daemon/image_commands.dart' show imageShowChannel;
|
||||
import 'package:clide/builtin/claude/src/team_chat_sidebar.dart' show TeamChatPane;
|
||||
import 'package:clide/builtin/claude/src/team_panel_host.dart';
|
||||
@@ -530,6 +532,19 @@ class ClaudeExtension extends ClideExtension {
|
||||
// writes the registry then publishes here; only the UI layer can respawn
|
||||
// the workspace's panes onto the newly-bound account or delete a config dir.
|
||||
_subs.add(ctx.messages.subscribe(channel: accountActionChannel).listen(_onAccountAction));
|
||||
|
||||
// A freshly-created project (T-488) announces itself once it's open; show the
|
||||
// per-repo account roadblock so the user binds it now (existing opens, which
|
||||
// never announce, are never prompted).
|
||||
_subs.add(ctx.messages.subscribe(channel: projectCreatedChannel).listen(_onProjectCreated));
|
||||
}
|
||||
|
||||
void _onProjectCreated(Message m) {
|
||||
final dir = m.data['dir'] as String?;
|
||||
final ctx = _ctx;
|
||||
if (dir == null || ctx == null) return;
|
||||
final name = dir.split('/').where((s) => s.isNotEmpty).lastOrNull ?? dir;
|
||||
ctx.dialog.show<Object>((c, dismiss) => ClaudeAccountRoadblockDialog(projectName: name, onClose: dismiss));
|
||||
}
|
||||
|
||||
/// Side-effects for the `claude account` verbs (T-480). The dispatcher does
|
||||
|
||||
Reference in New Issue
Block a user