feat(electrobun): groups, cloning, shortcuts, custom window — all 5 features
Groups Sidebar: - SQLite groups table (4 seeded: Development, Testing, DevOps, Research) - Left icon rail with emoji group icons, Ctrl+1-4 switching - Active group highlighted, projects filtered by group Project Cloning: - Clone button on project cards (fork icon) - git worktree add via Bun.spawn (array form, no shell strings) - 3-clone limit, branch name validation, pending-status pattern - Clone cards: WT badge + branch name + accent top border - Chain link SVG icons between linked clones in grid Keyboard Shortcuts: - keybinding-store.svelte.ts: 16 defaults across 4 categories - Two-scope: document capture + terminal focus guard - KeyboardSettings.svelte: search, click-to-capture, conflict detection - Per-binding reset + Reset All Custom Window: - titleBarStyle: "hidden" — no native title bar - Vertical "AGOR" text in left sidebar (writing-mode: vertical-rl) - Floating window controls badge (minimize/maximize/close) - Draggable region via -webkit-app-region: drag - Window frame persisted to SQLite (debounced 500ms) Window is resizable by default (Electrobun BrowserWindow).
This commit is contained in:
parent
5032021915
commit
a020f59cb4
14 changed files with 1741 additions and 189 deletions
|
|
@ -39,6 +39,26 @@ CREATE TABLE IF NOT EXISTS custom_themes (
|
|||
name TEXT NOT NULL,
|
||||
palette TEXT NOT NULL -- JSON blob
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
icon TEXT NOT NULL,
|
||||
position INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS keybindings (
|
||||
id TEXT PRIMARY KEY,
|
||||
chord TEXT NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
// Seed default groups (idempotent via INSERT OR IGNORE)
|
||||
const SEED_GROUPS = `
|
||||
INSERT OR IGNORE INTO groups VALUES ('dev', 'Development', '🔧', 0);
|
||||
INSERT OR IGNORE INTO groups VALUES ('test', 'Testing', '🧪', 1);
|
||||
INSERT OR IGNORE INTO groups VALUES ('ops', 'DevOps', '🚀', 2);
|
||||
INSERT OR IGNORE INTO groups VALUES ('research', 'Research', '🔬', 3);
|
||||
`;
|
||||
|
||||
// ── SettingsDb class ─────────────────────────────────────────────────────────
|
||||
|
|
@ -51,6 +71,18 @@ export interface ProjectConfig {
|
|||
provider?: string;
|
||||
profile?: string;
|
||||
model?: string;
|
||||
/** Group this project belongs to. Defaults to 'dev'. */
|
||||
groupId?: string;
|
||||
/** For clones: path of the source repo (worktree parent). */
|
||||
mainRepoPath?: string;
|
||||
/** For clones: ID of the original project this was cloned from. */
|
||||
cloneOf?: string;
|
||||
/** For clones: absolute path to the git worktree. */
|
||||
worktreePath?: string;
|
||||
/** For clones: branch name checked out in the worktree. */
|
||||
worktreeBranch?: string;
|
||||
/** 1-indexed clone number within the parent (1–3). */
|
||||
cloneIndex?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +92,13 @@ export interface CustomTheme {
|
|||
palette: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export class SettingsDb {
|
||||
private db: Database;
|
||||
|
||||
|
|
@ -69,6 +108,7 @@ export class SettingsDb {
|
|||
|
||||
this.db = new Database(DB_PATH);
|
||||
this.db.exec(SCHEMA);
|
||||
this.db.exec(SEED_GROUPS);
|
||||
|
||||
// Seed schema_version row if missing
|
||||
const version = this.db
|
||||
|
|
@ -136,6 +176,14 @@ export class SettingsDb {
|
|||
});
|
||||
}
|
||||
|
||||
// ── Groups ─────────────────────────────────────────────────────────────────
|
||||
|
||||
listGroups(): Group[] {
|
||||
return this.db
|
||||
.query<Group, []>("SELECT id, name, icon, position FROM groups ORDER BY position ASC")
|
||||
.all();
|
||||
}
|
||||
|
||||
// ── Custom Themes ─────────────────────────────────────────────────────────
|
||||
|
||||
getCustomThemes(): CustomTheme[] {
|
||||
|
|
@ -166,6 +214,25 @@ export class SettingsDb {
|
|||
this.db.query("DELETE FROM custom_themes WHERE id = ?").run(id);
|
||||
}
|
||||
|
||||
// ── Keybindings ───────────────────────────────────────────────────────────
|
||||
|
||||
getKeybindings(): Record<string, string> {
|
||||
const rows = this.db
|
||||
.query<{ id: string; chord: string }, []>("SELECT id, chord FROM keybindings")
|
||||
.all();
|
||||
return Object.fromEntries(rows.map((r) => [r.id, r.chord]));
|
||||
}
|
||||
|
||||
setKeybinding(id: string, chord: string): void {
|
||||
this.db
|
||||
.query("INSERT INTO keybindings (id, chord) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET chord = excluded.chord")
|
||||
.run(id, chord);
|
||||
}
|
||||
|
||||
deleteKeybinding(id: string): void {
|
||||
this.db.query("DELETE FROM keybindings WHERE id = ?").run(id);
|
||||
}
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
close(): void {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue