/** * App-Level State Machine * * Manages the top-level application state across three main modes: * - convo: Active conversation (voice or text) * - edit: Editing a node * - galaxy: 3D visualization of node graph * * This machine sits above the conversation machine (which contains voice/text modes). * It does NOT duplicate the voice mode logic - that lives in voice-machine.ts. */ import { setup, assign } from 'xstate'; export interface NodeDraft { title: string; content: string; conversationContext?: string; // Last N messages as context } export interface Node { id: string; title: string; content: string; atp_uri?: string; } interface AppContext { currentNodeId: string | null; pendingNodeDraft: NodeDraft | null; mode: 'mobile' | 'desktop'; lastError: string | null; } type AppEvent = | { type: 'NAVIGATE_TO_CONVO' } | { type: 'NAVIGATE_TO_EDIT'; nodeId?: string; draft?: NodeDraft } | { type: 'NAVIGATE_TO_GALAXY' } | { type: 'CREATE_NODE_FROM_CONVERSATION'; draft: NodeDraft } | { type: 'PUBLISH_NODE_SUCCESS'; nodeId: string } | { type: 'CANCEL_EDIT' } | { type: 'SET_MODE'; mode: 'mobile' | 'desktop' } | { type: 'ERROR'; message: string }; export const appMachine = setup({ types: { context: {} as AppContext, events: {} as AppEvent, }, actions: { setCurrentNode: assign({ currentNodeId: ({ event }) => event.type === 'NAVIGATE_TO_EDIT' ? event.nodeId || null : null, }), setPendingDraft: assign({ pendingNodeDraft: ({ event }) => { if (event.type === 'NAVIGATE_TO_EDIT' && event.draft) { console.log('[App Machine] Setting pending draft:', event.draft); return event.draft; } if (event.type === 'CREATE_NODE_FROM_CONVERSATION') { console.log('[App Machine] Creating node from conversation:', event.draft); return event.draft; } return null; }, }), clearDraft: assign({ pendingNodeDraft: null, currentNodeId: null, }), setPublishedNode: assign({ currentNodeId: ({ event }) => event.type === 'PUBLISH_NODE_SUCCESS' ? event.nodeId : null, pendingNodeDraft: null, }), setMode: assign({ mode: ({ event }) => (event.type === 'SET_MODE' ? event.mode : 'desktop'), }), setError: assign({ lastError: ({ event }) => (event.type === 'ERROR' ? event.message : null), }), clearError: assign({ lastError: null, }), logTransition: ({ context, event }) => { console.log('[App Machine] Event:', event.type); console.log('[App Machine] Context:', { currentNodeId: context.currentNodeId, hasDraft: !!context.pendingNodeDraft, mode: context.mode, }); }, }, }).createMachine({ id: 'app', initial: 'convo', context: { currentNodeId: null, pendingNodeDraft: null, mode: 'desktop', lastError: null, }, on: { SET_MODE: { actions: ['setMode', 'logTransition'], }, ERROR: { actions: ['setError', 'logTransition'], }, }, states: { convo: { tags: ['conversation'], entry: ['clearError', 'logTransition'], on: { NAVIGATE_TO_EDIT: { target: 'edit', actions: ['setCurrentNode', 'setPendingDraft', 'logTransition'], }, CREATE_NODE_FROM_CONVERSATION: { target: 'edit', actions: ['setPendingDraft', 'logTransition'], }, NAVIGATE_TO_GALAXY: { target: 'galaxy', actions: ['logTransition'], }, }, }, edit: { tags: ['editing'], entry: ['clearError', 'logTransition'], on: { NAVIGATE_TO_CONVO: { target: 'convo', actions: ['logTransition'], }, NAVIGATE_TO_GALAXY: { target: 'galaxy', actions: ['logTransition'], }, PUBLISH_NODE_SUCCESS: { target: 'galaxy', actions: ['setPublishedNode', 'logTransition'], }, CANCEL_EDIT: { target: 'convo', actions: ['clearDraft', 'logTransition'], }, }, }, galaxy: { tags: ['visualization'], entry: ['clearError', 'logTransition'], on: { NAVIGATE_TO_CONVO: { target: 'convo', actions: ['logTransition'], }, NAVIGATE_TO_EDIT: { target: 'edit', actions: ['setCurrentNode', 'setPendingDraft', 'logTransition'], }, }, }, }, });