Fix SessionListDialog behavior and document PersistentStorageDialog bug

- Fix SessionListDialog to not reload when user selects a session after deleting others
  - Track if dialog closed via selection vs other means
  - Only call delete callback if not closed via selection
  - Batch deletions to avoid reload on every delete
- Comment out PersistentStorageDialog in both web-ui example and browser extension (TODO: fix)
- Add Known Bugs section to both README.md files documenting PersistentStorageDialog issue
- Clean up navigation tracking variable names in browser extension
This commit is contained in:
Mario Zechner 2025-10-06 16:33:33 +02:00
parent 2d68594711
commit 5f04960f6d
5 changed files with 94 additions and 25 deletions

View file

@ -1182,3 +1182,7 @@ Error: Refused to evaluate a string as JavaScript because this document requires
--- ---
This CSP section should help both developers and LLMs understand the security constraints when working on extension features, especially those involving dynamic code execution or user-generated content. This CSP section should help both developers and LLMs understand the security constraints when working on extension features, especially those involving dynamic code execution or user-generated content.
## Known Bugs
- **PersistentStorageDialog**: Currently broken and commented out in sidepanel.ts. The dialog for requesting persistent storage does not work correctly and needs to be fixed.

View file

@ -10,7 +10,7 @@ import {
AppStorage, AppStorage,
ChatPanel, ChatPanel,
ChromeStorageBackend, ChromeStorageBackend,
PersistentStorageDialog, // PersistentStorageDialog, // TODO: Fix - currently broken
ProviderTransport, ProviderTransport,
ProxyTab, ProxyTab,
SessionIndexedDBBackend, SessionIndexedDBBackend,
@ -71,9 +71,9 @@ let agent: Agent;
let chatPanel: ChatPanel; let chatPanel: ChatPanel;
let agentUnsubscribe: (() => void) | undefined; let agentUnsubscribe: (() => void) | undefined;
// Track last navigation for inserting navigation messages // Track current active tab for real-time navigation updates
let lastSubmittedUrl: string | undefined; let currentTabUrl: string | undefined;
let lastSubmittedTabIndex: number | undefined; let currentTabIndex: number | undefined;
// ============================================================================ // ============================================================================
// HELPERS // HELPERS
@ -204,7 +204,7 @@ const renderApp = () => {
loadSession(sessionId); loadSession(sessionId);
}, },
(deletedSessionId) => { (deletedSessionId) => {
// If the deleted session is the current one, start a new session // Only reload if the current session was deleted
if (deletedSessionId === currentSessionId) { if (deletedSessionId === currentSessionId) {
newSession(); newSession();
} }
@ -294,6 +294,41 @@ const renderApp = () => {
render(appHtml, document.body); render(appHtml, document.body);
}; };
// ============================================================================
// TAB NAVIGATION TRACKING
// ============================================================================
// Listen for tab updates to insert navigation messages in real-time
chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => {
// Only care about URL changes on the active tab
if (changeInfo.url && tab.active && tab.url) {
handleTabNavigation(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index);
}
});
// Listen for tab activation (user switches tabs)
chrome.tabs.onActivated.addListener(async (activeInfo) => {
const tab = await chrome.tabs.get(activeInfo.tabId);
if (tab.url) {
handleTabNavigation(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index);
}
});
function handleTabNavigation(url: string, title: string, favicon?: string, tabIndex?: number) {
// Update current tab tracking
const urlChanged = currentTabUrl !== url;
const tabChanged = currentTabIndex !== tabIndex;
currentTabUrl = url;
currentTabIndex = tabIndex;
// Only insert navigation message if something changed and we have an agent
if ((urlChanged || tabChanged) && agent) {
const navMessage = createNavigationMessage(url, title, favicon, tabIndex);
agent.appendMessage(navMessage);
}
}
// ============================================================================ // ============================================================================
// INIT // INIT
// ============================================================================ // ============================================================================
@ -308,10 +343,11 @@ async function initApp() {
document.body, document.body,
); );
// TODO: Fix PersistentStorageDialog - currently broken
// Request persistent storage // Request persistent storage
if (storage.sessions) { // if (storage.sessions) {
await PersistentStorageDialog.request(); // await PersistentStorageDialog.request();
} // }
// Create ChatPanel // Create ChatPanel
chatPanel = new ChatPanel(); chatPanel = new ChatPanel();
@ -322,21 +358,33 @@ async function initApp() {
chatPanel.onBeforeSend = async () => { chatPanel.onBeforeSend = async () => {
// Get current tab info // Get current tab info
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || !tab.url) return; if (!tab?.url) return;
// Check if navigation changed since last submit // Find last navigation message in messages (reverse loop)
if (lastSubmittedUrl !== tab.url || lastSubmittedTabIndex !== tab.index) { const messages = agent.state.messages;
// Insert navigation message let lastNavMessage: ReturnType<typeof createNavigationMessage> | undefined;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === "navigation") {
lastNavMessage = messages[i] as ReturnType<typeof createNavigationMessage>;
break;
}
}
// Only insert if URL or tab changed
if (lastNavMessage?.url !== tab.url || lastNavMessage?.tabIndex !== tab.index) {
const navMessage = createNavigationMessage(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index); const navMessage = createNavigationMessage(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index);
agent.appendMessage(navMessage); agent.appendMessage(navMessage);
// Update tracking
lastSubmittedUrl = tab.url;
lastSubmittedTabIndex = tab.index;
} }
}; };
chatPanel.additionalTools = [browserJavaScriptTool]; chatPanel.additionalTools = [browserJavaScriptTool];
// Initialize current tab state
const [currentTab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (currentTab?.url) {
currentTabUrl = currentTab.url;
currentTabIndex = currentTab.index;
}
// Check for session in URL // Check for session in URL
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
let sessionIdFromUrl = urlParams.get("session"); let sessionIdFromUrl = urlParams.get("session");

View file

@ -322,6 +322,10 @@ setAppStorage(storage);
See [src/index.ts](src/index.ts) for the full public API. See [src/index.ts](src/index.ts) for the full public API.
## Known Bugs
- **PersistentStorageDialog**: Currently broken and commented out in examples. The dialog for requesting persistent storage does not work correctly and needs to be fixed.
## License ## License
MIT MIT

View file

@ -9,7 +9,7 @@ import {
type AppMessage, type AppMessage,
AppStorage, AppStorage,
ChatPanel, ChatPanel,
PersistentStorageDialog, // PersistentStorageDialog, // TODO: Fix - currently broken
ProviderTransport, ProviderTransport,
ProxyTab, ProxyTab,
SessionIndexedDBBackend, SessionIndexedDBBackend,
@ -192,7 +192,7 @@ const renderApp = () => {
await loadSession(sessionId); await loadSession(sessionId);
}, },
(deletedSessionId) => { (deletedSessionId) => {
// If the deleted session is the current one, start a new session // Only reload if the current session was deleted
if (deletedSessionId === currentSessionId) { if (deletedSessionId === currentSessionId) {
newSession(); newSession();
} }
@ -311,10 +311,11 @@ async function initApp() {
app, app,
); );
// TODO: Fix PersistentStorageDialog - currently broken
// Request persistent storage // Request persistent storage
if (storage.sessions) { // if (storage.sessions) {
await PersistentStorageDialog.request(); // await PersistentStorageDialog.request();
} // }
// Create ChatPanel // Create ChatPanel
chatPanel = new ChatPanel(); chatPanel = new ChatPanel();

View file

@ -12,6 +12,8 @@ export class SessionListDialog extends DialogBase {
private onSelectCallback?: (sessionId: string) => void; private onSelectCallback?: (sessionId: string) => void;
private onDeleteCallback?: (sessionId: string) => void; private onDeleteCallback?: (sessionId: string) => void;
private deletedSessions = new Set<string>();
private closedViaSelection = false;
protected modalWidth = "min(600px, 90vw)"; protected modalWidth = "min(600px, 90vw)";
protected modalHeight = "min(700px, 90vh)"; protected modalHeight = "min(700px, 90vh)";
@ -57,16 +59,26 @@ export class SessionListDialog extends DialogBase {
await storage.sessions.deleteSession(sessionId); await storage.sessions.deleteSession(sessionId);
await this.loadSessions(); await this.loadSessions();
// Notify callback that session was deleted // Track deleted session
if (this.onDeleteCallback) { this.deletedSessions.add(sessionId);
this.onDeleteCallback(sessionId);
}
} catch (err) { } catch (err) {
console.error("Failed to delete session:", err); console.error("Failed to delete session:", err);
} }
} }
override close() {
super.close();
// Only notify about deleted sessions if dialog wasn't closed via selection
if (!this.closedViaSelection && this.onDeleteCallback && this.deletedSessions.size > 0) {
for (const sessionId of this.deletedSessions) {
this.onDeleteCallback(sessionId);
}
}
}
private handleSelect(sessionId: string) { private handleSelect(sessionId: string) {
this.closedViaSelection = true;
if (this.onSelectCallback) { if (this.onSelectCallback) {
this.onSelectCallback(sessionId); this.onSelectCallback(sessionId);
} }