feat(v2): implement session persistence, file watcher, and markdown viewer

Phase 4 complete (MVP ship):
- SessionDb (rusqlite, WAL mode): sessions + layout_state tables, CRUD
- FileWatcherManager (notify v6): watch files, emit Tauri change events
- MarkdownPane: marked.js rendering with Catppuccin styles, live reload
- Layout store wired to persistence (addPane/removePane/setPreset persist)
- restoreFromDb() on startup restores panes in layout order
- Sidebar "M" button opens file picker for markdown files
- New adapters: session-bridge.ts, file-bridge.ts
- Deps: rusqlite (bundled), dirs 5, notify 6, marked
This commit is contained in:
Hibryda 2026-03-06 12:19:56 +01:00
parent 5ca035d438
commit bdb87978a9
14 changed files with 1075 additions and 17 deletions

View file

@ -33,15 +33,46 @@
title: `Agent ${num}`,
});
}
let fileInputEl: HTMLInputElement | undefined = $state();
function openMarkdown() {
fileInputEl?.click();
}
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
// Tauri file paths from input elements include the full path
const path = (file as any).path ?? file.name;
const id = crypto.randomUUID();
addPane({
id,
type: 'markdown',
title: file.name,
cwd: path,
});
input.value = '';
}
</script>
<div class="session-list">
<div class="header">
<h2>Sessions</h2>
<div class="header-buttons">
<button class="new-btn" onclick={openMarkdown} title="Open markdown file">M</button>
<button class="new-btn" onclick={newAgent} title="New agent (Ctrl+Shift+N)">A</button>
<button class="new-btn" onclick={newTerminal} title="New terminal (Ctrl+N)">+</button>
</div>
<input
bind:this={fileInputEl}
type="file"
accept=".md,.markdown,.txt"
onchange={handleFileSelect}
style="display: none;"
/>
</div>
<div class="layout-presets">
@ -65,7 +96,7 @@
{#each panes as pane (pane.id)}
<li class="pane-item" class:focused={pane.focused}>
<button class="pane-btn" onclick={() => focusPane(pane.id)}>
<span class="pane-icon">{pane.type === 'terminal' ? '>' : pane.type === 'agent' ? '*' : '#'}</span>
<span class="pane-icon">{pane.type === 'terminal' ? '>' : pane.type === 'agent' ? '*' : pane.type === 'markdown' ? 'M' : '#'}</span>
<span class="pane-name">{pane.title}</span>
</button>
<button class="remove-btn" onclick={() => removePane(pane.id)}>&times;</button>