feat(electrobun): wire persistence — SQLite, 17 themes, font system

Persistence:
- bun:sqlite at ~/.config/agor/settings.db (WAL mode, 500ms busy_timeout)
- 4 tables: schema_version, settings, projects, custom_themes
- 5 RPC handlers: settings.get/set/getAll, projects get/set

Theme system (LIVE switching):
- All 17 themes ported from Tauri (4 Catppuccin + 7 Editor + 6 Deep Dark)
- applyCssVars() sets 26 --ctp-* vars on document.documentElement
- Parallel xterm ITheme mapping per theme
- theme-store.svelte.ts: Svelte 5 rune store, persists to SQLite

Font system:
- font-store.svelte.ts: UI/terminal font family + size
- Live CSS var application (--ui-font-family/size, --term-font-family/size)
- onTermFontChange() callback registry for terminal instances
- Persists all 4 font settings to SQLite

AppearanceSettings wired: 17-theme grouped dropdown, font steppers
Init on startup: restores saved theme + fonts from SQLite
This commit is contained in:
Hibryda 2026-03-20 05:29:03 +01:00
parent 0b9e8b305a
commit 6002a379e4
13 changed files with 1043 additions and 53 deletions

View file

@ -231861,6 +231861,105 @@ class PtyClient extends EventEmitter2 {
}
}
// src/bun/settings-db.ts
import { Database as Database2 } from "bun:sqlite";
import { homedir as homedir3 } from "os";
import { mkdirSync as mkdirSync2 } from "fs";
import { join as join7 } from "path";
var CONFIG_DIR = join7(homedir3(), ".config", "agor");
var DB_PATH = join7(CONFIG_DIR, "settings.db");
var SCHEMA = `
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 500;
CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
config TEXT NOT NULL -- JSON blob
);
CREATE TABLE IF NOT EXISTS custom_themes (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
palette TEXT NOT NULL -- JSON blob
);
`;
class SettingsDb {
db;
constructor() {
mkdirSync2(CONFIG_DIR, { recursive: true });
this.db = new Database2(DB_PATH);
this.db.exec(SCHEMA);
const version = this.db.query("SELECT version FROM schema_version LIMIT 1").get();
if (!version) {
this.db.exec("INSERT INTO schema_version (version) VALUES (1)");
}
}
getSetting(key) {
const row = this.db.query("SELECT value FROM settings WHERE key = ?").get(key);
return row?.value ?? null;
}
setSetting(key, value) {
this.db.query("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
}
getAll() {
const rows = this.db.query("SELECT key, value FROM settings").all();
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
}
getProject(id) {
const row = this.db.query("SELECT config FROM projects WHERE id = ?").get(id);
if (!row)
return null;
try {
return JSON.parse(row.config);
} catch {
console.error(`[settings-db] Failed to parse project config for id=${id}`);
return null;
}
}
setProject(id, config) {
const json = JSON.stringify(config);
this.db.query("INSERT INTO projects (id, config) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET config = excluded.config").run(id, json);
}
listProjects() {
const rows = this.db.query("SELECT config FROM projects").all();
return rows.flatMap((r) => {
try {
return [JSON.parse(r.config)];
} catch {
return [];
}
});
}
getCustomThemes() {
const rows = this.db.query("SELECT id, name, palette FROM custom_themes").all();
return rows.flatMap((r) => {
try {
return [{ id: r.id, name: r.name, palette: JSON.parse(r.palette) }];
} catch {
return [];
}
});
}
saveCustomTheme(id, name531, palette) {
const json = JSON.stringify(palette);
this.db.query("INSERT INTO custom_themes (id, name, palette) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, palette = excluded.palette").run(id, name531, json);
}
close() {
this.db.close();
}
}
var settingsDb = new SettingsDb;
// src/bun/index.ts
var DEV_SERVER_PORT = 9760;
var DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
@ -231932,6 +232031,53 @@ var rpc = BrowserView.defineRPC({
ptyClient.closeSession(sessionId);
} catch {}
return { ok: true };
},
"settings.get": ({ key }) => {
try {
return { value: settingsDb.getSetting(key) };
} catch (err) {
console.error("[settings.get]", err);
return { value: null };
}
},
"settings.set": ({ key, value }) => {
try {
settingsDb.setSetting(key, value);
return { ok: true };
} catch (err) {
console.error("[settings.set]", err);
return { ok: false };
}
},
"settings.getAll": () => {
try {
return { settings: settingsDb.getAll() };
} catch (err) {
console.error("[settings.getAll]", err);
return { settings: {} };
}
},
"settings.getProjects": () => {
try {
const projects = settingsDb.listProjects().map((p) => ({
id: p.id,
config: JSON.stringify(p)
}));
return { projects };
} catch (err) {
console.error("[settings.getProjects]", err);
return { projects: [] };
}
},
"settings.setProject": ({ id, config }) => {
try {
const parsed = JSON.parse(config);
settingsDb.setProject(id, { id, ...parsed });
return { ok: true };
} catch (err) {
console.error("[settings.setProject]", err);
return { ok: false };
}
}
},
messages: {}

View file

@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte App</title>
<script type="module" crossorigin src="/assets/index-I2iZIyVf.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-x9Y0o9Mz.css">
<script type="module" crossorigin src="/assets/index-CZZnRPP5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DSZtflYD.css">
</head>
<body>
<div id="app"></div>