From 7e0950e3645f3f4ede61cf8942e6edc76328bd20 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Mon, 27 Apr 2026 15:46:32 -0400 Subject: [PATCH] feat(ext/popup): session expiry listener, open-vault links, Shift+F shortcut Co-Authored-By: Claude Opus 4.6 --- extension/src/popup/components/item-list.ts | 11 +++++++- extension/src/popup/components/unlock.ts | 5 +++- extension/src/popup/popup.ts | 31 +++++++++++++++++---- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/extension/src/popup/components/item-list.ts b/extension/src/popup/components/item-list.ts index 9b03960..d816856 100644 --- a/extension/src/popup/components/item-list.ts +++ b/extension/src/popup/components/item-list.ts @@ -2,7 +2,7 @@ /// type-iconed rows. Clicking a row fetches the full Item and navigates /// to the detail view. -import { getState, setState, sendMessage, navigate, escapeHtml } from '../popup'; +import { getState, setState, sendMessage, navigate, escapeHtml, openVaultTab } from '../popup'; import type { ItemId, ItemType, ManifestEntry, Item } from '../../shared/types'; /// Extract the display hostname from an icon_hint or fallback to the first tag. @@ -66,6 +66,7 @@ export function renderItemList(app: HTMLElement): void { + @@ -92,6 +93,8 @@ export function renderItemList(app: HTMLElement): void { wireRowClicks(); }); + document.getElementById('vault-btn')?.addEventListener('click', () => openVaultTab()); + document.getElementById('new-btn')?.addEventListener('click', () => { setState({ newType: null }); navigate('add'); @@ -203,6 +206,12 @@ function handleListKeydown(e: KeyboardEvent): void { return; } + if (e.key === 'F' && e.shiftKey) { + e.preventDefault(); + openVaultTab(); + return; + } + const filtered = getFilteredEntries(); if (e.key === 'ArrowDown') { diff --git a/extension/src/popup/components/unlock.ts b/extension/src/popup/components/unlock.ts index 2106ffc..8fe38f5 100644 --- a/extension/src/popup/components/unlock.ts +++ b/extension/src/popup/components/unlock.ts @@ -1,6 +1,6 @@ /// Unlock view — passphrase input with ENTER to submit. -import { getState, setState, sendMessage, navigate, escapeHtml } from '../popup'; +import { getState, setState, sendMessage, navigate, escapeHtml, openVaultTab } from '../popup'; import type { ItemId, ManifestEntry } from '../../shared/types'; export function renderUnlock(app: HTMLElement): void { @@ -23,6 +23,7 @@ export function renderUnlock(app: HTMLElement): void { ${state.loading ? '
' : ''} ${state.error ? `
${escapeHtml(state.error)}
` : ''}
+
@@ -52,6 +53,8 @@ export function renderUnlock(app: HTMLElement): void { }); } + document.getElementById('vault-btn')?.addEventListener('click', () => openVaultTab()); + const settingsBtn = document.getElementById('settings-btn'); settingsBtn?.addEventListener('click', () => navigate('settings')); } diff --git a/extension/src/popup/popup.ts b/extension/src/popup/popup.ts index 16be54f..a2998f0 100644 --- a/extension/src/popup/popup.ts +++ b/extension/src/popup/popup.ts @@ -34,13 +34,20 @@ export function isInTab(): boolean { return window.location.search.length > 0; } +export function openVaultTab(hash?: string): void { + const url = chrome.runtime.getURL('vault.html') + (hash ? `#${hash}` : ''); + chrome.tabs.create({ url }); +} + export function popOutToTab(): void { const state = getState(); - const params = new URLSearchParams(); - params.set('view', state.view); - if (state.newType) params.set('type', state.newType); - if (state.selectedId) params.set('id', state.selectedId); - chrome.tabs.create({ url: `popup.html?${params.toString()}` }); + if (state.newType) { + openVaultTab(`add/${state.newType}`); + } else if (state.selectedId) { + openVaultTab(`${state.view}/${state.selectedId}`); + } else { + openVaultTab(); + } window.close(); } @@ -272,4 +279,16 @@ async function init(): Promise { navigate('locked'); } -document.addEventListener('DOMContentLoaded', init); +document.addEventListener('DOMContentLoaded', () => { + init(); + + chrome.runtime.onMessage.addListener((msg) => { + if (msg.type === 'session_expired') { + currentState.view = 'locked'; + currentState.error = null; + currentState.selectedItem = null; + currentState.selectedId = null; + render(); + } + }); +});