diff --git a/extension/src/vault/vault-list.ts b/extension/src/vault/vault-list.ts
new file mode 100644
index 0000000..7e6b39c
--- /dev/null
+++ b/extension/src/vault/vault-list.ts
@@ -0,0 +1,52 @@
+// Vault-tab list column: renders the middle list pane (row markup, empty
+// state, and the row-click → drawer selection). Receives the VaultController
+// (`ctx`) and reaches sibling concerns through it; pure helpers come from
+// vault-context. Imports only from shared/ and vault-context.
+
+import type { ItemId, ManifestEntry, ItemType } from '../shared/types';
+import { relativeTime } from '../shared/relative-time';
+import {
+ type VaultController, escapeHtml, typeIcon, getFilteredEntries,
+} from './vault-context';
+
+export function renderListPane(ctx: VaultController): void {
+ const pane = document.getElementById('vault-list-pane');
+ if (!pane) return;
+
+ const group = ctx.state.activeGroup as ItemType | null;
+ let items = getFilteredEntries(ctx.state);
+ if (group) items = items.filter(([, e]) => e.type === group);
+
+ if (items.length === 0) {
+ pane.innerHTML = `
+
+
${ctx.state.searchQuery ? '⊘' : '◈'}
+
${ctx.state.searchQuery ? `No results for "${escapeHtml(ctx.state.searchQuery)}"` : 'No items yet'}
+
${ctx.state.searchQuery ? 'Try a shorter search term.' : 'Click + new item to get started.'}
+
+ `;
+ return;
+ }
+
+ pane.innerHTML = items.map(([id, e]: [ItemId, ManifestEntry]) => {
+ const sel = id === ctx.state.selectedId ? ' vault-list-row--selected' : '';
+ const subtitle = (e as any).icon_hint ?? (e.tags?.length > 0 ? e.tags.join(', ') : '');
+ const modifiedAgo = e.modified ? relativeTime(e.modified) : '';
+ return `
+
+
${typeIcon(e.type)}
+
+
${escapeHtml(e.title)}
+ ${subtitle ? `
${escapeHtml(subtitle)}
` : ''}
+
+ ${modifiedAgo ? `
${escapeHtml(modifiedAgo)}
` : ''}
+
+ `;
+ }).join('');
+
+ pane.querySelectorAll('.vault-list-row').forEach((row) => {
+ row.addEventListener('click', async () => {
+ await ctx.selectItemForDrawer(row.dataset.id!);
+ });
+ });
+}
diff --git a/extension/src/vault/vault.ts b/extension/src/vault/vault.ts
index 1521dec..825ef9d 100644
--- a/extension/src/vault/vault.ts
+++ b/extension/src/vault/vault.ts
@@ -10,7 +10,6 @@ import type {
} from '../shared/types';
import { registerHost } from '../shared/state';
import { type ErrorCta } from '../shared/error-copy';
-import { relativeTime } from '../shared/relative-time';
import { renderItemDetail } from '../popup/components/item-detail';
import { renderItemForm } from '../popup/components/item-form';
import { renderTrash, teardown as teardownTrash } from '../popup/components/trash';
@@ -23,7 +22,7 @@ import { renderBackupPanel, teardown as teardownBackup } from './components/back
import { renderImportPanel, teardown as teardownImport } from './components/import-panel';
import {
type VaultController, type VaultState, type VaultView, type HashRoute,
- escapeHtml, typeIcon, getFilteredEntries,
+ escapeHtml,
} from './vault-context';
import {
render, applyShellViewClass,
@@ -31,6 +30,7 @@ import {
wireSessionExpiredListener,
} from './vault-shell';
import { wireSidebar, renderSidebarCategories } from './vault-sidebar';
+import { renderListPane } from './vault-list';
// ---------------------------------------------------------------------------
// Helpers
@@ -144,7 +144,7 @@ const ctx: VaultController = {
sendMessage,
render: () => render(ctx),
renderPane: () => renderPane(),
- renderListPane: () => renderListPane(),
+ renderListPane: () => renderListPane(ctx),
renderSidebarCategories: () => renderSidebarCategories(ctx),
renderDrawer: (item) => renderDrawer(item),
applyShellViewClass: () => applyShellViewClass(ctx),
@@ -173,7 +173,7 @@ registerHost({
setHash(view as VaultView);
applyShellViewClass(ctx);
renderSidebarCategories(ctx);
- if (state.view === 'list') renderListPane();
+ if (state.view === 'list') renderListPane(ctx);
renderPane();
},
sendMessage,
@@ -281,7 +281,7 @@ function renderDrawer(item: Item): void {
document.getElementById('drawer-close-btn')?.addEventListener('click', () => {
closeDrawer();
- renderListPane();
+ renderListPane(ctx);
});
document.getElementById('drawer-edit-btn')?.addEventListener('click', () => {
@@ -304,57 +304,11 @@ async function selectItemForDrawer(id: string): Promise {
state.selectedItem = data.item;
state.drawerOpen = true;
renderSidebarCategories(ctx);
- renderListPane();
+ renderListPane(ctx);
renderDrawer(data.item);
openDrawer();
}
-// ---------------------------------------------------------------------------
-// List pane
-// ---------------------------------------------------------------------------
-
-function renderListPane(): void {
- const pane = document.getElementById('vault-list-pane');
- if (!pane) return;
-
- const group = state.activeGroup as ItemType | null;
- let items = getFilteredEntries(state);
- if (group) items = items.filter(([, e]) => e.type === group);
-
- if (items.length === 0) {
- pane.innerHTML = `
-
-
${state.searchQuery ? '⊘' : '◈'}
-
${state.searchQuery ? `No results for "${escapeHtml(state.searchQuery)}"` : 'No items yet'}
-
${state.searchQuery ? 'Try a shorter search term.' : 'Click + new item to get started.'}
-
- `;
- return;
- }
-
- pane.innerHTML = items.map(([id, e]) => {
- const sel = id === state.selectedId ? ' vault-list-row--selected' : '';
- const subtitle = (e as any).icon_hint ?? (e.tags?.length > 0 ? e.tags.join(', ') : '');
- const modifiedAgo = e.modified ? relativeTime(e.modified) : '';
- return `
-
-
${typeIcon(e.type)}
-
-
${escapeHtml(e.title)}
- ${subtitle ? `
${escapeHtml(subtitle)}
` : ''}
-
- ${modifiedAgo ? `
${escapeHtml(modifiedAgo)}
` : ''}
-
- `;
- }).join('');
-
- pane.querySelectorAll('.vault-list-row').forEach((row) => {
- row.addEventListener('click', async () => {
- await selectItemForDrawer(row.dataset.id!);
- });
- });
-}
-
// ---------------------------------------------------------------------------
// Platform-aware save hint
// ---------------------------------------------------------------------------
@@ -589,7 +543,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (state.selectedId === route.id && state.selectedItem) {
renderPane();
renderSidebarCategories(ctx);
- if (state.view === 'list') renderListPane();
+ if (state.view === 'list') renderListPane(ctx);
return;
}
// Need to fetch the item
@@ -601,7 +555,7 @@ document.addEventListener('DOMContentLoaded', async () => {
state.selectedId = null;
state.selectedItem = null;
renderSidebarCategories(ctx);
- if (state.view === 'list') renderListPane();
+ if (state.view === 'list') renderListPane(ctx);
renderPane();
});
});
@@ -620,7 +574,7 @@ async function selectItem(id: ItemId): Promise {
state.loading = false;
setHash('detail', id);
renderSidebarCategories(ctx);
- renderListPane();
+ renderListPane(ctx);
renderPane();
} else {
state.loading = false;