feat(v3): implement Mission Control MVP (Phases 1-5)
Phase 1: Data model - groups.rs (Rust structs + load/save groups.json), groups.ts (TypeScript interfaces), groups-bridge.ts (IPC adapter), workspace.svelte.ts (replaces layout store), SQLite migrations (agent_messages, project_agent_state tables, project_id column), --group CLI argument. Phase 2: Project shell layout - GlobalTabBar, ProjectGrid, ProjectBox, ProjectHeader, CommandPalette, DocsTab, ContextTab, SettingsTab, App.svelte full rewrite (no sidebar/TilingGrid). Phase 3: ClaudeSession.svelte wrapping AgentPane per-project. Phase 4: TerminalTabs.svelte with shell/SSH/agent tab types. Phase 5: TeamAgentsPanel + AgentCard for compact subagent view. Also fixes AgentPane Svelte 5 event modifier (on:click -> onclick).
This commit is contained in:
parent
293bed6dc5
commit
ab79dac4b3
20 changed files with 2296 additions and 65 deletions
216
v2/src/lib/stores/workspace.svelte.ts
Normal file
216
v2/src/lib/stores/workspace.svelte.ts
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import { loadGroups, saveGroups, getCliGroup } from '../adapters/groups-bridge';
|
||||
import type { GroupsFile, GroupConfig, ProjectConfig } from '../types/groups';
|
||||
|
||||
export type WorkspaceTab = 'sessions' | 'docs' | 'context' | 'settings';
|
||||
|
||||
export interface TerminalTab {
|
||||
id: string;
|
||||
title: string;
|
||||
type: 'shell' | 'ssh' | 'agent-terminal';
|
||||
/** SSH session ID if type === 'ssh' */
|
||||
sshSessionId?: string;
|
||||
}
|
||||
|
||||
// --- Core state ---
|
||||
|
||||
let groupsConfig = $state<GroupsFile | null>(null);
|
||||
let activeGroupId = $state<string>('');
|
||||
let activeTab = $state<WorkspaceTab>('sessions');
|
||||
let activeProjectId = $state<string | null>(null);
|
||||
|
||||
/** Terminal tabs per project (keyed by project ID) */
|
||||
let projectTerminals = $state<Map<string, TerminalTab[]>>(new Map());
|
||||
|
||||
// --- Getters ---
|
||||
|
||||
export function getGroupsConfig(): GroupsFile | null {
|
||||
return groupsConfig;
|
||||
}
|
||||
|
||||
export function getActiveGroupId(): string {
|
||||
return activeGroupId;
|
||||
}
|
||||
|
||||
export function getActiveTab(): WorkspaceTab {
|
||||
return activeTab;
|
||||
}
|
||||
|
||||
export function getActiveProjectId(): string | null {
|
||||
return activeProjectId;
|
||||
}
|
||||
|
||||
export function getActiveGroup(): GroupConfig | undefined {
|
||||
return groupsConfig?.groups.find(g => g.id === activeGroupId);
|
||||
}
|
||||
|
||||
export function getEnabledProjects(): ProjectConfig[] {
|
||||
const group = getActiveGroup();
|
||||
if (!group) return [];
|
||||
return group.projects.filter(p => p.enabled);
|
||||
}
|
||||
|
||||
export function getAllGroups(): GroupConfig[] {
|
||||
return groupsConfig?.groups ?? [];
|
||||
}
|
||||
|
||||
// --- Setters ---
|
||||
|
||||
export function setActiveTab(tab: WorkspaceTab): void {
|
||||
activeTab = tab;
|
||||
}
|
||||
|
||||
export function setActiveProject(projectId: string | null): void {
|
||||
activeProjectId = projectId;
|
||||
}
|
||||
|
||||
export async function switchGroup(groupId: string): Promise<void> {
|
||||
if (groupId === activeGroupId) return;
|
||||
|
||||
// Clear terminal tabs for the old group
|
||||
projectTerminals = new Map();
|
||||
|
||||
activeGroupId = groupId;
|
||||
activeProjectId = null;
|
||||
|
||||
// Auto-focus first enabled project
|
||||
const projects = getEnabledProjects();
|
||||
if (projects.length > 0) {
|
||||
activeProjectId = projects[0].id;
|
||||
}
|
||||
|
||||
// Persist active group
|
||||
if (groupsConfig) {
|
||||
groupsConfig.activeGroupId = groupId;
|
||||
saveGroups(groupsConfig).catch(e => console.warn('Failed to save groups:', e));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Terminal tab management per project ---
|
||||
|
||||
export function getTerminalTabs(projectId: string): TerminalTab[] {
|
||||
return projectTerminals.get(projectId) ?? [];
|
||||
}
|
||||
|
||||
export function addTerminalTab(projectId: string, tab: TerminalTab): void {
|
||||
const tabs = projectTerminals.get(projectId) ?? [];
|
||||
tabs.push(tab);
|
||||
projectTerminals.set(projectId, [...tabs]);
|
||||
}
|
||||
|
||||
export function removeTerminalTab(projectId: string, tabId: string): void {
|
||||
const tabs = projectTerminals.get(projectId) ?? [];
|
||||
const filtered = tabs.filter(t => t.id !== tabId);
|
||||
projectTerminals.set(projectId, filtered);
|
||||
}
|
||||
|
||||
// --- Persistence ---
|
||||
|
||||
export async function loadWorkspace(initialGroupId?: string): Promise<void> {
|
||||
try {
|
||||
const config = await loadGroups();
|
||||
groupsConfig = config;
|
||||
projectTerminals = new Map();
|
||||
|
||||
// CLI --group flag takes priority, then explicit param, then persisted
|
||||
let cliGroup: string | null = null;
|
||||
if (!initialGroupId) {
|
||||
cliGroup = await getCliGroup();
|
||||
}
|
||||
const targetId = initialGroupId || cliGroup || config.activeGroupId;
|
||||
// Match by ID or by name (CLI users may pass name)
|
||||
const targetGroup = config.groups.find(
|
||||
g => g.id === targetId || g.name === targetId,
|
||||
);
|
||||
|
||||
if (targetGroup) {
|
||||
activeGroupId = targetGroup.id;
|
||||
} else if (config.groups.length > 0) {
|
||||
activeGroupId = config.groups[0].id;
|
||||
}
|
||||
|
||||
// Auto-focus first enabled project
|
||||
const projects = getEnabledProjects();
|
||||
if (projects.length > 0) {
|
||||
activeProjectId = projects[0].id;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load groups config:', e);
|
||||
groupsConfig = { version: 1, groups: [], activeGroupId: '' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveWorkspace(): Promise<void> {
|
||||
if (!groupsConfig) return;
|
||||
await saveGroups(groupsConfig);
|
||||
}
|
||||
|
||||
// --- Group/project mutation ---
|
||||
|
||||
export function addGroup(group: GroupConfig): void {
|
||||
if (!groupsConfig) return;
|
||||
groupsConfig = {
|
||||
...groupsConfig,
|
||||
groups: [...groupsConfig.groups, group],
|
||||
};
|
||||
saveGroups(groupsConfig).catch(e => console.warn('Failed to save groups:', e));
|
||||
}
|
||||
|
||||
export function removeGroup(groupId: string): void {
|
||||
if (!groupsConfig) return;
|
||||
groupsConfig = {
|
||||
...groupsConfig,
|
||||
groups: groupsConfig.groups.filter(g => g.id !== groupId),
|
||||
};
|
||||
if (activeGroupId === groupId) {
|
||||
activeGroupId = groupsConfig.groups[0]?.id ?? '';
|
||||
activeProjectId = null;
|
||||
}
|
||||
saveGroups(groupsConfig).catch(e => console.warn('Failed to save groups:', e));
|
||||
}
|
||||
|
||||
export function updateProject(groupId: string, projectId: string, updates: Partial<ProjectConfig>): void {
|
||||
if (!groupsConfig) return;
|
||||
groupsConfig = {
|
||||
...groupsConfig,
|
||||
groups: groupsConfig.groups.map(g => {
|
||||
if (g.id !== groupId) return g;
|
||||
return {
|
||||
...g,
|
||||
projects: g.projects.map(p => {
|
||||
if (p.id !== projectId) return p;
|
||||
return { ...p, ...updates };
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
saveGroups(groupsConfig).catch(e => console.warn('Failed to save groups:', e));
|
||||
}
|
||||
|
||||
export function addProject(groupId: string, project: ProjectConfig): void {
|
||||
if (!groupsConfig) return;
|
||||
const group = groupsConfig.groups.find(g => g.id === groupId);
|
||||
if (!group || group.projects.length >= 5) return;
|
||||
groupsConfig = {
|
||||
...groupsConfig,
|
||||
groups: groupsConfig.groups.map(g => {
|
||||
if (g.id !== groupId) return g;
|
||||
return { ...g, projects: [...g.projects, project] };
|
||||
}),
|
||||
};
|
||||
saveGroups(groupsConfig).catch(e => console.warn('Failed to save groups:', e));
|
||||
}
|
||||
|
||||
export function removeProject(groupId: string, projectId: string): void {
|
||||
if (!groupsConfig) return;
|
||||
groupsConfig = {
|
||||
...groupsConfig,
|
||||
groups: groupsConfig.groups.map(g => {
|
||||
if (g.id !== groupId) return g;
|
||||
return { ...g, projects: g.projects.filter(p => p.id !== projectId) };
|
||||
}),
|
||||
};
|
||||
if (activeProjectId === projectId) {
|
||||
activeProjectId = null;
|
||||
}
|
||||
saveGroups(groupsConfig).catch(e => console.warn('Failed to save groups:', e));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue