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

@ -1,7 +1,8 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { marked } from 'marked';
import { marked, Renderer } from 'marked';
import { watchFile, unwatchFile, onFileChanged, type FileChangedPayload } from '../../adapters/file-bridge';
import { getHighlighter, highlightCode, escapeHtml } from '../../utils/highlight';
interface Props {
filePath: string;
@ -15,9 +16,18 @@
let error = $state('');
let unlisten: (() => void) | undefined;
const renderer = new Renderer();
renderer.code = function({ text, lang }: { text: string; lang?: string }) {
if (lang) {
const highlighted = highlightCode(text, lang);
if (highlighted !== escapeHtml(text)) return highlighted;
}
return `<pre><code>${escapeHtml(text)}</code></pre>`;
};
function renderMarkdown(source: string): void {
try {
renderedHtml = marked.parse(source, { async: false }) as string;
renderedHtml = marked.parse(source, { renderer, async: false }) as string;
error = '';
} catch (e) {
error = `Render error: ${e}`;
@ -26,6 +36,7 @@
onMount(async () => {
try {
await getHighlighter();
const content = await watchFile(paneId, filePath);
renderMarkdown(content);
@ -125,6 +136,21 @@
color: var(--text-primary);
}
.markdown-body :global(.shiki) {
background: var(--bg-surface) !important;
padding: 12px 14px;
border-radius: var(--border-radius);
overflow-x: auto;
font-size: 12px;
line-height: 1.5;
margin: 0.6em 0;
}
.markdown-body :global(.shiki code) {
background: none !important;
padding: 0;
}
.markdown-body :global(blockquote) {
border-left: 3px solid var(--ctp-mauve);
margin: 0.5em 0;