feat(electrobun): hierarchical state tree (Rule 58)
New files: - project-state.types.ts: all per-project state interfaces - project-state.svelte.ts: unified per-project state with version counter - app-state.svelte.ts: root facade re-exporting all stores as appState.* Rewired components (no more local $state): - ProjectCard: reads via appState.agent.* and appState.project.tab.* - TerminalTabs: state in appState.project.terminals.* - FileBrowser: state in appState.project.files.* - CommsTab: state in appState.project.comms.* - TaskBoardTab: state in appState.project.tasks.* All follow Rule 57 (no $derived with new objects) and Rule 58 (state tree architecture, components are pure renderers).
This commit is contained in:
parent
ae4c07c160
commit
162b5417e4
9 changed files with 870 additions and 400 deletions
|
|
@ -8,15 +8,8 @@
|
|||
import DocsTab from './DocsTab.svelte';
|
||||
import SshTab from './SshTab.svelte';
|
||||
import StatusDot from './ui/StatusDot.svelte';
|
||||
import {
|
||||
startAgent, stopAgent, sendPrompt, getSession, hasSession,
|
||||
loadLastSession,
|
||||
type AgentStatus, type AgentMessage,
|
||||
} from './agent-store.svelte.ts';
|
||||
import {
|
||||
getActiveTab, setActiveTab, isTabActivated,
|
||||
ALL_TABS, type ProjectTab,
|
||||
} from './project-tabs-store.svelte.ts';
|
||||
import { appState, type AgentStatus, type AgentMessage, type ProjectTab } from './app-state.svelte.ts';
|
||||
import { ALL_TABS } from './project-tabs-store.svelte.ts';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
|
@ -61,9 +54,7 @@
|
|||
|
||||
// ── Agent session (reactive from store) ──────────────────────────
|
||||
const EMPTY_MESSAGES: AgentMessage[] = [];
|
||||
// ALL agent session state as plain getter functions — NO $derived.
|
||||
// $derived + store getters that return new refs = infinite loops in Svelte 5.
|
||||
function getAgentSession() { return getSession(id); }
|
||||
function getAgentSession() { return appState.agent.getSession(id); }
|
||||
function getAgentStatus(): AgentStatus { return getAgentSession()?.status ?? 'idle'; }
|
||||
function getAgentMessages(): AgentMessage[] { return getAgentSession()?.messages ?? []; }
|
||||
function getAgentCost(): number { return getAgentSession()?.costUsd ?? 0; }
|
||||
|
|
@ -107,7 +98,7 @@
|
|||
return getAgentMessages().length > 0 ? getAgentMessages() : EMPTY_MESSAGES;
|
||||
}
|
||||
|
||||
// ── Clone dialog state ──────────────────────────────────────────
|
||||
// ── Clone dialog state (needs $state for template bindings) ─────
|
||||
let showCloneDialog = $state(false);
|
||||
let cloneBranchName = $state('');
|
||||
let cloneError = $state('');
|
||||
|
|
@ -129,33 +120,31 @@
|
|||
showCloneDialog = false;
|
||||
}
|
||||
|
||||
// Derived from project-tabs-store for reactive reads
|
||||
function getCurrentTab() { return getActiveTab(id); }
|
||||
// Tab state from project state tree
|
||||
function getCurrentTab() { return appState.project.tab.getActiveTab(id); }
|
||||
|
||||
// ── Load last session on mount (once, not reactive) ─────────────────
|
||||
import { onMount } from 'svelte';
|
||||
onMount(() => { loadLastSession(id); });
|
||||
onMount(() => { appState.agent.loadLastSession(id); });
|
||||
|
||||
function setTab(tab: ProjectTab) {
|
||||
setActiveTab(id, tab);
|
||||
appState.project.tab.setActiveTab(id, tab);
|
||||
}
|
||||
|
||||
function handleSend(text: string) {
|
||||
if (hasSession(id)) {
|
||||
// Session exists — send follow-up prompt
|
||||
sendPrompt(id, text).catch((err) => {
|
||||
if (appState.agent.hasSession(id)) {
|
||||
appState.agent.sendPrompt(id, text).catch((err) => {
|
||||
console.error('[agent.prompt] error:', err);
|
||||
});
|
||||
} else {
|
||||
// No session — start a new agent
|
||||
startAgent(id, provider, text, { cwd, model }).catch((err) => {
|
||||
appState.agent.startAgent(id, provider, text, { cwd, model }).catch((err) => {
|
||||
console.error('[agent.start] error:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleStop() {
|
||||
stopAgent(id).catch((err) => {
|
||||
appState.agent.stopAgent(id).catch((err) => {
|
||||
console.error('[agent.stop] error:', err);
|
||||
});
|
||||
}
|
||||
|
|
@ -373,7 +362,7 @@
|
|||
role="tabpanel"
|
||||
aria-label="Files"
|
||||
>
|
||||
<FileBrowser {cwd} />
|
||||
<FileBrowser {cwd} projectId={id} />
|
||||
</div>
|
||||
|
||||
<!-- SSH tab -->
|
||||
|
|
@ -409,7 +398,7 @@
|
|||
role="tabpanel"
|
||||
aria-label="Comms"
|
||||
>
|
||||
<CommsTab {groupId} agentId={id} />
|
||||
<CommsTab {groupId} projectId={id} agentId={id} />
|
||||
</div>
|
||||
|
||||
<!-- Tasks tab (kanban board) -->
|
||||
|
|
@ -421,7 +410,7 @@
|
|||
role="tabpanel"
|
||||
aria-label="Tasks"
|
||||
>
|
||||
<TaskBoardTab {groupId} agentId={id} />
|
||||
<TaskBoardTab {groupId} projectId={id} agentId={id} />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue