feat(v2): add SSH management, ctx integration, themes, detached mode, auto-updater

SSH session management:
- SshSession struct + ssh_sessions SQLite table in session.rs
- CRUD Tauri commands (ssh_session_list/save/delete) in lib.rs
- SshDialog.svelte (create/edit modal), SshSessionList.svelte (sidebar)
- SSH pane routes to TerminalPane with shell=/usr/bin/ssh + args

ctx context database integration:
- ctx.rs: read-only CtxDb (SQLITE_OPEN_READ_ONLY for ~/.claude-context/context.db)
- 5 Tauri commands (ctx_list_projects/get_context/get_shared/get_summaries/search)
- ContextPane.svelte with project selector, tabs, search
- ctx-bridge.ts adapter

Catppuccin theme flavors (Latte/Frappe/Macchiato/Mocha):
- themes.ts: all 4 palette definitions + buildXtermTheme/applyCssVariables
- theme.svelte.ts: reactive store with SQLite persistence
- SettingsDialog flavor dropdown, TerminalPane theme-aware

Detached pane mode (pop-out windows):
- detach.ts: isDetachedMode/getDetachedConfig from URL params
- App.svelte: conditional rendering of single pane without chrome

Other additions:
- Shiki syntax highlighting (highlight.ts, lazy singleton, 13 languages)
- Tauri auto-updater plugin (tauri-plugin-updater + updater.ts)
- AgentPane markdown rendering with Shiki code highlighting
- New deps: shiki, @tauri-apps/plugin-updater, tauri-plugin-updater
This commit is contained in:
Hibryda 2026-03-06 14:50:00 +01:00
parent 4f2614186d
commit 4db7ccff60
28 changed files with 2992 additions and 51 deletions

View file

@ -0,0 +1,68 @@
// Detachable pane support — opens panes in separate OS windows
// Uses Tauri's WebviewWindow API
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import type { Pane } from '../stores/layout.svelte';
let detachCounter = 0;
export async function detachPane(pane: Pane): Promise<void> {
detachCounter++;
const label = `detached-${detachCounter}`;
const params = new URLSearchParams({
detached: 'true',
type: pane.type,
title: pane.title,
});
if (pane.shell) params.set('shell', pane.shell);
if (pane.cwd) params.set('cwd', pane.cwd);
if (pane.args) params.set('args', JSON.stringify(pane.args));
if (pane.type === 'agent') params.set('sessionId', pane.id);
const webview = new WebviewWindow(label, {
url: `index.html?${params.toString()}`,
title: `BTerminal — ${pane.title}`,
width: 800,
height: 600,
decorations: true,
resizable: true,
});
// Wait for the window to be created
await webview.once('tauri://created', () => {
// Window created successfully
});
await webview.once('tauri://error', (e) => {
console.error('Failed to create detached window:', e);
});
}
export function isDetachedMode(): boolean {
const params = new URLSearchParams(window.location.search);
return params.get('detached') === 'true';
}
export function getDetachedConfig(): {
type: string;
title: string;
shell?: string;
cwd?: string;
args?: string[];
sessionId?: string;
} | null {
const params = new URLSearchParams(window.location.search);
if (params.get('detached') !== 'true') return null;
const argsStr = params.get('args');
return {
type: params.get('type') ?? 'terminal',
title: params.get('title') ?? 'Detached',
shell: params.get('shell') ?? undefined,
cwd: params.get('cwd') ?? undefined,
args: argsStr ? JSON.parse(argsStr) : undefined,
sessionId: params.get('sessionId') ?? undefined,
};
}

View file

@ -0,0 +1,51 @@
import { createHighlighter, type Highlighter } from 'shiki';
let highlighter: Highlighter | null = null;
let initPromise: Promise<Highlighter> | null = null;
// Use catppuccin-mocha theme (bundled with shiki)
const THEME = 'catppuccin-mocha';
// Common languages to preload
const LANGS = [
'typescript', 'javascript', 'rust', 'python', 'bash',
'json', 'html', 'css', 'svelte', 'sql', 'yaml', 'toml', 'markdown',
];
export async function getHighlighter(): Promise<Highlighter> {
if (highlighter) return highlighter;
if (initPromise) return initPromise;
initPromise = createHighlighter({
themes: [THEME],
langs: LANGS,
});
highlighter = await initPromise;
return highlighter;
}
export function highlightCode(code: string, lang: string): string {
if (!highlighter) return escapeHtml(code);
try {
const loadedLangs = highlighter.getLoadedLanguages();
if (!loadedLangs.includes(lang as any)) {
return escapeHtml(code);
}
return highlighter.codeToHtml(code, {
lang,
theme: THEME,
});
} catch {
return escapeHtml(code);
}
}
export function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}

View file

@ -0,0 +1,32 @@
// Auto-update checker — uses Tauri updater plugin
// Requires signing key to be configured in tauri.conf.json before use
import { check } from '@tauri-apps/plugin-updater';
export async function checkForUpdates(): Promise<{
available: boolean;
version?: string;
notes?: string;
}> {
try {
const update = await check();
if (update) {
return {
available: true,
version: update.version,
notes: update.body ?? undefined,
};
}
return { available: false };
} catch {
// Updater not configured or network error — silently skip
return { available: false };
}
}
export async function installUpdate(): Promise<void> {
const update = await check();
if (update) {
await update.downloadAndInstall();
}
}