From a47fe260856c3b04d92a45dd05d4d8b2a20d7b3d Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Wed, 22 Apr 2026 21:41:43 -0700 Subject: [PATCH] fix(channels): allow removing user-added keys for server-known channels (#898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Adding a channel key in the Channels UI for a channel the server already knows about (e.g. `#public` from rainbow / config) leaves the localStorage entry **unremovable**: - `mergeUserChannels` sees the name already exists in the channel list and skips the user entry. - The existing channel row is never marked `userAdded:true`. - The ✕ button (`[data-remove-channel]`) is only rendered for `userAdded` rows. - Result: stuck localStorage key, no UI to delete it. There was also a latent bug in the remove handler — for non-`user:` rows, it used the raw hash (e.g. `enc_11`) as the `ChannelDecrypt.removeKey()` argument, but the storage key is the channel **name**. ## Fix 1. **`mergeUserChannels`**: when a stored key matches an existing channel by name/hash, mark the existing channel `userAdded=true` so the ✕ renders on it. (No magical/auto deletion of stored keys — the user explicitly chooses to remove.) 2. **Remove handler**: - Look up the channel object to get the correct display name for the localStorage key. - Keep server-known channels in the list when their ✕ is clicked (only the user's localStorage entry + cache are cleared, `userAdded` is unset). The channel still exists upstream. - Pure `user:`-prefixed channels are removed from the list as before. ## Repro 1. Open Channels. 2. Add a key for `#public` (or any rainbow-known channel). 3. Reload. Before this PR: row has no ✕, key is stuck. After this PR: ✕ appears, click clears the local key and cache. ## Files - `public/channels.js` only. ## Notes - No backend changes. - No new APIs. - Behaviour for purely user-added channels (e.g. `user:#somechannel` not known to the server) is unchanged. --------- Co-authored-by: you --- public/channels.js | 61 +++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/public/channels.js b/public/channels.js index ffe83eb3..aa14cd9c 100644 --- a/public/channels.js +++ b/public/channels.js @@ -393,17 +393,25 @@ } } - // Merge user-stored keys into the channel list + // Merge user-stored keys into the channel list. + // If a stored key matches a server-known channel, mark that channel as + // userAdded so the ✕ button appears — otherwise the user has no way to + // remove a key they added but that the server already knows about. function mergeUserChannels() { var keys = ChannelDecrypt.getStoredKeys(); var names = Object.keys(keys); for (var i = 0; i < names.length; i++) { var name = names[i]; - // Check if channel already exists by name - var exists = channels.some(function (ch) { - return ch.name === name || ch.hash === name || ch.hash === ('user:' + name); - }); - if (!exists) { + var matched = false; + for (var j = 0; j < channels.length; j++) { + var ch = channels[j]; + if (ch.name === name || ch.hash === name || ch.hash === ('user:' + name)) { + ch.userAdded = true; + matched = true; + break; + } + } + if (!matched) { channels.push({ hash: 'user:' + name, name: name, @@ -749,19 +757,38 @@ e.stopPropagation(); var channelHash = removeBtn.getAttribute('data-remove-channel'); if (!channelHash) return; - var chName = channelHash.startsWith('user:') ? channelHash.substring(5) : channelHash; + // The localStorage key is the channel name. For user:-prefixed entries + // strip the prefix; for server-known channels look up the channel + // object so we use its display name (the hash itself isn't the key). + var ch = channels.find(function (c) { return c.hash === channelHash; }); + var chName = channelHash.startsWith('user:') + ? channelHash.substring(5) + : (ch && ch.name) || channelHash; if (!confirm('Remove channel "' + chName + '"? This will clear saved keys and cached messages.')) return; ChannelDecrypt.removeKey(chName); - // Remove from channels array - channels = channels.filter(function (c) { return c.hash !== channelHash; }); - if (selectedHash === channelHash) { - selectedHash = null; - messages = []; - history.replaceState(null, '', '#/channels'); - var msgEl2 = document.getElementById('chMessages'); - if (msgEl2) msgEl2.innerHTML = '
Choose a channel from the sidebar to view messages
'; - var header2 = document.getElementById('chHeader'); - if (header2) header2.querySelector('.ch-header-text').textContent = 'Select a channel'; + if (channelHash.startsWith('user:')) { + // Pure user-added channel — drop from the list entirely. + channels = channels.filter(function (c) { return c.hash !== channelHash; }); + if (selectedHash === channelHash) { + selectedHash = null; + messages = []; + history.replaceState(null, '', '#/channels'); + var msgEl2 = document.getElementById('chMessages'); + if (msgEl2) msgEl2.innerHTML = '
Choose a channel from the sidebar to view messages
'; + var header2 = document.getElementById('chHeader'); + if (header2) header2.querySelector('.ch-header-text').textContent = 'Select a channel'; + } + } else if (ch) { + // Server-known channel: keep the row, just unmark as user-added so + // the ✕ disappears until they re-add a key. + ch.userAdded = false; + // If this was the selected channel, clear decrypted messages since + // the key is gone — they can't be re-decrypted without re-adding it. + if (selectedHash === channelHash) { + messages = []; + var msgEl2 = document.getElementById('chMessages'); + if (msgEl2) msgEl2.innerHTML = '
Key removed — add a key to decrypt messages
'; + } } renderChannelList(); return;