Matthias Nott
2026-03-15 32ede5388bb6c66c5d5679c2d73fd6ec6b2342bf
Update splash screen config and add session unread indicator

High-level changes:
- Configure expo-splash-screen plugin with unified icon asset
- Add visual unread indicator for sessions with new activity
- Improve session rename UX by auto-scrolling to edited item

app.json:
- Replace splash-icon.png reference with icon.png for
consistency
- Add expo-splash-screen plugin configuration with 200px
image width and dark background color

components/SessionDrawer.tsx:
- Add `isUnread` prop to `SessionRow` to show server-pushed
unread state
- Display blue dot next to session name when `isUnread` is
true but `unreadCount` is 0 (indicates new activity not
yet reflected in count)
- Add `listRef` to DraggableFlatList for programmatic
scrolling
- Auto-scroll to session being renamed after keyboard
appears (400ms delay) so input remains visible
- Update `renderItem` dependencies to include
`unreadSessions` and `orderedSessions`

components/SessionPicker.tsx:
- Add `sessionScrollRef` to ScrollView for programmatic
scrolling
- Auto-scroll to session being renamed after keyboard
appears to keep rename input visible during editing

These changes improve session management UX by providing
better visual feedback for unread sessions and ensuring
rename inputs remain visible when the keyboard appears.
3 files modified
changed files
app.json patch | view | blame | history
components/SessionDrawer.tsx patch | view | blame | history
components/SessionPicker.tsx patch | view | blame | history
app.json
....@@ -9,7 +9,7 @@
99 "newArchEnabled": true,
1010 "scheme": "pailot",
1111 "splash": {
12
- "image": "./assets/splash-icon.png",
12
+ "image": "./assets/icon.png",
1313 "resizeMode": "contain",
1414 "backgroundColor": "#0A0A0F"
1515 },
....@@ -45,6 +45,11 @@
4545 "bundler": "metro"
4646 },
4747 "plugins": [
48
+ ["expo-splash-screen", {
49
+ "image": "./assets/icon.png",
50
+ "backgroundColor": "#0A0A0F",
51
+ "imageWidth": 200
52
+ }],
4853 "expo-router",
4954 [
5055 "expo-audio",
components/SessionDrawer.tsx
....@@ -44,6 +44,7 @@
4444 function SessionRow({
4545 session,
4646 unreadCount,
47
+ isUnread,
4748 onSwitch,
4849 onLongPress,
4950 onDelete,
....@@ -53,6 +54,7 @@
5354 }: {
5455 session: WsSession;
5556 unreadCount: number;
57
+ isUnread: boolean;
5658 onSwitch: () => void;
5759 onLongPress: () => void;
5860 onDelete: () => void;
....@@ -152,16 +154,32 @@
152154
153155 {/* Name + subtitle — middle */}
154156 <View style={{ flex: 1, marginLeft: 14 }}>
155
- <Text
156
- style={{
157
- color: session.isActive ? colors.accent : colors.text,
158
- fontSize: 17,
159
- fontWeight: session.isActive ? "700" : "600",
160
- }}
161
- numberOfLines={1}
162
- >
163
- {session.name}
164
- </Text>
157
+ <View style={{ flexDirection: "row", alignItems: "center" }}>
158
+ <Text
159
+ style={{
160
+ color: session.isActive ? colors.accent : colors.text,
161
+ fontSize: 17,
162
+ fontWeight: session.isActive ? "700" : "600",
163
+ flexShrink: 1,
164
+ }}
165
+ numberOfLines={1}
166
+ >
167
+ {session.name}
168
+ </Text>
169
+ {/* Server-pushed unread dot — shown when the server signals new activity */}
170
+ {isUnread && unreadCount === 0 && (
171
+ <View
172
+ style={{
173
+ width: 8,
174
+ height: 8,
175
+ borderRadius: 4,
176
+ backgroundColor: colors.accent,
177
+ marginLeft: 6,
178
+ flexShrink: 0,
179
+ }}
180
+ />
181
+ )}
182
+ </View>
165183 <Text
166184 style={{
167185 color: colors.textMuted,
....@@ -266,6 +284,7 @@
266284 fetchProjects,
267285 projects,
268286 unreadCounts,
287
+ unreadSessions,
269288 } = useChat();
270289 const { colors } = useTheme();
271290 const [editingId, setEditingId] = useState<string | null>(null);
....@@ -276,6 +295,7 @@
276295 const [rendered, setRendered] = useState(false);
277296 const [keyboardHeight, setKeyboardHeight] = useState(0);
278297 const pickerScrollRef = useRef<ScrollView>(null);
298
+ const listRef = useRef<DraggableFlatList<WsSession>>(null);
279299
280300 useEffect(() => {
281301 const showSub = Keyboard.addListener("keyboardWillShow", (e) => {
....@@ -361,7 +381,16 @@
361381 const handleStartRename = useCallback((session: WsSession) => {
362382 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
363383 setEditingId(session.id);
364
- }, []);
384
+ // Scroll to the item after keyboard appears so it's visible
385
+ const idx = orderedSessions.findIndex(s => s.id === session.id);
386
+ if (idx >= 0 && listRef.current) {
387
+ setTimeout(() => {
388
+ try {
389
+ (listRef.current as any)?.scrollToIndex({ index: idx, animated: true, viewPosition: 0.3 });
390
+ } catch { /* ignore if index out of range */ }
391
+ }, 400);
392
+ }
393
+ }, [orderedSessions]);
365394
366395 const handleConfirmRename = useCallback(
367396 (sessionId: string, newName: string) => {
....@@ -414,6 +443,7 @@
414443 <SessionRow
415444 session={item}
416445 unreadCount={unreadCounts[item.id] ?? 0}
446
+ isUnread={unreadSessions.has(item.id)}
417447 onSwitch={() => handleSwitch(item)}
418448 onLongPress={() => handleStartRename(item)}
419449 onDelete={() => handleRemove(item)}
....@@ -424,7 +454,7 @@
424454 </ScaleDecorator>
425455 );
426456 },
427
- [editingId, unreadCounts, colors, handleSwitch, handleStartRename, handleRemove, handleConfirmRename],
457
+ [editingId, unreadCounts, unreadSessions, colors, handleSwitch, handleStartRename, handleRemove, handleConfirmRename],
428458 );
429459
430460 const keyExtractor = useCallback((item: WsSession) => item.id, []);
....@@ -537,6 +567,7 @@
537567 </View>
538568 ) : (
539569 <DraggableFlatList
570
+ ref={listRef}
540571 data={orderedSessions}
541572 keyExtractor={keyExtractor}
542573 renderItem={renderItem}
components/SessionPicker.tsx
....@@ -265,6 +265,7 @@
265265 } = useChat();
266266 const [editingId, setEditingId] = useState<string | null>(null);
267267 const [keyboardHeight, setKeyboardHeight] = useState(0);
268
+ const sessionScrollRef = useRef<ScrollView>(null);
268269
269270 // Sort: active first, then by index
270271 const sortedSessions = [...sessions].sort((a, b) => {
....@@ -312,7 +313,14 @@
312313 const handleStartRename = useCallback((session: WsSession) => {
313314 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
314315 setEditingId(session.id);
315
- }, []);
316
+ // Scroll down after keyboard appears so the rename field is visible
317
+ const idx = sortedSessions.findIndex(s => s.id === session.id);
318
+ if (idx >= 0 && sessionScrollRef.current) {
319
+ setTimeout(() => {
320
+ sessionScrollRef.current?.scrollTo({ y: idx * 60, animated: true });
321
+ }, 400);
322
+ }
323
+ }, [sortedSessions]);
316324
317325 const handleConfirmRename = useCallback(
318326 (sessionId: string, newName: string) => {
....@@ -407,6 +415,7 @@
407415
408416 {/* Session list */}
409417 <ScrollView
418
+ ref={sessionScrollRef}
410419 style={{ paddingHorizontal: 16 }}
411420 showsVerticalScrollIndicator={false}
412421 keyboardShouldPersistTaps="handled"