diff --git a/js/mobile/src/features/stream-view/TaskParticleView.tsx b/js/mobile/src/features/stream-view/TaskParticleView.tsx
index c14ceb4..649e08c 100644
--- a/js/mobile/src/features/stream-view/TaskParticleView.tsx
+++ b/js/mobile/src/features/stream-view/TaskParticleView.tsx
@@ -1,5 +1,13 @@
import { useCallback, useEffect, useRef, useState } from 'react';
-import { Pressable, ScrollView, Text, TextInput, View } from 'react-native';
+import {
+ Keyboard,
+ Platform,
+ Pressable,
+ ScrollView,
+ Text,
+ TextInput,
+ View,
+} from 'react-native';
import { deleteField } from 'firebase/firestore';
import { Check, Plus, X } from 'lucide-react-native';
import type { ChecklistItem, Particle } from '@/api/types';
@@ -158,119 +166,146 @@ export function TaskParticleView({
className="flex-1 items-center justify-center px-6"
style={{ paddingTop: safe.top + 16, paddingBottom: safe.bottom + 16 }}
>
-
- {/* Title + done */}
-
-
+
+ {/* Title + done */}
+
- {done ? : null}
-
+
+ {done ? : null}
+
+ setEditing(true)}
+ onBlur={() => setEditing(false)}
+ onEndEditing={(e) => {
+ const value = e.nativeEvent.text;
+ if (value !== title) {
+ void updateParticleProperties<'task'>(docPath, {
+ title: value,
+ });
+ }
+ }}
+ placeholder="Task title"
+ placeholderTextColor="rgba(255,255,255,0.3)"
+ multiline
+ className={cn(
+ 'flex-1 text-white text-2xl font-semibold',
+ done && 'text-white/50 line-through',
+ )}
+ />
+
+
+ {/* Notes */}
setEditing(true)}
onBlur={() => setEditing(false)}
onEndEditing={(e) => {
const value = e.nativeEvent.text;
- if (value !== title) {
+ if (value !== (notes ?? '')) {
void updateParticleProperties<'task'>(docPath, {
- title: value,
+ notes: value,
});
}
}}
- placeholder="Task title"
+ placeholder="Add notes…"
placeholderTextColor="rgba(255,255,255,0.3)"
multiline
- className={cn(
- 'flex-1 text-white text-2xl font-semibold',
- done && 'text-white/50 line-through',
- )}
+ className="text-white/80 text-base"
/>
-
- {/* Notes */}
- setEditing(true)}
- onBlur={() => setEditing(false)}
- onEndEditing={(e) => {
- const value = e.nativeEvent.text;
- if (value !== (notes ?? '')) {
- void updateParticleProperties<'task'>(docPath, { notes: value });
- }
- }}
- placeholder="Add notes…"
- placeholderTextColor="rgba(255,255,255,0.3)"
- multiline
- className="text-white/80 text-base"
- />
-
- {/* Checklist */}
-
- {checklist.length > 0 ? (
-
- {doneCount} / {checklist.length} done
-
- ) : null}
- {checklist.map((item, index) => (
- handleToggleItem(index)}
- onCommitText={(text) => handleCommitItemText(index, text)}
- onRemove={() => handleRemoveItem(index)}
+ {/* Checklist */}
+
+ {checklist.length > 0 ? (
+
+ {doneCount} / {checklist.length} done
+
+ ) : null}
+ {checklist.map((item, index) => (
+ handleToggleItem(index)}
+ onCommitText={(text) => handleCommitItemText(index, text)}
+ onRemove={() => handleRemoveItem(index)}
+ onFocusChange={setEditing}
+ />
+ ))}
+
- ))}
-
-
+
- {/* Assignee */}
-
- Assignee
-
- handleAssign(null)}
- />
- {network?.humans?.map((human) => {
- const display = resolveHumanDisplay(human.id, network?.humans);
- return (
- handleAssign(human.id)}
- avatarHumanId={human.id}
- humans={network?.humans}
- />
- );
- })}
-
-
-
+ {/* Assignee */}
+
+ Assignee
+
+ handleAssign(null)}
+ />
+ {network?.humans?.map((human) => {
+ const display = resolveHumanDisplay(human.id, network?.humans);
+ return (
+ handleAssign(human.id)}
+ avatarHumanId={human.id}
+ humans={network?.humans}
+ />
+ );
+ })}
+
+
+
+
+ {/* While a field is focused the keyboard hides the stream's tap-zones,
+ so offer an explicit way out. Dismissing blurs the active field,
+ which flips `editing` off and resumes the dwell + tap navigation. */}
+ {editing ? (
+
+ Keyboard.dismiss()}
+ hitSlop={8}
+ accessibilityLabel="Done editing"
+ className="rounded-full bg-white/15 px-3 py-1.5"
+ >
+ Done
+
+
+ ) : null}
+
);
}