Make thread/channel/message deletion complete (hard delete)
Deleting a thread, channel, or message was a no-op/soft-delete: messages were only dropped from the plugin outbox (still in hermes' session store, hence searchable/recoverable) and channels/threads were merely archived. Now deletion is complete and non-recoverable, with no search trace: - purge.py (new): hard-delete from hermes' session store (state.db). delete_lane wipes a channel's/thread's sessions + messages; deleting a messages row also drops it from the FTS5 index via the delete triggers. delete_message removes one message, matched by (session, role, exact content, closest timestamp) since plugin m_<hex> ids aren't persisted. - channels.py: delete() hard-deletes the row (and a channel's child threads) instead of archiving. - outbox.py: add delete_lane() (wipe all frames for a lane) and message_info() (read a message's final role/text/ts for the match). - adapter.py: on_channel_delete wipes outbox + session store; on_message_delete purges the session-store row per message. - App: delete confirmations no longer claim history stays for search; ChannelStore removes a channel's threads on channel delete. - Docs updated to describe hard deletion.
This commit is contained in:
1 parent
9286937e2d
commit
17bf41a0b9
11 files changed
+373
-51
No files matched your search
@@ -29,12 +29,21 @@ class ChannelStore {
|
||||
/** Reconcile a server frame into the cache. */
|
||||
fun onFrame(frame: Frame) {
|
||||
when (frame.type) {
|
||||
TYPE_CHANNEL_CREATED, TYPE_CHANNEL_RENAMED -> upsert(frame)
|
||||
TYPE_CHANNEL_DELETED -> remove(frame)
|
||||
TYPE_CHANNEL_CREATED, TYPE_CHANNEL_RENAMED -> {
|
||||
upsert(frame)
|
||||
}
|
||||
|
||||
TYPE_CHANNEL_DELETED -> {
|
||||
remove(frame)
|
||||
}
|
||||
|
||||
TYPE_CHANNEL_LIST -> {
|
||||
frame.payloadAs<ChannelListPayload>()?.let { setAll(it.channels) }
|
||||
}
|
||||
else -> Unit
|
||||
|
||||
else -> {
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +57,10 @@ class ChannelStore {
|
||||
|
||||
private fun remove(frame: Frame) {
|
||||
val p = frame.payloadAs<ChannelDeletedPayload>() ?: return
|
||||
_channels.value = _channels.value.filter { it.chatId != p.chatId }
|
||||
// A channel delete also removes its threads (child entries); a thread
|
||||
// delete removes just that thread. Both are hard deletes server-side.
|
||||
_channels.value =
|
||||
_channels.value.filter { it.chatId != p.chatId && it.parentChatId != p.chatId }
|
||||
}
|
||||
|
||||
private fun sorted(list: List<ChannelInfo>): List<ChannelInfo> =
|
||||
@@ -62,20 +74,16 @@ class ChannelStore {
|
||||
// ── UI helpers ────────────────────────────────────────────────────────
|
||||
|
||||
/** Channels (default + user channels) for the drawer/rail. */
|
||||
fun channelsForDrawer(): List<ChannelInfo> =
|
||||
_channels.value.filter { it.kind != "thread" }
|
||||
fun channelsForDrawer(): List<ChannelInfo> = _channels.value.filter { it.kind != "thread" }
|
||||
|
||||
/** Threads under a channel, for the topic switcher. */
|
||||
fun threadsFor(chatId: String): List<ChannelInfo> =
|
||||
_channels.value.filter { it.kind == "thread" && it.parentChatId == chatId }
|
||||
fun threadsFor(chatId: String): List<ChannelInfo> = _channels.value.filter { it.kind == "thread" && it.parentChatId == chatId }
|
||||
|
||||
fun byId(chatId: String): ChannelInfo? =
|
||||
_channels.value.firstOrNull { it.chatId == chatId }
|
||||
fun byId(chatId: String): ChannelInfo? = _channels.value.firstOrNull { it.chatId == chatId }
|
||||
|
||||
fun defaultChannel(): ChannelInfo? =
|
||||
_channels.value.firstOrNull { it.isDefault } ?: _channels.value.firstOrNull()
|
||||
fun defaultChannel(): ChannelInfo? = _channels.value.firstOrNull { it.isDefault } ?: _channels.value.firstOrNull()
|
||||
|
||||
fun clear() {
|
||||
_channels.value = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -839,7 +839,7 @@ fun ChatScreen(controller: IrisController) {
|
||||
onDismissRequest = { deleteThread = null },
|
||||
title = { Text("Delete topic?") },
|
||||
text = {
|
||||
Text("Delete \"${t.name}\"? It will be removed from this channel.")
|
||||
Text("Delete \"${t.name}\"? The topic and all its messages will be permanently deleted.")
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
@@ -861,9 +861,9 @@ fun ChatScreen(controller: IrisController) {
|
||||
text = {
|
||||
Text(
|
||||
if (n == 1) {
|
||||
"This message will be deleted for all devices."
|
||||
"This message will be permanently deleted for all devices and can no longer be found in search."
|
||||
} else {
|
||||
"These messages will be deleted for all devices."
|
||||
"These messages will be permanently deleted for all devices and can no longer be found in search."
|
||||
},
|
||||
)
|
||||
},
|
||||
@@ -1049,7 +1049,7 @@ fun ChatScreen(controller: IrisController) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { channelToDelete = null },
|
||||
title = { Text("Delete channel?") },
|
||||
text = { Text("Delete \"${ch.name}\"? Its history stays available for search.") },
|
||||
text = { Text("Delete \"${ch.name}\"? The channel and all its messages will be permanently deleted.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
channelToDelete = null
|
||||
|
||||
Reference in new issue
Block a user