diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts index f11759ceaa2..14dd66b6680 100644 --- a/apps/desktop/electron.vite.config.ts +++ b/apps/desktop/electron.vite.config.ts @@ -93,9 +93,14 @@ export default defineConfig({ process.env.DESKTOP_NOTIFICATIONS_PORT, ), "process.env.ELECTRIC_PORT": defineEnv(process.env.ELECTRIC_PORT), - "process.env.SUPERSET_WORKSPACE_NAME": defineEnv( - process.env.SUPERSET_WORKSPACE_NAME, - ), + // Workspace isolation only applies in dev (each worktree's setup.sh writes a + // per-workspace SUPERSET_WORKSPACE_NAME to .env). In production builds the env + // var must NOT be baked in — a stray value (e.g. inherited from a dev shell) + // would silently redirect ~/.superset → ~/.superset-, hiding all user data. + "process.env.SUPERSET_WORKSPACE_NAME": + process.env.NODE_ENV === "development" + ? defineEnv(process.env.SUPERSET_WORKSPACE_NAME) + : JSON.stringify("superset"), }, build: { @@ -210,9 +215,11 @@ export default defineConfig({ process.env.DESKTOP_NOTIFICATIONS_PORT, ), "process.env.ELECTRIC_PORT": defineEnv(process.env.ELECTRIC_PORT), - "process.env.SUPERSET_WORKSPACE_NAME": defineEnv( - process.env.SUPERSET_WORKSPACE_NAME, - ), + // See main define block above for rationale. + "process.env.SUPERSET_WORKSPACE_NAME": + process.env.NODE_ENV === "development" + ? defineEnv(process.env.SUPERSET_WORKSPACE_NAME) + : JSON.stringify("superset"), }, server: { diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts index bb21e3b09fd..432f3e6ae56 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/index.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts @@ -1030,5 +1030,25 @@ export const createSettingsRouter = () => { .mutation(() => { return { success: true }; }), + + getShowWorkspaceNumbersOnModifier: publicProcedure.query(() => { + const row = getSettings(); + return row.showWorkspaceNumbersOnModifier ?? false; + }), + + setShowWorkspaceNumbersOnModifier: publicProcedure + .input(z.object({ enabled: z.boolean() })) + .mutation(({ input }) => { + localDb + .insert(settings) + .values({ id: 1, showWorkspaceNumbersOnModifier: input.enabled }) + .onConflictDoUpdate({ + target: settings.id, + set: { showWorkspaceNumbersOnModifier: input.enabled }, + }) + .run(); + + return { success: true }; + }), }); }; diff --git a/apps/desktop/src/renderer/hooks/useModifierKeyListener.ts b/apps/desktop/src/renderer/hooks/useModifierKeyListener.ts new file mode 100644 index 00000000000..12ea05008a1 --- /dev/null +++ b/apps/desktop/src/renderer/hooks/useModifierKeyListener.ts @@ -0,0 +1,61 @@ +import { useEffect } from "react"; +import { useModifierKeyStateStore } from "renderer/stores/modifier-key-state"; +import { useWorkspaceShortcutModifiers } from "./useWorkspaceShortcutModifiers"; + +export function useModifierKeyListener(enabled: boolean) { + const { allModifierKeys, comboToIndices } = useWorkspaceShortcutModifiers(); + const { pressKey, releaseKey, clearAll } = useModifierKeyStateStore(); + + useEffect(() => { + if (!enabled) { + clearAll(); + return; + } + + function updateIsModifierHeld() { + const held = useModifierKeyStateStore.getState().heldKeys; + for (const combo of comboToIndices.keys()) { + const comboKeys = combo.split("+"); + if (comboKeys.length > 0 && comboKeys.every((k) => held.has(k))) { + useModifierKeyStateStore.setState({ isModifierHeld: true }); + return; + } + } + useModifierKeyStateStore.setState({ isModifierHeld: false }); + } + + function handleKeyDown(e: KeyboardEvent) { + if (allModifierKeys.has(e.key)) { + pressKey(e.key); + updateIsModifierHeld(); + } + } + function handleKeyUp(e: KeyboardEvent) { + if (allModifierKeys.has(e.key)) { + releaseKey(e.key); + updateIsModifierHeld(); + } + } + function handleBlur() { + clearAll(); + } + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + window.addEventListener("blur", handleBlur); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + window.removeEventListener("blur", handleBlur); + clearAll(); + }; + }, [ + enabled, + allModifierKeys, + comboToIndices, + pressKey, + releaseKey, + clearAll, + ]); +} diff --git a/apps/desktop/src/renderer/hooks/useWorkspaceShortcutModifiers.test.ts b/apps/desktop/src/renderer/hooks/useWorkspaceShortcutModifiers.test.ts new file mode 100644 index 00000000000..64cdef6c0de --- /dev/null +++ b/apps/desktop/src/renderer/hooks/useWorkspaceShortcutModifiers.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from "bun:test"; +import { parseBinding } from "./useWorkspaceShortcutModifiers"; + +describe("parseBinding", () => { + test("parses a simple binding like 'meta+1'", () => { + const result = parseBinding("meta+1"); + expect(result.modifierKeys).toEqual(["Meta"]); + expect(result.triggerKey).toBe("1"); + }); + + test("parses a multi-modifier binding like 'ctrl+shift+3'", () => { + const result = parseBinding("ctrl+shift+3"); + expect(result.modifierKeys).toEqual(["Control", "Shift"]); + expect(result.triggerKey).toBe("3"); + }); + + test("handles unknown modifier gracefully (passes through as-is)", () => { + const result = parseBinding("hyper+x"); + expect(result.modifierKeys).toEqual(["hyper"]); + expect(result.triggerKey).toBe("x"); + }); +}); diff --git a/apps/desktop/src/renderer/hooks/useWorkspaceShortcutModifiers.ts b/apps/desktop/src/renderer/hooks/useWorkspaceShortcutModifiers.ts new file mode 100644 index 00000000000..146e91f6f40 --- /dev/null +++ b/apps/desktop/src/renderer/hooks/useWorkspaceShortcutModifiers.ts @@ -0,0 +1,76 @@ +import { useMemo } from "react"; +import { + formatHotkeyDisplay, + getBinding, + type HotkeyId, + PLATFORM, + useHotkeyOverridesStore, +} from "renderer/hotkeys"; +import { parseBinding as parseShortcutBinding } from "renderer/hotkeys/utils/binding"; + +const WORKSPACE_HOTKEY_IDS: HotkeyId[] = [ + "JUMP_TO_WORKSPACE_1", + "JUMP_TO_WORKSPACE_2", + "JUMP_TO_WORKSPACE_3", + "JUMP_TO_WORKSPACE_4", + "JUMP_TO_WORKSPACE_5", + "JUMP_TO_WORKSPACE_6", + "JUMP_TO_WORKSPACE_7", + "JUMP_TO_WORKSPACE_8", + "JUMP_TO_WORKSPACE_9", +]; + +const MODIFIER_KEY_MAP: Record = { + meta: "Meta", + ctrl: "Control", + shift: "Shift", + alt: "Alt", +}; + +export interface WorkspaceShortcutInfo { + index: number; + triggerKey: string; + modifierKeys: string[]; +} + +export function parseBinding(binding: string): { + modifierKeys: string[]; + triggerKey: string; +} { + const parts = binding.split("+"); + const triggerKey = parts[parts.length - 1]; + const modifierKeys = parts.slice(0, -1).map((m) => MODIFIER_KEY_MAP[m] ?? m); + return { modifierKeys, triggerKey }; +} + +export function useWorkspaceShortcutModifiers() { + const overrides = useHotkeyOverridesStore((s) => s.overrides); + + return useMemo(() => { + // `overrides` isn't read directly here but `getBinding` reads the + // override store imperatively. Referencing the value forces the + // memo to recompute when the user changes hotkey bindings. + void overrides; + + const allModifierKeys = new Set(); + const shortcuts: WorkspaceShortcutInfo[] = []; + const comboToIndices = new Map(); + const shortcutLabels = new Map(); + + for (let i = 0; i < WORKSPACE_HOTKEY_IDS.length; i++) { + const rawBinding = getBinding(WORKSPACE_HOTKEY_IDS[i]); + if (!rawBinding) continue; + const chord = parseShortcutBinding(rawBinding).chord; + const { modifierKeys, triggerKey } = parseBinding(chord); + for (const key of modifierKeys) allModifierKeys.add(key); + shortcuts.push({ index: i, triggerKey, modifierKeys }); + const comboKey = [...modifierKeys].sort().join("+"); + const existing = comboToIndices.get(comboKey) ?? []; + existing.push(i); + comboToIndices.set(comboKey, existing); + shortcutLabels.set(i, formatHotkeyDisplay(chord, PLATFORM).text); + } + + return { allModifierKeys, shortcuts, comboToIndices, shortcutLabels }; + }, [overrides]); +} diff --git a/apps/desktop/src/renderer/hotkeys/hooks/useRecordHotkeys/useRecordHotkeys.test.ts b/apps/desktop/src/renderer/hotkeys/hooks/useRecordHotkeys/useRecordHotkeys.test.ts index 3329c66f6bb..4bcf6da0491 100644 --- a/apps/desktop/src/renderer/hotkeys/hooks/useRecordHotkeys/useRecordHotkeys.test.ts +++ b/apps/desktop/src/renderer/hotkeys/hooks/useRecordHotkeys/useRecordHotkeys.test.ts @@ -113,6 +113,17 @@ describe("captureHotkeyFromEvent — codeChord uses event.code, not event.key", ).toBe("f12"); }); + it("requires ctrl, meta, or alt (Mac) for non-F-keys", () => { + expect( + captureHotkeyFromEvent(ev({ code: "KeyA", key: "a", shiftKey: true })), + ).toBeNull(); + // On Mac (test runtime PLATFORM), alt is a valid app modifier + expect( + captureHotkeyFromEvent(ev({ code: "KeyA", key: "a", altKey: true })) + ?.codeChord, + ).toBe("alt+a"); + }); + it("returns null when event.code is undefined", () => { expect( captureHotkeyFromEvent(ev({ code: undefined, ctrlKey: true })), diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx index ecbd1a2d622..2e512446107 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx @@ -25,7 +25,9 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { HiOutlineCog6Tooth } from "react-icons/hi2"; import { V2AvailableBanner } from "renderer/components/V2AvailableBanner"; +import { useModifierKeyListener } from "renderer/hooks/useModifierKeyListener"; import { useHotkeyDisplay } from "renderer/hotkeys"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { DashboardSidebarHeader } from "./components/DashboardSidebarHeader"; @@ -96,6 +98,10 @@ const SortableProjectWrapper = memo(function SortableProjectWrapper({ export function DashboardSidebar({ isCollapsed = false, }: DashboardSidebarProps) { + const { data: showNumbersOnModifier = false } = + electronTrpc.settings.getShowWorkspaceNumbersOnModifier.useQuery(); + useModifierKeyListener(showNumbersOnModifier); + const { groups, refreshWorkspacePullRequest, toggleProjectCollapsed } = useDashboardSidebarData(); const { reorderProjects } = useDashboardSidebarState(); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx index 6d38dc6eb9b..2195ee49c80 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx @@ -11,6 +11,8 @@ import type { DiffStats } from "renderer/hooks/host-service/useDiffStats"; import { HotkeyLabel } from "renderer/hotkeys"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { RenameInput } from "renderer/screens/main/components/WorkspaceSidebar/RenameInput"; +import { WorkspaceShortcutBadge } from "renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceShortcutBadge"; +import { useModifierKeyStateStore } from "renderer/stores/modifier-key-state"; import type { ActivePaneStatus } from "shared/tabs-types"; import type { DashboardSidebarWorkspace, @@ -84,6 +86,8 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef< pendingTransaction, } = workspace; const isPending = pendingTransaction?.type === "insert"; + const isModifierHeld = useModifierKeyStateStore((s) => s.isModifierHeld); + const showShortcutBadge = isModifierHeld && !!shortcutLabel; const showsStandaloneActiveStripe = accentColor == null; const localRef = useRef(null); const openUrl = electronTrpc.external.openUrl.useMutation(); @@ -250,91 +254,98 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef< )} -
- {creationStatusText ? ( - - {creationStatusText} - - ) : ( - diffStats && - (diffStats.additions > 0 || diffStats.deletions > 0) && ( - - ) - )} - {!isPending && ( -
- {shortcutLabel && ( - - {shortcutLabel} - - )} - {isMainWorkspace ? ( - - - - - - - - - ) : ( - - - + + + + + + ) : ( + + + - - - - - - )} -
- )} -
+ onCloseWorkspaceClick(); + }} + onKeyDown={(event) => { + if ( + event.key === "Enter" || + event.key === " " || + event.key === "Spacebar" + ) { + event.stopPropagation(); + } + }} + className="flex items-center justify-center text-muted-foreground hover:text-foreground" + aria-label="Close workspace" + > + + + + + + + + )} + + )} + + )} ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx index 8fb0050fc90..e1d84ba76fa 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx @@ -36,6 +36,10 @@ export function BehaviorSettings({ visibleItems }: BehaviorSettingsProps) { SETTING_ITEM_ID.BEHAVIOR_OPEN_LINKS_IN_APP, visibleItems, ); + const showWorkspaceNumbers = isItemVisible( + SETTING_ITEM_ID.BEHAVIOR_WORKSPACE_NUMBERS, + visibleItems, + ); const utils = electronTrpc.useUtils(); @@ -125,6 +129,37 @@ export function BehaviorSettings({ visibleItems }: BehaviorSettingsProps) { }, ); + const { data: showWorkspaceNumbersOnModifier, isLoading: isNumbersLoading } = + electronTrpc.settings.getShowWorkspaceNumbersOnModifier.useQuery(); + const setShowWorkspaceNumbersOnModifier = + electronTrpc.settings.setShowWorkspaceNumbersOnModifier.useMutation({ + onMutate: async ({ enabled }) => { + await utils.settings.getShowWorkspaceNumbersOnModifier.cancel(); + const previous = + utils.settings.getShowWorkspaceNumbersOnModifier.getData(); + utils.settings.getShowWorkspaceNumbersOnModifier.setData( + undefined, + enabled, + ); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getShowWorkspaceNumbersOnModifier.setData( + undefined, + context.previous, + ); + } + }, + onSettled: () => { + utils.settings.getShowWorkspaceNumbersOnModifier.invalidate(); + }, + }); + + const handleNumbersToggle = (enabled: boolean) => { + setShowWorkspaceNumbersOnModifier.mutate({ enabled }); + }; + return (
@@ -227,6 +262,30 @@ export function BehaviorSettings({ visibleItems }: BehaviorSettingsProps) { />
)} + {showWorkspaceNumbers && ( +
+
+ +

+ Display numbered badges on workspaces when holding the shortcut + modifier key +

+
+ +
+ )}
); 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 d7f25fab62f..a50bd5cb657 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 @@ -28,6 +28,7 @@ export const SETTING_ITEM_ID = { BEHAVIOR_FILE_OPEN_MODE: "behavior-file-open-mode", BEHAVIOR_RESOURCE_MONITOR: "behavior-resource-monitor", BEHAVIOR_OPEN_LINKS_IN_APP: "behavior-open-links-in-app", + BEHAVIOR_WORKSPACE_NUMBERS: "behavior-workspace-numbers", GIT_BRANCH_PREFIX: "git-branch-prefix", GIT_DELETE_LOCAL_BRANCH: "git-delete-local-branch", @@ -137,6 +138,7 @@ export const SETTING_ITEM_VARIANT: Record = { [SETTING_ITEM_ID.BEHAVIOR_FILE_OPEN_MODE]: "v1", [SETTING_ITEM_ID.BEHAVIOR_RESOURCE_MONITOR]: "shared", [SETTING_ITEM_ID.BEHAVIOR_OPEN_LINKS_IN_APP]: "v1", + [SETTING_ITEM_ID.BEHAVIOR_WORKSPACE_NUMBERS]: "shared", // Branch prefix exists in both UIs — v1 `GitSettings`, v2 `V2GitSettings`. [SETTING_ITEM_ID.GIT_BRANCH_PREFIX]: "shared", @@ -623,6 +625,21 @@ export const SETTINGS_ITEMS: SettingsItem[] = [ "url", ], }, + { + id: SETTING_ITEM_ID.BEHAVIOR_WORKSPACE_NUMBERS, + section: "behavior", + title: "Show workspace numbers on modifier hold", + description: + "Display numbered badges on workspaces when holding the shortcut modifier key", + keywords: [ + "workspace", + "number", + "badge", + "shortcut", + "modifier", + "keyboard", + ], + }, { id: SETTING_ITEM_ID.AGENTS_ENABLED, section: "agents", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx index 94aabb808a1..372c8fa4e60 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx @@ -31,6 +31,7 @@ import { navigateToWorkspace } from "renderer/routes/_authenticated/_dashboard/u import { useProjectRename } from "renderer/screens/main/hooks/useProjectRename"; import { STROKE_WIDTH } from "../constants"; import { RenameInput } from "../RenameInput"; +import { WorkspaceShortcutBadge } from "../WorkspaceListItem/WorkspaceShortcutBadge"; import { CloseProjectDialog } from "./CloseProjectDialog"; import { ProjectThumbnail } from "./ProjectThumbnail"; @@ -49,6 +50,8 @@ interface ProjectHeaderProps { onToggleCollapse: () => void; workspaceCount: number; onNewWorkspace: () => void; + /** Shortcut labels to display as badges when project is collapsed and modifier is held */ + shortcutLabels?: string[]; } export function ProjectHeader({ @@ -64,6 +67,7 @@ export function ProjectHeader({ onToggleCollapse, workspaceCount, onNewWorkspace, + shortcutLabels = [], }: ProjectHeaderProps) { const utils = electronTrpc.useUtils(); const navigate = useNavigate(); @@ -206,6 +210,13 @@ export function ProjectHeader({ {workspaceCount} workspace{workspaceCount !== 1 ? "s" : ""} + {shortcutLabels.length > 0 && ( +
+ {shortcutLabels.map((label) => ( + + ))} +
+ )} @@ -303,6 +314,13 @@ export function ProjectHeader({ ({workspaceCount}) + {shortcutLabels.length > 0 && ( +
+ {shortcutLabels.map((label) => ( + + ))} +
+ )} )} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx index 2e51d011de0..8dc94cac08c 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx @@ -3,13 +3,16 @@ import { cn } from "@superset/ui/utils"; import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useMemo, useRef } from "react"; import { useDrag, useDrop } from "react-dnd"; +import { useWorkspaceShortcutModifiers } from "renderer/hooks/useWorkspaceShortcutModifiers"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useReorderProjects } from "renderer/react-query/projects"; import { useWorkspaceSidebarStore } from "renderer/stores"; +import { useModifierKeyStateStore } from "renderer/stores/modifier-key-state"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; import { useSectionDropZone } from "../hooks"; import type { SidebarSection, SidebarWorkspace } from "../types"; import { WorkspaceListItem } from "../WorkspaceListItem"; +import { MAX_KEYBOARD_SHORTCUT_INDEX } from "../WorkspaceListItem/constants"; import { WorkspaceSection } from "../WorkspaceSection"; import { ProjectHeader } from "./ProjectHeader"; @@ -125,6 +128,31 @@ export function ProjectSection({ }; }, [shortcutBaseIndex, sections, topLevelItems, workspaces]); + const isModifierHeld = useModifierKeyStateStore((s) => s.isModifierHeld); + const { shortcutLabels } = useWorkspaceShortcutModifiers(); + + // Collect shortcut labels for this project's workspaces (for collapsed header badges) + const projectShortcutLabels = useMemo(() => { + const labels: string[] = []; + for (const child of topLevelChildren) { + if (child.kind === "workspace") { + if (child.shortcutIndex < MAX_KEYBOARD_SHORTCUT_INDEX) { + const label = shortcutLabels.get(child.shortcutIndex); + if (label) labels.push(label); + } + } else { + for (let i = 0; i < child.section.workspaces.length; i++) { + const idx = child.shortcutBaseIndex + i; + if (idx < MAX_KEYBOARD_SHORTCUT_INDEX) { + const label = shortcutLabels.get(idx); + if (label) labels.push(label); + } + } + } + } + return labels; + }, [topLevelChildren, shortcutLabels]); + const topUngroupedDropZone = useSectionDropZone({ canAccept: (item) => item.sectionId !== null && item.projectId === projectId, @@ -247,6 +275,9 @@ export function ProjectSection({ onToggleCollapse={() => toggleProjectCollapsed(projectId)} workspaceCount={totalWorkspaceCount} onNewWorkspace={handleNewWorkspace} + shortcutLabels={ + isCollapsed && isModifierHeld ? projectShortcutLabels : [] + } /> @@ -349,6 +380,9 @@ export function ProjectSection({ onToggleCollapse={() => toggleProjectCollapsed(projectId)} workspaceCount={totalWorkspaceCount} onNewWorkspace={handleNewWorkspace} + shortcutLabels={ + isCollapsed && isModifierHeld ? projectShortcutLabels : [] + } /> diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx index 171f55ea6c9..378b7ff3a40 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx @@ -6,6 +6,7 @@ import { useMatchRoute, useNavigate } from "@tanstack/react-router"; import { useEffect, useMemo, useRef } from "react"; import { HiMiniXMark } from "react-icons/hi2"; import { useCopyToClipboard } from "renderer/hooks/useCopyToClipboard"; +import { useWorkspaceShortcutModifiers } from "renderer/hooks/useWorkspaceShortcutModifiers"; import { HotkeyLabel } from "renderer/hotkeys"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useHoverGitHubStatus } from "renderer/lib/githubQueryPolicy"; @@ -16,6 +17,7 @@ import { useBranchSyncInvalidation } from "renderer/screens/main/hooks/useBranch import { useGitChangesStatus } from "renderer/screens/main/hooks/useGitChangesStatus"; import { useWorkspaceRename } from "renderer/screens/main/hooks/useWorkspaceRename"; import { useActiveDragItemStore } from "renderer/stores/active-drag-item"; +import { useModifierKeyStateStore } from "renderer/stores/modifier-key-state"; import { useTabsStore } from "renderer/stores/tabs/store"; import { extractPaneIdsFromLayout } from "renderer/stores/tabs/utils"; import { useWorkspaceSelectionStore } from "renderer/stores/workspace-selection"; @@ -31,6 +33,7 @@ import { WorkspaceAheadBehind } from "./WorkspaceAheadBehind"; import { WorkspaceContextMenu } from "./WorkspaceContextMenu"; import { WorkspaceDiffStats } from "./WorkspaceDiffStats"; import { WorkspaceIcon } from "./WorkspaceIcon"; +import { WorkspaceShortcutBadge } from "./WorkspaceShortcutBadge"; import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge"; interface WorkspaceListItemProps { @@ -108,6 +111,15 @@ export function WorkspaceListItem({ s.activeDragItem?.selectedIds?.includes(id) && s.activeDragItem.id !== id, ); + const isModifierHeld = useModifierKeyStateStore((s) => s.isModifierHeld); + const { shortcutLabels } = useWorkspaceShortcutModifiers(); + const shortcutLabel = + shortcutIndex !== undefined ? shortcutLabels.get(shortcutIndex) : undefined; + const showShortcutBadge = + isModifierHeld && + shortcutIndex !== undefined && + shortcutIndex < MAX_KEYBOARD_SHORTCUT_INDEX; + const isActive = !!matchRoute({ to: "/workspace/$workspaceId", params: { workspaceId: id }, @@ -395,46 +407,50 @@ export function WorkspaceListItem({ /> )} -
- {diffStats && ( - - )} -
- {shortcutIndex !== undefined && - shortcutIndex < MAX_KEYBOARD_SHORTCUT_INDEX && ( - - ⌘{shortcutIndex + 1} - - )} - {!isBranchWorkspace && ( - - - - - - - - + {showShortcutBadge && shortcutLabel ? ( + + ) : ( +
+ {diffStats && ( + )} +
+ {shortcutLabel && ( + + )} + {!isBranchWorkspace && ( + + + + + + + + + )} +
-
+ )}
{(showBranchSubtitle || pr) && ( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceShortcutBadge.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceShortcutBadge.tsx new file mode 100644 index 00000000000..34346e5457a --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceShortcutBadge.tsx @@ -0,0 +1,23 @@ +import { cn } from "@superset/ui/utils"; + +interface WorkspaceShortcutBadgeProps { + /** The formatted shortcut label to display (e.g., "⌘1", "Ctrl+Shift+1") */ + label: string; + className?: string; +} + +export function WorkspaceShortcutBadge({ + label, + className, +}: WorkspaceShortcutBadgeProps) { + return ( + + {label} + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/index.ts index ed2db7c33f1..e84cf77cdf6 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/index.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/index.ts @@ -2,4 +2,5 @@ export type { DragItem } from "../types"; export { WORKSPACE_DND_TYPE } from "./constants"; export { WorkspaceDiffStats } from "./WorkspaceDiffStats"; export { WorkspaceListItem } from "./WorkspaceListItem"; +export { WorkspaceShortcutBadge } from "./WorkspaceShortcutBadge"; export { WorkspaceStatusBadge } from "./WorkspaceStatusBadge"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx index e1415e2e854..851d454d867 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx @@ -1,6 +1,8 @@ import { useCallback, useEffect, useMemo } from "react"; import { V2AvailableBanner } from "renderer/components/V2AvailableBanner"; +import { useModifierKeyListener } from "renderer/hooks/useModifierKeyListener"; import { useWorkspaceShortcuts } from "renderer/hooks/useWorkspaceShortcuts"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useWorkspaceSelectionStore } from "renderer/stores/workspace-selection"; import { MultiDragPreview } from "./MultiDragPreview"; import { PortsList } from "./PortsList"; @@ -24,6 +26,10 @@ export function WorkspaceSidebar({ const { groups } = useWorkspaceShortcuts(); const clearSelection = useWorkspaceSelectionStore((s) => s.clearSelection); + const { data: showNumbersOnModifier = false } = + electronTrpc.settings.getShowWorkspaceNumbersOnModifier.useQuery(); + useModifierKeyListener(showNumbersOnModifier); + const projectShortcutIndices = useMemo( () => groups.reduce<{ indices: number[]; cumulative: number }>( diff --git a/apps/desktop/src/renderer/stores/modifier-key-state.test.ts b/apps/desktop/src/renderer/stores/modifier-key-state.test.ts new file mode 100644 index 00000000000..180a43fcbb8 --- /dev/null +++ b/apps/desktop/src/renderer/stores/modifier-key-state.test.ts @@ -0,0 +1,56 @@ +import { beforeEach, describe, expect, test } from "bun:test"; +import { useModifierKeyStateStore } from "./modifier-key-state"; + +describe("useModifierKeyStateStore", () => { + beforeEach(() => { + useModifierKeyStateStore.getState().clearAll(); + }); + + test("initial state has empty heldKeys and isModifierHeld false", () => { + const state = useModifierKeyStateStore.getState(); + expect(state.heldKeys).toEqual(new Set()); + expect(state.isModifierHeld).toBe(false); + }); + + test("pressKey adds a key to heldKeys", () => { + useModifierKeyStateStore.getState().pressKey("Meta"); + expect(useModifierKeyStateStore.getState().heldKeys).toEqual( + new Set(["Meta"]), + ); + }); + + test("pressKey is a no-op if key already held", () => { + useModifierKeyStateStore.getState().pressKey("Meta"); + useModifierKeyStateStore.getState().pressKey("Meta"); + expect(useModifierKeyStateStore.getState().heldKeys).toEqual( + new Set(["Meta"]), + ); + expect(useModifierKeyStateStore.getState().heldKeys.size).toBe(1); + }); + + test("releaseKey removes a key from heldKeys", () => { + useModifierKeyStateStore.getState().pressKey("Meta"); + useModifierKeyStateStore.getState().pressKey("Control"); + useModifierKeyStateStore.getState().releaseKey("Meta"); + expect(useModifierKeyStateStore.getState().heldKeys).toEqual( + new Set(["Control"]), + ); + }); + + test("releaseKey is a no-op if key not held", () => { + useModifierKeyStateStore.getState().pressKey("Meta"); + useModifierKeyStateStore.getState().releaseKey("Control"); + expect(useModifierKeyStateStore.getState().heldKeys).toEqual( + new Set(["Meta"]), + ); + }); + + test("clearAll resets heldKeys to empty and isModifierHeld to false", () => { + useModifierKeyStateStore.getState().pressKey("Meta"); + useModifierKeyStateStore.getState().pressKey("Control"); + useModifierKeyStateStore.setState({ isModifierHeld: true }); + useModifierKeyStateStore.getState().clearAll(); + expect(useModifierKeyStateStore.getState().heldKeys).toEqual(new Set()); + expect(useModifierKeyStateStore.getState().isModifierHeld).toBe(false); + }); +}); diff --git a/apps/desktop/src/renderer/stores/modifier-key-state.ts b/apps/desktop/src/renderer/stores/modifier-key-state.ts new file mode 100644 index 00000000000..6ec1a4f3c9c --- /dev/null +++ b/apps/desktop/src/renderer/stores/modifier-key-state.ts @@ -0,0 +1,29 @@ +import { create } from "zustand"; + +interface ModifierKeyState { + heldKeys: Set; + isModifierHeld: boolean; + pressKey: (key: string) => void; + releaseKey: (key: string) => void; + clearAll: () => void; +} + +export const useModifierKeyStateStore = create((set) => ({ + heldKeys: new Set(), + isModifierHeld: false, + pressKey: (key) => + set((state) => { + if (state.heldKeys.has(key)) return state; + const next = new Set(state.heldKeys); + next.add(key); + return { heldKeys: next }; + }), + releaseKey: (key) => + set((state) => { + if (!state.heldKeys.has(key)) return state; + const next = new Set(state.heldKeys); + next.delete(key); + return { heldKeys: next }; + }), + clearAll: () => set({ heldKeys: new Set(), isModifierHeld: false }), +})); diff --git a/packages/local-db/drizzle/0042_add_show_workspace_numbers_on_modifier.sql b/packages/local-db/drizzle/0042_add_show_workspace_numbers_on_modifier.sql new file mode 100644 index 00000000000..6c4fed87774 --- /dev/null +++ b/packages/local-db/drizzle/0042_add_show_workspace_numbers_on_modifier.sql @@ -0,0 +1 @@ +ALTER TABLE `settings` ADD `show_workspace_numbers_on_modifier` integer; \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0042_snapshot.json b/packages/local-db/drizzle/meta/0042_snapshot.json new file mode 100644 index 00000000000..fff68dce351 --- /dev/null +++ b/packages/local-db/drizzle/meta/0042_snapshot.json @@ -0,0 +1,1494 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "c1f2a3b4-d5e6-7890-abcd-ef1234567890", + "prevId": "3d1d0ce7-6731-4286-970a-304b4e0a1f6a", + "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": {} + }, + "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 + }, + "agent_preset_permissions_migrated_at": { + "name": "agent_preset_permissions_migrated_at", + "type": "integer", + "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 + }, + "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 + }, + "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 + }, + "expose_host_service_via_relay": { + "name": "expose_host_service_via_relay", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_workspace_numbers_on_modifier": { + "name": "show_workspace_numbers_on_modifier", + "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": {} + }, + "v1_migration_state": { + "name": "v1_migration_state", + "columns": { + "v1_id": { + "name": "v1_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "v2_id": { + "name": "v2_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "migrated_at": { + "name": "migrated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "v1_migration_state_v2_id_idx": { + "name": "v1_migration_state_v2_id_idx", + "columns": [ + "v2_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "v1_migration_state_organization_id_v1_id_kind_pk": { + "columns": [ + "organization_id", + "v1_id", + "kind" + ], + "name": "v1_migration_state_organization_id_v1_id_kind_pk" + } + }, + "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": {} + } +} diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index 1d0509d1b1d..2eaf47f732b 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -295,6 +295,13 @@ "when": 1776928440569, "tag": "0041_v1_migration_state", "breakpoints": true + }, + { + "idx": 42, + "version": "6", + "when": 1775757922654, + "tag": "0042_add_show_workspace_numbers_on_modifier", + "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 7009f0aa5dd..46bfdba244e 100644 --- a/packages/local-db/src/schema/schema.ts +++ b/packages/local-db/src/schema/schema.ts @@ -234,6 +234,10 @@ export const settings = sqliteTable("settings", { exposeHostServiceViaRelay: integer("expose_host_service_via_relay", { mode: "boolean", }), + showWorkspaceNumbersOnModifier: integer( + "show_workspace_numbers_on_modifier", + { mode: "boolean" }, + ), }); export type InsertSettings = typeof settings.$inferInsert;