feat(mobile): wire review note input to inbox approve/reject

- note-agent-actions API: updateApprovalState now accepts optional reviewNote
- inbox-store: approve/reject actions pass reviewNote through to backend
- Inbox screen: TextInput for review note above approval cards
- Review note cleared after successful approve/reject

Verification: mobile typecheck passes.
This commit is contained in:
saravanakumardb1 2026-03-10 19:41:39 -07:00
parent 12d90098eb
commit f8a4c18f27
3 changed files with 38 additions and 9 deletions

View File

@ -114,6 +114,7 @@ export async function updateApprovalState(
id: string,
workspaceId: string,
state: 'approved' | 'rejected',
reviewNote?: string,
): Promise<MobileApprovalItem> {
const updated = await getApiClient().fetch<NoteAgentActionDoc>(
`/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
@ -122,6 +123,7 @@ export async function updateApprovalState(
body: JSON.stringify({
state,
reviewedAt: new Date().toISOString(),
...(reviewNote ? { reviewNote } : {}),
}),
},
);

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { useInboxStore, type ActivityItem, type ApprovalItem, type InboxState } from '../../store/inbox-store';
import { colors } from '../../theme';
@ -11,6 +11,7 @@ export default function InboxScreen() {
const approve = useInboxStore((state: InboxState) => state.approve);
const reject = useInboxStore((state: InboxState) => state.reject);
const [pendingApprovalId, setPendingApprovalId] = useState<string | null>(null);
const [reviewNote, setReviewNote] = useState('');
useEffect(() => {
void hydrate();
@ -21,6 +22,17 @@ export default function InboxScreen() {
<Text style={styles.title}>Inbox</Text>
<Text style={styles.body}>Review simple agent actions and approve or reject them from mobile.</Text>
{isLoading ? <Text style={styles.empty}>Loading approvals</Text> : null}
{approvals.length > 0 ? (
<TextInput
style={styles.reviewNoteInput}
placeholder="Optional review note…"
placeholderTextColor={colors.textSecondary}
value={reviewNote}
onChangeText={setReviewNote}
multiline
numberOfLines={2}
/>
) : null}
{approvals.map((item: ApprovalItem) => (
<View key={item.id} style={styles.card}>
<Text style={styles.cardTitle}>{item.title}</Text>
@ -36,7 +48,9 @@ export default function InboxScreen() {
onPress={async () => {
setPendingApprovalId(item.id);
try {
await approve(item.id);
const note = reviewNote.trim() || undefined;
await approve(item.id, note);
setReviewNote('');
} finally {
setPendingApprovalId(null);
}
@ -55,7 +69,9 @@ export default function InboxScreen() {
onPress={async () => {
setPendingApprovalId(item.id);
try {
await reject(item.id);
const note = reviewNote.trim() || undefined;
await reject(item.id, note);
setReviewNote('');
} finally {
setPendingApprovalId(null);
}
@ -181,4 +197,15 @@ const styles = StyleSheet.create({
fontWeight: '700',
textTransform: 'uppercase',
},
reviewNoteInput: {
backgroundColor: colors.bgElevated,
borderRadius: 12,
borderWidth: 1,
borderColor: colors.borderDefault,
color: colors.textPrimary,
padding: 12,
fontSize: 14,
minHeight: 48,
textAlignVertical: 'top' as const,
},
});

View File

@ -28,8 +28,8 @@ export type InboxState = {
activity: ActivityItem[];
isLoading: boolean;
hydrate: () => Promise<void>;
approve: (id: string) => Promise<void>;
reject: (id: string) => Promise<void>;
approve: (id: string, reviewNote?: string) => Promise<void>;
reject: (id: string, reviewNote?: string) => Promise<void>;
};
function toApprovalItem(item: MobileApprovalItem): ApprovalItem {
@ -53,26 +53,26 @@ export const useInboxStore = create<InboxState>((set, get) => ({
isLoading: false,
});
},
async approve(id: string) {
async approve(id: string, reviewNote?: string) {
const current = get().approvals.find((item) => item.id === id);
if (!current) {
return;
}
const updated = await updateApprovalState(id, current.workspaceId, 'approved');
const updated = await updateApprovalState(id, current.workspaceId, 'approved', reviewNote);
set((state) => ({
approvals: state.approvals.map((item) =>
item.id === id ? toApprovalItem(updated) : item,
),
}));
},
async reject(id: string) {
async reject(id: string, reviewNote?: string) {
const current = get().approvals.find((item) => item.id === id);
if (!current) {
return;
}
const updated = await updateApprovalState(id, current.workspaceId, 'rejected');
const updated = await updateApprovalState(id, current.workspaceId, 'rejected', reviewNote);
set((state) => ({
approvals: state.approvals.map((item) =>
item.id === id ? toApprovalItem(updated) : item,