From 5456ed1959761a388173cfa915daf14954d91d7f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:35:22 +0000 Subject: [PATCH 1/2] feat: implement real git commit on approval in ChatScreen - Replaced mocked 'Approve' action with `GitService` integration - Implemented `handleApprovalResponse` to handle commit approvals - Added staging and commit logic using `GitService` - Added error handling and user feedback via `Alert` - Updated imports to include `GitService` and `Alert` Co-authored-by: jbdevprimary <2650679+jbdevprimary@users.noreply.github.com> --- app/(tabs)/chat.tsx | 73 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx index d8de9899..857232a8 100644 --- a/app/(tabs)/chat.tsx +++ b/app/(tabs)/chat.tsx @@ -5,9 +5,10 @@ * Uses paint daube icons for brand consistency. */ -import { type Message, useChatStore } from '@thumbcode/state'; -import { useEffect, useMemo, useRef } from 'react'; -import { FlatList, KeyboardAvoidingView, Platform, Pressable, View } from 'react-native'; +import { GitService } from '@thumbcode/core'; +import { type ApprovalMessage, type Message, useChatStore, useProjectStore, useUserStore } from '@thumbcode/state'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { Alert, FlatList, KeyboardAvoidingView, Platform, Pressable, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ChatInput, ChatMessage, ThreadList } from '@/components/chat'; import { ChevronDownIcon } from '@/components/icons'; @@ -22,9 +23,13 @@ export default function ChatScreen() { const setActiveThread = useChatStore((s) => s.setActiveThread); const respondToApproval = useChatStore((s) => s.respondToApproval); - const activeThreadTitle = useChatStore((s) => - activeThreadId ? s.threads.find((t) => t.id === activeThreadId)?.title : undefined + const projects = useProjectStore((s) => s.projects); + const userProfile = useUserStore((s) => s.githubProfile); + + const activeThread = useChatStore((s) => + activeThreadId ? s.threads.find((t) => t.id === activeThreadId) : undefined ); + const activeThreadTitle = activeThread?.title; const messages = useChatStore((s) => (activeThreadId ? (s.messages[activeThreadId] ?? []) : [])); const typingSenders = useChatStore((s) => @@ -55,6 +60,56 @@ export default function ChatScreen() { setActiveThread(id); }; + const handleApprovalResponse = useCallback( + async (messageId: string, approved: boolean) => { + if (!activeThreadId) return; + + const message = messages.find((m) => m.id === messageId); + + if (approved && message?.contentType === 'approval_request') { + const approvalMsg = message as ApprovalMessage; + if (approvalMsg.metadata.actionType === 'commit') { + try { + // Find project path + const projectId = activeThread?.projectId; + const project = projectId ? projects.find((p) => p.id === projectId) : undefined; + const repoDir = project?.localPath; + + if (!repoDir) { + Alert.alert('Error', 'No repository path found for project'); + return; + } + + // Stage all changes + await GitService.stage({ dir: repoDir, filepath: '.' }); + + // Commit + const author = { + name: userProfile?.name || userProfile?.login || 'User', + email: userProfile?.email || 'user@example.com', + }; + + await GitService.commit({ + dir: repoDir, + message: approvalMsg.metadata.actionDescription || 'Commit from chat', + author, + }); + + // Only mark as approved if commit succeeded + respondToApproval(messageId, activeThreadId, approved); + } catch (error) { + console.error('Failed to commit:', error); + Alert.alert('Commit Failed', error instanceof Error ? error.message : 'Unknown error'); + } + return; + } + } + + respondToApproval(messageId, activeThreadId, approved); + }, + [activeThreadId, activeThread, projects, userProfile, messages, respondToApproval] + ); + return ( item.id} renderItem={({ item }) => ( - { - if (!activeThreadId) return; - respondToApproval(messageId, activeThreadId, approved); - }} - /> + )} contentContainerStyle={{ paddingTop: 12, From 932df9602e0dd85d79c90cc669a29cd4e994cd36 Mon Sep 17 00:00:00 2001 From: Jon B Date: Wed, 11 Feb 2026 21:17:29 -0600 Subject: [PATCH 2/2] fix: resolve lint issues after rebase on main --- app/(tabs)/chat.tsx | 87 ++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx index 857232a8..9884ce79 100644 --- a/app/(tabs)/chat.tsx +++ b/app/(tabs)/chat.tsx @@ -6,7 +6,13 @@ */ import { GitService } from '@thumbcode/core'; -import { type ApprovalMessage, type Message, useChatStore, useProjectStore, useUserStore } from '@thumbcode/state'; +import { + type ApprovalMessage, + type Message, + useChatStore, + useProjectStore, + useUserStore, +} from '@thumbcode/state'; import { useCallback, useEffect, useMemo, useRef } from 'react'; import { Alert, FlatList, KeyboardAvoidingView, Platform, Pressable, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -60,54 +66,61 @@ export default function ChatScreen() { setActiveThread(id); }; + const performCommit = useCallback( + async (approvalMsg: ApprovalMessage) => { + const projectId = activeThread?.projectId; + const project = projectId ? projects.find((p) => p.id === projectId) : undefined; + const repoDir = project?.localPath; + + if (!repoDir) { + Alert.alert('Error', 'No repository path found for project'); + return false; + } + + await GitService.stage({ dir: repoDir, filepath: '.' }); + + const author = { + name: userProfile?.name || userProfile?.login || 'User', + email: userProfile?.email || 'user@example.com', + }; + + await GitService.commit({ + dir: repoDir, + message: approvalMsg.metadata.actionDescription || 'Commit from chat', + author, + }); + + return true; + }, + [activeThread, projects, userProfile] + ); + const handleApprovalResponse = useCallback( async (messageId: string, approved: boolean) => { if (!activeThreadId) return; const message = messages.find((m) => m.id === messageId); - - if (approved && message?.contentType === 'approval_request') { - const approvalMsg = message as ApprovalMessage; - if (approvalMsg.metadata.actionType === 'commit') { - try { - // Find project path - const projectId = activeThread?.projectId; - const project = projectId ? projects.find((p) => p.id === projectId) : undefined; - const repoDir = project?.localPath; - - if (!repoDir) { - Alert.alert('Error', 'No repository path found for project'); - return; - } - - // Stage all changes - await GitService.stage({ dir: repoDir, filepath: '.' }); - - // Commit - const author = { - name: userProfile?.name || userProfile?.login || 'User', - email: userProfile?.email || 'user@example.com', - }; - - await GitService.commit({ - dir: repoDir, - message: approvalMsg.metadata.actionDescription || 'Commit from chat', - author, - }); - - // Only mark as approved if commit succeeded + const isCommitApproval = + approved && + message?.contentType === 'approval_request' && + (message as ApprovalMessage).metadata.actionType === 'commit'; + + if (isCommitApproval) { + try { + const committed = await performCommit(message as ApprovalMessage); + if (committed) { respondToApproval(messageId, activeThreadId, approved); - } catch (error) { - console.error('Failed to commit:', error); - Alert.alert('Commit Failed', error instanceof Error ? error.message : 'Unknown error'); } - return; + } catch (error) { + console.error('Failed to commit:', error); + Alert.alert('Commit Failed', error instanceof Error ? error.message : 'Unknown error'); } + return; } respondToApproval(messageId, activeThreadId, approved); }, - [activeThreadId, activeThread, projects, userProfile, messages, respondToApproval] + [activeThreadId, messages, performCommit, respondToApproval] ); return (