Say which kind of delete this is, and stop offering what is already open

**Deleting was one word for two different acts.** An imported session's
real transcript belongs to Claude Code and outlives anything this app
does, so removing it here undoes a view. A session started here has no
copy anywhere, and removing it ends the conversation. The dialog warned
"this can't be undone" of both, which makes the warning worthless on the
one where it is true -- and frightening on the one where it is not, since
what it actually deletes is a cache of a conversation still sitting on
the machine.

Sessions now report whether they were imported, and the dialog says which
act this is. No new mechanism: the soft delete already existed, it was
just indistinguishable from the hard one.

**And a session already open here is no longer offered for import.**
Importing one twice would leave two `--resume` processes appending to the
same transcript, each seeing the other's writes as work done elsewhere and
replaying them -- both sessions then showing a conversation neither is
having. The route refuses it as well, so the rule holds for anything not
going through the app.

Left out of the list rather than shown and disabled. The usual argument
says absence is ambiguous, and it is wrong here: an imported session has
not disappeared, it has moved to the session list, which is where it now
belongs. Absence means "already somewhere you can reach it". Deleting the
app's copy puts it straight back -- verified: 68 offered, 67 after
importing one, 68 again after the soft delete, which is also the clearest
demonstration that a soft delete keeps the conversation.
This commit is contained in:
iris committed 2026-08-28 22:53:42 -04:00
1 parent d2915c12fa
commit 3f8805a610
4 files changed
+78 -5

No files matched your search

@@ -120,6 +120,10 @@ data class SessionSummary(
val model: String?,
/** How much the session asks before acting; null when it was never set. */
val permissionMode: String?,
/**
* Whether this continues a session the machine already had, which changes what deleting means.
*/
val imported: Boolean,
val status: String,
val lastActivity: Double,
)
@@ -132,6 +136,7 @@ private fun parseSession(session: JSONObject) =
title = session.getString("title"),
model = session.optString("model").ifEmpty { null },
permissionMode = session.optString("permissionMode").ifEmpty { null },
imported = session.optBoolean("imported", false),
status = session.getString("status"),
lastActivity = session.getDouble("lastActivity"),
)
@@ -163,7 +163,19 @@ fun SessionListScreen(
AlertDialog(
onDismissRequest = { confirmingDelete = null },
title = { Text("Delete \"${session.title}\"?") },
text = { Text("Kills the process and deletes its transcript. This can't be undone.") },
text = {
// Two different acts behind one button, so it says which one this is. An
// imported session's real transcript belongs to Claude Code and outlives
// this: removing it here undoes a view. A session started here has no copy
// anywhere, and removing it ends the conversation. Warning "this cannot be
// undone" of both would make the warning worthless where it is true.
Text(
if (session.imported)
"Stops the process and removes this app's copy. The conversation " +
"itself stays on the machine and can be imported again."
else "Kills the process and deletes its transcript. This can't be undone."
)
},
confirmButton = {
TextButton(
onClick = {