feat(mobile): synchronized pill composer transitions and draft screen fixes
Drop the animated pill/composer morph in favor of instant swaps that are synchronized with the keyboard choreography: a new oc:keyboard-intent event collapses the composer (flushSync) before the hide compensation is measured, so keyboard travel and composer height change land as a single chat motion on both iOS and Android (Android also gains keyboard signals and deterministic re-pins around its native resize). The WKWebView caret is hidden during the transition so it no longer flies to its new position. Draft screen: starter chips hide instantly while the keyboard is up and the centered title rides the keyboard shift compensation instead of double-jumping; the composer drag handle also works in dictation mode; the highlight mirror is disabled on mobile so the caret matches the text. Fixes: worktree discovery and the GitHub auth probe now wait for the runtime connection (no more empty branch pickers / stale auth on cold start), worktree discovery merges per project instead of clobbering the persisted map, the cross-project session list resets on instance switch (with an in-flight load guard) so no stale sessions linger, and mobile overlay content contains its overscroll instead of bouncing the page.
This commit is contained in:
@@ -25,11 +25,17 @@ type GlobalSessionsState = {
|
||||
upsertSession: (session: Session) => void;
|
||||
removeSessions: (ids: Iterable<string>) => void;
|
||||
archiveSessions: (ids: Iterable<string>, archivedAt?: number) => void;
|
||||
/** Drop every session from the previous runtime instance and go back to the
|
||||
unloaded state, so a fresh load runs against the new endpoint. */
|
||||
resetForRuntimeSwitch: () => void;
|
||||
};
|
||||
|
||||
const PAGE_SIZE = 500;
|
||||
|
||||
let inflightLoad: Promise<LoadResult> | null = null;
|
||||
// Bumped on runtime switch: an in-flight load from the previous instance must
|
||||
// not apply its (stale) snapshot after the reset.
|
||||
let loadGeneration = 0;
|
||||
|
||||
const normalizePath = (value?: string | null): string | null => {
|
||||
if (typeof value !== 'string') {
|
||||
@@ -363,6 +369,19 @@ export const useGlobalSessionsStore = create<GlobalSessionsState>((set, get) =>
|
||||
set((state) => applySnapshot(state, activeSessions, archivedSessions, status));
|
||||
},
|
||||
|
||||
resetForRuntimeSwitch: () => {
|
||||
loadGeneration += 1;
|
||||
inflightLoad = null;
|
||||
set({
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
sessionsByDirectory: new Map(),
|
||||
reviewTransferBySessionId: new Map(),
|
||||
hasLoaded: false,
|
||||
status: 'idle',
|
||||
});
|
||||
},
|
||||
|
||||
loadSessions: async (fallbackActive) => {
|
||||
if (inflightLoad) {
|
||||
return inflightLoad;
|
||||
@@ -370,6 +389,7 @@ export const useGlobalSessionsStore = create<GlobalSessionsState>((set, get) =>
|
||||
|
||||
set((state) => (state.status === 'loading' ? state : { status: 'loading' }));
|
||||
|
||||
const generation = loadGeneration;
|
||||
inflightLoad = (async () => {
|
||||
const current = get();
|
||||
|
||||
@@ -395,9 +415,17 @@ export const useGlobalSessionsStore = create<GlobalSessionsState>((set, get) =>
|
||||
console.warn('[GlobalSessions] Failed to load archived sessions, preserving current snapshot:', archivedResult.reason);
|
||||
}
|
||||
|
||||
if (generation !== loadGeneration) {
|
||||
// Runtime switched mid-load: this snapshot belongs to the previous
|
||||
// instance — drop it.
|
||||
return { activeSessions: [], archivedSessions: [] };
|
||||
}
|
||||
set((state) => applySnapshot(state, nextActiveSessions, nextArchivedSessions, 'ready'));
|
||||
return { activeSessions: nextActiveSessions, archivedSessions: nextArchivedSessions };
|
||||
} catch (error) {
|
||||
if (generation !== loadGeneration) {
|
||||
return { activeSessions: [], archivedSessions: [] };
|
||||
}
|
||||
const nextActiveSessions = mergeSessionLists(current.activeSessions, fallbackActive);
|
||||
const nextArchivedSessions = current.archivedSessions;
|
||||
console.warn('[GlobalSessions] Failed to load sessions, using fallback snapshot:', error);
|
||||
|
||||
Reference in New Issue
Block a user