From 869f21ee2efaefed6a5aa4fbd417c25df8dec02a Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 18 Jan 2026 17:44:50 +0000 Subject: Add React Native mobile app for Makima (#3) * [WIP] Heartbeat checkpoint - 2026-01-18 02:58:27 UTC * feat(mobile): complete mobile app integration and verification - Add ThemeColors type export to Colors.ts for type safety - Export SUPABASE_URL from supabase.ts and use environment variables - Update .env.example with correct default URLs - Add comprehensive README.md with setup instructions Verified: - TypeScript compiles without errors - App exports successfully for iOS and Android - All screens accessible (login, dashboard, tasks, settings, task detail) - Auth flow working with Zustand store and Supabase Co-Authored-By: Claude Opus 4.5 * Task completion checkpoint --------- Co-authored-by: Claude Opus 4.5 --- apps/mobile/stores/taskStore.ts | 84 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 apps/mobile/stores/taskStore.ts (limited to 'apps/mobile/stores/taskStore.ts') diff --git a/apps/mobile/stores/taskStore.ts b/apps/mobile/stores/taskStore.ts new file mode 100644 index 0000000..1a08a45 --- /dev/null +++ b/apps/mobile/stores/taskStore.ts @@ -0,0 +1,84 @@ +import { create } from 'zustand'; +import type { TaskSummary, TaskOutputEntry } from '../lib/api'; + +interface TaskState { + // Data + tasks: TaskSummary[]; + selectedTaskId: string | null; + taskOutputs: Record; + + // Actions + setTasks: (tasks: TaskSummary[]) => void; + updateTask: (taskId: string, update: Partial) => void; + selectTask: (taskId: string | null) => void; + appendOutput: (taskId: string, output: TaskOutputEntry) => void; + setTaskOutputs: (taskId: string, outputs: TaskOutputEntry[]) => void; + clearTaskOutputs: (taskId: string) => void; +} + +export const useTaskStore = create((set) => ({ + // Initial state + tasks: [], + selectedTaskId: null, + taskOutputs: {}, + + // Actions + setTasks: (tasks) => + set({ tasks }), + + updateTask: (taskId, update) => + set((state) => ({ + tasks: state.tasks.map((task) => + task.id === taskId ? { ...task, ...update } : task + ), + })), + + selectTask: (taskId) => + set({ selectedTaskId: taskId }), + + appendOutput: (taskId, output) => + set((state) => { + const existing = state.taskOutputs[taskId] ?? []; + // Avoid duplicates by checking ID + if (existing.some((o) => o.id === output.id)) { + return state; + } + return { + taskOutputs: { + ...state.taskOutputs, + [taskId]: [...existing, output], + }, + }; + }), + + setTaskOutputs: (taskId, outputs) => + set((state) => ({ + taskOutputs: { + ...state.taskOutputs, + [taskId]: outputs, + }, + })), + + clearTaskOutputs: (taskId) => + set((state) => { + const { [taskId]: _, ...rest } = state.taskOutputs; + return { taskOutputs: rest }; + }), +})); + +// Selectors for common use cases +export const selectSelectedTask = (state: TaskState) => + state.tasks.find((t) => t.id === state.selectedTaskId); + +export const selectRunningTasks = (state: TaskState) => + state.tasks.filter((t) => + ['running', 'initializing', 'starting'].includes(t.status) + ); + +export const selectPendingTasks = (state: TaskState) => + state.tasks.filter((t) => t.status === 'pending'); + +export const selectCompletedTasks = (state: TaskState) => + state.tasks.filter((t) => + ['done', 'failed', 'merged'].includes(t.status) + ); -- cgit v1.2.3