From d9e1b88a700596ee2c0753aa32a94f7a2cac8557 Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Thu, 12 Feb 2026 21:40:07 +0000 Subject: [PATCH] fix: persist deleted sessions via localStorage blacklist Deleted sessions would reappear after page refresh because the gateway sessions.list response still included them. Now maintains a localStorage blacklist of deleted session keys that filters them out on every load, regardless of gateway-side deletion support. --- FEEDBACK.md | 74 +++++++++++++++++++++++++++++++++++++++++ src/hooks/useGateway.ts | 25 +++++++++++--- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/FEEDBACK.md b/FEEDBACK.md index 7558c3a..1301a00 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -389,3 +389,77 @@ - May need to also hide the horizontal scrollbar entirely (textarea shouldn't need horizontal scroll — use word-wrap/overflow-wrap instead) - Add `overflow-x: hidden` on the textarea to prevent horizontal scrollbar entirely - Also check that `resize: none` is set and `word-break: break-word` / `overflow-wrap: break-word` to avoid horizontal overflow + +## Item #38 +- **Date:** 2026-02-12 +- **Priority:** high +- **Status:** pending +- **Description:** Session deletion doesn't persist — deleted sessions reappear after page refresh +- **Details:** + - The delete session feature (v1.7.0) only deletes from local state/localStorage + - On refresh, sessions are reloaded from the gateway via loadHistory/listSessions API + - Need to actually call the gateway API to delete the session server-side + - Check if the gateway WS protocol supports session deletion (DELETE or a command message) + - If not, at minimum maintain a localStorage blacklist of deleted session keys and filter them out on reload + +## Item #39 +- **Date:** 2026-02-12 +- **Priority:** medium +- **Status:** pending +- **Description:** Message metadata viewer — discreet button on each message to show raw metadata +- **Details:** + - Small icon button (ℹ️ or ⋯) on hover of each message + - Click to expand/toggle a subtle panel showing all available metadata: timestamp, message_id, channel, sender info, session key, etc. + - Should show whatever the gateway sends in the message object (raw dump or formatted key/value pairs) + - Keep it discreet — not visible by default, only on hover, doesn't clutter the UI + - Useful for debugging and understanding message routing + - Collapsed by default, like the tool call details + +## Item #40 +- **Date:** 2026-02-12 +- **Priority:** medium +- **Status:** pending +- **Description:** Better thinking/reasoning indicator when content is hidden +- **Details:** + - When the gateway streams a thinking block without visible content (thinking=low), PinchChat shows "Thinking..." but nothing happens for a while + - Improve the UX: show elapsed time counter, maybe a pulsing animation, and a label like "Reasoning..." or "Thinking (hidden)..." + - If the gateway sends thinking content (thinking=stream), display it in a collapsible block (already works) + - If no content is sent, at least show the user that the agent is actively reasoning and how long it's been + - Keep it purely client-side, no gateway modifications + +## Item #41 +- **Date:** 2026-02-12 +- **Priority:** medium +- **Status:** pending +- **Description:** Tool call payload viewer — word-wrap toggle instead of horizontal scroll +- **Details:** + - Currently tool call JSON payloads/results have long lines that require horizontal scrolling + - Add a toggle button (wrap/nowrap) on the tool call content viewer + - Default to word-wrap (break-word / white-space: pre-wrap) so content fits without horizontal scroll + - Optional toggle to switch to nowrap (pre + overflow-x scroll) for when users want to see raw formatting + - Apply to both tool call parameters and tool results + +## Item #42 +- **Date:** 2026-02-12 +- **Priority:** high +- **Status:** pending +- **Description:** Visual differentiation between user messages and assistant messages/tool calls +- **Details:** + - Currently user and assistant messages look too similar + - Add distinct background color or left border color for user messages (like classic chat apps) + - Keep it subtle and matching the dark theme (e.g. slightly different zinc shade, or a colored left border like cyan for user) + - Assistant messages stay as-is, tool calls already have their own styling + - System events should also be visually distinct (already somewhat handled but could be improved) + - Think WhatsApp/Telegram style: your messages vs their messages are clearly different + - Keep the soft palette for keratoconus — no harsh contrast + +## Item #43 +- **Date:** 2026-02-12 +- **Priority:** medium +- **Status:** pending +- **Description:** Textarea scrollbar always visible — should only show on overflow +- **Details:** + - The vertical scrollbar in the message input textarea is always displayed + - It should only appear when the content exceeds the max height (overflow) + - Use overflow-y: auto instead of overflow-y: scroll on the textarea + - The scrollbar should be hidden when the textarea hasn't reached its max size diff --git a/src/hooks/useGateway.ts b/src/hooks/useGateway.ts index 0ff764a..eab1e02 100644 --- a/src/hooks/useGateway.ts +++ b/src/hooks/useGateway.ts @@ -80,12 +80,27 @@ export function useGateway() { }); }, []); + // Deleted sessions blacklist (persisted in localStorage) + const getDeletedSessions = useCallback((): Set => { + try { + const raw = localStorage.getItem('pinchchat-deleted-sessions'); + return raw ? new Set(JSON.parse(raw)) : new Set(); + } catch { return new Set(); } + }, []); + + const addDeletedSession = useCallback((key: string) => { + const deleted = getDeletedSessions(); + deleted.add(key); + localStorage.setItem('pinchchat-deleted-sessions', JSON.stringify([...deleted])); + }, [getDeletedSessions]); + const loadSessions = useCallback(async () => { try { const res = await clientRef.current?.send('sessions.list', {}); const sessionList = res?.sessions as Array> | undefined; if (sessionList) { - setSessions(sessionList.map((s) => ({ + const deleted = getDeletedSessions(); + setSessions(sessionList.filter((s) => !deleted.has((s.key || s.sessionKey) as string)).map((s) => ({ key: (s.key || s.sessionKey) as string, label: (s.label || s.key || s.sessionKey) as string, messageCount: s.messageCount as number | undefined, @@ -104,7 +119,7 @@ export function useGateway() { } catch { // Silently ignore session list failures (e.g. disconnected) } - }, []); + }, [getDeletedSessions]); const loadHistory = useCallback(async (sessionKey: string) => { setIsLoadingHistory(true); @@ -394,8 +409,10 @@ export function useGateway() { try { await clientRef.current?.send('sessions.delete', { key, deleteTranscript: true }); } catch { - // Ignore delete failures + // Ignore delete failures — blacklist will hide it anyway } + // Persist to blacklist so it stays hidden after refresh + addDeletedSession(key); // Remove from local state setSessions(prev => prev.filter(s => s.key !== key)); // If we deleted the active session, switch to main @@ -406,7 +423,7 @@ export function useGateway() { setMessages([]); loadHistory(mainKey); } - }, [loadHistory]); + }, [loadHistory, addDeletedSession]); const logout = useCallback(() => { if (clientRef.current) {