// [DEF:taskDrawer:Store] // @TIER: CRITICAL // @PURPOSE: Manage Task Drawer visibility and resource-to-task mapping // @LAYER: UI // @INVARIANT: resourceTaskMap always reflects current task associations // // @UX_STATE: Closed -> Drawer hidden, no active task // @UX_STATE: Open -> Drawer visible, logs streaming // @UX_STATE: InputRequired -> Interactive form rendered in drawer // // @TEST_CONTRACT: TaskDrawerStore -> // { // required_fields: {isOpen: boolean, activeTaskId: string|null, resourceTaskMap: Object}, // invariants: [ // "Updates isOpen and activeTaskId properly on openDrawerForTask", // "Updates isOpen and activeTaskId=null on openDrawer", // "Properly sets isOpen=false on closeDrawer", // "Maintains mapping in resourceTaskMap correctly via updateResourceTask" // ] // } // @TEST_FIXTURE: valid_store_state -> {"isOpen": true, "activeTaskId": "test_1", "resourceTaskMap": {"res1": {"taskId": "test_1", "status": "RUNNING"}}} // @TEST_INVARIANT: state_management -> verifies: [valid_store_state] import { writable } from 'svelte/store'; const TASK_DRAWER_AUTO_OPEN_STORAGE_KEY = "ss_tools.profile.auto_open_task_drawer"; function readAutoOpenTaskDrawerPreference() { if (typeof window === "undefined") { return true; } const rawValue = window.localStorage.getItem(TASK_DRAWER_AUTO_OPEN_STORAGE_KEY); if (rawValue === "false") return false; if (rawValue === "true") return true; return true; } let autoOpenTaskDrawerPreference = readAutoOpenTaskDrawerPreference(); const initialState = { isOpen: false, activeTaskId: null, resourceTaskMap: {} }; export const taskDrawerStore = writable(initialState); /** * Open drawer for a specific task * @param {string} taskId - The task ID to show in drawer * @UX_STATE: Open -> Drawer visible, logs streaming */ export function openDrawerForTask(taskId) { if (!taskId) { console.log("[taskDrawer.openDrawerForTask][Action] Skip open: taskId is empty"); return false; } console.log(`[taskDrawer.openDrawerForTask][Action] Opening drawer for task ${taskId}`); taskDrawerStore.update(state => ({ ...state, isOpen: true, activeTaskId: taskId })); return true; } /** * Update user preference for automatic drawer opening. * @param {boolean} enabled - Whether automatic opening is enabled. */ export function setTaskDrawerAutoOpenPreference(enabled) { autoOpenTaskDrawerPreference = enabled !== false; if (typeof window !== "undefined") { window.localStorage.setItem( TASK_DRAWER_AUTO_OPEN_STORAGE_KEY, autoOpenTaskDrawerPreference ? "true" : "false", ); } } /** * Read current user preference for automatic drawer opening. * @returns {boolean} true if automatic drawer opening is enabled. */ export function getTaskDrawerAutoOpenPreference() { return autoOpenTaskDrawerPreference; } /** * Open drawer for a task only when user preference allows auto-open. * @param {string} taskId - The task ID to show in drawer. * @returns {boolean} true if drawer was opened. */ export function openDrawerForTaskIfPreferred(taskId) { if (autoOpenTaskDrawerPreference !== true) { console.log( `[taskDrawer.openDrawerForTaskIfPreferred][Action] Skip auto-open for task ${taskId}`, ); return false; } return openDrawerForTask(taskId); } /** * Open drawer in list mode (no specific task) * @UX_STATE: Open -> Drawer visible, showing recent task list */ export function openDrawer() { console.log('[taskDrawer.openDrawer][Action] Opening drawer in list mode'); taskDrawerStore.update(state => ({ ...state, isOpen: true, activeTaskId: null })); } /** * Close the drawer (task continues running) * @UX_STATE: Closed -> Drawer hidden, no active task */ export function closeDrawer() { console.log('[taskDrawer.closeDrawer][Action] Closing drawer'); taskDrawerStore.update(state => ({ ...state, isOpen: false, activeTaskId: null })); } /** * Update resource-to-task mapping * @param {string} resourceId - Resource ID (dashboard uuid, dataset id, etc.) * @param {string} taskId - Task ID associated with this resource * @param {string} status - Task status (IDLE, RUNNING, WAITING_INPUT, SUCCESS, ERROR) */ export function updateResourceTask(resourceId, taskId, status) { console.log(`[taskDrawer.updateResourceTask][Action] Updating resource ${resourceId} -> task ${taskId}, status ${status}`); taskDrawerStore.update(state => { const newMap = { ...state.resourceTaskMap }; if (status === 'IDLE' || status === 'SUCCESS' || status === 'ERROR') { // Remove mapping when task completes delete newMap[resourceId]; } else { // Add or update mapping newMap[resourceId] = { taskId, status }; } return { ...state, resourceTaskMap: newMap }; }); } /** * Get task status for a specific resource * @param {string} resourceId - Resource ID * @returns {Object|null} Task info or null if no active task */ export function getTaskForResource(resourceId) { let result = null; taskDrawerStore.subscribe(state => { result = state.resourceTaskMap[resourceId] || null; })(); return result; } // [/DEF:taskDrawer:Store]