feat(electrobun): ProjectWizard — 3-step project creation with 5 source types

Step 1 — Source: local folder (path browser + validation), git clone,
GitHub URL, template (4 built-in), remote SSH
Step 2 — Configure: name, branch selector, worktree toggle, group, icon, shell
Step 3 — Agent: provider, model, permission mode, system prompt, auto-start

- ProjectWizard.svelte: 3-step wizard with display toggle (rule 55)
- PathBrowser.svelte: inline directory browser with breadcrumbs + shortcuts
- git-handlers.ts: git.branches + git.clone RPC handlers
- files.statEx RPC: path validation + git detection + writable check
- 39 new i18n keys, 172 total TranslationKey entries
- App.svelte: wizard overlay replaces simple add-project card
This commit is contained in:
Hibryda 2026-03-22 11:17:05 +01:00
parent 1d2975b07b
commit 45bca3b96f
9 changed files with 1203 additions and 45 deletions

View file

@ -8,6 +8,7 @@
import StatusBar from './StatusBar.svelte';
import SearchOverlay from './SearchOverlay.svelte';
import SplashScreen from './SplashScreen.svelte';
import ProjectWizard from './ProjectWizard.svelte';
import { themeStore } from './theme-store.svelte.ts';
import { fontStore } from './font-store.svelte.ts';
import { keybindingStore } from './keybinding-store.svelte.ts';
@ -71,34 +72,37 @@
]);
// ── Add/Remove project UI state ──────────────────────────────────
let showAddProject = $state(false);
let newProjectName = $state('');
let newProjectCwd = $state('');
let showWizard = $state(false);
let projectToDelete = $state<string | null>(null);
async function addProject() {
const name = newProjectName.trim();
const cwd = newProjectCwd.trim();
if (!name || !cwd) return;
const id = `p-${Date.now()}`;
function handleWizardCreated(result: {
id: string; name: string; cwd: string; provider?: string; model?: string;
systemPrompt?: string; autoStart?: boolean; groupId?: string;
useWorktrees?: boolean; shell?: string; icon?: string;
}) {
const accent = ACCENTS[PROJECTS.length % ACCENTS.length];
const project: Project = {
id, name, cwd, accent,
status: 'idle', costUsd: 0, tokens: 0, messages: [],
provider: 'claude', groupId: activeGroupId,
id: result.id,
name: result.name,
cwd: result.cwd,
accent,
status: 'idle',
costUsd: 0,
tokens: 0,
messages: [],
provider: result.provider ?? 'claude',
model: result.model,
groupId: result.groupId ?? activeGroupId,
};
PROJECTS = [...PROJECTS, project];
trackProject(id);
trackProject(project.id);
await appRpc.request['settings.setProject']({
id,
config: JSON.stringify(project),
appRpc.request['settings.setProject']({
id: project.id,
config: JSON.stringify({ ...project, ...result }),
}).catch(console.error);
showAddProject = false;
newProjectName = '';
newProjectCwd = '';
showWizard = false;
}
async function confirmDeleteProject() {
@ -357,7 +361,7 @@
switch (detail) {
case 'settings': settingsOpen = !settingsOpen; break;
case 'search': searchOpen = !searchOpen; break;
case 'new-project': showAddProject = true; break;
case 'new-project': showWizard = true; break;
case 'toggle-sidebar': settingsOpen = !settingsOpen; break;
default: console.log(`[palette] unhandled command: ${detail}`);
}
@ -439,7 +443,7 @@
<!-- Add project button -->
<button
class="sidebar-icon"
onclick={() => showAddProject = !showAddProject}
onclick={() => showWizard = !showWizard}
aria-label="Add project"
title="Add project"
>
@ -501,28 +505,14 @@
<p class="empty-group-text">No projects in {activeGroup?.name ?? 'this group'}</p>
</div>
<!-- Add project card -->
<div class="add-card" role="listitem" style:display={showAddProject ? 'flex' : 'none'}>
<div class="add-card-form">
<input
class="add-input"
type="text"
placeholder="Project name"
bind:value={newProjectName}
onkeydown={(e) => { if (e.key === 'Enter') addProject(); if (e.key === 'Escape') showAddProject = false; }}
/>
<input
class="add-input"
type="text"
placeholder="Working directory (e.g. ~/code/myproject)"
bind:value={newProjectCwd}
onkeydown={(e) => { if (e.key === 'Enter') addProject(); if (e.key === 'Escape') showAddProject = false; }}
/>
<div class="add-card-actions">
<button class="add-cancel" onclick={() => showAddProject = false}>Cancel</button>
<button class="add-confirm" onclick={addProject}>Add</button>
</div>
</div>
<!-- Project wizard overlay (display toggle) -->
<div style:display={showWizard ? 'contents' : 'none'}>
<ProjectWizard
onClose={() => showWizard = false}
onCreated={handleWizardCreated}
groupId={activeGroupId}
groups={groups.map(g => ({ id: g.id, name: g.name }))}
/>
</div>
<!-- Delete project confirmation -->