Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb20fcf
feat: add preview sidebar with dev server support
nilskroe Jan 25, 2026
bdc7c90
feat: persist preview URL across worktree switches
nilskroe Jan 25, 2026
5c50ac9
fix: recreate webview when preview sidebar reopens
nilskroe Jan 25, 2026
de6d2f5
fix: skip about:blank in webview navigation handlers
nilskroe Jan 25, 2026
089a97f
feat: add dev accounts for preview login auto-fill
nilskroe Jan 25, 2026
eba7323
fix: change devCredentials.get to mutation for imperative calls
nilskroe Jan 25, 2026
73bf5d7
feat: add screenshot capture to preview sidebar
nilskroe Jan 25, 2026
b188290
chore: clean up debug logging and unused imports
nilskroe Jan 25, 2026
e4c1d8c
fix: only export attachment handlers from active subchat
nilskroe Jan 25, 2026
72816fa
chore: add debug logging for preview element selection
nilskroe Jan 25, 2026
57651e3
feat: cmd+R triggers start/stop when preview is open
nilskroe Jan 25, 2026
5b0bbf8
Merge remote-tracking branch 'origin/main' into feature/start-mac-app
nilskroe Jan 25, 2026
e43bd94
fix: wrap TooltipTrigger buttons in span to prevent React 19 infinite…
nilskroe Jan 25, 2026
b6709ca
feat: add beta flag for preview sidebar
nilskroe Jan 25, 2026
ea61963
feat: improve sidebar mutual exclusion and port scanning
nilskroe Jan 25, 2026
dd53e22
perf: optimize render performance across multiple components
nilskroe Jan 25, 2026
2aa20cc
fix: improve React Grab integration for element selector
nilskroe Jan 25, 2026
ebde458
refactor: move running servers menu to help popover
nilskroe Jan 25, 2026
4541312
fix: always use React Grab plugin API and hide button if unavailable
nilskroe Jan 25, 2026
50e7a54
refactor: simplify MCP servers indicator
nilskroe Jan 25, 2026
e1ee091
fix: preview sidebar now closes diff sidebar (side-peek mode)
nilskroe Jan 25, 2026
af649b9
feat: add cmd+R shortcut to toggle preview sidebar
nilskroe Jan 25, 2026
f722b61
fix: correct React Grab plugin API structure for element selection
nilskroe Jan 25, 2026
49b02c8
fix: add missing devCredentials router to app router
nilskroe Jan 25, 2026
ca92611
feat: add Changes View display mode setting to Appearance tab
nilskroe Jan 25, 2026
5ac4c21
Merge main branch into feature/start-mac-app
nilskroe Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions drizzle/0006_acoustic_luke_cage.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TABLE `dev_credentials` (
`id` text PRIMARY KEY NOT NULL,
`label` text NOT NULL,
`email` text NOT NULL,
`encrypted_password` text NOT NULL,
`domain` text,
`created_at` integer,
`updated_at` integer
);
3 changes: 3 additions & 0 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export default defineConfig({
: undefined,
}),
],
server: {
port: 5199, // Use non-default port to avoid conflict with worktree dev servers
},
resolve: {
alias: {
"@": resolve(__dirname, "src/renderer"),
Expand Down
20 changes: 20 additions & 0 deletions src/main/lib/db/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,24 @@ export const claudeCodeCredentials = sqliteTable("claude_code_credentials", {
userId: text("user_id"), // Desktop auth user ID (for reference)
})

// ============ DEV CREDENTIALS ============
// Stores encrypted credentials for auto-filling login forms in preview
export const devCredentials = sqliteTable("dev_credentials", {
id: text("id")
.primaryKey()
.$defaultFn(() => createId()),
label: text("label").notNull(), // Friendly name like "Test User 1"
email: text("email").notNull(),
encryptedPassword: text("encrypted_password").notNull(), // Encrypted with safeStorage
domain: text("domain"), // Optional domain hint for filtering
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
() => new Date(),
),
updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(
() => new Date(),
),
})

// ============ ANTHROPIC ACCOUNTS (Multi-account support) ============
// Stores multiple Anthropic OAuth accounts for quick switching
export const anthropicAccounts = sqliteTable("anthropic_accounts", {
Expand Down Expand Up @@ -135,6 +153,8 @@ export type SubChat = typeof subChats.$inferSelect
export type NewSubChat = typeof subChats.$inferInsert
export type ClaudeCodeCredential = typeof claudeCodeCredentials.$inferSelect
export type NewClaudeCodeCredential = typeof claudeCodeCredentials.$inferInsert
export type DevCredential = typeof devCredentials.$inferSelect
export type NewDevCredential = typeof devCredentials.$inferInsert
export type AnthropicAccount = typeof anthropicAccounts.$inferSelect
export type NewAnthropicAccount = typeof anthropicAccounts.$inferInsert
export type AnthropicSettings = typeof anthropicSettings.$inferSelect
39 changes: 36 additions & 3 deletions src/main/lib/terminal/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export class TerminalManager extends EventEmitter {

portManager.registerSession(session, workspaceId || "")

// Emit started event so subscribers know a session was created
this.emit(`started:${paneId}`, session.cwd)

return {
isNew: true,
serializedState: "",
Expand Down Expand Up @@ -193,11 +196,41 @@ export class TerminalManager extends EventEmitter {
return
}

if (session.isAlive) {
session.pty.kill()
} else {
if (!session.isAlive) {
this.sessions.delete(paneId)
return
}

// Send SIGTERM first
try {
session.pty.kill("SIGTERM")
} catch (error) {
console.error(`[TerminalManager] Failed to send SIGTERM to ${paneId}:`, error)
}

// Wait for graceful shutdown, then escalate to SIGKILL
await new Promise<void>((resolve) => {
const checkInterval = setInterval(() => {
if (!session.isAlive) {
clearInterval(checkInterval)
clearTimeout(forceKillTimeout)
resolve()
}
}, 100)

const forceKillTimeout = setTimeout(() => {
clearInterval(checkInterval)
if (session.isAlive) {
try {
session.pty.kill("SIGKILL")
} catch (error) {
console.error(`[TerminalManager] Failed to send SIGKILL to ${paneId}:`, error)
}
}
// Give SIGKILL a moment to take effect
setTimeout(resolve, 100)
}, 2000)
})
}

detach(params: { paneId: string; serializedState?: string }): void {
Expand Down
Loading