From 869f21ee2efaefed6a5aa4fbd417c25df8dec02a Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
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 <noreply@anthropic.com>

* Task completion checkpoint

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
---
 apps/mobile/components/TaskListItem.tsx | 199 ++++++++++++++++++++++++++++++++
 1 file changed, 199 insertions(+)
 create mode 100644 apps/mobile/components/TaskListItem.tsx

(limited to 'apps/mobile/components/TaskListItem.tsx')

diff --git a/apps/mobile/components/TaskListItem.tsx b/apps/mobile/components/TaskListItem.tsx
new file mode 100644
index 0000000..c21f41a
--- /dev/null
+++ b/apps/mobile/components/TaskListItem.tsx
@@ -0,0 +1,199 @@
+import React from 'react';
+import {
+  View,
+  Text,
+  StyleSheet,
+  TouchableOpacity,
+  useColorScheme,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { Colors } from '../constants/Colors';
+import { TaskStatusBadge } from './TaskStatusBadge';
+import type { TaskSummary } from '../lib/api';
+
+interface TaskListItemProps {
+  task: TaskSummary;
+  onPress: (task: TaskSummary) => void;
+}
+
+/**
+ * Format relative time from a date string
+ */
+function formatRelativeTime(dateString: string): string {
+  const date = new Date(dateString);
+  const now = new Date();
+  const diffMs = now.getTime() - date.getTime();
+  const diffSec = Math.floor(diffMs / 1000);
+  const diffMin = Math.floor(diffSec / 60);
+  const diffHour = Math.floor(diffMin / 60);
+  const diffDay = Math.floor(diffHour / 24);
+
+  if (diffSec < 60) return 'just now';
+  if (diffMin < 60) return `${diffMin}m ago`;
+  if (diffHour < 24) return `${diffHour}h ago`;
+  if (diffDay < 7) return `${diffDay}d ago`;
+
+  return date.toLocaleDateString();
+}
+
+/**
+ * Truncate text with ellipsis
+ */
+function truncate(text: string, maxLength: number): string {
+  if (text.length <= maxLength) return text;
+  return text.slice(0, maxLength - 3) + '...';
+}
+
+export function TaskListItem({ task, onPress }: TaskListItemProps) {
+  const colorScheme = useColorScheme() ?? 'light';
+  const colors = Colors[colorScheme];
+
+  const isRunning = ['running', 'initializing', 'starting'].includes(task.status);
+  const isCompleted = ['done', 'merged'].includes(task.status);
+  const isFailed = task.status === 'failed';
+
+  return (
+    <TouchableOpacity
+      style={[
+        styles.container,
+        {
+          backgroundColor: colors.card,
+          borderColor: colors.border,
+        },
+      ]}
+      onPress={() => onPress(task)}
+      activeOpacity={0.7}
+    >
+      <View style={styles.leftSection}>
+        <TaskStatusBadge status={task.status} size="medium" />
+      </View>
+
+      <View style={styles.content}>
+        <View style={styles.header}>
+          <Text
+            style={[styles.name, { color: colors.text }]}
+            numberOfLines={1}
+          >
+            {task.name}
+          </Text>
+          {task.isSupervisor && (
+            <View style={styles.supervisorBadge}>
+              <Ionicons name="star" size={12} color={colors.tint} />
+            </View>
+          )}
+        </View>
+
+        {task.contractName && (
+          <Text
+            style={[styles.contractName, { color: colors.secondaryText }]}
+            numberOfLines={1}
+          >
+            {task.contractName}
+          </Text>
+        )}
+
+        {task.progressSummary && (
+          <Text
+            style={[styles.progressSummary, { color: colors.secondaryText }]}
+            numberOfLines={2}
+          >
+            {truncate(task.progressSummary, 100)}
+          </Text>
+        )}
+
+        <View style={styles.footer}>
+          <Text style={[styles.time, { color: colors.secondaryText }]}>
+            {isRunning
+              ? `Started ${formatRelativeTime(task.updatedAt)}`
+              : isCompleted
+              ? `Completed ${formatRelativeTime(task.updatedAt)}`
+              : isFailed
+              ? `Failed ${formatRelativeTime(task.updatedAt)}`
+              : `Created ${formatRelativeTime(task.createdAt)}`}
+          </Text>
+
+          {task.subtaskCount > 0 && (
+            <View style={styles.subtaskBadge}>
+              <Ionicons
+                name="git-branch-outline"
+                size={12}
+                color={colors.secondaryText}
+              />
+              <Text style={[styles.subtaskCount, { color: colors.secondaryText }]}>
+                {task.subtaskCount}
+              </Text>
+            </View>
+          )}
+        </View>
+      </View>
+
+      <View style={styles.rightSection}>
+        <Ionicons
+          name="chevron-forward"
+          size={20}
+          color={colors.secondaryText}
+        />
+      </View>
+    </TouchableOpacity>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 12,
+    paddingHorizontal: 16,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    minHeight: 64,
+  },
+  leftSection: {
+    marginRight: 12,
+    alignItems: 'center',
+    justifyContent: 'center',
+  },
+  content: {
+    flex: 1,
+    gap: 2,
+  },
+  header: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 6,
+  },
+  name: {
+    fontSize: 16,
+    fontWeight: '600',
+    flex: 1,
+  },
+  supervisorBadge: {
+    padding: 2,
+  },
+  contractName: {
+    fontSize: 13,
+  },
+  progressSummary: {
+    fontSize: 13,
+    marginTop: 2,
+  },
+  footer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    marginTop: 4,
+  },
+  time: {
+    fontSize: 12,
+  },
+  subtaskBadge: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 4,
+  },
+  subtaskCount: {
+    fontSize: 12,
+  },
+  rightSection: {
+    marginLeft: 8,
+  },
+});
-- 
cgit v1.2.3

