diff --git a/apps/desktop/src/lib/trpc/routers/browser/browser.ts b/apps/desktop/src/lib/trpc/routers/browser/browser.ts index 8827d016742..47750092e04 100644 --- a/apps/desktop/src/lib/trpc/routers/browser/browser.ts +++ b/apps/desktop/src/lib/trpc/routers/browser/browser.ts @@ -168,6 +168,81 @@ export const createBrowserRouter = () => { return { success: true }; }), + findInPage: publicProcedure + .input( + z.object({ + paneId: z.string(), + text: z.string(), + forward: z.boolean().optional(), + findNext: z.boolean().optional(), + matchCase: z.boolean().optional(), + }), + ) + .mutation(({ input }) => { + const requestId = browserManager.findInPage(input.paneId, input.text, { + forward: input.forward, + findNext: input.findNext, + matchCase: input.matchCase, + }); + return { requestId }; + }), + + stopFindInPage: publicProcedure + .input( + z.object({ + paneId: z.string(), + action: z + .enum(["clearSelection", "keepSelection", "activateSelection"]) + .optional(), + }), + ) + .mutation(({ input }) => { + browserManager.stopFindInPage( + input.paneId, + input.action ?? "clearSelection", + ); + return { success: true }; + }), + + onFoundInPage: publicProcedure + .input(z.object({ paneId: z.string() })) + .subscription(({ input }) => { + return observable<{ + requestId: number; + activeMatchOrdinal: number; + matches: number; + finalUpdate: boolean; + }>((emit) => { + const handler = (data: { + requestId: number; + activeMatchOrdinal: number; + matches: number; + finalUpdate: boolean; + }) => { + emit.next(data); + }; + browserManager.on(`found-in-page:${input.paneId}`, handler); + return () => { + browserManager.off(`found-in-page:${input.paneId}`, handler); + }; + }); + }), + + onFindRequested: publicProcedure + .input(z.object({ paneId: z.string() })) + .subscription(({ input }) => { + return observable<{ type: "open" | "escape" }>((emit) => { + const openHandler = () => emit.next({ type: "open" }); + const escapeHandler = () => emit.next({ type: "escape" }); + browserManager.on(`find-requested:${input.paneId}`, openHandler); + browserManager.on(`find-escape:${input.paneId}`, escapeHandler); + return () => { + browserManager.off(`find-requested:${input.paneId}`, openHandler); + browserManager.off(`find-escape:${input.paneId}`, escapeHandler); + }; + }); + }), + setZoomLevel: publicProcedure .input(z.object({ paneId: z.string(), level: z.number() })) .mutation(({ input }) => { diff --git a/apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts b/apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts index 7c8cca03bdd..3699bf221de 100644 --- a/apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts +++ b/apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts @@ -294,6 +294,20 @@ export async function gitStageAll(worktreePath: string): Promise { await git.add("-A"); } +/** + * Stage all changes to tracked files only. + * + * Uses `git add -u` so modifications and deletions of tracked files + * are staged, but untracked files are left alone. Matches the + * VS Code `git.smartCommitChanges: "tracked"` behavior. + */ +export async function gitStageTracked(worktreePath: string): Promise { + assertRegisteredWorktree(worktreePath); + + const git = await getGitWithShellPath(worktreePath); + await git.add(["-u"]); +} + /** * Unstage a file (remove from staging area). * diff --git a/apps/desktop/src/lib/trpc/routers/changes/staging.ts b/apps/desktop/src/lib/trpc/routers/changes/staging.ts index cbf7598eb4f..b3d7bc28bed 100644 --- a/apps/desktop/src/lib/trpc/routers/changes/staging.ts +++ b/apps/desktop/src/lib/trpc/routers/changes/staging.ts @@ -10,6 +10,7 @@ import { gitStageAll, gitStageFile, gitStageFiles, + gitStageTracked, gitStash, gitStashIncludeUntracked, gitStashPop, @@ -127,6 +128,14 @@ export const createStagingRouter = () => { return { success: true }; }), + stageTracked: publicProcedure + .input(z.object({ worktreePath: z.string() })) + .mutation(async ({ input }): Promise<{ success: boolean }> => { + await gitStageTracked(input.worktreePath); + clearStatusCacheForWorktree(input.worktreePath); + return { success: true }; + }), + unstageAll: publicProcedure .input(z.object({ worktreePath: z.string() })) .mutation(async ({ input }): Promise<{ success: boolean }> => { diff --git a/apps/desktop/src/lib/trpc/routers/projects/projects.ts b/apps/desktop/src/lib/trpc/routers/projects/projects.ts index 614e0cc7441..73d8c6598ab 100644 --- a/apps/desktop/src/lib/trpc/routers/projects/projects.ts +++ b/apps/desktop/src/lib/trpc/routers/projects/projects.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from "node:events"; import { existsSync, statSync } from "node:fs"; import { access, mkdir, rm } from "node:fs/promises"; import { basename, join } from "node:path"; @@ -12,6 +13,7 @@ import { worktrees, } from "@superset/local-db"; import { TRPCError } from "@trpc/server"; +import { observable } from "@trpc/server/observable"; import { and, desc, eq, inArray, isNotNull, isNull, not } from "drizzle-orm"; import type { BrowserWindow } from "electron"; import { dialog } from "electron"; @@ -23,6 +25,7 @@ import { } from "main/lib/project-icons"; import { getWorkspaceRuntimeRegistry } from "main/lib/workspace-runtime"; import { PROJECT_COLOR_VALUES } from "shared/constants/project-colors"; +import simpleGit, { type SimpleGitProgressEvent } from "simple-git"; import { z } from "zod"; import { publicProcedure, router } from "../.."; import { resolveDefaultEditor } from "../external"; @@ -43,7 +46,10 @@ import { sanitizeAuthorPrefix, } from "../workspaces/utils/git"; import { getSimpleGitWithShellPath } from "../workspaces/utils/git-client"; -import { execWithShellEnv } from "../workspaces/utils/shell-env"; +import { + execWithShellEnv, + getProcessEnvWithShellPath, +} from "../workspaces/utils/shell-env"; import { getDefaultProjectColor } from "./utils/colors"; import { discoverAndSaveProjectIcon } from "./utils/favicon-discovery"; import { fetchGitHubOwner, getGitHubAvatarUrl } from "./utils/github"; @@ -323,6 +329,119 @@ function extractRepoName(urlInput: string): string | null { return repoSegment; } +interface CloneEventBase { + cloneId: string; + /** Monotonic sequence number per cloneId, used by subscribers to dedupe. */ + seq: number; + time: number; +} + +export type CloneProgressEvent = + | (CloneEventBase & { + type: "log"; + message: string; + level: "info" | "warn" | "error"; + }) + | (CloneEventBase & { + type: "progress"; + stage: string; + progress: number; + processed: number; + total: number; + }) + | (CloneEventBase & { type: "done" }) + | (CloneEventBase & { type: "error"; message: string }) + | (CloneEventBase & { type: "canceled" }); + +const cloneEventBus = new EventEmitter(); +cloneEventBus.setMaxListeners(0); +const cloneAbortControllers = new Map(); + +/** + * Per-cloneId replay buffer. The tRPC subscription is established after the + * mutation is fired from the client, so events emitted in the window between + * `cloneRepo` starting and the subscription connecting would otherwise be + * lost. The buffer is flushed to the first subscriber and trimmed on a short + * timeout after any terminal event (done / error / canceled). + */ +const cloneEventBuffers = new Map(); +const cloneBufferEvictTimers = new Map(); +const cloneSeqCounters = new Map(); +const MAX_BUFFERED_EVENTS = 1000; +const TERMINAL_BUFFER_EVICT_MS = 30_000; + +function isTerminalCloneEvent(event: CloneProgressEvent): boolean { + return ( + event.type === "done" || event.type === "error" || event.type === "canceled" + ); +} + +function nextCloneSeq(cloneId: string): number { + const next = (cloneSeqCounters.get(cloneId) ?? 0) + 1; + cloneSeqCounters.set(cloneId, next); + return next; +} + +// Distributive omit preserves the discriminated-union shape so callers can +// still pass type-specific fields (`message`, `stage`, …) without TS +// collapsing everything to the common intersection. +type DistributiveOmit = T extends unknown + ? Omit + : never; +type CloneEventInput = DistributiveOmit; + +function emitCloneEvent(input: CloneEventInput) { + const event = { + ...input, + seq: nextCloneSeq(input.cloneId), + } as CloneProgressEvent; + let buffer = cloneEventBuffers.get(event.cloneId); + if (!buffer) { + buffer = []; + cloneEventBuffers.set(event.cloneId, buffer); + } + buffer.push(event); + if (buffer.length > MAX_BUFFERED_EVENTS) { + buffer.splice(0, buffer.length - MAX_BUFFERED_EVENTS); + } + cloneEventBus.emit(event.cloneId, event); + + if (isTerminalCloneEvent(event)) { + const existing = cloneBufferEvictTimers.get(event.cloneId); + if (existing) clearTimeout(existing); + const timer = setTimeout(() => { + cloneEventBuffers.delete(event.cloneId); + cloneBufferEvictTimers.delete(event.cloneId); + cloneSeqCounters.delete(event.cloneId); + }, TERMINAL_BUFFER_EVICT_MS); + cloneBufferEvictTimers.set(event.cloneId, timer); + } +} + +function emitCloneLog( + cloneId: string, + message: string, + level: "info" | "warn" | "error" = "info", +) { + emitCloneEvent({ + type: "log", + cloneId, + message, + level, + time: Date.now(), + }); +} + +/** + * Strip `userinfo` (credentials embedded in URLs such as + * `https://token@host/...` or `https://user:pass@host/...`) so that PATs and + * basic-auth tokens never reach the renderer via progress logs or error + * messages. Applied to every string emitted through the clone event bus. + */ +function redactGitCredentials(value: string): string { + return value.replace(/\/\/([^/\s@]+)(?::[^/\s@]*)?@/g, "//***@"); +} + /** Create the tRPC router for project CRUD, branch listing, and git operations. */ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { return router({ @@ -1200,6 +1319,53 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { return { project }; }), + cloneProgress: publicProcedure + .input(z.object({ cloneId: z.string().min(1) })) + .subscription(({ input }) => { + return observable((emit) => { + // Dedupe by monotonic seq so that we can safely attach the live + // listener first and then replay the buffer: any event that + // reaches both paths only passes the `> lastSeq` guard once. + let lastSeq = 0; + const deliver = (event: CloneProgressEvent) => { + if (event.seq <= lastSeq) return; + lastSeq = event.seq; + emit.next(event); + }; + const handler = (event: CloneProgressEvent) => { + deliver(event); + }; + cloneEventBus.on(input.cloneId, handler); + const buffered = cloneEventBuffers.get(input.cloneId); + if (buffered) { + for (const event of buffered) { + deliver(event); + } + } + return () => { + cloneEventBus.off(input.cloneId, handler); + }; + }); + }), + + cancelClone: publicProcedure + .input(z.object({ cloneId: z.string().min(1) })) + .mutation(({ input }) => { + const controller = cloneAbortControllers.get(input.cloneId); + if (!controller) { + return { canceled: false as const }; + } + controller.abort(); + cloneAbortControllers.delete(input.cloneId); + emitCloneLog(input.cloneId, "Clone canceled by user", "warn"); + emitCloneEvent({ + type: "canceled", + cloneId: input.cloneId, + time: Date.now(), + }); + return { canceled: true as const }; + }), + cloneRepo: publicProcedure .input( z.object({ @@ -1223,6 +1389,7 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { .trim() .optional() .transform((v) => (v && v.length > 0 ? v : undefined)), + cloneId: z.string().min(1).optional(), }), ) .mutation(async ({ input }) => { @@ -1316,9 +1483,72 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { }; } - // Clone the repository - const git = await getSimpleGitWithShellPath(); - await git.clone(input.url, clonePath); + // Clone the repository (with streaming progress when cloneId given) + const cloneId = input.cloneId; + if (cloneId) { + const abortController = new AbortController(); + cloneAbortControllers.set(cloneId, abortController); + emitCloneLog( + cloneId, + `Preparing clone into ${basename(clonePath)}`, + ); + try { + const gitWithProgress = simpleGit({ + abort: abortController.signal, + progress: (event: SimpleGitProgressEvent) => { + emitCloneEvent({ + type: "progress", + cloneId, + stage: event.stage, + progress: event.progress, + processed: event.processed, + total: event.total, + time: Date.now(), + }); + }, + }); + gitWithProgress.env(await getProcessEnvWithShellPath()); + emitCloneLog( + cloneId, + `Cloning ${redactGitCredentials(input.url)}`, + ); + await gitWithProgress.clone(input.url, clonePath); + emitCloneLog(cloneId, "Clone finished, preparing project"); + } catch (cloneError) { + const message = redactGitCredentials( + cloneError instanceof Error + ? cloneError.message + : String(cloneError), + ); + // `git clone` creates the destination directory eagerly; + // leaving a partial checkout behind would block every retry + // against the same path via the existing-folder guard above. + await rm(clonePath, { recursive: true, force: true }).catch( + () => undefined, + ); + if (!abortController.signal.aborted) { + emitCloneEvent({ + type: "error", + cloneId, + message, + time: Date.now(), + }); + } + throw cloneError; + } finally { + cloneAbortControllers.delete(cloneId); + } + } else { + const git = await getSimpleGitWithShellPath(); + try { + await git.clone(input.url, clonePath); + } catch (cloneError) { + await rm(clonePath, { recursive: true, force: true }).catch( + () => undefined, + ); + throw cloneError; + } + } // Create new project const name = basename(clonePath); @@ -1344,14 +1574,43 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { method: "clone", }); + if (input.cloneId) { + emitCloneEvent({ + type: "done", + cloneId: input.cloneId, + time: Date.now(), + }); + } + return { canceled: false as const, success: true as const, project, }; } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); + const errorMessage = redactGitCredentials( + error instanceof Error ? error.message : String(error), + ); + // Surface post-clone failures (getDefaultBranch / DB insert / + // ensureMainWorkspace / etc) to any streaming subscriber, unless + // the git clone step itself already emitted an error event. + if (input.cloneId) { + const buffered = cloneEventBuffers.get(input.cloneId); + const hasTerminal = buffered?.some( + (event) => + event.type === "error" || + event.type === "canceled" || + event.type === "done", + ); + if (!hasTerminal) { + emitCloneEvent({ + type: "error", + cloneId: input.cloneId, + message: errorMessage, + time: Date.now(), + }); + } + } return { canceled: false as const, success: false as const, diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts index 502604f2ace..ec824b96f3a 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/index.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts @@ -2,10 +2,13 @@ import { type AgentCustomDefinition, type AgentPresetOverrideEnvelope, BRANCH_PREFIX_MODES, + BRANCH_SORT_ORDERS, EXECUTION_MODES, EXTERNAL_APPS, FILE_OPEN_MODES, NON_EDITOR_APPS, + POST_COMMIT_COMMANDS, + SMART_COMMIT_CHANGES_MODES, settings, TERMINAL_LINK_BEHAVIORS, type TerminalPreset, @@ -59,7 +62,12 @@ import { import { z } from "zod"; import { publicProcedure, router } from "../.."; import { loadToken } from "../auth/utils/auth-functions"; -import { getGitAuthorName, getGitHubUsername } from "../workspaces/utils/git"; +import { + getGitAuthorEmail, + getGitAuthorName, + getGitHubUsername, +} from "../workspaces/utils/git"; +import { getSimpleGitWithShellPath } from "../workspaces/utils/git-client"; import { createCustomAgentInputSchema, normalizeAgentPresetPatch, @@ -832,13 +840,36 @@ export const createSettingsRouter = () => { getGitInfo: publicProcedure.query(async () => { const githubUsername = await getGitHubUsername(); const authorName = await getGitAuthorName(); + const authorEmail = await getGitAuthorEmail(); return { githubUsername, authorName, + authorEmail, authorPrefix: authorName?.toLowerCase().replace(/\s+/g, "-") ?? null, }; }), + setGlobalGitUserConfig: publicProcedure + .input( + z.object({ + name: z.string().trim().min(1, "Name is required"), + email: z + .string() + .trim() + .min(1, "Email is required") + .email("Must be a valid email"), + }), + ) + .mutation(async ({ input }) => { + // Write to the user's global git config so the identity is + // picked up by every future repository. `simple-git` resolves + // the same path as `git config --global`. + const git = await getSimpleGitWithShellPath(); + await git.addConfig("user.name", input.name, false, "global"); + await git.addConfig("user.email", input.email, false, "global"); + return { success: true }; + }), + getDeleteLocalBranch: publicProcedure.query(() => { const row = getSettings(); return row.deleteLocalBranch ?? false; @@ -859,6 +890,116 @@ export const createSettingsRouter = () => { return { success: true }; }), + getSmartCommit: publicProcedure.query(() => { + const row = getSettings(); + return { + enabled: row.enableSmartCommit ?? false, + changes: row.smartCommitChanges ?? "all", + }; + }), + + setSmartCommit: publicProcedure + .input( + z.object({ + enabled: z.boolean(), + changes: z.enum(SMART_COMMIT_CHANGES_MODES), + }), + ) + .mutation(({ input }) => { + localDb + .insert(settings) + .values({ + id: 1, + enableSmartCommit: input.enabled, + smartCommitChanges: input.changes, + }) + .onConflictDoUpdate({ + target: settings.id, + set: { + enableSmartCommit: input.enabled, + smartCommitChanges: input.changes, + }, + }) + .run(); + + return { success: true }; + }), + + getAutoStash: publicProcedure.query(() => { + const row = getSettings(); + return row.autoStash ?? false; + }), + + setAutoStash: publicProcedure + .input(z.object({ enabled: z.boolean() })) + .mutation(({ input }) => { + localDb + .insert(settings) + .values({ id: 1, autoStash: input.enabled }) + .onConflictDoUpdate({ + target: settings.id, + set: { autoStash: input.enabled }, + }) + .run(); + + return { success: true }; + }), + + getBranchSortOrder: publicProcedure.query(() => { + const row = getSettings(); + return { + sortOrder: row.branchSortOrder ?? "committerdate", + pinDefault: row.pinDefaultBranch ?? true, + }; + }), + + setBranchSortOrder: publicProcedure + .input( + z.object({ + sortOrder: z.enum(BRANCH_SORT_ORDERS), + pinDefault: z.boolean(), + }), + ) + .mutation(({ input }) => { + localDb + .insert(settings) + .values({ + id: 1, + branchSortOrder: input.sortOrder, + pinDefaultBranch: input.pinDefault, + }) + .onConflictDoUpdate({ + target: settings.id, + set: { + branchSortOrder: input.sortOrder, + pinDefaultBranch: input.pinDefault, + }, + }) + .run(); + + return { success: true }; + }), + + getPostCommitCommand: publicProcedure.query(() => { + const row = getSettings(); + return row.postCommitCommand ?? "none"; + }), + + setPostCommitCommand: publicProcedure + .input(z.object({ command: z.enum(POST_COMMIT_COMMANDS) })) + .mutation(({ input }) => { + localDb + .insert(settings) + .values({ id: 1, postCommitCommand: input.command }) + .onConflictDoUpdate({ + target: settings.id, + set: { postCommitCommand: input.command }, + }) + .run(); + + return { success: true }; + }), + getNotificationSoundsMuted: publicProcedure.query(() => { const row = getSettings(); return row.notificationSoundsMuted ?? false; diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts index 84033314620..63324b30b59 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts @@ -409,6 +409,22 @@ export async function getGitAuthorName( } } +export async function getGitAuthorEmail( + repoPath?: string, +): Promise { + try { + const git = await getSimpleGitWithShellPath(repoPath); + const email = await git.getConfig("user.email"); + return email.value?.trim() || null; + } catch (error) { + console.warn( + "[git/getGitAuthorEmail] Failed to read git user.email:", + error, + ); + return null; + } +} + let cachedGitHubUsername: { value: string | null; timestamp: number } | null = null; const GITHUB_USERNAME_CACHE_TTL = 5 * 60 * 1000; // 5 minutes diff --git a/apps/desktop/src/main/lib/browser/browser-manager.ts b/apps/desktop/src/main/lib/browser/browser-manager.ts index ab0376ab393..1c6a875b8d6 100644 --- a/apps/desktop/src/main/lib/browser/browser-manager.ts +++ b/apps/desktop/src/main/lib/browser/browser-manager.ts @@ -40,6 +40,7 @@ class BrowserManager extends EventEmitter { private contextMenuListeners = new Map void>(); private fullscreenListeners = new Map void>(); private popupListeners = new Map void>(); + private findListeners = new Map void>(); /** Track which pane is currently in HTML fullscreen */ private fullscreenPaneId: string | null = null; @@ -56,6 +57,7 @@ class BrowserManager extends EventEmitter { this.contextMenuListeners, this.fullscreenListeners, this.popupListeners, + this.findListeners, ]) { const cleanup = map.get(paneId); if (cleanup) { @@ -103,6 +105,7 @@ class BrowserManager extends EventEmitter { this.setupFullscreenHandler(paneId, wc); this.setupConsoleCapture(paneId, wc); this.setupContextMenu(paneId, wc); + this.setupFindInPage(paneId, wc); } } @@ -112,6 +115,7 @@ class BrowserManager extends EventEmitter { this.contextMenuListeners, this.fullscreenListeners, this.popupListeners, + this.findListeners, ]) { const cleanup = map.get(paneId); if (cleanup) { @@ -184,6 +188,71 @@ class BrowserManager extends EventEmitter { wc.openDevTools({ mode: "detach" }); } + findInPage( + paneId: string, + text: string, + options?: { forward?: boolean; findNext?: boolean; matchCase?: boolean }, + ): number | null { + const wc = this.getWebContents(paneId); + if (!wc || !text) return null; + return wc.findInPage(text, options); + } + + stopFindInPage( + paneId: string, + action: "clearSelection" | "keepSelection" | "activateSelection", + ): void { + const wc = this.getWebContents(paneId); + if (!wc) return; + wc.stopFindInPage(action); + } + + /** + * Listen for native `found-in-page` results and for Cmd/Ctrl+F keypresses + * happening inside the webview. The renderer cannot see keydown events + * dispatched to the guest page, so we intercept them here via + * `before-input-event` and emit a request to open the find overlay. + */ + private setupFindInPage(paneId: string, wc: Electron.WebContents): void { + const foundHandler = (_event: Electron.Event, result: Electron.Result) => { + this.emit(`found-in-page:${paneId}`, { + requestId: result.requestId, + activeMatchOrdinal: result.activeMatchOrdinal, + matches: result.matches, + finalUpdate: result.finalUpdate, + }); + }; + + const inputHandler = (event: Electron.Event, input: Electron.Input) => { + if (input.type !== "keyDown") return; + const isFindKey = + (input.meta || input.control) && + input.key.toLowerCase() === "f" && + !input.alt && + !input.shift; + if (isFindKey) { + event.preventDefault(); + this.emit(`find-requested:${paneId}`); + return; + } + if (input.key === "Escape") { + this.emit(`find-escape:${paneId}`); + } + }; + + wc.on("found-in-page", foundHandler); + wc.on("before-input-event", inputHandler); + + this.findListeners.set(paneId, () => { + try { + wc.off("found-in-page", foundHandler); + wc.off("before-input-event", inputHandler); + } catch { + // webContents may be destroyed + } + }); + } + /** * Configure child windows created by window.open() (OAuth popups etc.). * The child BrowserWindow preserves window.opener so postMessage-based diff --git a/apps/desktop/src/renderer/globals.css b/apps/desktop/src/renderer/globals.css index 13622185490..568bedee3d6 100644 --- a/apps/desktop/src/renderer/globals.css +++ b/apps/desktop/src/renderer/globals.css @@ -420,4 +420,16 @@ .review-comment-body .markdown-alert-caution .markdown-alert-title { color: #f85149; } + + @keyframes clone-indeterminate { + 0% { + left: -40%; + } + 100% { + left: 100%; + } + } + .animate-clone-indeterminate { + animation: clone-indeterminate 1.4s ease-in-out infinite; + } } diff --git a/apps/desktop/src/renderer/lib/git/gitErrorDialog.ts b/apps/desktop/src/renderer/lib/git/gitErrorDialog.tsx similarity index 94% rename from apps/desktop/src/renderer/lib/git/gitErrorDialog.ts rename to apps/desktop/src/renderer/lib/git/gitErrorDialog.tsx index bfdb75da8ae..6b0ec8a905d 100644 --- a/apps/desktop/src/renderer/lib/git/gitErrorDialog.ts +++ b/apps/desktop/src/renderer/lib/git/gitErrorDialog.tsx @@ -4,7 +4,9 @@ * their operation — the builder picks which to show per kind. */ +import { MissingGitUserConfigForm } from "renderer/screens/main/components/GitOperationDialog/MissingGitUserConfigForm"; import { + closeGitOperationDialog, type GitOperationDialogSpec, openGitOperationDialog, } from "renderer/stores/git-operation-dialog"; @@ -249,15 +251,23 @@ function buildSpec({ tone: "info", title: "コミット作者情報が未設定です", description: - "Git の user.name / user.email が設定されていないためコミットできません。ターミナルで以下を実行してください。", - details: `git config user.name "Your Name"\ngit config user.email "you@example.com"`, - primaryAction: handlers.retry - ? { - label: "設定後に再試行", - variant: "primary", - onClick: handlers.retry, - } - : undefined, + "Git の user.name / user.email が未設定のためコミットできません。以下のフォームから保存すると `git config --global` に書き込まれます。", + extraContent: ( + { + // Close the dialog before firing the retry so the + // retry's own onError (a fresh classifyGitError call) + // can open a new dialog if something still fails. + closeGitOperationDialog(); + handlers.retry?.(); + }} + /> + ), + // No primaryAction: the form's own "保存して再試行" button + // drives the flow. A stale "設定後に再試行" button would let + // the user retry without saving, which almost always fails + // with the same error. + dismissLabel: "キャンセル", }; case "nothing-to-commit": diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/BaseBranchSelector/BaseBranchSelector.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/BaseBranchSelector/BaseBranchSelector.tsx index 2b63dc65804..8e04d323de9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/BaseBranchSelector/BaseBranchSelector.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/BaseBranchSelector/BaseBranchSelector.tsx @@ -25,7 +25,10 @@ export function BaseBranchSelector({ const filtered = useMemo(() => { if (!search) return branches; const lower = search.toLowerCase(); - return branches.filter((b) => b.name.toLowerCase().includes(lower)); + return branches.filter((b) => { + const display = b.isRemote ? `origin/${b.name}` : b.name; + return display.toLowerCase().includes(lower); + }); }, [branches, search]); return ( @@ -53,23 +56,35 @@ export function BaseBranchSelector({
- {filtered.map((branch) => ( - - ))} + {filtered.map((branch) => { + const display = branch.isRemote + ? `origin/${branch.name}` + : branch.name; + return ( + + ); + })} {filtered.length === 0 && (
No branches found diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx index 906a2d55db0..fefd3ed6468 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx @@ -2,6 +2,7 @@ import { toast } from "@superset/ui/sonner"; import { workspaceTrpc } from "@superset/workspace-client"; import { useCallback, useMemo } from "react"; import type { useGitStatus } from "renderer/hooks/host-service/useGitStatus"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import type { ChangesFilter } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema"; import type { SidebarTabDefinition } from "../../types"; @@ -56,8 +57,16 @@ export function useChangesTab({ { refetchOnWindowFocus: true }, ); + const { data: branchSortSettings } = + electronTrpc.settings.getBranchSortOrder.useQuery(undefined, { + staleTime: 10_000, + }); const branches = workspaceTrpc.git.listBranches.useQuery( - { workspaceId }, + { + workspaceId, + sortOrder: branchSortSettings?.sortOrder, + pinDefault: branchSortSettings?.pinDefault, + }, { refetchInterval: 30_000, refetchOnWindowFocus: true }, ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx b/apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx index 9393785049a..7b599980871 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx @@ -1,6 +1,6 @@ import { Button } from "@superset/ui/button"; import { Input } from "@superset/ui/input"; -import { useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useProjectCreationHandler } from "../../hooks/useProjectCreationHandler"; @@ -9,11 +9,167 @@ interface CloneRepoTabProps { parentDir: string; } +type CloneStatus = "idle" | "cloning" | "done" | "error" | "canceled"; + +interface LogLine { + id: number; + time: string; + message: string; + level: "info" | "warn" | "error" | "good"; +} + +interface ProgressState { + stage: string; + progress: number; + processed: number; + total: number; +} + +const STAGE_LABELS: Record = { + counting: "Counting objects", + compressing: "Compressing objects", + receiving: "Receiving objects", + resolving: "Resolving deltas", + writing: "Writing objects", + unknown: "Working", +}; + +const MAX_LOG_LINES = 500; + +function formatTime(ms: number): string { + const seconds = Math.floor(ms / 1000); + const m = Math.floor(seconds / 60); + const s = seconds % 60; + const ms2 = Math.floor((ms % 1000) / 10); + return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(ms2).padStart(2, "0")}`; +} + export function CloneRepoTab({ onError, parentDir }: CloneRepoTabProps) { const [url, setUrl] = useState(""); + const [status, setStatus] = useState("idle"); + const [cloneId, setCloneId] = useState(null); + const [progress, setProgress] = useState(null); + const [logLines, setLogLines] = useState([]); + const [startedAt, setStartedAt] = useState(null); + const [elapsedMs, setElapsedMs] = useState(0); + const logIdRef = useRef(0); + const logContainerRef = useRef(null); + const wasCanceledRef = useRef(false); + const cloneRepo = electronTrpc.projects.cloneRepo.useMutation(); + const cancelClone = electronTrpc.projects.cancelClone.useMutation(); const { handleResult, handleError } = useProjectCreationHandler(onError); - const isLoading = cloneRepo.isPending; + + const isActive = status === "cloning"; + // Keep the subscription open as long as a cloneId exists so terminal + // events (done/error/canceled) are delivered even after `status` flips + // in the cloneRepo / cancelClone callbacks. + electronTrpc.projects.cloneProgress.useSubscription( + { cloneId: cloneId ?? "" }, + { + enabled: cloneId !== null, + onData: (event) => { + const t = startedAt + ? formatTime(event.time - startedAt) + : formatTime(0); + if (event.type === "log") { + const level: LogLine["level"] = event.level; + setLogLines((prev) => { + logIdRef.current += 1; + const next: LogLine[] = [ + ...prev, + { id: logIdRef.current, time: t, message: event.message, level }, + ]; + return next.length > MAX_LOG_LINES + ? next.slice(next.length - MAX_LOG_LINES) + : next; + }); + } else if (event.type === "progress") { + setProgress({ + stage: event.stage, + progress: event.progress, + processed: event.processed, + total: event.total, + }); + const label = STAGE_LABELS[event.stage] ?? event.stage; + setLogLines((prev) => { + logIdRef.current += 1; + const message = `${label}: ${event.progress}% (${event.processed}/${event.total})`; + const last = prev[prev.length - 1]; + if (last?.message.startsWith(`${label}:`)) { + return [...prev.slice(0, -1), { ...last, time: t, message }]; + } + return [ + ...prev, + { id: logIdRef.current, time: t, message, level: "info" }, + ]; + }); + } else if (event.type === "done") { + setLogLines((prev) => { + logIdRef.current += 1; + return [ + ...prev, + { + id: logIdRef.current, + time: t, + message: "Clone complete", + level: "good", + }, + ]; + }); + } else if (event.type === "error") { + setLogLines((prev) => { + logIdRef.current += 1; + return [ + ...prev, + { + id: logIdRef.current, + time: t, + message: event.message, + level: "error", + }, + ]; + }); + } else if (event.type === "canceled") { + setLogLines((prev) => { + logIdRef.current += 1; + return [ + ...prev, + { + id: logIdRef.current, + time: t, + message: "Canceled", + level: "warn", + }, + ]; + }); + } + }, + }, + ); + + useEffect(() => { + if (!isActive || !startedAt) return; + const interval = setInterval(() => { + setElapsedMs(Date.now() - startedAt); + }, 200); + return () => clearInterval(interval); + }, [isActive, startedAt]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: auto-scroll on new log lines + useEffect(() => { + if (logContainerRef.current) { + logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; + } + }, [logLines]); + + const resetProgressState = useCallback(() => { + setProgress(null); + setLogLines([]); + logIdRef.current = 0; + setElapsedMs(0); + setStartedAt(null); + }, []); const handleClone = () => { if (!url.trim()) { @@ -25,15 +181,83 @@ export function CloneRepoTab({ onError, parentDir }: CloneRepoTabProps) { return; } + const newCloneId = crypto.randomUUID(); + resetProgressState(); + wasCanceledRef.current = false; + setCloneId(newCloneId); + setStatus("cloning"); + setStartedAt(Date.now()); + cloneRepo.mutate( - { url: url.trim(), targetDirectory: parentDir.trim() }, { - onSuccess: (result) => handleResult(result, () => setUrl("")), - onError: handleError, + url: url.trim(), + targetDirectory: parentDir.trim(), + cloneId: newCloneId, + }, + { + onSuccess: (result) => { + if (wasCanceledRef.current) return; + setStatus(result.success ? "done" : "error"); + handleResult(result, () => { + setUrl(""); + }); + }, + onError: (err) => { + if (wasCanceledRef.current) return; + setStatus("error"); + handleError(err); + }, + }, + ); + }; + + const handleCancel = () => { + if (!cloneId) return; + cancelClone.mutate( + { cloneId }, + { + onSuccess: (result) => { + if (!result.canceled) { + // Backend had already finished or no controller remains — + // leave the onSuccess/onError path from cloneRepo handle it. + return; + } + wasCanceledRef.current = true; + setStatus("canceled"); + }, + onError: (err) => { + handleError(err); + }, }, ); }; + const handleReset = () => { + setStatus("idle"); + setCloneId(null); + resetProgressState(); + }; + + const phaseLabel = useMemo(() => { + if (status === "done") return "Clone complete"; + if (status === "error") return "Clone failed"; + if (status === "canceled") return "Canceled"; + if (!progress) return "Connecting..."; + const label = STAGE_LABELS[progress.stage] ?? progress.stage; + return `${label} — ${progress.progress}%`; + }, [status, progress]); + + const barClass = useMemo(() => { + if (status === "done") return "bg-emerald-500"; + if (status === "error" || status === "canceled") return "bg-red-500"; + return "bg-blue-500"; + }, [status]); + + const barIndeterminate = isActive && !progress; + const barWidth = progress ? `${progress.progress}%` : "100%"; + + const showProgress = status !== "idle"; + return (
@@ -48,19 +272,111 @@ export function CloneRepoTab({ onError, parentDir }: CloneRepoTabProps) { value={url} onChange={(e) => setUrl(e.target.value)} placeholder="https:// or git@github.com:user/repo.git" - disabled={isLoading} + disabled={isActive || status === "done"} onKeyDown={(e) => { - if (e.key === "Enter" && !isLoading) { + if (e.key === "Enter" && !isActive) { handleClone(); } }} autoFocus />
-
- + + {showProgress ? ( +
+
+
+ {isActive ? ( +
+ ) : status === "done" ? ( +
+ ) : ( +
+ )} + {phaseLabel} +
+
+ {formatTime(elapsedMs)} +
+
+
+ {barIndeterminate ? ( +
+ ) : ( +
+ )} +
+
+ {logLines.map((line) => ( +
+ {line.time}{" "} + {line.message} +
+ ))} +
+ {progress && progress.total > 0 ? ( +
+
+ + {progress.processed.toLocaleString()} + + {" / "} + {progress.total.toLocaleString()} +
+
+ stage: {progress.stage} +
+
+ ) : null} +
+ ) : null} + +
+ {status === "error" || status === "canceled" ? ( + + ) : null} + {isActive ? ( + + ) : ( + + )}
); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx index 39a190cef10..53e8e46cc3d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx @@ -1,4 +1,9 @@ -import type { BranchPrefixMode } from "@superset/local-db"; +import type { + BranchPrefixMode, + BranchSortOrder, + PostCommitCommand, + SmartCommitChangesMode, +} from "@superset/local-db"; import { Input } from "@superset/ui/input"; import { Label } from "@superset/ui/label"; import { @@ -40,6 +45,22 @@ export function GitSettings({ visibleItems }: GitSettingsProps) { SETTING_ITEM_ID.GIT_WORKTREE_LOCATION, visibleItems, ); + const showSmartCommit = isItemVisible( + SETTING_ITEM_ID.GIT_SMART_COMMIT, + visibleItems, + ); + const showAutoStash = isItemVisible( + SETTING_ITEM_ID.GIT_AUTO_STASH, + visibleItems, + ); + const showBranchSortOrder = isItemVisible( + SETTING_ITEM_ID.GIT_BRANCH_SORT_ORDER, + visibleItems, + ); + const showPostCommitCommand = isItemVisible( + SETTING_ITEM_ID.GIT_POST_COMMIT_COMMAND, + visibleItems, + ); const utils = electronTrpc.useUtils(); @@ -107,6 +128,122 @@ export function GitSettings({ visibleItems }: GitSettingsProps) { }); }; + const { data: smartCommit, isLoading: isSmartCommitLoading } = + electronTrpc.settings.getSmartCommit.useQuery(); + const setSmartCommit = electronTrpc.settings.setSmartCommit.useMutation({ + onMutate: async (next) => { + await utils.settings.getSmartCommit.cancel(); + const previous = utils.settings.getSmartCommit.getData(); + utils.settings.getSmartCommit.setData(undefined, next); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getSmartCommit.setData(undefined, context.previous); + } + }, + onSettled: () => { + utils.settings.getSmartCommit.invalidate(); + }, + }); + + const smartCommitEnabled = smartCommit?.enabled ?? false; + const smartCommitChanges: SmartCommitChangesMode = + smartCommit?.changes ?? "all"; + + const handleSmartCommitToggle = (enabled: boolean) => { + setSmartCommit.mutate({ enabled, changes: smartCommitChanges }); + }; + const handleSmartCommitChangesChange = (value: SmartCommitChangesMode) => { + setSmartCommit.mutate({ enabled: smartCommitEnabled, changes: value }); + }; + + const { data: autoStash, isLoading: isAutoStashLoading } = + electronTrpc.settings.getAutoStash.useQuery(); + const setAutoStash = electronTrpc.settings.setAutoStash.useMutation({ + onMutate: async ({ enabled }) => { + await utils.settings.getAutoStash.cancel(); + const previous = utils.settings.getAutoStash.getData(); + utils.settings.getAutoStash.setData(undefined, enabled); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getAutoStash.setData(undefined, context.previous); + } + }, + onSettled: () => { + utils.settings.getAutoStash.invalidate(); + }, + }); + const handleAutoStashToggle = (enabled: boolean) => { + setAutoStash.mutate({ enabled }); + }; + + const { data: branchSort, isLoading: isBranchSortLoading } = + electronTrpc.settings.getBranchSortOrder.useQuery(); + const setBranchSortOrderMutation = + electronTrpc.settings.setBranchSortOrder.useMutation({ + onMutate: async (next) => { + await utils.settings.getBranchSortOrder.cancel(); + const previous = utils.settings.getBranchSortOrder.getData(); + utils.settings.getBranchSortOrder.setData(undefined, next); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getBranchSortOrder.setData( + undefined, + context.previous, + ); + } + }, + onSettled: () => { + utils.settings.getBranchSortOrder.invalidate(); + }, + }); + const branchSortOrder: BranchSortOrder = + branchSort?.sortOrder ?? "committerdate"; + const branchSortPinDefault = branchSort?.pinDefault ?? true; + const handleBranchSortOrderChange = (value: BranchSortOrder) => { + setBranchSortOrderMutation.mutate({ + sortOrder: value, + pinDefault: branchSortPinDefault, + }); + }; + const handleBranchSortPinDefaultChange = (enabled: boolean) => { + setBranchSortOrderMutation.mutate({ + sortOrder: branchSortOrder, + pinDefault: enabled, + }); + }; + + const { data: postCommitCommand, isLoading: isPostCommitCommandLoading } = + electronTrpc.settings.getPostCommitCommand.useQuery(); + const setPostCommitCommandMutation = + electronTrpc.settings.setPostCommitCommand.useMutation({ + onMutate: async ({ command }) => { + await utils.settings.getPostCommitCommand.cancel(); + const previous = utils.settings.getPostCommitCommand.getData(); + utils.settings.getPostCommitCommand.setData(undefined, command); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getPostCommitCommand.setData( + undefined, + context.previous, + ); + } + }, + onSettled: () => { + utils.settings.getPostCommitCommand.invalidate(); + }, + }); + const handlePostCommitCommandChange = (value: PostCommitCommand) => { + setPostCommitCommandMutation.mutate({ command: value }); + }; + const { data: worktreeBaseDir, isLoading: isWorktreeBaseDirLoading } = electronTrpc.settings.getWorktreeBaseDir.useQuery(); const setWorktreeBaseDir = @@ -228,6 +365,171 @@ export function GitSettings({ visibleItems }: GitSettingsProps) {
)} + {showSmartCommit && ( +
+
+
+ +

+ When no files are staged, commit all changes in a single step + (matches VS Code's git.enableSmartCommit) +

+
+ +
+ {smartCommitEnabled && ( +
+
+ +

+ all: include untracked (git add -A + ). tracked: tracked files only ( + git add -u). +

+
+ +
+ )} +
+ )} + + {showAutoStash && ( +
+
+ +

+ Stash local changes before pull / sync and restore them after it + finishes (matches VS Code's git.autoStash). + Untracked files are included. If the operation fails the stash + is kept on the stack. +

+
+ +
+ )} + + {showBranchSortOrder && ( +
+
+
+ +

+ Order used in the base branch picker. Remote-only branches are + shown as origin/<name>. +

+
+ +
+
+
+ +

+ Keep main / master / the repo's + default branch as the first entry regardless of sort order. +

+
+ +
+
+ )} + + {showPostCommitCommand && ( +
+
+ +

+ Run push or sync automatically after a + successful commit (matches VS Code's{" "} + git.postCommitCommand). +

+
+ +
+ )} + {showWorktreeLocation && (
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts index 803f9f6ff65..897311591af 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts @@ -35,6 +35,10 @@ export const SETTING_ITEM_ID = { GIT_BRANCH_PREFIX: "git-branch-prefix", GIT_DELETE_LOCAL_BRANCH: "git-delete-local-branch", GIT_WORKTREE_LOCATION: "git-worktree-location", + GIT_SMART_COMMIT: "git-smart-commit", + GIT_AUTO_STASH: "git-auto-stash", + GIT_BRANCH_SORT_ORDER: "git-branch-sort-order", + GIT_POST_COMMIT_COMMAND: "git-post-commit-command", AGENTS_ENABLED: "agents-enabled", AGENTS_COMMANDS: "agents-commands", @@ -418,6 +422,59 @@ export const SETTINGS_ITEMS: SettingsItem[] = [ "custom", ], }, + { + id: SETTING_ITEM_ID.GIT_SMART_COMMIT, + section: "git", + title: "Smart Commit", + description: + "Commit all changes when there are no staged changes (VS Code style)", + keywords: [ + "git", + "commit", + "smart", + "stage", + "staged", + "unstaged", + "tracked", + "auto", + "all", + ], + }, + { + id: SETTING_ITEM_ID.GIT_AUTO_STASH, + section: "git", + title: "Auto Stash", + description: + "Stash local changes before pull/sync and restore after (VS Code style)", + keywords: ["git", "stash", "pull", "sync", "auto", "restore", "pop"], + }, + { + id: SETTING_ITEM_ID.GIT_BRANCH_SORT_ORDER, + section: "git", + title: "Branch Sort Order", + description: + "Order used in the base branch picker (committer date or alphabetical)", + keywords: [ + "git", + "branch", + "sort", + "order", + "committer", + "date", + "alphabetical", + "picker", + "pin", + "default", + ], + }, + { + id: SETTING_ITEM_ID.GIT_POST_COMMIT_COMMAND, + section: "git", + title: "Post Commit Command", + description: + "Run push or sync automatically after a successful commit (VS Code style)", + keywords: ["git", "commit", "post", "push", "sync", "auto", "after"], + }, { id: SETTING_ITEM_ID.BEHAVIOR_TELEMETRY, section: "behavior", diff --git a/apps/desktop/src/renderer/screens/main/components/GitOperationDialog/MissingGitUserConfigForm.tsx b/apps/desktop/src/renderer/screens/main/components/GitOperationDialog/MissingGitUserConfigForm.tsx new file mode 100644 index 00000000000..0287df3cf0c --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/GitOperationDialog/MissingGitUserConfigForm.tsx @@ -0,0 +1,116 @@ +import { Button } from "@superset/ui/button"; +import { Input } from "@superset/ui/input"; +import { Label } from "@superset/ui/label"; +import { toast } from "@superset/ui/sonner"; +import { useState } from "react"; +import { electronTrpc } from "renderer/lib/electron-trpc"; + +interface MissingGitUserConfigFormProps { + initialName?: string | null; + initialEmail?: string | null; + /** + * Called with the saved values when the form successfully writes the + * global git config. Callers typically invoke the original commit + * retry from inside this handler so the commit runs immediately with + * the new identity. + */ + onSaved: (values: { name: string; email: string }) => void; +} + +export function MissingGitUserConfigForm({ + initialName, + initialEmail, + onSaved, +}: MissingGitUserConfigFormProps) { + const [name, setName] = useState(initialName ?? ""); + const [email, setEmail] = useState(initialEmail ?? ""); + const [validationError, setValidationError] = useState(null); + const utils = electronTrpc.useUtils(); + + const setGlobalMutation = + electronTrpc.settings.setGlobalGitUserConfig.useMutation({ + onSuccess: async (_result, variables) => { + await utils.settings.getGitInfo.invalidate(); + toast.success("Git ユーザー設定を保存しました"); + onSaved(variables); + }, + onError: (err) => { + setValidationError( + err instanceof Error ? err.message : "保存に失敗しました", + ); + }, + }); + + const handleSave = () => { + const trimmedName = name.trim(); + const trimmedEmail = email.trim(); + if (!trimmedName) { + setValidationError("Name を入力してください"); + return; + } + if (!trimmedEmail) { + setValidationError("Email を入力してください"); + return; + } + setValidationError(null); + setGlobalMutation.mutate({ name: trimmedName, email: trimmedEmail }); + }; + + return ( +
+
+ + setName(e.target.value)} + placeholder="Your Name" + disabled={setGlobalMutation.isPending} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + handleSave(); + } + }} + /> +
+
+ + setEmail(e.target.value)} + placeholder="you@example.com" + disabled={setGlobalMutation.isPending} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + handleSave(); + } + }} + /> +
+ {validationError && ( +

{validationError}

+ )} +

+ git config --global で保存されます。全てのリポジトリで共通 + の identity として使われます。 +

+
+ +
+
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx index 438076b9167..ae14a667520 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx @@ -11,12 +11,10 @@ interface PersistentTabRendererProps { } /** - * Renders workspace tabs, keeping tabs with persistent embedded views mounted - * when inactive. Regular tabs are unmounted normally. - * - * Browser panes use Electron , and VS Code extension panes use iframes - * backed by persistent view IDs. Keeping these tabs mounted avoids dropping - * subscriptions or forcing unnecessary view resolution while the tab is hidden. + * Renders workspace tabs, keeping tabs with embedded views mounted when inactive. + * Keeping these tabs mounted preserves scroll position, search state, cursor, + * subscriptions, and avoids forcing view re-resolution when the tab returns to + * focus. */ export function PersistentTabRenderer({ isWorkspaceActive, @@ -35,7 +33,8 @@ export function PersistentTabRenderer({ return ( type === "webview" || type === "vscode-extension" || - type === "reference-graph" + type === "reference-graph" || + type === "file-viewer" ); }) ) { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx index c126028f25f..27676aa52c8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx @@ -1,6 +1,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { GlobeIcon } from "lucide-react"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { LuMinus, LuPlus } from "react-icons/lu"; import { TbDeviceDesktop } from "react-icons/tb"; import type { MosaicBranch } from "react-mosaic-component"; @@ -15,6 +15,10 @@ import type { SplitPaneOptions } from "renderer/stores/tabs/types"; import { BasePaneWindow, PaneToolbarActions } from "../components"; import { BookmarkBar } from "./components/BookmarkBar"; import { BrowserErrorOverlay } from "./components/BrowserErrorOverlay"; +import { + BrowserFindOverlay, + type BrowserFindOverlayHandle, +} from "./components/BrowserFindOverlay"; import { BrowserToolbar } from "./components/BrowserToolbar"; import { BrowserOverflowMenu } from "./components/BrowserToolbar/components/BrowserOverflowMenu"; import { ExtensionToolbar } from "./components/ExtensionToolbar"; @@ -74,6 +78,108 @@ export function BrowserPane({ electronTrpc.browser.openDevTools.useMutation(); const { mutate: setZoomLevel } = electronTrpc.browser.setZoomLevel.useMutation(); + const { mutate: findInPage } = electronTrpc.browser.findInPage.useMutation(); + const { mutate: stopFindInPage } = + electronTrpc.browser.stopFindInPage.useMutation(); + + // -- Find in page ------------------------------------------------------ + + const [isFindOpen, setIsFindOpen] = useState(false); + const [findQuery, setFindQuery] = useState(""); + const [findMatchCase, setFindMatchCase] = useState(false); + const [findMatches, setFindMatches] = useState(0); + const [findActiveOrdinal, setFindActiveOrdinal] = useState(0); + const findOverlayRef = useRef(null); + + const openFindOverlay = useCallback(() => { + setIsFindOpen(true); + // Refocus + select even if already open (repeat Cmd+F). + findOverlayRef.current?.focusInput(); + }, []); + + const closeFindOverlay = useCallback(() => { + setIsFindOpen(false); + setFindMatches(0); + setFindActiveOrdinal(0); + stopFindInPage({ paneId, action: "clearSelection" }); + }, [paneId, stopFindInPage]); + + const runFindQuery = useCallback( + ( + text: string, + opts?: { findNext?: boolean; forward?: boolean; matchCase?: boolean }, + ) => { + if (!text) { + setFindMatches(0); + setFindActiveOrdinal(0); + stopFindInPage({ paneId, action: "clearSelection" }); + return; + } + findInPage({ + paneId, + text, + forward: opts?.forward ?? true, + findNext: opts?.findNext ?? false, + matchCase: opts?.matchCase ?? findMatchCase, + }); + }, + [findInPage, findMatchCase, paneId, stopFindInPage], + ); + + const handleFindQueryChange = useCallback( + (next: string) => { + setFindQuery(next); + runFindQuery(next, { findNext: false, forward: true }); + }, + [runFindQuery], + ); + + const handleFindNext = useCallback(() => { + if (!findQuery) return; + runFindQuery(findQuery, { findNext: true, forward: true }); + }, [findQuery, runFindQuery]); + + const handleFindPrevious = useCallback(() => { + if (!findQuery) return; + runFindQuery(findQuery, { findNext: true, forward: false }); + }, [findQuery, runFindQuery]); + + const handleMatchCaseChange = useCallback( + (next: boolean) => { + setFindMatchCase(next); + if (findQuery) { + runFindQuery(findQuery, { + findNext: false, + forward: true, + matchCase: next, + }); + } + }, + [findQuery, runFindQuery], + ); + + electronTrpc.browser.onFindRequested.useSubscription( + { paneId }, + { + onData: (event) => { + if (event.type === "open") { + openFindOverlay(); + } else if (event.type === "escape") { + if (isFindOpen) closeFindOverlay(); + } + }, + }, + ); + + electronTrpc.browser.onFoundInPage.useSubscription( + { paneId }, + { + onData: (result) => { + setFindMatches(result.matches); + setFindActiveOrdinal(result.activeMatchOrdinal); + }, + }, + ); const { containerRef, @@ -257,7 +363,24 @@ export function BrowserPane({
)} > -
+
{ + if ( + (event.metaKey || event.ctrlKey) && + !event.altKey && + !event.shiftKey && + event.key.toLowerCase() === "f" + ) { + event.preventDefault(); + event.stopPropagation(); + openFindOverlay(); + } else if (event.key === "Escape" && isFindOpen) { + event.preventDefault(); + closeFindOverlay(); + } + }} + > {!isFullscreen && ( )} @@ -267,6 +390,19 @@ export function BrowserPane({ className="h-full w-full" style={{ flex: 1 }} /> + {loadError && !isLoading && ( )} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx new file mode 100644 index 00000000000..eee0c1a91c8 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx @@ -0,0 +1,167 @@ +import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; +import { HiChevronDown, HiChevronUp, HiMiniXMark } from "react-icons/hi2"; + +export interface BrowserFindOverlayHandle { + focusInput: () => void; +} + +interface BrowserFindOverlayProps { + isOpen: boolean; + query: string; + matchCount: number; + activeMatchOrdinal: number; + matchCase: boolean; + onQueryChange: (query: string) => void; + onMatchCaseChange: (next: boolean) => void; + onFindNext: () => void; + onFindPrevious: () => void; + onClose: () => void; +} + +function OptionToggle({ + active, + onClick, + title, + children, +}: { + active: boolean; + onClick: () => void; + title: string; + children: React.ReactNode; +}) { + return ( + + ); +} + +export const BrowserFindOverlay = forwardRef< + BrowserFindOverlayHandle, + BrowserFindOverlayProps +>(function BrowserFindOverlay( + { + isOpen, + query, + matchCount, + activeMatchOrdinal, + matchCase, + onQueryChange, + onMatchCaseChange, + onFindNext, + onFindPrevious, + onClose, + }, + ref, +) { + const inputRef = useRef(null); + + useImperativeHandle( + ref, + () => ({ + focusInput: () => { + inputRef.current?.focus(); + inputRef.current?.select(); + }, + }), + [], + ); + + useEffect(() => { + if (isOpen) { + inputRef.current?.focus(); + inputRef.current?.select(); + } + }, [isOpen]); + + if (!isOpen) return null; + + const activeMatchLabel = + matchCount === 0 + ? query + ? "No results" + : "" + : `${Math.max(activeMatchOrdinal, 1)} of ${matchCount}`; + + return ( +
+
+
+
+ onQueryChange(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + if (event.shiftKey) onFindPrevious(); + else onFindNext(); + } else if (event.key === "Escape") { + event.preventDefault(); + onClose(); + } + }} + placeholder="Find in page" + className="min-w-0 flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none" + /> +
+ onMatchCaseChange(!matchCase)} + title="Match Case" + > + Aa + +
+
+ + {query ? ( + + {activeMatchLabel} + + ) : null} + + + + +
+
+
+ ); +}); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/index.ts new file mode 100644 index 00000000000..2765d45ad84 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/index.ts @@ -0,0 +1,4 @@ +export { + BrowserFindOverlay, + type BrowserFindOverlayHandle, +} from "./BrowserFindOverlay"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx index 6056a7074c8..4574124b977 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx @@ -1,7 +1,22 @@ import { defaultKeymap, indentWithTab } from "@codemirror/commands"; import { getChunks, MergeView } from "@codemirror/merge"; -import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; -import { Compartment, EditorState } from "@codemirror/state"; +import { + closeSearchPanel, + getSearchQuery, + highlightSelectionMatches, + openSearchPanel, + findNext as runFindNext, + findPrevious as runFindPrevious, + SearchQuery, + search, + searchKeymap, + setSearchQuery, +} from "@codemirror/search"; +import { + Compartment, + type EditorSelection, + EditorState, +} from "@codemirror/state"; import { Decoration, type DecorationSet, @@ -13,8 +28,9 @@ import { ViewPlugin, type ViewUpdate, } from "@codemirror/view"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; +import { CodeEditorSearchOverlay } from "renderer/screens/main/components/WorkspaceView/components/CodeEditor/components/CodeEditorSearchOverlay"; import { type BlameEntry, createBlamePlugin, @@ -30,6 +46,68 @@ import { useResolvedTheme } from "renderer/stores/theme"; import type { DiffViewMode } from "shared/changes-types"; import { getEditorTheme } from "shared/themes"; +const SEARCH_MATCH_LIMIT = 10_000; + +/** + * See comment in CodeEditor.tsx — `display: none` on the panel DOM causes + * `PanelGroup.scrollMargin()` to return a value equal to the full scroller + * height, which in turn breaks CM's drag-select autoscroll (it fires + * unconditionally because the computed bottom edge is at 0). Keep the + * panel in flow but collapsed to zero height. + */ +function hideHiddenSearchPanelContainer(container: HTMLElement) { + container.style.height = "0px"; + container.style.minHeight = "0px"; + container.style.maxHeight = "0px"; + container.style.margin = "0"; + container.style.padding = "0"; + container.style.border = "0"; + container.style.overflow = "hidden"; + container.style.visibility = "hidden"; + container.style.pointerEvents = "none"; +} + +function createHiddenSearchPanel() { + const dom = document.createElement("div"); + dom.className = "cm-search cm-hidden-search-panel"; + dom.style.height = "0px"; + dom.style.overflow = "hidden"; + dom.style.visibility = "hidden"; + dom.style.pointerEvents = "none"; + + return { + dom, + mount() { + const panelContainer = dom.parentElement; + if (panelContainer instanceof HTMLElement) { + hideHiddenSearchPanelContainer(panelContainer); + } + }, + }; +} + +function getActiveSearchMatchIndex( + matches: Array<{ from: number; to: number }>, + selection: EditorSelection["main"], +) { + if (matches.length === 0) return -1; + + const exactMatchIndex = matches.findIndex( + (match) => match.from === selection.from && match.to === selection.to, + ); + if (exactMatchIndex >= 0) return exactMatchIndex; + + const containingMatchIndex = matches.findIndex( + (match) => selection.from >= match.from && selection.from <= match.to, + ); + if (containingMatchIndex >= 0) return containingMatchIndex; + + const nextMatchIndex = matches.findIndex( + (match) => match.from >= selection.from, + ); + return nextMatchIndex >= 0 ? nextMatchIndex : 0; +} + // Line decoration that suppresses inline cm-changedText highlights const suppressLineDeco = Decoration.line({ class: "cm-suppress-inline-diff" }); @@ -216,6 +294,17 @@ export function CodeMirrorDiffViewer({ }: CodeMirrorDiffViewerProps) { const containerRef = useRef(null); const mergeViewRef = useRef(null); + const activeEditorRef = useRef(null); + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [searchQueryText, setSearchQueryText] = useState(""); + const [isCaseSensitive, setIsCaseSensitive] = useState(false); + const [isRegexp, setIsRegexp] = useState(false); + const [isWholeWord, setIsWholeWord] = useState(false); + const [searchMatchCount, setSearchMatchCount] = useState(0); + const [activeSearchMatchIndex, setActiveSearchMatchIndex] = useState(-1); + const isSearchOpenRef = useRef(false); + const syncSearchOverlayStateRef = useRef<(() => void) | null>(null); + isSearchOpenRef.current = isSearchOpen; const langCompartmentA = useRef(new Compartment()).current; const langCompartmentB = useRef(new Compartment()).current; const themeCompartmentA = useRef(new Compartment()).current; @@ -239,10 +328,199 @@ export function CodeMirrorDiffViewer({ onSaveRef.current = onSave; inlineCompletionRequestRef.current = inlineCompletionRequest; + const getActiveEditor = (): EditorView | null => { + const mv = mergeViewRef.current; + if (!mv) return null; + return activeEditorRef.current ?? mv.b; + }; + + const forEachEditor = (fn: (view: EditorView) => void) => { + const mv = mergeViewRef.current; + if (!mv) return; + fn(mv.a); + fn(mv.b); + }; + + const syncSearchOverlayState = () => { + const view = getActiveEditor(); + if (!view) return; + const query = getSearchQuery(view.state); + const matches: Array<{ from: number; to: number }> = []; + if (query.valid) { + const cursor = query.getCursor(view.state); + let nextMatch = cursor.next(); + while (!nextMatch.done) { + if (matches.length >= SEARCH_MATCH_LIMIT) break; + matches.push(nextMatch.value); + nextMatch = cursor.next(); + } + } + setSearchQueryText(query.search); + setIsCaseSensitive(query.caseSensitive); + setIsRegexp(query.regexp); + setIsWholeWord(query.wholeWord); + setSearchMatchCount(matches.length); + setActiveSearchMatchIndex( + getActiveSearchMatchIndex(matches, view.state.selection.main), + ); + }; + syncSearchOverlayStateRef.current = syncSearchOverlayState; + + const ensureOverlaySearchOpen = () => { + const mv = mergeViewRef.current; + if (!mv) return; + // Open hidden panel on both editors so setSearchQuery effects are accepted. + openSearchPanel(mv.a); + openSearchPanel(mv.b); + setIsSearchOpen(true); + syncSearchOverlayState(); + }; + + const updateOverlaySearchQuery = ( + overrides: Partial<{ + search: string; + caseSensitive: boolean; + regexp: boolean; + wholeWord: boolean; + }>, + ) => { + const mv = mergeViewRef.current; + if (!mv) return; + openSearchPanel(mv.a); + openSearchPanel(mv.b); + const current = getSearchQuery((activeEditorRef.current ?? mv.b).state); + const next = new SearchQuery({ + search: overrides.search ?? current.search, + replace: current.replace, + caseSensitive: overrides.caseSensitive ?? current.caseSensitive, + regexp: overrides.regexp ?? current.regexp, + wholeWord: overrides.wholeWord ?? current.wholeWord, + literal: current.literal, + }); + forEachEditor((view) => { + view.dispatch({ effects: setSearchQuery.of(next) }); + }); + syncSearchOverlayState(); + }; + + // Manual center scroll using CM's line-block cache — see CodeEditor.tsx + // comment for the rationale (CM's y: "center" effect is unreliable on + // virtualized content after a find dispatch). + const scrollActiveSelectionToCenter = (view: EditorView) => { + requestAnimationFrame(() => { + const scroller = view.scrollDOM; + const head = view.state.selection.main.head; + const block = view.lineBlockAt(head); + const targetScrollTop = Math.max( + 0, + Math.round(block.top + block.height / 2 - scroller.clientHeight / 2), + ); + scroller.scrollTop = targetScrollTop; + }); + }; + + const handleOverlayFindNext = () => { + const view = getActiveEditor(); + if (!view) return; + if (!getSearchQuery(view.state).search) { + ensureOverlaySearchOpen(); + return; + } + runFindNext(view); + scrollActiveSelectionToCenter(view); + }; + + const handleOverlayFindPrevious = () => { + const view = getActiveEditor(); + if (!view) return; + if (!getSearchQuery(view.state).search) { + ensureOverlaySearchOpen(); + return; + } + runFindPrevious(view); + scrollActiveSelectionToCenter(view); + }; + + const handleOverlaySearchClose = () => { + forEachEditor((view) => { + closeSearchPanel(view); + }); + setIsSearchOpen(false); + }; + // biome-ignore lint/correctness/useExhaustiveDependencies: MergeView is created once and destroyed on unmount useEffect(() => { if (!containerRef.current) return; + const overlaySearchKeymap = keymap.of([ + { + key: "Mod-f", + run: () => { + ensureOverlaySearchOpen(); + return true; + }, + }, + { + key: "F3", + run: () => { + handleOverlayFindNext(); + return true; + }, + shift: () => { + handleOverlayFindPrevious(); + return true; + }, + preventDefault: true, + }, + { + key: "Mod-g", + run: () => { + handleOverlayFindNext(); + return true; + }, + shift: () => { + handleOverlayFindPrevious(); + return true; + }, + preventDefault: true, + }, + { + key: "Escape", + run: () => { + if (!isSearchOpenRef.current) return false; + handleOverlaySearchClose(); + return true; + }, + }, + ]); + + const focusTracker = EditorView.domEventHandlers({ + focus: (_event, view) => { + activeEditorRef.current = view; + // Re-sync overlay counts/ordinal against the newly focused editor. + syncSearchOverlayStateRef.current?.(); + }, + }); + + const overlaySearchUpdateListener = EditorView.updateListener.of( + (update) => { + if ( + !( + update.docChanged || + update.selectionSet || + update.transactions.some((tr) => + tr.effects.some((effect) => effect.is(setSearchQuery)), + ) + ) + ) { + return; + } + syncSearchOverlayStateRef.current?.(); + }, + ); + + const searchExtension = search({ createPanel: createHiddenSearchPanel }); + const readOnlyExtensions = [ lineNumbers(), highlightSpecialChars(), @@ -251,6 +529,10 @@ export function CodeMirrorDiffViewer({ EditorState.readOnly.of(true), EditorView.editable.of(false), EditorView.lineWrapping, + searchExtension, + focusTracker, + overlaySearchUpdateListener, + overlaySearchKeymap, keymap.of([indentWithTab, ...defaultKeymap, ...searchKeymap]), suppressDeletions, ]; @@ -261,6 +543,10 @@ export function CodeMirrorDiffViewer({ drawSelection(), highlightSelectionMatches(), EditorView.lineWrapping, + searchExtension, + focusTracker, + overlaySearchUpdateListener, + overlaySearchKeymap, keymap.of([ indentWithTab, ...defaultKeymap, @@ -344,6 +630,14 @@ export function CodeMirrorDiffViewer({ return () => { mergeView.destroy(); mergeViewRef.current = null; + // Reset search state so the overlay does not keep pointing at the + // destroyed EditorView once the next MergeView instance is built. + activeEditorRef.current = null; + isSearchOpenRef.current = false; + setIsSearchOpen(false); + setSearchQueryText(""); + setSearchMatchCount(0); + setActiveSearchMatchIndex(-1); }; }, [original, modified, language, viewMode]); @@ -413,5 +707,39 @@ export function CodeMirrorDiffViewer({ }); }, [inlineCompletionCompartmentB, hasInlineCompletionRequest]); - return
; + return ( +
+
+ { + updateOverlaySearchQuery({ search: nextQuery }); + }} + onReplaceTextChange={() => {}} + onCaseSensitiveChange={(next) => { + updateOverlaySearchQuery({ caseSensitive: next }); + }} + onRegexpChange={(next) => { + updateOverlaySearchQuery({ regexp: next }); + }} + onWholeWordChange={(next) => { + updateOverlaySearchQuery({ wholeWord: next }); + }} + onFindNext={handleOverlayFindNext} + onFindPrevious={handleOverlayFindPrevious} + onSelectAllMatches={() => {}} + onReplaceNext={() => {}} + onReplaceAll={() => {}} + onClose={handleOverlaySearchClose} + /> +
+ ); } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx index 30307e19ec0..330fc89a3e3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx @@ -1008,6 +1008,8 @@ export function ChangesView({ ; interface CommitInputProps { worktreePath: string; hasStagedChanges: boolean; + unstagedChangeCount: number; + /** + * Number of unstaged changes to **tracked** files only (excludes + * untracked). Used to guard the `tracked` smart-commit mode: with only + * new/untracked files `git add -u` stages nothing and the commit would + * fail with "nothing to commit". + */ + unstagedTrackedCount: number; pushCount: number; pullCount: number; hasUpstream: boolean; @@ -48,6 +57,8 @@ interface CommitInputProps { export function CommitInput({ worktreePath, hasStagedChanges, + unstagedChangeCount, + unstagedTrackedCount, pushCount, pullCount, hasUpstream, @@ -60,9 +71,37 @@ export function CommitInput({ }: CommitInputProps) { const [isOpen, setIsOpen] = useState(false); + const { data: smartCommit } = electronTrpc.settings.getSmartCommit.useQuery( + undefined, + { staleTime: 10_000 }, + ); + const smartCommitEnabled = smartCommit?.enabled ?? false; + const smartCommitMode = smartCommit?.changes ?? "all"; + + const { data: autoStashEnabled = false } = + electronTrpc.settings.getAutoStash.useQuery(undefined, { + staleTime: 10_000, + }); + const { data: postCommitCommand = "none" } = + electronTrpc.settings.getPostCommitCommand.useQuery(undefined, { + staleTime: 10_000, + }); + // Read the latest setting inside mutation callbacks without recreating + // the mutation when the value changes. + const postCommitCommandRef = useRef(postCommitCommand); + postCommitCommandRef.current = postCommitCommand; + // When auto-stash is orchestrating a pull/sync, we want the custom + // Japanese dialogs (pull-failed-with-stash / pop-conflict) to own the + // error UX. This ref is used by the default onError handlers below to + // opt out — otherwise both the built-in pull-error dialog and our + // dialog would fire for the same failure. + const autoStashInFlightRef = useRef(false); + const stashIncludeUntrackedMutation = electronTrpc.changes.stashIncludeUntracked.useMutation(); const stashPopMutation = electronTrpc.changes.stashPop.useMutation(); + const stageAllMutation = electronTrpc.changes.stageAll.useMutation(); + const stageTrackedMutation = electronTrpc.changes.stageTracked.useMutation(); const commitMutation = electronTrpc.changes.commit.useMutation({ onSuccess: () => { @@ -129,6 +168,9 @@ export function CommitInput({ onRefresh(); }, onError: (error) => { + if (autoStashInFlightRef.current) { + return; + } showGitErrorDialog(error, "pull", { retry: () => pullMutation.mutate({ worktreePath }), stashAndRetry: () => { @@ -178,6 +220,9 @@ export function CommitInput({ }); }, onError: (error) => { + if (autoStashInFlightRef.current) { + return; + } showGitErrorDialog(error, "sync", { retry: () => syncMutation.mutate({ worktreePath }), pullRebaseAndRetryPush: () => { @@ -220,9 +265,26 @@ export function CommitInput({ pullMutation.isPending || syncMutation.isPending || isCreateOrOpenPRPending || - fetchMutation.isPending; + fetchMutation.isPending || + stageAllMutation.isPending || + stageTrackedMutation.isPending || + stashIncludeUntrackedMutation.isPending || + stashPopMutation.isPending; - const canCommit = hasStagedChanges && commitMessage.trim(); + // Smart commit lets the user commit with an empty index as long as + // there is at least one change that the chosen mode will actually stage. + // "tracked" mode uses `git add -u` which ignores untracked files, so + // it needs at least one tracked unstaged change; otherwise the button + // would be enabled but the commit would fail with "nothing to commit". + const smartCommitAvailable = + smartCommitEnabled && + !hasStagedChanges && + (smartCommitMode === "tracked" + ? unstagedTrackedCount > 0 + : unstagedChangeCount > 0); + const willSmartCommit = smartCommitAvailable; + const canCommit = + (hasStagedChanges || smartCommitAvailable) && commitMessage.trim(); const hasExistingPR = Boolean(pullRequest); const prUrl = pullRequest?.url; const pushActionCopy = getPushActionCopy({ @@ -231,9 +293,35 @@ export function CommitInput({ pullRequest, }); + const commitLabel = willSmartCommit + ? `Commit All (${unstagedChangeCount})` + : "Commit"; + const commitTooltip = willSmartCommit + ? smartCommitMode === "tracked" + ? `Stage ${unstagedChangeCount} tracked changes, then commit` + : `Stage ${unstagedChangeCount} changes (including untracked), then commit` + : undefined; + + // Kicks off the configured post-commit command once the commit itself + // has succeeded. Uses a ref so the value reflects the latest setting + // without recreating the commit mutation on every setting change, and + // chains into the existing push / sync mutations so their own + // onSuccess (toast / warnings / PR flow) and onError (retry dialogs) + // handlers still run untouched. + const runPostCommitCommand = () => { + const command = postCommitCommandRef.current; + if (command === "push") { + pushMutation.mutate({ worktreePath, setUpstream: true }); + } else if (command === "sync") { + // handleSync routes through the auto-stash orchestrator so it + // also stays compatible with git.autoStash. + handleSync(); + } + }; + const handleCommit = () => { if (!canCommit) return; - commitMutation.mutate({ worktreePath, message: commitMessage.trim() }); + runCommitWithCallback(runPostCommitCommand); }; const handlePush = () => { @@ -253,13 +341,112 @@ export function CommitInput({ }, ); }; - const handlePull = () => pullMutation.mutate({ worktreePath }); - const handleSync = () => syncMutation.mutate({ worktreePath }); + const hasLocalChanges = hasStagedChanges || unstagedChangeCount > 0; + + /** + * Auto-stash orchestration for pull / sync. When the user has enabled + * `git.autoStash` and their working tree has local changes, we: + * 1. stash (include untracked) + * 2. run the network op + * 3. on success: stash pop (restore local changes) + * 4. on network failure: leave the stash intact and show a Japanese + * dialog telling the user their changes are safe on the stack + * 5. on pop failure (usually a conflict): show a different Japanese + * dialog and do not auto-retry — the user will resolve manually + * + * When auto-stash is disabled, or the working tree is already clean, + * fall through to the plain mutation (no extra steps). + */ + const showAutoStashPullFailedDialog = (errorMessage: string) => { + openGitOperationDialog({ + kind: "auto-stash-pull-failed", + tone: "warn", + title: "Pull に失敗しました", + description: + "ローカルの変更は stash に退避されたままです。Git の状態を確認してから、手動で `git stash pop` で変更を復元してください。", + details: errorMessage, + }); + }; + const showAutoStashPopConflictDialog = (errorMessage: string) => { + openGitOperationDialog({ + kind: "auto-stash-pop-conflict", + tone: "warn", + title: "Stash の復元で競合が発生しました", + description: + "Pull は成功しましたが、stash の pop でコンフリクトが起きました。stash stack に変更が残っているので、手動で `git stash pop` を実行してコンフリクトを解決してください。", + details: errorMessage, + }); + }; + + const runPullOrSyncWithAutoStash = (operation: "pull" | "sync") => { + const runNetworkOp = (onDone: () => void) => { + const mutation = operation === "pull" ? pullMutation : syncMutation; + mutation.mutate( + { worktreePath }, + { + onSuccess: () => onDone(), + onError: (error) => { + // Only fires when autoStashInFlightRef is true (see guard in + // the mutation definition above). Clear the flag and show the + // auto-stash specific Japanese dialog instead of the default + // pull/sync error flow. + autoStashInFlightRef.current = false; + const message = + error instanceof Error ? error.message : String(error); + showAutoStashPullFailedDialog(message); + }, + }, + ); + }; + + if (!autoStashEnabled || !hasLocalChanges) { + const mutation = operation === "pull" ? pullMutation : syncMutation; + mutation.mutate({ worktreePath }); + return; + } + + autoStashInFlightRef.current = true; + stashIncludeUntrackedMutation.mutate( + { worktreePath }, + { + onSuccess: () => { + runNetworkOp(() => { + stashPopMutation.mutate( + { worktreePath }, + { + onSuccess: () => { + autoStashInFlightRef.current = false; + }, + onError: (popError) => { + autoStashInFlightRef.current = false; + const message = + popError instanceof Error + ? popError.message + : String(popError); + showAutoStashPopConflictDialog(message); + }, + }, + ); + }); + }, + onError: (stashError) => { + autoStashInFlightRef.current = false; + showGitErrorDialog(stashError, "stash"); + }, + }, + ); + }; + + const handlePull = () => runPullOrSyncWithAutoStash("pull"); + const handleSync = () => runPullOrSyncWithAutoStash("sync"); const handleFetch = () => fetchMutation.mutate({ worktreePath }); + // Fix C: Fetch & Pull must go through the auto-stash orchestrator so + // that git.autoStash is honoured, matching the behaviour of the plain + // Pull button. const handleFetchAndPull = () => { fetchMutation.mutate( { worktreePath }, - { onSuccess: () => pullMutation.mutate({ worktreePath }) }, + { onSuccess: () => runPullOrSyncWithAutoStash("pull") }, ); }; const handleCreatePR = () => { @@ -268,27 +455,49 @@ export function CommitInput({ }; const handleOpenPR = () => prUrl && window.open(prUrl, "_blank"); + // Fix B: Commit+Push / Commit+Push+PR must run the same smart-commit + // staging logic as the primary Commit button so that git.enableSmartCommit + // is honoured when the staging area is empty. + const runCommitWithCallback = (onCommitSuccess: () => void) => { + const message = commitMessage.trim(); + if (willSmartCommit) { + const stageMutation = + smartCommitMode === "tracked" ? stageTrackedMutation : stageAllMutation; + stageMutation.mutate( + { worktreePath }, + { + onSuccess: () => { + commitMutation.mutate( + { worktreePath, message }, + { onSuccess: onCommitSuccess }, + ); + }, + onError: (error) => { + showGitErrorDialog(error, "stage"); + }, + }, + ); + } else { + commitMutation.mutate( + { worktreePath, message }, + { onSuccess: onCommitSuccess }, + ); + } + }; + const handleCommitAndPush = () => { if (!canCommit) return; - commitMutation.mutate( - { worktreePath, message: commitMessage.trim() }, - { onSuccess: handlePush }, - ); + runCommitWithCallback(handlePush); }; const handleCommitPushAndCreatePR = () => { if (!canCommit) return; - commitMutation.mutate( - { worktreePath, message: commitMessage.trim() }, - { - onSuccess: () => { - pushMutation.mutate( - { worktreePath, setUpstream: true }, - { onSuccess: handleCreatePR }, - ); - }, - }, - ); + runCommitWithCallback(() => { + pushMutation.mutate( + { worktreePath, setUpstream: true }, + { onSuccess: handleCreatePR }, + ); + }); }; const primaryAction = getPrimaryAction({ @@ -303,6 +512,16 @@ export function CommitInput({ const primary = { ...primaryAction, + label: + primaryAction.action === "commit" && willSmartCommit + ? commitLabel + : primaryAction.action === "commit" && !hasStagedChanges + ? primaryAction.label + : primaryAction.label, + tooltip: + primaryAction.action === "commit" && willSmartCommit && commitTooltip + ? commitTooltip + : primaryAction.tooltip, icon: primaryAction.action === "commit" ? ( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx index 4a28124b7cf..8c2079e21d5 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx @@ -24,6 +24,7 @@ import { createFileKey, useScrollContext } from "../../../../ChangesContent"; import { useFileDrag, usePathActions } from "../../hooks"; import { getStatusColor, getStatusIndicator } from "../../utils"; import { DiscardConfirmDialog } from "../DiscardConfirmDialog"; +import { useMultiSelect } from "../MultiSelectContext"; import type { RowHoverAction } from "../RowHoverActions"; import { RowHoverActions } from "../RowHoverActions"; @@ -85,6 +86,8 @@ export function FileItem({ const [showDiscardDialog, setShowDiscardDialog] = useState(false); const { activeFileKey } = useScrollContext(); const clickTimeoutRef = useRef | null>(null); + const multiSelect = useMultiSelect(); + const isMultiSelected = multiSelect?.isSelected(file.path) ?? false; const fileName = getFileName(file.path); const statusBadgeColor = getStatusColor(file.status); @@ -100,7 +103,9 @@ export function FileItem({ const isScrollSyncActive = category && activeFileKey === createFileKey(file, category, commitHash, worktreePath); - const isHighlighted = isExpandedView ? isScrollSyncActive : isSelected; + const isHighlighted = isExpandedView + ? isScrollSyncActive || isMultiSelected + : isSelected || isMultiSelected; const { copyPath, copyRelativePath, revealInFinder, openInEditor } = usePathActions({ @@ -115,11 +120,23 @@ export function FileItem({ const handleClick = useCallback( (e: React.MouseEvent) => { - if (e.metaKey || e.ctrlKey) { - openInEditor(); - return; + // Multi-select (Shift / Cmd / Ctrl + click). Must run before any + // navigation logic so modifier clicks never fire onClick. If there's + // no surrounding MultiSelectProvider we fall through to the default. + if (multiSelect && (e.shiftKey || e.metaKey || e.ctrlKey)) { + const result = multiSelect.handleClick(file.path, e); + if (result === "multi") { + if (clickTimeoutRef.current) { + clearTimeout(clickTimeoutRef.current); + clickTimeoutRef.current = null; + } + return; + } } + // Normal click also clears any lingering multi-selection via the ctx. + multiSelect?.handleClick(file.path, e); + if (clickTimeoutRef.current) { clearTimeout(clickTimeoutRef.current); clickTimeoutRef.current = null; @@ -130,7 +147,7 @@ export function FileItem({ onClick(); }, 300); }, - [onClick, openInEditor], + [file.path, multiSelect, onClick], ); const handleDoubleClick = useCallback( @@ -156,23 +173,71 @@ export function FileItem({ }; }, []); + // VS Code-style: hover / context-menu actions on a file that is part + // of a multi-selection apply to the whole selection. Clicking an + // action on a non-selected file falls back to the single-file handler + // (and does not disturb any existing selection, matching VS Code). + const isPartOfActiveSelection = + isMultiSelected && (multiSelect?.selectionCount ?? 0) > 1; + const selectedFilesSnapshot = multiSelect?.selectedFiles ?? []; + + const runStage = () => { + if (isPartOfActiveSelection && multiSelect?.onStageSelected) { + multiSelect.onStageSelected(selectedFilesSnapshot); + multiSelect.clear(); + return; + } + onStage?.(); + }; + + const runUnstage = () => { + if (isPartOfActiveSelection && multiSelect?.onUnstageSelected) { + multiSelect.onUnstageSelected(selectedFilesSnapshot); + multiSelect.clear(); + return; + } + onUnstage?.(); + }; + + const runDiscardConfirmed = () => { + if (isPartOfActiveSelection && multiSelect?.onDiscardSelected) { + multiSelect.onDiscardSelected(selectedFilesSnapshot); + multiSelect.clear(); + return; + } + onDiscard?.(); + }; + const handleDiscardClick = () => { setShowDiscardDialog(true); }; const handleConfirmDiscard = () => { setShowDiscardDialog(false); - onDiscard?.(); + runDiscardConfirmed(); }; const isDeleteAction = file.status === "untracked" || file.status === "added"; const discardLabel = isDeleteAction ? "Delete" : "Discard"; - const discardDialogTitle = isDeleteAction - ? `Delete "${fileName}"?` - : `Discard changes to "${fileName}"?`; - const discardDialogDescription = isDeleteAction - ? "This will permanently delete this file. This action cannot be undone." - : "This will revert all changes to this file. This action cannot be undone."; + const bulkCount = isPartOfActiveSelection + ? (multiSelect?.selectionCount ?? 0) + : 0; + const discardDialogTitle = + bulkCount > 0 + ? isDeleteAction + ? `Delete ${bulkCount} files?` + : `Discard changes to ${bulkCount} files?` + : isDeleteAction + ? `Delete "${fileName}"?` + : `Discard changes to "${fileName}"?`; + const discardDialogDescription = + bulkCount > 0 + ? isDeleteAction + ? "This will permanently delete the selected files. This action cannot be undone." + : "This will revert all changes to the selected files. This action cannot be undone." + : isDeleteAction + ? "This will permanently delete this file. This action cannot be undone." + : "This will revert all changes to this file. This action cannot be undone."; const hoverActions: RowHoverAction[] = [ ...(onDiscard ? [ @@ -196,7 +261,7 @@ export function FileItem({ key: "stage", label: "Stage", icon: , - onClick: onStage, + onClick: runStage, disabled: isActioning, }, ] @@ -207,7 +272,7 @@ export function FileItem({ key: "unstage", label: "Unstage", icon: , - onClick: onUnstage, + onClick: runUnstage, disabled: isActioning, }, ] @@ -217,6 +282,7 @@ export function FileItem({ const fileContent = (
} {onStage && ( - + - Stage + {bulkCount > 0 ? `Stage ${bulkCount} files` : "Stage"} )} {onUnstage && ( - + - Unstage + {bulkCount > 0 ? `Unstage ${bulkCount} files` : "Unstage"} )} @@ -331,7 +397,9 @@ export function FileItem({ ) : ( )} - {discardLabel} + {bulkCount > 0 + ? `${discardLabel} ${bulkCount} files` + : discardLabel} )} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/fileListOrdering.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/fileListOrdering.ts new file mode 100644 index 00000000000..264a240ee94 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/fileListOrdering.ts @@ -0,0 +1,111 @@ +import type { ChangedFile } from "shared/changes-types"; +import type { ChangesViewMode } from "../../types"; +import { sortFilesForCompactView } from "./compact-view"; + +/** + * Return the flat visual order of files as rendered by each FileList view + * mode. This MUST match the per-variant sort logic: + * + * - compact → {@link sortFilesForCompactView} (filename A-Z) + * - grouped → folder A-Z, files within a folder A-Z (mirrors + * `groupFilesByFolder` in FileListGrouped) + * - tree → DFS, folders before files at each level, name A-Z + * (mirrors `buildFileTree` in FileListTree) + * + * Used by the multi-select range selection so that shift-clicking + * produces a contiguous selection that matches what the user sees. + * The tree mode order assumes all folders are expanded — collapsed + * folders will still contribute their files, which is an acceptable + * approximation until we thread the expand/collapse state here. + */ +export function orderFilesForViewMode( + files: ChangedFile[], + viewMode: ChangesViewMode, +): ChangedFile[] { + if (viewMode === "compact") { + return sortFilesForCompactView(files); + } + if (viewMode === "grouped") { + return groupedFlatOrder(files); + } + if (viewMode === "tree") { + return treeFlatOrder(files); + } + return files; +} + +function groupedFlatOrder(files: ChangedFile[]): ChangedFile[] { + const folderMap = new Map(); + for (const file of files) { + const parts = file.path.split("/"); + const folderPath = parts.length > 1 ? parts.slice(0, -1).join("/") : ""; + const list = folderMap.get(folderPath); + if (list) { + list.push(file); + } else { + folderMap.set(folderPath, [file]); + } + } + const groups = Array.from(folderMap.entries()) + .map(([folderPath, groupFiles]) => ({ + folderPath, + files: [...groupFiles].sort((a, b) => { + const aName = a.path.split("/").pop() ?? ""; + const bName = b.path.split("/").pop() ?? ""; + return aName.localeCompare(bName); + }), + })) + .sort((a, b) => a.folderPath.localeCompare(b.folderPath)); + return groups.flatMap((g) => g.files); +} + +interface TreeOrderingNode { + name: string; + type: "file" | "folder"; + file?: ChangedFile; + children?: Map; +} + +function treeFlatOrder(files: ChangedFile[]): ChangedFile[] { + const root = new Map(); + for (const file of files) { + const parts = file.path.split("/"); + let current = root; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (part === undefined) continue; + const isLast = i === parts.length - 1; + let node = current.get(part); + if (!node) { + node = { + name: part, + type: isLast ? "file" : "folder", + file: isLast ? file : undefined, + children: isLast ? undefined : new Map(), + }; + current.set(part, node); + } + if (!isLast && node.children) { + current = node.children; + } + } + } + const result: ChangedFile[] = []; + const walk = (nodes: Map) => { + const sorted = [...nodes.values()].sort((a, b) => { + if (a.type !== b.type) { + return a.type === "folder" ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + for (const node of sorted) { + if (node.type === "file" && node.file) { + result.push(node.file); + } else if (node.children) { + walk(node.children); + } + } + }; + walk(root); + return result; +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx new file mode 100644 index 00000000000..d3988c94565 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx @@ -0,0 +1,203 @@ +import { + createContext, + type ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import type { ChangedFile } from "shared/changes-types"; + +export type MultiSelectClickResult = "multi" | "none"; + +export interface MultiSelectApi { + isSelected(path: string): boolean; + hasSelection: boolean; + selectionCount: number; + selectedFiles: ChangedFile[]; + handleClick(path: string, event: React.MouseEvent): MultiSelectClickResult; + clear(): void; + /** + * Bulk action handlers — VS Code style. When an action (stage / + * unstage / discard) is invoked from a FileItem that is part of a + * multi-selection, callers should apply the action to the whole + * selection instead of the single file. `null` means the action is + * not available in this section. + */ + onStageSelected: ((files: ChangedFile[]) => void) | null; + onUnstageSelected: ((files: ChangedFile[]) => void) | null; + onDiscardSelected: ((files: ChangedFile[]) => void) | null; +} + +const MultiSelectCtx = createContext(null); + +interface MultiSelectProviderProps { + files: ChangedFile[]; + children: ReactNode; + onStageSelected?: (files: ChangedFile[]) => void; + onUnstageSelected?: (files: ChangedFile[]) => void; + onDiscardSelected?: (files: ChangedFile[]) => void; +} + +const MULTI_SELECT_PATH_ATTR = "data-multi-select-path"; + +/** + * Per-section selection model enabling Shift/Cmd click multi-select. + * Range selection order resolves DOM-first (so tree-view collapsed + * folders are naturally skipped and any view mode's real rendered + * ordering is the ground truth), falling back to the `files` prop + * order for cases where the DOM doesn't contain both endpoints + * (virtualized lists where the anchor scrolled out). + */ +export function MultiSelectProvider({ + files, + children, + onStageSelected, + onUnstageSelected, + onDiscardSelected, +}: MultiSelectProviderProps) { + const [selectedPaths, setSelectedPaths] = useState>( + () => new Set(), + ); + const [anchorPath, setAnchorPath] = useState(null); + const filesRef = useRef(files); + filesRef.current = files; + const wrapperRef = useRef(null); + const onStageSelectedRef = useRef(onStageSelected); + const onUnstageSelectedRef = useRef(onUnstageSelected); + const onDiscardSelectedRef = useRef(onDiscardSelected); + onStageSelectedRef.current = onStageSelected; + onUnstageSelectedRef.current = onUnstageSelected; + onDiscardSelectedRef.current = onDiscardSelected; + + // Drop stale selections when files list changes (e.g., after staging). + useEffect(() => { + const validPaths = new Set(files.map((f) => f.path)); + setSelectedPaths((prev) => { + let mutated = false; + const next = new Set(); + for (const path of prev) { + if (validPaths.has(path)) { + next.add(path); + } else { + mutated = true; + } + } + return mutated ? next : prev; + }); + if (anchorPath && !validPaths.has(anchorPath)) { + setAnchorPath(null); + } + }, [files, anchorPath]); + + const resolveOrderedPathsForRange = useCallback( + (anchor: string, target: string): string[] => { + const wrapper = wrapperRef.current; + if (wrapper) { + const nodes = wrapper.querySelectorAll( + `[${MULTI_SELECT_PATH_ATTR}]`, + ); + if (nodes.length > 0) { + const domPaths: string[] = []; + for (const node of nodes) { + const path = node.dataset.multiSelectPath; + if (path) domPaths.push(path); + } + if (domPaths.includes(anchor) && domPaths.includes(target)) { + return domPaths; + } + } + } + return filesRef.current.map((f) => f.path); + }, + [], + ); + + const handleClick = useCallback( + (path, event) => { + if (event.shiftKey) { + // VS Code fallback: if the user shift-clicks without having + // previously clicked anything, treat the first visible file + // as the anchor so the range extends from the top of the + // list down to the clicked item. + const effectiveAnchor = anchorPath ?? filesRef.current[0]?.path; + if (effectiveAnchor) { + const paths = resolveOrderedPathsForRange(effectiveAnchor, path); + const fromIdx = paths.indexOf(effectiveAnchor); + const toIdx = paths.indexOf(path); + if (fromIdx >= 0 && toIdx >= 0) { + const [a, b] = + fromIdx <= toIdx ? [fromIdx, toIdx] : [toIdx, fromIdx]; + setSelectedPaths(new Set(paths.slice(a, b + 1))); + if (!anchorPath) { + setAnchorPath(effectiveAnchor); + } + return "multi"; + } + } + } + if (event.metaKey || event.ctrlKey) { + setSelectedPaths((prev) => { + const next = new Set(prev); + if (next.has(path)) { + next.delete(path); + } else { + next.add(path); + } + return next; + }); + setAnchorPath(path); + return "multi"; + } + // Normal click: clear any prior selection and let caller navigate. + setSelectedPaths((prev) => (prev.size > 0 ? new Set() : prev)); + setAnchorPath(path); + return "none"; + }, + [anchorPath, resolveOrderedPathsForRange], + ); + + const selectedFiles = useMemo( + () => files.filter((f) => selectedPaths.has(f.path)), + [files, selectedPaths], + ); + + const api = useMemo( + () => ({ + isSelected: (path: string) => selectedPaths.has(path), + hasSelection: selectedPaths.size > 0, + selectionCount: selectedPaths.size, + selectedFiles, + handleClick, + clear: () => setSelectedPaths(new Set()), + onStageSelected: onStageSelected ?? null, + onUnstageSelected: onUnstageSelected ?? null, + onDiscardSelected: onDiscardSelected ?? null, + }), + [ + selectedPaths, + selectedFiles, + handleClick, + onStageSelected, + onUnstageSelected, + onDiscardSelected, + ], + ); + + return ( + + {/* display: contents keeps the wrapper layout-transparent so it + does not break existing flex / scroll containers while still + scoping DOM queries to this provider's subtree. */} +
+ {children} +
+
+ ); +} + +export function useMultiSelect(): MultiSelectApi | null { + return useContext(MultiSelectCtx); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/index.ts new file mode 100644 index 00000000000..ec33703982b --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/index.ts @@ -0,0 +1,5 @@ +export { + type MultiSelectApi, + MultiSelectProvider, + useMultiSelect, +} from "./MultiSelectContext"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsx index 518b8bb692c..39a7d96c98c 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsx @@ -1,6 +1,6 @@ import { Button } from "@superset/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import type { ReactNode } from "react"; +import { type ReactNode, useMemo } from "react"; import { VscAdd, VscDiscard, VscRemove, VscWarning } from "react-icons/vsc"; import { getOrderedChangeSectionIds } from "renderer/stores/changes/section-order"; import type { @@ -10,6 +10,8 @@ import type { } from "shared/changes-types"; import { CommitListVirtualized } from "../../components/CommitListVirtualized"; import { FileList } from "../../components/FileList"; +import { orderFilesForViewMode } from "../../components/FileList/fileListOrdering"; +import { MultiSelectProvider } from "../../components/MultiSelectContext"; import type { ChangesViewMode } from "../../types"; export interface OrderedSection { @@ -102,6 +104,17 @@ export function useOrderedSections({ isUnstagedActioning, }: UseOrderedSectionsInput) { const commitCount = commitsWithFiles.length; + // Mirror each FileList variant's visual sort so that shift-click range + // selection in MultiSelectProvider picks a contiguous run matching the + // order the user actually sees. + const orderedUnstagedFiles = useMemo( + () => orderFilesForViewMode(unstagedFiles, fileListViewMode), + [unstagedFiles, fileListViewMode], + ); + const orderedStagedFiles = useMemo( + () => orderFilesForViewMode(stagedFiles, fileListViewMode), + [stagedFiles, fileListViewMode], + ); const sectionDefinitions: Record = { conflicted: { @@ -209,20 +222,25 @@ export function useOrderedSections({
), content: expandedSections.staged ? ( - + + + ) : null, }, unstaged: { @@ -264,21 +282,31 @@ export function useOrderedSections({
), content: expandedSections.unstaged ? ( - + { + for (const file of files) { + onDiscardFile(file); + } + }} + > + + ) : null, }, }; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index d1f98fdcf0f..d36c78f508f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -79,16 +79,42 @@ const HIGHLIGHT_MAX_RETRIES = 8; const SCROLL_STABILIZE_DELAY_MS = 120; const SEARCH_MATCH_LIMIT = 10_000; +/** + * Hidden search panel used when `searchMode === "overlay"`. The panel must + * stay part of the CM layout — setting `display: none` on the panel DOM + * makes `getBoundingClientRect()` return all-zeros, which breaks CM's + * `PanelGroup.scrollMargin()` calculation (it ends up equal to the full + * scroller height) and causes the drag-select autoscroll to fire + * continuously anywhere inside the editor. Collapsing the panel to zero + * height while keeping it in flow makes `top ≈ scroller.bottom` so + * scrollMargin resolves to 0 as intended. + */ +function hideHiddenSearchPanelContainer(container: HTMLElement) { + container.style.height = "0px"; + container.style.minHeight = "0px"; + container.style.maxHeight = "0px"; + container.style.margin = "0"; + container.style.padding = "0"; + container.style.border = "0"; + container.style.overflow = "hidden"; + container.style.visibility = "hidden"; + container.style.pointerEvents = "none"; +} + function createHiddenSearchPanel() { const dom = document.createElement("div"); dom.className = "cm-search cm-hidden-search-panel"; + dom.style.height = "0px"; + dom.style.overflow = "hidden"; + dom.style.visibility = "hidden"; + dom.style.pointerEvents = "none"; return { dom, mount() { const panelContainer = dom.parentElement; if (panelContainer instanceof HTMLElement) { - panelContainer.style.display = "none"; + hideHiddenSearchPanelContainer(panelContainer); } }, }; @@ -643,6 +669,27 @@ export function CodeEditor({ setIsSearchOpen(false); }; + // CM's find commands dispatch `scrollIntoView: true` (→ y: "nearest") + // synchronously. Following that up with a `y: "center"` effect is + // unreliable on huge virtualized files because `coordsAtPos` for an + // un-rendered line returns estimated coords and CM's internal scroll + // math ends up a near-noop. Instead, wait one frame for CM to apply + // its nearest scroll, then read the line block (which uses CM's own + // doc-relative line-height cache kept accurate by the measure cycle) + // and set `scrollTop` directly. + const scrollSearchMatchToCenter = (view: EditorView) => { + requestAnimationFrame(() => { + const scroller = view.scrollDOM; + const head = view.state.selection.main.head; + const block = view.lineBlockAt(head); + const targetScrollTop = Math.max( + 0, + Math.round(block.top + block.height / 2 - scroller.clientHeight / 2), + ); + scroller.scrollTop = targetScrollTop; + }); + }; + const handleOverlayFindNext = () => { const view = viewRef.current; if (!view) { @@ -655,6 +702,7 @@ export function CodeEditor({ } runFindNext(view); + scrollSearchMatchToCenter(view); }; const handleOverlayFindPrevious = () => { @@ -669,6 +717,7 @@ export function CodeEditor({ } runFindPrevious(view); + scrollSearchMatchToCenter(view); }; searchControlsRef.current = { diff --git a/packages/host-service/src/trpc/router/git/git.ts b/packages/host-service/src/trpc/router/git/git.ts index 2c3e3ddabc1..7fdf45b089f 100644 --- a/packages/host-service/src/trpc/router/git/git.ts +++ b/packages/host-service/src/trpc/router/git/git.ts @@ -32,7 +32,13 @@ import { resolveWorktreePath } from "./utils/resolve-worktree"; export const gitRouter = router({ listBranches: protectedProcedure - .input(z.object({ workspaceId: z.string() })) + .input( + z.object({ + workspaceId: z.string(), + sortOrder: z.enum(["committerdate", "alphabetical"]).optional(), + pinDefault: z.boolean().optional(), + }), + ) .query(async ({ ctx, input }) => { const worktreePath = resolveWorktreePath(ctx, input.workspaceId); const git = await ctx.git(worktreePath); @@ -42,27 +48,108 @@ export const gitRouter = router({ ).trim(); const defaultBranchName = await getDefaultBranchName(git); - let branchNames: string[] = []; - try { - const raw = await git.raw([ - "branch", - "--list", - "--format=%(refname:short)", - ]); - branchNames = raw.trim().split("\n").filter(Boolean); - } catch {} + const sortOrder = input.sortOrder ?? "committerdate"; + const pinDefault = input.pinDefault ?? true; + // `git branch` supports `--sort` directly (falls back to git default + // if the flag is rejected by a very old git). committerdate is + // descending via a leading `-`; alphabetical uses refname. + const sortArg = + sortOrder === "committerdate" + ? "--sort=-committerdate" + : "--sort=refname"; + + const readRefs = async ( + extraArgs: string[], + stripPrefix?: string, + ): Promise => { + try { + const raw = await git.raw([ + "branch", + "--list", + sortArg, + "--format=%(refname:short)", + ...extraArgs, + ]); + return ( + raw + .trim() + .split("\n") + .map((line) => line.trim()) + .filter(Boolean) + .filter((line) => !line.includes("->")) + // When a prefix filter is given (e.g. "origin/") only keep + // entries that start with that prefix. Lines from non-origin + // remotes (e.g. "upstream/main") would survive the map step + // unchanged and then cause buildBranch to construct invalid + // refs like "origin/upstream/main". + .filter((line) => !stripPrefix || line.startsWith(stripPrefix)) + .map((line) => + stripPrefix && line.startsWith(stripPrefix) + ? line.slice(stripPrefix.length) + : line, + ) + ); + } catch { + return []; + } + }; + + const localNames = await readRefs([]); + const remoteNames = await readRefs(["-r"], "origin/"); - const branches = await Promise.all( - branchNames.map((name) => - buildBranch( - git, - name, - name === currentBranchName, - defaultBranchName ? `origin/${defaultBranchName}` : undefined, - ), + const localSet = new Set(localNames); + // Deduplicate: a remote-only branch is one that has no local + // counterpart. Local branches always win. + const remoteOnlyNames = remoteNames.filter( + (name) => !localSet.has(name) && name !== "HEAD", + ); + + // For alphabetical sort we re-sort in JS with case-insensitive + // comparison so that `Feat-a` and `feat-b` interleave the way + // users expect. committerdate already comes back ordered by git. + const sortAlphabetical = (names: string[]): string[] => + sortOrder === "alphabetical" + ? [...names].sort((a, b) => + a.localeCompare(b, undefined, { sensitivity: "base" }), + ) + : names; + + const sortedLocal = sortAlphabetical(localNames); + const sortedRemoteOnly = sortAlphabetical(remoteOnlyNames); + + const compareRef = defaultBranchName + ? `origin/${defaultBranchName}` + : undefined; + + const localBranches = await Promise.all( + sortedLocal.map((name) => + buildBranch(git, name, name === currentBranchName, compareRef), + ), + ); + const remoteBranches = await Promise.all( + sortedRemoteOnly.map((name) => + buildBranch(git, name, false, compareRef, { isRemote: true }), ), ); + let branches = [...localBranches, ...remoteBranches]; + + // Optionally pin the default branch (main / master / trunk / etc) + // at the top of the list regardless of sort order. Only looks at + // local branches — if the default exists only remotely we leave + // it where the sort put it. + if (pinDefault && defaultBranchName) { + const defaultIdx = branches.findIndex( + (b) => !b.isRemote && b.name === defaultBranchName, + ); + if (defaultIdx > 0) { + const [defaultBranch] = branches.splice(defaultIdx, 1); + if (defaultBranch) { + branches = [defaultBranch, ...branches]; + } + } + } + return { branches }; }), diff --git a/packages/host-service/src/trpc/router/git/types.ts b/packages/host-service/src/trpc/router/git/types.ts index a604fbb03bd..9bd9b791132 100644 --- a/packages/host-service/src/trpc/router/git/types.ts +++ b/packages/host-service/src/trpc/router/git/types.ts @@ -105,6 +105,15 @@ export type FileStatus = PatchStatus | "untracked"; export interface Branch { name: string; isHead: boolean; + /** + * True when this entry represents a remote-tracking branch (e.g. + * `origin/feat-x`) that has no local counterpart. The `name` field + * always holds the short ref name without the `origin/` prefix so + * that `git.listCommits` / `git.getStatus` can keep interpreting it + * as a base ref; the UI is responsible for prefixing the display + * string when `isRemote` is true. + */ + isRemote: boolean; upstream: string | null; aheadCount: number; behindCount: number; diff --git a/packages/host-service/src/trpc/router/git/utils/git-helpers.ts b/packages/host-service/src/trpc/router/git/utils/git-helpers.ts index 2e50c1e02ec..022ea30bc3f 100644 --- a/packages/host-service/src/trpc/router/git/utils/git-helpers.ts +++ b/packages/host-service/src/trpc/router/git/utils/git-helpers.ts @@ -80,26 +80,38 @@ export async function buildBranch( name: string, isHead: boolean, compareRef?: string, + options?: { isRemote?: boolean }, ): Promise { + const isRemote = options?.isRemote ?? false; let upstream: string | null = null; let aheadCount = 0; let behindCount = 0; let lastCommitHash = ""; let lastCommitDate = ""; - try { - const remote = ( - await git.raw(["config", `branch.${name}.remote`]).catch(() => "") - ).trim(); - const merge = ( - await git.raw(["config", `branch.${name}.merge`]).catch(() => "") - ).trim(); - upstream = - remote && merge ? `${remote}/${merge.replace("refs/heads/", "")}` : null; - } catch { - upstream = null; + // Remote-tracking branches don't have `branch..remote` config + // entries, so skip the upstream probe entirely for them. + if (!isRemote) { + try { + const remote = ( + await git.raw(["config", `branch.${name}.remote`]).catch(() => "") + ).trim(); + const merge = ( + await git.raw(["config", `branch.${name}.merge`]).catch(() => "") + ).trim(); + upstream = + remote && merge + ? `${remote}/${merge.replace("refs/heads/", "")}` + : null; + } catch { + upstream = null; + } } + // The ref used for git commands — remote branches must be read via + // `origin/` even though we store the short name in the Branch. + const ref = isRemote ? `origin/${name}` : name; + if (compareRef) { try { const counts = ( @@ -107,7 +119,7 @@ export async function buildBranch( "rev-list", "--left-right", "--count", - `${compareRef}...${name}`, + `${compareRef}...${ref}`, ]) ).trim(); const [behind, ahead] = counts.split("\t").map(Number); @@ -117,7 +129,7 @@ export async function buildBranch( } try { - const log = (await git.raw(["log", "-1", "--format=%H\t%aI", name])).trim(); + const log = (await git.raw(["log", "-1", "--format=%H\t%aI", ref])).trim(); const [hash, date] = log.split("\t"); lastCommitHash = hash ?? ""; lastCommitDate = date ?? ""; @@ -126,6 +138,7 @@ export async function buildBranch( return { name, isHead, + isRemote, upstream, aheadCount, behindCount, diff --git a/packages/local-db/drizzle/0045_smart_commit_settings.sql b/packages/local-db/drizzle/0045_smart_commit_settings.sql new file mode 100644 index 00000000000..954a927bfaa --- /dev/null +++ b/packages/local-db/drizzle/0045_smart_commit_settings.sql @@ -0,0 +1,2 @@ +ALTER TABLE `settings` ADD `enable_smart_commit` integer;--> statement-breakpoint +ALTER TABLE `settings` ADD `smart_commit_changes` text; \ No newline at end of file diff --git a/packages/local-db/drizzle/0046_auto_stash_setting.sql b/packages/local-db/drizzle/0046_auto_stash_setting.sql new file mode 100644 index 00000000000..8c07891ac56 --- /dev/null +++ b/packages/local-db/drizzle/0046_auto_stash_setting.sql @@ -0,0 +1 @@ +ALTER TABLE `settings` ADD `auto_stash` integer; \ No newline at end of file diff --git a/packages/local-db/drizzle/0047_branch_sort_order_settings.sql b/packages/local-db/drizzle/0047_branch_sort_order_settings.sql new file mode 100644 index 00000000000..a048c1c8303 --- /dev/null +++ b/packages/local-db/drizzle/0047_branch_sort_order_settings.sql @@ -0,0 +1,2 @@ +ALTER TABLE `settings` ADD `branch_sort_order` text;--> statement-breakpoint +ALTER TABLE `settings` ADD `pin_default_branch` integer; \ No newline at end of file diff --git a/packages/local-db/drizzle/0048_post_commit_command.sql b/packages/local-db/drizzle/0048_post_commit_command.sql new file mode 100644 index 00000000000..d97f5f7eea1 --- /dev/null +++ b/packages/local-db/drizzle/0048_post_commit_command.sql @@ -0,0 +1 @@ +ALTER TABLE `settings` ADD `post_commit_command` text; \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0045_snapshot.json b/packages/local-db/drizzle/meta/0045_snapshot.json new file mode 100644 index 00000000000..c5d45de6ba0 --- /dev/null +++ b/packages/local-db/drizzle/meta/0045_snapshot.json @@ -0,0 +1,1536 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "699cfbda-967a-4ed9-9346-610e4e1a224c", + "prevId": "727b07cc-f66c-4301-94e4-0f02d3ecc3ca", + "tables": { + "browser_history": { + "name": "browser_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_visited_at": { + "name": "last_visited_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visit_count": { + "name": "visit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "browser_history_url_unique": { + "name": "browser_history_url_unique", + "columns": [ + "url" + ], + "isUnique": true + }, + "browser_history_url_idx": { + "name": "browser_history_url_idx", + "columns": [ + "url" + ], + "isUnique": false + }, + "browser_history_last_visited_at_idx": { + "name": "browser_history_last_visited_at_idx", + "columns": [ + "last_visited_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "browser_site_permissions": { + "name": "browser_site_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ask'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "browser_site_permissions_origin_idx": { + "name": "browser_site_permissions_origin_idx", + "columns": [ + "origin" + ], + "isUnique": false + }, + "browser_site_permissions_origin_kind_unique": { + "name": "browser_site_permissions_origin_kind_unique", + "columns": [ + "origin", + "kind" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organization_members_organization_id_idx": { + "name": "organization_members_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_members_user_id_idx": { + "name": "organization_members_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_org_id": { + "name": "clerk_org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_org": { + "name": "github_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_clerk_org_id_unique": { + "name": "organizations_clerk_org_id_unique", + "columns": [ + "clerk_org_id" + ], + "isUnique": true + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "organizations_clerk_org_id_idx": { + "name": "organizations_clerk_org_id_idx", + "columns": [ + "clerk_org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "main_repo_path": { + "name": "main_repo_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_toast_dismissed": { + "name": "config_toast_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_base_branch": { + "name": "workspace_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_owner": { + "name": "github_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hide_image": { + "name": "hide_image", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "neon_project_id": { + "name": "neon_project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_app": { + "name": "default_app", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_main_repo_path_idx": { + "name": "projects_main_repo_path_idx", + "columns": [ + "main_repo_path" + ], + "isUnique": false + }, + "projects_last_opened_at_idx": { + "name": "projects_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets": { + "name": "terminal_presets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets_initialized": { + "name": "terminal_presets_initialized", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_preset_overrides": { + "name": "agent_preset_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_custom_definitions": { + "name": "agent_custom_definitions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_ringtone_id": { + "name": "selected_ringtone_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_on_quit": { + "name": "confirm_on_quit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_link_behavior": { + "name": "terminal_link_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persist_terminal": { + "name": "persist_terminal", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "auto_apply_default_preset": { + "name": "auto_apply_default_preset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_sounds_muted": { + "name": "notification_sounds_muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_volume": { + "name": "notification_volume", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prevent_agent_sleep": { + "name": "prevent_agent_sleep", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_local_branch": { + "name": "delete_local_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_open_mode": { + "name": "file_open_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "right_sidebar_open_view_width": { + "name": "right_sidebar_open_view_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_presets_bar": { + "name": "show_presets_bar", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_compact_terminal_add_button": { + "name": "use_compact_terminal_add_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_family": { + "name": "terminal_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_size": { + "name": "terminal_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_family": { + "name": "editor_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_size": { + "name": "editor_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_resource_monitor": { + "name": "show_resource_monitor", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "open_links_in_app": { + "name": "open_links_in_app", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_editor": { + "name": "default_editor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_enabled": { + "name": "indent_rainbow_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_colors": { + "name": "indent_rainbow_colors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_enabled": { + "name": "trailing_spaces_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_color": { + "name": "trailing_spaces_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_graph_enabled": { + "name": "reference_graph_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expose_host_service_via_relay": { + "name": "expose_host_service_via_relay", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_smart_commit": { + "name": "enable_smart_commit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "smart_commit_changes": { + "name": "smart_commit_changes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_color": { + "name": "status_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_type": { + "name": "status_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_position": { + "name": "status_position", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "labels": { + "name": "labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_provider": { + "name": "external_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_slug_unique": { + "name": "tasks_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + "assignee_id" + ], + "isUnique": false + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_id": { + "name": "clerk_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "columns": [ + "clerk_id" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_clerk_id_idx": { + "name": "users_clerk_id_idx", + "columns": [ + "clerk_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_sections": { + "name": "workspace_sections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_collapsed": { + "name": "is_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_sections_project_id_idx": { + "name": "workspace_sections_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspace_sections_project_id_projects_id_fk": { + "name": "workspace_sections_project_id_projects_id_fk", + "tableFrom": "workspace_sections", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_unread": { + "name": "is_unread", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_unnamed": { + "name": "is_unnamed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_base": { + "name": "port_base", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "workspaces_worktree_id_idx": { + "name": "workspaces_worktree_id_idx", + "columns": [ + "worktree_id" + ], + "isUnique": false + }, + "workspaces_last_opened_at_idx": { + "name": "workspaces_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + }, + "workspaces_section_id_idx": { + "name": "workspaces_section_id_idx", + "columns": [ + "section_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_worktree_id_worktrees_id_fk": { + "name": "workspaces_worktree_id_worktrees_id_fk", + "tableFrom": "workspaces", + "tableTo": "worktrees", + "columnsFrom": [ + "worktree_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_section_id_workspace_sections_id_fk": { + "name": "workspaces_section_id_workspace_sections_id_fk", + "tableFrom": "workspaces", + "tableTo": "workspace_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "git_status": { + "name": "git_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_status": { + "name": "github_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_superset": { + "name": "created_by_superset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "worktrees_project_id_idx": { + "name": "worktrees_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "worktrees_branch_idx": { + "name": "worktrees_branch_idx", + "columns": [ + "branch" + ], + "isUnique": false + } + }, + "foreignKeys": { + "worktrees_project_id_projects_id_fk": { + "name": "worktrees_project_id_projects_id_fk", + "tableFrom": "worktrees", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0046_snapshot.json b/packages/local-db/drizzle/meta/0046_snapshot.json new file mode 100644 index 00000000000..e04c4924cd6 --- /dev/null +++ b/packages/local-db/drizzle/meta/0046_snapshot.json @@ -0,0 +1,1543 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "85a60430-beae-4fcd-bbfe-748cbc797d51", + "prevId": "699cfbda-967a-4ed9-9346-610e4e1a224c", + "tables": { + "browser_history": { + "name": "browser_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_visited_at": { + "name": "last_visited_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visit_count": { + "name": "visit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "browser_history_url_unique": { + "name": "browser_history_url_unique", + "columns": [ + "url" + ], + "isUnique": true + }, + "browser_history_url_idx": { + "name": "browser_history_url_idx", + "columns": [ + "url" + ], + "isUnique": false + }, + "browser_history_last_visited_at_idx": { + "name": "browser_history_last_visited_at_idx", + "columns": [ + "last_visited_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "browser_site_permissions": { + "name": "browser_site_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ask'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "browser_site_permissions_origin_idx": { + "name": "browser_site_permissions_origin_idx", + "columns": [ + "origin" + ], + "isUnique": false + }, + "browser_site_permissions_origin_kind_unique": { + "name": "browser_site_permissions_origin_kind_unique", + "columns": [ + "origin", + "kind" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organization_members_organization_id_idx": { + "name": "organization_members_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_members_user_id_idx": { + "name": "organization_members_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_org_id": { + "name": "clerk_org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_org": { + "name": "github_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_clerk_org_id_unique": { + "name": "organizations_clerk_org_id_unique", + "columns": [ + "clerk_org_id" + ], + "isUnique": true + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "organizations_clerk_org_id_idx": { + "name": "organizations_clerk_org_id_idx", + "columns": [ + "clerk_org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "main_repo_path": { + "name": "main_repo_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_toast_dismissed": { + "name": "config_toast_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_base_branch": { + "name": "workspace_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_owner": { + "name": "github_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hide_image": { + "name": "hide_image", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "neon_project_id": { + "name": "neon_project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_app": { + "name": "default_app", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_main_repo_path_idx": { + "name": "projects_main_repo_path_idx", + "columns": [ + "main_repo_path" + ], + "isUnique": false + }, + "projects_last_opened_at_idx": { + "name": "projects_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets": { + "name": "terminal_presets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets_initialized": { + "name": "terminal_presets_initialized", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_preset_overrides": { + "name": "agent_preset_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_custom_definitions": { + "name": "agent_custom_definitions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_ringtone_id": { + "name": "selected_ringtone_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_on_quit": { + "name": "confirm_on_quit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_link_behavior": { + "name": "terminal_link_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persist_terminal": { + "name": "persist_terminal", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "auto_apply_default_preset": { + "name": "auto_apply_default_preset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_sounds_muted": { + "name": "notification_sounds_muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_volume": { + "name": "notification_volume", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prevent_agent_sleep": { + "name": "prevent_agent_sleep", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_local_branch": { + "name": "delete_local_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_open_mode": { + "name": "file_open_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "right_sidebar_open_view_width": { + "name": "right_sidebar_open_view_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_presets_bar": { + "name": "show_presets_bar", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_compact_terminal_add_button": { + "name": "use_compact_terminal_add_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_family": { + "name": "terminal_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_size": { + "name": "terminal_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_family": { + "name": "editor_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_size": { + "name": "editor_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_resource_monitor": { + "name": "show_resource_monitor", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "open_links_in_app": { + "name": "open_links_in_app", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_editor": { + "name": "default_editor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_enabled": { + "name": "indent_rainbow_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_colors": { + "name": "indent_rainbow_colors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_enabled": { + "name": "trailing_spaces_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_color": { + "name": "trailing_spaces_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_graph_enabled": { + "name": "reference_graph_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expose_host_service_via_relay": { + "name": "expose_host_service_via_relay", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_smart_commit": { + "name": "enable_smart_commit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "smart_commit_changes": { + "name": "smart_commit_changes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auto_stash": { + "name": "auto_stash", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_color": { + "name": "status_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_type": { + "name": "status_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_position": { + "name": "status_position", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "labels": { + "name": "labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_provider": { + "name": "external_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_slug_unique": { + "name": "tasks_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + "assignee_id" + ], + "isUnique": false + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_id": { + "name": "clerk_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "columns": [ + "clerk_id" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_clerk_id_idx": { + "name": "users_clerk_id_idx", + "columns": [ + "clerk_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_sections": { + "name": "workspace_sections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_collapsed": { + "name": "is_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_sections_project_id_idx": { + "name": "workspace_sections_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspace_sections_project_id_projects_id_fk": { + "name": "workspace_sections_project_id_projects_id_fk", + "tableFrom": "workspace_sections", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_unread": { + "name": "is_unread", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_unnamed": { + "name": "is_unnamed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_base": { + "name": "port_base", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "workspaces_worktree_id_idx": { + "name": "workspaces_worktree_id_idx", + "columns": [ + "worktree_id" + ], + "isUnique": false + }, + "workspaces_last_opened_at_idx": { + "name": "workspaces_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + }, + "workspaces_section_id_idx": { + "name": "workspaces_section_id_idx", + "columns": [ + "section_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_worktree_id_worktrees_id_fk": { + "name": "workspaces_worktree_id_worktrees_id_fk", + "tableFrom": "workspaces", + "tableTo": "worktrees", + "columnsFrom": [ + "worktree_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_section_id_workspace_sections_id_fk": { + "name": "workspaces_section_id_workspace_sections_id_fk", + "tableFrom": "workspaces", + "tableTo": "workspace_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "git_status": { + "name": "git_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_status": { + "name": "github_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_superset": { + "name": "created_by_superset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "worktrees_project_id_idx": { + "name": "worktrees_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "worktrees_branch_idx": { + "name": "worktrees_branch_idx", + "columns": [ + "branch" + ], + "isUnique": false + } + }, + "foreignKeys": { + "worktrees_project_id_projects_id_fk": { + "name": "worktrees_project_id_projects_id_fk", + "tableFrom": "worktrees", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0047_snapshot.json b/packages/local-db/drizzle/meta/0047_snapshot.json new file mode 100644 index 00000000000..0f4dd23e0a6 --- /dev/null +++ b/packages/local-db/drizzle/meta/0047_snapshot.json @@ -0,0 +1,1557 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1595146c-a407-448a-9a7f-7ca37eceaeaf", + "prevId": "85a60430-beae-4fcd-bbfe-748cbc797d51", + "tables": { + "browser_history": { + "name": "browser_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_visited_at": { + "name": "last_visited_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visit_count": { + "name": "visit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "browser_history_url_unique": { + "name": "browser_history_url_unique", + "columns": [ + "url" + ], + "isUnique": true + }, + "browser_history_url_idx": { + "name": "browser_history_url_idx", + "columns": [ + "url" + ], + "isUnique": false + }, + "browser_history_last_visited_at_idx": { + "name": "browser_history_last_visited_at_idx", + "columns": [ + "last_visited_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "browser_site_permissions": { + "name": "browser_site_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ask'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "browser_site_permissions_origin_idx": { + "name": "browser_site_permissions_origin_idx", + "columns": [ + "origin" + ], + "isUnique": false + }, + "browser_site_permissions_origin_kind_unique": { + "name": "browser_site_permissions_origin_kind_unique", + "columns": [ + "origin", + "kind" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organization_members_organization_id_idx": { + "name": "organization_members_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_members_user_id_idx": { + "name": "organization_members_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_org_id": { + "name": "clerk_org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_org": { + "name": "github_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_clerk_org_id_unique": { + "name": "organizations_clerk_org_id_unique", + "columns": [ + "clerk_org_id" + ], + "isUnique": true + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "organizations_clerk_org_id_idx": { + "name": "organizations_clerk_org_id_idx", + "columns": [ + "clerk_org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "main_repo_path": { + "name": "main_repo_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_toast_dismissed": { + "name": "config_toast_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_base_branch": { + "name": "workspace_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_owner": { + "name": "github_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hide_image": { + "name": "hide_image", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "neon_project_id": { + "name": "neon_project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_app": { + "name": "default_app", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_main_repo_path_idx": { + "name": "projects_main_repo_path_idx", + "columns": [ + "main_repo_path" + ], + "isUnique": false + }, + "projects_last_opened_at_idx": { + "name": "projects_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets": { + "name": "terminal_presets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets_initialized": { + "name": "terminal_presets_initialized", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_preset_overrides": { + "name": "agent_preset_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_custom_definitions": { + "name": "agent_custom_definitions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_ringtone_id": { + "name": "selected_ringtone_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_on_quit": { + "name": "confirm_on_quit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_link_behavior": { + "name": "terminal_link_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persist_terminal": { + "name": "persist_terminal", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "auto_apply_default_preset": { + "name": "auto_apply_default_preset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_sounds_muted": { + "name": "notification_sounds_muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_volume": { + "name": "notification_volume", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prevent_agent_sleep": { + "name": "prevent_agent_sleep", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_local_branch": { + "name": "delete_local_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_open_mode": { + "name": "file_open_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "right_sidebar_open_view_width": { + "name": "right_sidebar_open_view_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_presets_bar": { + "name": "show_presets_bar", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_compact_terminal_add_button": { + "name": "use_compact_terminal_add_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_family": { + "name": "terminal_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_size": { + "name": "terminal_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_family": { + "name": "editor_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_size": { + "name": "editor_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_resource_monitor": { + "name": "show_resource_monitor", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "open_links_in_app": { + "name": "open_links_in_app", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_editor": { + "name": "default_editor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_enabled": { + "name": "indent_rainbow_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_colors": { + "name": "indent_rainbow_colors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_enabled": { + "name": "trailing_spaces_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_color": { + "name": "trailing_spaces_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_graph_enabled": { + "name": "reference_graph_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expose_host_service_via_relay": { + "name": "expose_host_service_via_relay", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_smart_commit": { + "name": "enable_smart_commit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "smart_commit_changes": { + "name": "smart_commit_changes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auto_stash": { + "name": "auto_stash", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_sort_order": { + "name": "branch_sort_order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_default_branch": { + "name": "pin_default_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_color": { + "name": "status_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_type": { + "name": "status_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_position": { + "name": "status_position", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "labels": { + "name": "labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_provider": { + "name": "external_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_slug_unique": { + "name": "tasks_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + "assignee_id" + ], + "isUnique": false + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_id": { + "name": "clerk_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "columns": [ + "clerk_id" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_clerk_id_idx": { + "name": "users_clerk_id_idx", + "columns": [ + "clerk_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_sections": { + "name": "workspace_sections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_collapsed": { + "name": "is_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_sections_project_id_idx": { + "name": "workspace_sections_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspace_sections_project_id_projects_id_fk": { + "name": "workspace_sections_project_id_projects_id_fk", + "tableFrom": "workspace_sections", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_unread": { + "name": "is_unread", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_unnamed": { + "name": "is_unnamed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_base": { + "name": "port_base", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "workspaces_worktree_id_idx": { + "name": "workspaces_worktree_id_idx", + "columns": [ + "worktree_id" + ], + "isUnique": false + }, + "workspaces_last_opened_at_idx": { + "name": "workspaces_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + }, + "workspaces_section_id_idx": { + "name": "workspaces_section_id_idx", + "columns": [ + "section_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_worktree_id_worktrees_id_fk": { + "name": "workspaces_worktree_id_worktrees_id_fk", + "tableFrom": "workspaces", + "tableTo": "worktrees", + "columnsFrom": [ + "worktree_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_section_id_workspace_sections_id_fk": { + "name": "workspaces_section_id_workspace_sections_id_fk", + "tableFrom": "workspaces", + "tableTo": "workspace_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "git_status": { + "name": "git_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_status": { + "name": "github_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_superset": { + "name": "created_by_superset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "worktrees_project_id_idx": { + "name": "worktrees_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "worktrees_branch_idx": { + "name": "worktrees_branch_idx", + "columns": [ + "branch" + ], + "isUnique": false + } + }, + "foreignKeys": { + "worktrees_project_id_projects_id_fk": { + "name": "worktrees_project_id_projects_id_fk", + "tableFrom": "worktrees", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0048_snapshot.json b/packages/local-db/drizzle/meta/0048_snapshot.json new file mode 100644 index 00000000000..75fabd0366d --- /dev/null +++ b/packages/local-db/drizzle/meta/0048_snapshot.json @@ -0,0 +1,1564 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "089cd34e-caf6-40a8-ba11-c93450775165", + "prevId": "1595146c-a407-448a-9a7f-7ca37eceaeaf", + "tables": { + "browser_history": { + "name": "browser_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_visited_at": { + "name": "last_visited_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visit_count": { + "name": "visit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "browser_history_url_unique": { + "name": "browser_history_url_unique", + "columns": [ + "url" + ], + "isUnique": true + }, + "browser_history_url_idx": { + "name": "browser_history_url_idx", + "columns": [ + "url" + ], + "isUnique": false + }, + "browser_history_last_visited_at_idx": { + "name": "browser_history_last_visited_at_idx", + "columns": [ + "last_visited_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "browser_site_permissions": { + "name": "browser_site_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ask'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "browser_site_permissions_origin_idx": { + "name": "browser_site_permissions_origin_idx", + "columns": [ + "origin" + ], + "isUnique": false + }, + "browser_site_permissions_origin_kind_unique": { + "name": "browser_site_permissions_origin_kind_unique", + "columns": [ + "origin", + "kind" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organization_members_organization_id_idx": { + "name": "organization_members_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_members_user_id_idx": { + "name": "organization_members_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_org_id": { + "name": "clerk_org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_org": { + "name": "github_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_clerk_org_id_unique": { + "name": "organizations_clerk_org_id_unique", + "columns": [ + "clerk_org_id" + ], + "isUnique": true + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "organizations_clerk_org_id_idx": { + "name": "organizations_clerk_org_id_idx", + "columns": [ + "clerk_org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "main_repo_path": { + "name": "main_repo_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_toast_dismissed": { + "name": "config_toast_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_base_branch": { + "name": "workspace_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_owner": { + "name": "github_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hide_image": { + "name": "hide_image", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "neon_project_id": { + "name": "neon_project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_app": { + "name": "default_app", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_main_repo_path_idx": { + "name": "projects_main_repo_path_idx", + "columns": [ + "main_repo_path" + ], + "isUnique": false + }, + "projects_last_opened_at_idx": { + "name": "projects_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets": { + "name": "terminal_presets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets_initialized": { + "name": "terminal_presets_initialized", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_preset_overrides": { + "name": "agent_preset_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_custom_definitions": { + "name": "agent_custom_definitions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_ringtone_id": { + "name": "selected_ringtone_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_on_quit": { + "name": "confirm_on_quit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_link_behavior": { + "name": "terminal_link_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persist_terminal": { + "name": "persist_terminal", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "auto_apply_default_preset": { + "name": "auto_apply_default_preset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_sounds_muted": { + "name": "notification_sounds_muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_volume": { + "name": "notification_volume", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prevent_agent_sleep": { + "name": "prevent_agent_sleep", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_local_branch": { + "name": "delete_local_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_open_mode": { + "name": "file_open_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "right_sidebar_open_view_width": { + "name": "right_sidebar_open_view_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_presets_bar": { + "name": "show_presets_bar", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "use_compact_terminal_add_button": { + "name": "use_compact_terminal_add_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_family": { + "name": "terminal_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_size": { + "name": "terminal_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_family": { + "name": "editor_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_size": { + "name": "editor_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_resource_monitor": { + "name": "show_resource_monitor", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_base_dir": { + "name": "worktree_base_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "open_links_in_app": { + "name": "open_links_in_app", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_editor": { + "name": "default_editor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_enabled": { + "name": "indent_rainbow_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "indent_rainbow_colors": { + "name": "indent_rainbow_colors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_enabled": { + "name": "trailing_spaces_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trailing_spaces_color": { + "name": "trailing_spaces_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_graph_enabled": { + "name": "reference_graph_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expose_host_service_via_relay": { + "name": "expose_host_service_via_relay", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_smart_commit": { + "name": "enable_smart_commit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "smart_commit_changes": { + "name": "smart_commit_changes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auto_stash": { + "name": "auto_stash", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_sort_order": { + "name": "branch_sort_order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_default_branch": { + "name": "pin_default_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "post_commit_command": { + "name": "post_commit_command", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_color": { + "name": "status_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_type": { + "name": "status_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_position": { + "name": "status_position", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "labels": { + "name": "labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_provider": { + "name": "external_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_slug_unique": { + "name": "tasks_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + "assignee_id" + ], + "isUnique": false + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_id": { + "name": "clerk_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "columns": [ + "clerk_id" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_clerk_id_idx": { + "name": "users_clerk_id_idx", + "columns": [ + "clerk_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_sections": { + "name": "workspace_sections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_collapsed": { + "name": "is_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_sections_project_id_idx": { + "name": "workspace_sections_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspace_sections_project_id_projects_id_fk": { + "name": "workspace_sections_project_id_projects_id_fk", + "tableFrom": "workspace_sections", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_unread": { + "name": "is_unread", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_unnamed": { + "name": "is_unnamed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_base": { + "name": "port_base", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "workspaces_worktree_id_idx": { + "name": "workspaces_worktree_id_idx", + "columns": [ + "worktree_id" + ], + "isUnique": false + }, + "workspaces_last_opened_at_idx": { + "name": "workspaces_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + }, + "workspaces_section_id_idx": { + "name": "workspaces_section_id_idx", + "columns": [ + "section_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_worktree_id_worktrees_id_fk": { + "name": "workspaces_worktree_id_worktrees_id_fk", + "tableFrom": "workspaces", + "tableTo": "worktrees", + "columnsFrom": [ + "worktree_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_section_id_workspace_sections_id_fk": { + "name": "workspaces_section_id_workspace_sections_id_fk", + "tableFrom": "workspaces", + "tableTo": "workspace_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "git_status": { + "name": "git_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_status": { + "name": "github_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_superset": { + "name": "created_by_superset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "worktrees_project_id_idx": { + "name": "worktrees_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "worktrees_branch_idx": { + "name": "worktrees_branch_idx", + "columns": [ + "branch" + ], + "isUnique": false + } + }, + "foreignKeys": { + "worktrees_project_id_projects_id_fk": { + "name": "worktrees_project_id_projects_id_fk", + "tableFrom": "worktrees", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index 6203d5f0a48..99cf975c476 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -316,6 +316,34 @@ "when": 1775884928353, "tag": "0044_add_reference_graph_enabled_setting", "breakpoints": true + }, + { + "idx": 45, + "version": "6", + "when": 1776064763011, + "tag": "0045_smart_commit_settings", + "breakpoints": true + }, + { + "idx": 46, + "version": "6", + "when": 1776066177884, + "tag": "0046_auto_stash_setting", + "breakpoints": true + }, + { + "idx": 47, + "version": "6", + "when": 1776067576503, + "tag": "0047_branch_sort_order_settings", + "breakpoints": true + }, + { + "idx": 48, + "version": "6", + "when": 1776068037113, + "tag": "0048_post_commit_command", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/local-db/src/schema/schema.ts b/packages/local-db/src/schema/schema.ts index b4230633248..9782b5dfb55 100644 --- a/packages/local-db/src/schema/schema.ts +++ b/packages/local-db/src/schema/schema.ts @@ -11,12 +11,15 @@ import type { AgentCustomDefinition, AgentPresetOverrideEnvelope, BranchPrefixMode, + BranchSortOrder, ExternalApp, FileOpenMode, GitHubStatus, GitStatus, + PostCommitCommand, SitePermissionKind, SitePermissionValue, + SmartCommitChangesMode, TerminalLinkBehavior, TerminalPreset, WorkspaceType, @@ -246,6 +249,14 @@ export const settings = sqliteTable("settings", { exposeHostServiceViaRelay: integer("expose_host_service_via_relay", { mode: "boolean", }), + enableSmartCommit: integer("enable_smart_commit", { mode: "boolean" }), + smartCommitChanges: text( + "smart_commit_changes", + ).$type(), + autoStash: integer("auto_stash", { mode: "boolean" }), + branchSortOrder: text("branch_sort_order").$type(), + pinDefaultBranch: integer("pin_default_branch", { mode: "boolean" }), + postCommitCommand: text("post_commit_command").$type(), }); export type InsertSettings = typeof settings.$inferInsert; diff --git a/packages/local-db/src/schema/zod.ts b/packages/local-db/src/schema/zod.ts index 691b8aa6720..c2d4ab37162 100644 --- a/packages/local-db/src/schema/zod.ts +++ b/packages/local-db/src/schema/zod.ts @@ -250,6 +250,19 @@ export const FILE_OPEN_MODES = ["split-pane", "new-tab"] as const; export type FileOpenMode = (typeof FILE_OPEN_MODES)[number]; +export const SMART_COMMIT_CHANGES_MODES = ["all", "tracked"] as const; + +export type SmartCommitChangesMode = + (typeof SMART_COMMIT_CHANGES_MODES)[number]; + +export const BRANCH_SORT_ORDERS = ["committerdate", "alphabetical"] as const; + +export type BranchSortOrder = (typeof BRANCH_SORT_ORDERS)[number]; + +export const POST_COMMIT_COMMANDS = ["none", "push", "sync"] as const; + +export type PostCommitCommand = (typeof POST_COMMIT_COMMANDS)[number]; + export const SITE_PERMISSION_KINDS = ["microphone", "camera"] as const; export type SitePermissionKind = (typeof SITE_PERMISSION_KINDS)[number];