mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 00:02:56 +00:00
* fix(mobile): dismiss the keyboard after sending to an agent Sending a message left the software keyboard up, covering the reply the user was waiting on. Drop it once the send is accepted, on all three send paths: the terminal live input, the buffered command input, and the chat composer. Gated on the tab being an agent session. A plain shell keeps the keyboard so back-to-back commands stay typeable, a rejected send keeps it so the handed-back draft stays editable, and the accessory shortcut row is untouched because dismissing would pull away the row being tapped. * fix(mobile): gate keyboard dismissal on accepted sends * fix(mobile): fence keyboard dismissal completions * fix(mobile): fence stale send completions * test(mobile): update terminal guard expectations * fix(mobile): restore rejected buffered drafts by origin * fix(mobile): preserve intentional buffered draft clears * fix(mobile): harden send dismissal authority * test(mobile): preserve Strict Mode send dismissal * fix(mobile): preserve drafts across terminal remints * fix(mobile): preserve draft ownership through terminal races * fix(mobile): harden draft recovery and send freshness * fix(mobile): fence route reuse and native draft clears * fix(mobile): preserve native draft edits before clear * test(mobile): pin the terminal-list sweep that bounds buffered drafts `bufferedTerminalDraftState.pruneDrafts(retainedHandles)` is the only bound on two structures that live as long as the session screen — the buffered-draft record and the pending-restoration map — and nothing failed when it was deleted or when it was pointed at the raw `terminal.list` handles instead of the retained set. Both mutations reddened 0 of 3,949 mobile tests. Adds the wiring pin (both mutations now redden it) plus two behavioural tests showing why the argument matters: `terminal.list` omits a chat-covered handle while the desktop graph reloads, so the raw list drops a draft the user is still holding while the retained set keeps it. --------- Co-authored-by: Merge Sim <merge@sim.local> Co-authored-by: Merge Sim <sim@local>
148 lines
5.2 KiB
TypeScript
148 lines
5.2 KiB
TypeScript
import { Pressable, Text, TextInput, View } from 'react-native'
|
|
import { MessageSquare, Plus, X } from 'lucide-react-native'
|
|
import { MobileSyntaxSegments } from '../components/MobileSyntaxSegments'
|
|
import { colors } from '../theme/mobile-theme'
|
|
import { styles } from './mobile-session-styles'
|
|
import type { DiffComment } from '../../../src/shared/diff-comment-types'
|
|
import type { RenderableDiffLine } from './mobile-session-route-types'
|
|
|
|
export function MobileDiffCommentLineRow({
|
|
line,
|
|
title,
|
|
index,
|
|
comments,
|
|
activeCommentLine,
|
|
commentDraft,
|
|
commentsBusy,
|
|
onStartComment,
|
|
onCancelComment,
|
|
onDraftChange,
|
|
onSubmitComment,
|
|
onDeleteComment
|
|
}: {
|
|
line: RenderableDiffLine
|
|
title: string
|
|
index: number
|
|
comments: DiffComment[]
|
|
activeCommentLine: number | null
|
|
commentDraft: string
|
|
commentsBusy: boolean
|
|
onStartComment: (lineNumber: number) => void
|
|
onCancelComment: () => void
|
|
onDraftChange: (value: string) => void
|
|
onSubmitComment: (lineNumber: number) => void
|
|
onDeleteComment: (commentId: string) => void
|
|
}) {
|
|
const commentLine = line.newLineNumber
|
|
const isCommenting = commentLine !== undefined && activeCommentLine === commentLine
|
|
const canComment = commentLine !== undefined
|
|
// Why: review notes anchor to the modified side, so show that line number in the single mobile gutter.
|
|
const gutterLineNumber = line.newLineNumber ?? line.oldLineNumber ?? ''
|
|
return (
|
|
<View style={styles.diffLineBlock}>
|
|
<View
|
|
style={[
|
|
styles.diffLine,
|
|
line.kind === 'add' && styles.diffLineAdded,
|
|
line.kind === 'delete' && styles.diffLineDeleted
|
|
]}
|
|
>
|
|
<Text style={styles.diffGutter}>{gutterLineNumber}</Text>
|
|
<Text
|
|
selectable
|
|
style={styles.diffText}
|
|
accessibilityLabel={`${title} diff line ${index + 1}`}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.diffPrefix,
|
|
line.kind === 'add' && styles.diffPrefixAdded,
|
|
line.kind === 'delete' && styles.diffPrefixDeleted
|
|
]}
|
|
>
|
|
{line.kind === 'add' ? '+ ' : line.kind === 'delete' ? '- ' : ' '}
|
|
</Text>
|
|
<MobileSyntaxSegments segments={line.segments} />
|
|
</Text>
|
|
{canComment ? (
|
|
<Pressable
|
|
style={({ pressed }) => [
|
|
styles.diffCommentAddButton,
|
|
pressed && styles.diffCommentAddButtonPressed,
|
|
commentsBusy && styles.diffCommentButtonDisabled
|
|
]}
|
|
disabled={commentsBusy}
|
|
onPress={() => {
|
|
if (commentLine !== undefined) {
|
|
onStartComment(commentLine)
|
|
}
|
|
}}
|
|
accessibilityLabel={`Add note on line ${commentLine}`}
|
|
>
|
|
<Plus size={12} color={colors.textSecondary} strokeWidth={2.3} />
|
|
</Pressable>
|
|
) : null}
|
|
</View>
|
|
{comments.length > 0 ? (
|
|
<View style={styles.diffCommentList}>
|
|
{comments.map((comment) => (
|
|
<View key={comment.id} style={styles.diffCommentCard}>
|
|
<View style={styles.diffCommentHeader}>
|
|
<MessageSquare size={12} color={colors.textMuted} strokeWidth={2.2} />
|
|
<Text style={styles.diffCommentMeta}>Line {comment.lineNumber}</Text>
|
|
<Pressable
|
|
style={styles.diffCommentDeleteButton}
|
|
disabled={commentsBusy}
|
|
onPress={() => onDeleteComment(comment.id)}
|
|
accessibilityLabel={`Delete note on line ${comment.lineNumber}`}
|
|
>
|
|
<X size={12} color={colors.textMuted} strokeWidth={2.2} />
|
|
</Pressable>
|
|
</View>
|
|
<Text style={styles.diffCommentBody}>{comment.body}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
) : null}
|
|
{isCommenting ? (
|
|
<View style={styles.diffCommentComposer}>
|
|
<TextInput
|
|
style={[styles.textInput, styles.diffCommentInput]}
|
|
value={commentDraft}
|
|
onChangeText={onDraftChange}
|
|
placeholder="Add review note"
|
|
placeholderTextColor={colors.textMuted}
|
|
editable={!commentsBusy}
|
|
multiline
|
|
textAlignVertical="top"
|
|
autoFocus
|
|
/>
|
|
<View style={styles.diffCommentComposerActions}>
|
|
<Pressable
|
|
style={styles.diffCommentSecondaryAction}
|
|
disabled={commentsBusy}
|
|
onPress={onCancelComment}
|
|
>
|
|
<Text style={styles.diffCommentSecondaryText}>Cancel</Text>
|
|
</Pressable>
|
|
<Pressable
|
|
style={[
|
|
styles.diffCommentPrimaryAction,
|
|
(!commentDraft.trim() || commentsBusy) && styles.diffCommentButtonDisabled
|
|
]}
|
|
disabled={!commentDraft.trim() || commentsBusy}
|
|
onPress={() => {
|
|
if (commentLine !== undefined) {
|
|
onSubmitComment(commentLine)
|
|
}
|
|
}}
|
|
>
|
|
<Text style={styles.diffCommentPrimaryText}>Save note</Text>
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
)
|
|
}
|