@@ -55,6 +55,11 @@ open class DraftStore private constructor(
5555 /** The full in-memory snapshot of all drafts, updated when prefs finish loading. */
5656 open val drafts: StateFlow<Map<String, String>> = _drafts.asStateFlow()
5757
58+ /** Queued follow-up messages per session, persisted under a namespaced key so a
59+ * follow-up queued mid-run survives process death (the in-memory-only StateFlow in
60+ * ChatViewModel would otherwise lose it, along with the cleared draft). */
61+ private val _followUps = MutableStateFlow<Map<String, String>>(emptyMap())
62+
5863 private val _ready = MutableStateFlow(false)
5964 /** True once the background prefs load has completed. */
6065 open val ready: StateFlow<Boolean> = _ready.asStateFlow()
@@ -65,16 +70,23 @@ open class DraftStore private constructor(
6570 // so callers can observe readiness without blocking.
6671 if (scope != null && appContext != null) {
6772 scope.launch(Dispatchers.IO) {
68− val snapshot = runCatching {
73+ val all = runCatching {
6974 prefs.all.mapValues { it.value as? String ?: "" }
7075 }.getOrDefault(emptyMap())
76+ // Partition persisted keys: follow-up keys carry a prefix, everything else
77+ // is a plain per-session draft.
78+ val drafts = mutableMapOf<String, String>()
79+ val followUps = mutableMapOf<String, String>()
80+ all.forEach { (k, v) ->
81+ if (k.startsWith(FOLLOWUP_PREFIX)) followUps[k.removePrefix(FOLLOWUP_PREFIX)] = v
82+ else drafts[k] = v
83+ }
7184 // Merge instead of overwriting: any in-memory writes that arrived before
7285 // the background load completed (e.g. a share-intent draft injected via
7386 // setImmediate) take priority over the persisted snapshot so they aren't
7487 // clobbered.
75− _drafts.update { current ->
76− snapshot.toMutableMap().apply { putAll(current) }
77− }
88+ _drafts.update { current -> drafts.apply { putAll(current) } }
89+ _followUps.update { current -> followUps.apply { putAll(current) } }
7890 _ready.value = true
7991 }
8092 } else {
@@ -85,6 +97,35 @@ open class DraftStore private constructor(
8597 /** Returns the draft for [sessionId], or "" if not found or still loading. */
8698 open fun get(sessionId: String): String = _drafts.value[sessionId].orEmpty()
8799
100+ /** Returns the queued follow-up for [sessionId], or "" if none / still loading. */
101+ open fun getFollowUp(sessionId: String): String = _followUps.value[sessionId].orEmpty()
102+
103+ /** Persist a queued follow-up immediately (fire-and-forget, main-thread-safe).
104+ * Mirrors [flushDraft]: async apply() when prefs are open, else a background thread,
105+ * so a follow-up queued just before process death is flushed before the process exits.
106+ * A blank [text] clears the stored follow-up. */
107+ open fun flushFollowUp(sessionId: String, text: String) {
108+ _followUps.update { current ->
109+ val updated = current.toMutableMap()
110+ if (text.isBlank()) updated.remove(sessionId) else updated[sessionId] = text
111+ updated
112+ }
113+ writeAsync(FOLLOWUP_PREFIX + sessionId, text)
114+ }
115+
116+ /** Persist [key]=[text] (or remove it when blank) without blocking the main thread on
117+ * first-time disk I/O — see [flushDraft] for the prefs-not-yet-open rationale. */
118+ private fun writeAsync(key: String, text: String) {
119+ val write = {
120+ runCatching {
121+ prefs.edit().apply {
122+ if (text.isBlank()) remove(key) else putString(key, text)
123+ }.apply()
124+ }.onFailure { Log.w("DraftStore", "Failed to persist $key", it) }
125+ }
126+ if (prefsInitialized.get()) write() else flushExecutor.execute { write() }
127+ }
128+
88129 /** Update the in-memory draft immediately without persisting to disk.
89130 * Used when navigation must see the new value synchronously (e.g. share-intent
90131 * injection before ChatScreen composes). Call [set] afterwards to persist. */
@@ -111,16 +152,22 @@ open class DraftStore private constructor(
111152 }
112153 }
113154
114− /** Remove the draft for a session (call on session deletion to avoid orphaned entries). */
155+ /** Remove the draft for a session (call on session deletion to avoid orphaned entries).
156+ * Also clears any queued follow-up so a deleted session leaves nothing behind. */
115157 open suspend fun remove(sessionId: String) {
116158 _drafts.update { current ->
117159 val updated = current.toMutableMap()
118160 updated.remove(sessionId)
119161 updated
120162 }
163+ _followUps.update { current ->
164+ val updated = current.toMutableMap()
165+ updated.remove(sessionId)
166+ updated
167+ }
121168 withContext(Dispatchers.IO) {
122169 runCatching {
123− prefs.edit().remove(sessionId).apply()
170+ prefs.edit().remove(sessionId).remove(FOLLOWUP_PREFIX + sessionId).apply()
124171 }.onFailure { Log.w("DraftStore", "Failed to remove draft for $sessionId", it) }
125172 }
126173 }
@@ -166,6 +213,12 @@ open class DraftStore private constructor(
166213 }
167214 }
168215
216+ private companion object {
217+ /** Key prefix distinguishing persisted follow-ups from plain drafts in the shared
218+ * prefs file. Uses a NUL so it can't collide with a real session id. */
219+ const val FOLLOWUP_PREFIX = "