diff --git a/docs/AGENTS.md b/docs/AGENTS.md index f59c2460aa..621021b115 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -116,9 +116,9 @@ Mobile app tests live in `mobile/src/**/*.test.ts` and use Bun's built-in test r ## Styling - Never use emoji characters as UI icons or status indicators; emoji rendering varies across platforms and fonts. -- Prefer SVG icons (usually from `lucide-react`) or shared icon components under `src/browser/components/icons/`. +- Prefer SVG icons (usually from `lucide-react`) or shared icon components under `src/browser/components/Icons/`. - For tool call headers, use `ToolIcon` from `src/browser/components/tools/shared/ToolPrimitives.tsx`. -- If a tool/agent provides an emoji string (e.g., `status_set` or `displayStatus`), render via `EmojiIcon` (`src/browser/components/icons/EmojiIcon.tsx`) instead of rendering the emoji. +- If a tool/agent provides an emoji string (e.g., `status_set` or `displayStatus`), render via `EmojiIcon` (`src/browser/components/Icons/EmojiIcon.tsx`) instead of rendering the emoji. - If a new emoji appears in tool output, extend `EmojiIcon` to map it to an SVG icon. - Colors defined in `src/browser/styles/globals.css` (`:root @theme` block). Reference via CSS variables (e.g., `var(--color-plan-mode)`), never hardcode hex values. diff --git a/src/browser/App.tsx b/src/browser/App.tsx index dba6636336..eef7880fdd 100644 --- a/src/browser/App.tsx +++ b/src/browser/App.tsx @@ -6,10 +6,10 @@ import "./styles/globals.css"; import { useWorkspaceContext, toWorkspaceSelection } from "./contexts/WorkspaceContext"; import { MUX_HELP_CHAT_WORKSPACE_ID } from "@/common/constants/muxChat"; import { useProjectContext } from "./contexts/ProjectContext"; -import type { WorkspaceSelection } from "./components/ProjectSidebar/ProjectSidebar"; +import type { WorkspaceSelection } from "./features/Project/ProjectSidebar/ProjectSidebar"; import { LeftSidebar } from "./components/LeftSidebar/LeftSidebar"; import { ProjectCreateModal } from "./components/ProjectCreateModal/ProjectCreateModal"; -import { AIView } from "./components/AIView/AIView"; +import { AIView } from "./features/Chat/AIView/AIView"; import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary"; import { usePersistedState, @@ -29,7 +29,7 @@ import { CommandRegistryProvider, useCommandRegistry } from "./contexts/CommandR import { useOpenTerminal } from "./hooks/useOpenTerminal"; import type { CommandAction } from "./contexts/CommandRegistryContext"; import { useTheme, type ThemeMode } from "./contexts/ThemeContext"; -import { CommandPalette } from "./components/CommandPalette/CommandPalette"; +import { CommandPalette } from "./features/CommandPalette/CommandPalette/CommandPalette"; import { buildCoreSources, type BuildSourcesParams } from "./utils/commands/sources"; import { THINKING_LEVELS, type ThinkingLevel } from "@/common/types/thinking"; @@ -81,7 +81,7 @@ import { FeatureFlagsProvider } from "./contexts/FeatureFlagsContext"; import { ExperimentsProvider } from "./contexts/ExperimentsContext"; import { ProviderOptionsProvider } from "./contexts/ProviderOptionsContext"; import { getWorkspaceSidebarKey } from "./utils/workspace"; -import { WindowsToolchainBanner } from "./components/WindowsToolchainBanner/WindowsToolchainBanner"; +import { WindowsToolchainBanner } from "./features/AppShell/WindowsToolchainBanner/WindowsToolchainBanner"; import { RosettaBanner } from "./components/RosettaBanner/RosettaBanner"; import { isDesktopMode } from "./hooks/useDesktopTitlebar"; import { cn } from "@/common/lib/utils"; diff --git a/src/browser/assets/file-icons/seti-icon-theme.json b/src/browser/assets/file-icons/seti-icon-theme.json index 2610c1a3a6..fe0e806c1c 100644 --- a/src/browser/assets/file-icons/seti-icon-theme.json +++ b/src/browser/assets/file-icons/seti-icon-theme.json @@ -3,7 +3,7 @@ "This file has been generated from data in https://github.com/jesseweed/seti-ui", "- icon definitions: https://github.com/jesseweed/seti-ui/blob/master/styles/_fonts/seti.less", "- icon colors: https://github.com/jesseweed/seti-ui/blob/master/styles/ui-variables.less", - "- file associations: https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less", + "- file associations: https://github.com/jesseweed/seti-ui/tree/master/styles/components/icons", "If you want to provide a fix or improvement, please create a pull request against the jesseweed/seti-ui repository.", "Once accepted there, we are happy to receive an update request." ], diff --git a/src/browser/components/AppLoader/AppLoader.auth.test.tsx b/src/browser/components/AppLoader/AppLoader.auth.test.tsx deleted file mode 100644 index 6e3bcc3d8e..0000000000 --- a/src/browser/components/AppLoader/AppLoader.auth.test.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import "../../../../tests/ui/dom"; - -import React from "react"; -import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; -import { cleanup, render } from "@testing-library/react"; -import { useTheme } from "../../contexts/ThemeContext"; -import { installDom } from "../../../../tests/ui/dom"; - -let cleanupDom: (() => void) | null = null; - -let apiStatus: "auth_required" | "connecting" | "error" = "auth_required"; -let apiError: string | null = "Authentication required"; - -// AppLoader imports App, which pulls in Lottie-based components. In happy-dom, -// lottie-web's canvas bootstrap can throw during module evaluation. -void mock.module("lottie-react", () => ({ - __esModule: true, - default: () =>
, -})); - -void mock.module("@/browser/contexts/API", () => ({ - APIProvider: (props: { children: React.ReactNode }) => props.children, - useAPI: () => { - if (apiStatus === "auth_required") { - return { - api: null, - status: "auth_required" as const, - error: apiError, - authenticate: () => undefined, - retry: () => undefined, - }; - } - - if (apiStatus === "error") { - return { - api: null, - status: "error" as const, - error: apiError ?? "Connection error", - authenticate: () => undefined, - retry: () => undefined, - }; - } - - return { - api: null, - status: "connecting" as const, - error: null, - authenticate: () => undefined, - retry: () => undefined, - }; - }, -})); - -void mock.module("@/browser/components/LoadingScreen/LoadingScreen", () => ({ - LoadingScreen: () => { - const { theme } = useTheme(); - return
{theme}
; - }, -})); - -void mock.module("@/browser/components/StartupConnectionError/StartupConnectionError", () => ({ - StartupConnectionError: (props: { error: string }) => ( -
{props.error}
- ), -})); - -void mock.module("@/browser/components/AuthTokenModal/AuthTokenModal", () => ({ - // Note: Module mocks leak between bun test files. - // Export all commonly-used symbols to avoid cross-test import errors. - AuthTokenModal: (props: { error?: string | null }) => ( -
{props.error ?? "no-error"}
- ), - getStoredAuthToken: () => null, - // eslint-disable-next-line @typescript-eslint/no-empty-function - setStoredAuthToken: () => {}, - // eslint-disable-next-line @typescript-eslint/no-empty-function - clearStoredAuthToken: () => {}, -})); - -import { AppLoader } from "../AppLoader/AppLoader"; - -describe("AppLoader", () => { - beforeEach(() => { - cleanupDom = installDom(); - }); - - afterEach(() => { - cleanup(); - cleanupDom?.(); - cleanupDom = null; - }); - - test("renders AuthTokenModal when API status is auth_required (before workspaces load)", () => { - apiStatus = "auth_required"; - apiError = "Authentication required"; - - const { getByTestId, queryByText } = render(); - - expect(queryByText("Loading Mux")).toBeNull(); - expect(getByTestId("AuthTokenModalMock").textContent).toContain("Authentication required"); - }); - - test("renders StartupConnectionError when API status is error (before workspaces load)", () => { - apiStatus = "error"; - apiError = "Connection error"; - - const { getByTestId, queryByTestId } = render(); - - expect(queryByTestId("LoadingScreenMock")).toBeNull(); - expect(queryByTestId("AuthTokenModalMock")).toBeNull(); - expect(getByTestId("StartupConnectionErrorMock").textContent).toContain("Connection error"); - }); - - test("wraps LoadingScreen in ThemeProvider", () => { - apiStatus = "connecting"; - apiError = null; - - const { getByTestId } = render(); - - // If ThemeProvider is missing, useTheme() will throw. - expect(getByTestId("LoadingScreenMock").textContent).toBeTruthy(); - }); -}); diff --git a/src/browser/features/Runtime/CoderControls.tsx b/src/browser/components/CoderControls/CoderControls.tsx similarity index 100% rename from src/browser/features/Runtime/CoderControls.tsx rename to src/browser/components/CoderControls/CoderControls.tsx diff --git a/src/browser/components/CopyButton/CopyButton.tsx b/src/browser/components/CopyButton/CopyButton.tsx index 5caa3563f4..2352a6d13b 100644 --- a/src/browser/components/CopyButton/CopyButton.tsx +++ b/src/browser/components/CopyButton/CopyButton.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { CopyIcon } from "@/browser/components/icons/CopyIcon/CopyIcon"; +import { CopyIcon } from "@/browser/components/Icons/CopyIcon/CopyIcon"; import { copyToClipboard } from "@/browser/utils/clipboard"; interface CopyButtonProps { diff --git a/src/browser/components/GatewayToggleButton/GatewayToggleButton.tsx b/src/browser/components/GatewayToggleButton/GatewayToggleButton.tsx index 7976d6180d..36432bf750 100644 --- a/src/browser/components/GatewayToggleButton/GatewayToggleButton.tsx +++ b/src/browser/components/GatewayToggleButton/GatewayToggleButton.tsx @@ -1,5 +1,5 @@ import { cn } from "@/common/lib/utils"; -import { GatewayIcon } from "../icons/GatewayIcon/GatewayIcon"; +import { GatewayIcon } from "../Icons/GatewayIcon/GatewayIcon"; import { Tooltip, TooltipContent, TooltipTrigger } from "../Tooltip/Tooltip"; interface GatewayToggleButtonProps { diff --git a/src/browser/components/icons/ArchiveIcon/ArchiveIcon.tsx b/src/browser/components/Icons/ArchiveIcon/ArchiveIcon.tsx similarity index 100% rename from src/browser/components/icons/ArchiveIcon/ArchiveIcon.tsx rename to src/browser/components/Icons/ArchiveIcon/ArchiveIcon.tsx diff --git a/src/browser/components/icons/CopyIcon/CopyIcon.tsx b/src/browser/components/Icons/CopyIcon/CopyIcon.tsx similarity index 100% rename from src/browser/components/icons/CopyIcon/CopyIcon.tsx rename to src/browser/components/Icons/CopyIcon/CopyIcon.tsx diff --git a/src/browser/components/icons/EmojiIcon/EmojiIcon.tsx b/src/browser/components/Icons/EmojiIcon/EmojiIcon.tsx similarity index 100% rename from src/browser/components/icons/EmojiIcon/EmojiIcon.tsx rename to src/browser/components/Icons/EmojiIcon/EmojiIcon.tsx diff --git a/src/browser/components/icons/GatewayIcon/GatewayIcon.tsx b/src/browser/components/Icons/GatewayIcon/GatewayIcon.tsx similarity index 100% rename from src/browser/components/icons/GatewayIcon/GatewayIcon.tsx rename to src/browser/components/Icons/GatewayIcon/GatewayIcon.tsx diff --git a/src/browser/components/icons/RuntimeIcons/RuntimeIcons.tsx b/src/browser/components/Icons/RuntimeIcons/RuntimeIcons.tsx similarity index 100% rename from src/browser/components/icons/RuntimeIcons/RuntimeIcons.tsx rename to src/browser/components/Icons/RuntimeIcons/RuntimeIcons.tsx diff --git a/src/browser/components/icons/SkillIcon/SkillIcon.tsx b/src/browser/components/Icons/SkillIcon/SkillIcon.tsx similarity index 100% rename from src/browser/components/icons/SkillIcon/SkillIcon.tsx rename to src/browser/components/Icons/SkillIcon/SkillIcon.tsx diff --git a/src/browser/components/icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon.tsx b/src/browser/components/Icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon.tsx similarity index 100% rename from src/browser/components/icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon.tsx rename to src/browser/components/Icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon.tsx diff --git a/src/browser/components/LeftSidebar/LeftSidebar.tsx b/src/browser/components/LeftSidebar/LeftSidebar.tsx index 7af348f288..99293006f9 100644 --- a/src/browser/components/LeftSidebar/LeftSidebar.tsx +++ b/src/browser/components/LeftSidebar/LeftSidebar.tsx @@ -1,7 +1,7 @@ import React from "react"; import { cn } from "@/common/lib/utils"; import type { FrontendWorkspaceMetadata } from "@/common/types/workspace"; -import ProjectSidebar from "../ProjectSidebar/ProjectSidebar"; +import ProjectSidebar from "@/browser/features/Project/ProjectSidebar/ProjectSidebar"; import { TitleBar } from "../TitleBar/TitleBar"; import { isDesktopMode } from "@/browser/hooks/useDesktopTitlebar"; diff --git a/src/browser/features/PowerMode/PowerModeOverlay.tsx b/src/browser/components/PowerModeOverlay/PowerModeOverlay.tsx similarity index 100% rename from src/browser/features/PowerMode/PowerModeOverlay.tsx rename to src/browser/components/PowerModeOverlay/PowerModeOverlay.tsx diff --git a/src/browser/components/ProjectCreateModal/ProjectCreateModal.tsx b/src/browser/components/ProjectCreateModal/ProjectCreateModal.tsx index 123b1a0d8a..d64e58ae43 100644 --- a/src/browser/components/ProjectCreateModal/ProjectCreateModal.tsx +++ b/src/browser/components/ProjectCreateModal/ProjectCreateModal.tsx @@ -8,7 +8,7 @@ import { DialogDescription, DialogFooter, } from "@/browser/components/Dialog/Dialog"; -import { DirectoryPickerModal } from "../DirectoryPickerModal/DirectoryPickerModal"; +import { DirectoryPickerModal } from "@/browser/features/Project/DirectoryPickerModal/DirectoryPickerModal"; import { Button } from "@/browser/components/Button/Button"; import { ToggleGroup, diff --git a/src/browser/components/ProjectPage/ProjectPage.tsx b/src/browser/components/ProjectPage/ProjectPage.tsx index 2935ed7be3..3549b1733f 100644 --- a/src/browser/components/ProjectPage/ProjectPage.tsx +++ b/src/browser/components/ProjectPage/ProjectPage.tsx @@ -7,10 +7,10 @@ import { ThinkingProvider } from "@/browser/contexts/ThinkingContext"; import { ChatInput } from "@/browser/features/ChatInput/index"; import type { ChatInputAPI, WorkspaceCreatedOptions } from "@/browser/features/ChatInput/types"; import { ProjectMCPOverview } from "../ProjectMCPOverview/ProjectMCPOverview"; -import { ArchivedWorkspaces } from "../ArchivedWorkspaces/ArchivedWorkspaces"; +import { ArchivedWorkspaces } from "@/browser/features/Workspace/ArchivedWorkspaces/ArchivedWorkspaces"; import { useAPI } from "@/browser/contexts/API"; import { isWorkspaceArchived } from "@/common/utils/archive"; -import { GitInitBanner } from "../GitInitBanner/GitInitBanner"; +import { GitInitBanner } from "@/browser/features/Project/GitInitBanner/GitInitBanner"; import { ConfiguredProvidersBar } from "../ConfiguredProvidersBar/ConfiguredProvidersBar"; import { ConfigureProvidersPrompt } from "../ConfigureProvidersPrompt/ConfigureProvidersPrompt"; import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig"; diff --git a/src/browser/components/ProviderIcon/ProviderIcon.tsx b/src/browser/components/ProviderIcon/ProviderIcon.tsx index 39638f5292..80c7941625 100644 --- a/src/browser/components/ProviderIcon/ProviderIcon.tsx +++ b/src/browser/components/ProviderIcon/ProviderIcon.tsx @@ -8,7 +8,7 @@ import OllamaIcon from "@/browser/assets/icons/ollama.svg?react"; import DeepSeekIcon from "@/browser/assets/icons/deepseek.svg?react"; import AWSIcon from "@/browser/assets/icons/aws.svg?react"; import GitHubIcon from "@/browser/assets/icons/github.svg?react"; -import { GatewayIcon } from "@/browser/components/icons/GatewayIcon/GatewayIcon"; +import { GatewayIcon } from "@/browser/components/Icons/GatewayIcon/GatewayIcon"; import { PROVIDER_DEFINITIONS, PROVIDER_DISPLAY_NAMES, diff --git a/src/browser/components/ShareMessagePopover/ShareMessagePopover.tsx b/src/browser/components/ShareMessagePopover/ShareMessagePopover.tsx index 317c29785e..3db326784e 100644 --- a/src/browser/components/ShareMessagePopover/ShareMessagePopover.tsx +++ b/src/browser/components/ShareMessagePopover/ShareMessagePopover.tsx @@ -10,7 +10,7 @@ import { import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; import { Button } from "@/browser/components/Button/Button"; import { Check, ExternalLink, Link2, Loader2, Trash2 } from "lucide-react"; -import { CopyIcon } from "@/browser/components/icons/CopyIcon/CopyIcon"; +import { CopyIcon } from "@/browser/components/Icons/CopyIcon/CopyIcon"; import { copyToClipboard } from "@/browser/utils/clipboard"; import { diff --git a/src/browser/components/ShareTranscriptDialog/ShareTranscriptDialog.tsx b/src/browser/components/ShareTranscriptDialog/ShareTranscriptDialog.tsx index f8bd883e9d..54384aa7aa 100644 --- a/src/browser/components/ShareTranscriptDialog/ShareTranscriptDialog.tsx +++ b/src/browser/components/ShareTranscriptDialog/ShareTranscriptDialog.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { Check, ExternalLink, Loader2, Trash2 } from "lucide-react"; -import { CopyIcon } from "@/browser/components/icons/CopyIcon/CopyIcon"; +import { CopyIcon } from "@/browser/components/Icons/CopyIcon/CopyIcon"; import { Button } from "@/browser/components/Button/Button"; import { Checkbox } from "@/browser/components/Checkbox/Checkbox"; import { diff --git a/src/browser/features/AIElements/Shimmer.tsx b/src/browser/components/Shimmer/Shimmer.tsx similarity index 100% rename from src/browser/features/AIElements/Shimmer.tsx rename to src/browser/components/Shimmer/Shimmer.tsx diff --git a/src/browser/components/SkillIndicator/SkillIndicator.tsx b/src/browser/components/SkillIndicator/SkillIndicator.tsx index 2855b08e01..32605519ef 100644 --- a/src/browser/components/SkillIndicator/SkillIndicator.tsx +++ b/src/browser/components/SkillIndicator/SkillIndicator.tsx @@ -1,7 +1,7 @@ import React from "react"; import { AlertTriangle, Check, EyeOff, XCircle } from "lucide-react"; import { cn } from "@/common/lib/utils"; -import { SkillIcon } from "@/browser/components/icons/SkillIcon/SkillIcon"; +import { SkillIcon } from "@/browser/components/Icons/SkillIcon/SkillIcon"; import { HoverClickPopover } from "@/browser/components/HoverClickPopover/HoverClickPopover"; import type { LoadedSkill, diff --git a/src/browser/components/TitleBar/TitleBar.tsx b/src/browser/components/TitleBar/TitleBar.tsx index 518a264fbc..79c099403e 100644 --- a/src/browser/components/TitleBar/TitleBar.tsx +++ b/src/browser/components/TitleBar/TitleBar.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { cn } from "@/common/lib/utils"; import { VERSION } from "@/version"; import { SettingsButton } from "../SettingsButton/SettingsButton"; -import { GatewayIcon } from "../icons/GatewayIcon/GatewayIcon"; +import { GatewayIcon } from "../Icons/GatewayIcon/GatewayIcon"; import { Button } from "../Button/Button"; import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; import type { UpdateStatus } from "@/common/orpc/types"; diff --git a/src/browser/components/WorkspaceActionsMenuContent/WorkspaceActionsMenuContent.tsx b/src/browser/components/WorkspaceActionsMenuContent/WorkspaceActionsMenuContent.tsx index 7b318c309c..8a1ad87651 100644 --- a/src/browser/components/WorkspaceActionsMenuContent/WorkspaceActionsMenuContent.tsx +++ b/src/browser/components/WorkspaceActionsMenuContent/WorkspaceActionsMenuContent.tsx @@ -1,5 +1,5 @@ import { formatKeybind, KEYBINDS } from "@/browser/utils/ui/keybinds"; -import { ArchiveIcon } from "../icons/ArchiveIcon/ArchiveIcon"; +import { ArchiveIcon } from "../Icons/ArchiveIcon/ArchiveIcon"; import { GitBranch, Link2, Maximize2, Pencil, Server } from "lucide-react"; import React from "react"; diff --git a/src/browser/contexts/PowerModeContext.test.tsx b/src/browser/contexts/PowerModeContext.test.tsx index 5537693cf0..8ea45c61bf 100644 --- a/src/browser/contexts/PowerModeContext.test.tsx +++ b/src/browser/contexts/PowerModeContext.test.tsx @@ -6,7 +6,7 @@ import { GlobalWindow } from "happy-dom"; import { POWER_MODE_ENABLED_KEY } from "@/common/constants/storage"; import { PowerModeEngine } from "@/browser/utils/powerMode/PowerModeEngine"; -void mock.module("@/browser/features/PowerMode/PowerModeOverlay", () => ({ +void mock.module("@/browser/components/PowerModeOverlay/PowerModeOverlay", () => ({ // Overlay rendering is unrelated to caret alignment; keep tests focused on context behavior. PowerModeOverlay: () => null, })); diff --git a/src/browser/contexts/PowerModeContext.tsx b/src/browser/contexts/PowerModeContext.tsx index 741bc391a8..f15e17a037 100644 --- a/src/browser/contexts/PowerModeContext.tsx +++ b/src/browser/contexts/PowerModeContext.tsx @@ -16,7 +16,7 @@ import { PowerModeEngine, type PowerModeBurstKind, } from "@/browser/utils/powerMode/PowerModeEngine"; -import { PowerModeOverlay } from "@/browser/features/PowerMode/PowerModeOverlay"; +import { PowerModeOverlay } from "@/browser/components/PowerModeOverlay/PowerModeOverlay"; interface PowerModeContextValue { enabled: boolean; diff --git a/src/browser/contexts/RouterContext.tsx b/src/browser/contexts/RouterContext.tsx index 6c202c19be..14530bda08 100644 --- a/src/browser/contexts/RouterContext.tsx +++ b/src/browser/contexts/RouterContext.tsx @@ -12,7 +12,7 @@ import { readPersistedState } from "@/browser/hooks/usePersistedState"; import { MUX_HELP_CHAT_WORKSPACE_ID } from "@/common/constants/muxChat"; import { SELECTED_WORKSPACE_KEY } from "@/common/constants/storage"; import { getProjectRouteId } from "@/common/utils/projectRouteId"; -import type { WorkspaceSelection } from "@/browser/components/ProjectSidebar/ProjectSidebar"; +import type { WorkspaceSelection } from "@/browser/features/Project/ProjectSidebar/ProjectSidebar"; export interface RouterContext { navigateToWorkspace: (workspaceId: string) => void; diff --git a/src/browser/contexts/WorkspaceContext.tsx b/src/browser/contexts/WorkspaceContext.tsx index 54e3ad3133..47f0896f7d 100644 --- a/src/browser/contexts/WorkspaceContext.tsx +++ b/src/browser/contexts/WorkspaceContext.tsx @@ -12,7 +12,7 @@ import { } from "react"; import type { FrontendWorkspaceMetadata } from "@/common/types/workspace"; import type { ThinkingLevel } from "@/common/types/thinking"; -import type { WorkspaceSelection } from "@/browser/components/ProjectSidebar/ProjectSidebar"; +import type { WorkspaceSelection } from "@/browser/features/Project/ProjectSidebar/ProjectSidebar"; import type { RuntimeConfig } from "@/common/types/runtime"; import type { MuxDeepLinkPayload } from "@/common/types/deepLink"; import { MUX_HELP_CHAT_WORKSPACE_ID } from "@/common/constants/muxChat"; diff --git a/src/browser/features/AppShell/AppLoader/AppLoader.auth.test.tsx b/src/browser/features/AppShell/AppLoader/AppLoader.auth.test.tsx new file mode 100644 index 0000000000..1e3f033e4f --- /dev/null +++ b/src/browser/features/AppShell/AppLoader/AppLoader.auth.test.tsx @@ -0,0 +1,380 @@ +import "../../../../../tests/ui/dom"; + +import React from "react"; +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; +import { cleanup, render } from "@testing-library/react"; +import { useTheme } from "@/browser/contexts/ThemeContext"; +import { installDom } from "../../../../../tests/ui/dom"; + +// AppLoader transitively imports react-dnd, and @react-dnd/asap touches +// document APIs at module evaluation time. +// Install a full happy-dom instance before importing AppLoader. +installDom(); + +let cleanupDom: (() => void) | null = null; + +let apiStatus: "auth_required" | "connecting" | "error" = "auth_required"; +let apiError: string | null = "Authentication required"; + +// AppLoader imports App, which pulls in Lottie-based components. In happy-dom, +// lottie-web's canvas bootstrap can throw during module evaluation. +void mock.module("lottie-react", () => ({ + __esModule: true, + default: () =>
, +})); + +void mock.module("@/browser/App", () => ({ + __esModule: true, + default: () =>
, +})); + +void mock.module("@/browser/contexts/API", () => ({ + APIProvider: (props: { children: React.ReactNode }) => props.children, + useAPI: () => { + if (apiStatus === "auth_required") { + return { + api: null, + status: "auth_required" as const, + error: apiError, + authenticate: () => undefined, + retry: () => undefined, + }; + } + + if (apiStatus === "error") { + return { + api: null, + status: "error" as const, + error: apiError ?? "Connection error", + authenticate: () => undefined, + retry: () => undefined, + }; + } + + return { + api: null, + status: "connecting" as const, + error: null, + authenticate: () => undefined, + retry: () => undefined, + }; + }, +})); + +const noop = (): void => undefined; +const noopPromise = () => Promise.resolve(); + +const mockPolicyState = { + source: "none" as const, + status: { state: "disabled" as const }, + policy: null, + loading: false, + refresh: noopPromise, +}; + +const mockRouterState = { + navigateToWorkspace: noop, + navigateToProject: noop, + navigateToHome: noop, + navigateToSettings: noop, + navigateFromSettings: noop, + navigateToAnalytics: noop, + navigateFromAnalytics: noop, + currentWorkspaceId: null, + currentSettingsSection: null, + currentProjectId: null, + currentProjectPathFromState: null, + pendingSectionId: null, + pendingDraftId: null, + isAnalyticsOpen: false, +}; + +const mockProjectContextValue = { + userProjects: new Map(), + systemProjectPath: null, + resolveProjectPath: () => null, + getProjectConfig: () => undefined, + loading: true, + refreshProjects: noopPromise, + addProject: noop, + removeProject: () => Promise.resolve({ success: true }), + isProjectCreateModalOpen: false, + openProjectCreateModal: noop, + closeProjectCreateModal: noop, + workspaceModalState: { + isOpen: false, + projectPath: null, + projectName: "", + branches: [], + defaultTrunkBranch: undefined, + loadErrorMessage: null, + isLoading: false, + }, + openWorkspaceModal: noopPromise, + closeWorkspaceModal: noop, + getBranchesForProject: () => Promise.resolve({ branches: [], recommendedTrunk: null }), + getSecrets: () => Promise.resolve([]), + updateSecrets: noopPromise, + createSection: () => Promise.resolve({ success: false, error: "not implemented" }), + updateSection: () => Promise.resolve({ success: false, error: "not implemented" }), + removeSection: () => Promise.resolve({ success: false, error: "not implemented" }), + reorderSections: () => Promise.resolve({ success: false, error: "not implemented" }), + assignWorkspaceToSection: () => Promise.resolve({ success: false, error: "not implemented" }), + hasAnyProject: false, + resolveNewChatProjectPath: () => null, +}; + +const mockWorkspaceMetadata = new Map(); +const mockWorkspaceActions = { + workspaceDraftPromotionsByProject: {}, + promoteWorkspaceDraft: noop, + createWorkspace: () => + Promise.resolve({ + projectPath: "", + projectName: "", + namedWorkspacePath: "", + workspaceId: "", + }), + removeWorkspace: () => Promise.resolve({ success: true }), + updateWorkspaceTitle: () => Promise.resolve({ success: true }), + archiveWorkspace: () => Promise.resolve({ success: true }), + unarchiveWorkspace: () => Promise.resolve({ success: true }), + refreshWorkspaceMetadata: noopPromise, + setWorkspaceMetadata: noop, + selectedWorkspace: null, + setSelectedWorkspace: noop, + pendingNewWorkspaceProject: null, + pendingNewWorkspaceSectionId: null, + pendingNewWorkspaceDraftId: null, + beginWorkspaceCreation: noop, + workspaceDraftsByProject: {}, + createWorkspaceDraft: noop, + updateWorkspaceDraftSection: noop, + openWorkspaceDraft: noop, + deleteWorkspaceDraft: noop, + getWorkspaceInfo: () => Promise.resolve(null), +}; + +const mockWorkspaceContextValue = { + workspaceMetadata: mockWorkspaceMetadata, + loading: true, + ...mockWorkspaceActions, +}; + +const mockWorkspaceStoreRaw = { + setClient: noop, + syncWorkspaces: noop, +}; + +const mockWorkspaceStoreSingleton = { + subscribeFileModifyingTool: () => () => undefined, + getFileModifyingToolMs: () => null, + clearFileModifyingToolMs: noop, + simulateFileModifyingToolEnd: noop, + getWorkspaceSidebarState: () => ({}), + addWorkspace: noop, + setActiveWorkspaceId: noop, +}; + +const mockGitStatusStoreRaw = { + setClient: noop, + syncWorkspaces: noop, + subscribeToFileModifications: noop, +}; + +const mockBackgroundBashStoreRaw = { + setClient: noop, +}; + +let mockPRStatusStoreInstance = { + setClient: noop, +}; + +class MockWorkspaceStore { + setClient() { + return undefined; + } + + syncWorkspaces() { + return undefined; + } +} + +class MockGitStatusStore { + setClient() { + return undefined; + } + + syncWorkspaces() { + return undefined; + } + + subscribeToFileModifications() { + return undefined; + } +} + +class MockBackgroundBashStore { + setClient() { + return undefined; + } +} + +class MockPRStatusStore { + setClient() { + return undefined; + } +} + +void mock.module("@/browser/contexts/PolicyContext", () => ({ + PolicyProvider: (props: { children: React.ReactNode }) => props.children, + usePolicy: () => mockPolicyState, +})); + +void mock.module("@/browser/contexts/RouterContext", () => ({ + RouterProvider: (props: { children: React.ReactNode }) => props.children, + useRouter: () => mockRouterState, +})); + +void mock.module("@/browser/contexts/ProjectContext", () => ({ + ProjectProvider: (props: { children: React.ReactNode }) => props.children, + useProjectContext: () => mockProjectContextValue, +})); + +void mock.module("@/browser/contexts/WorkspaceContext", () => ({ + WorkspaceProvider: (props: { children: React.ReactNode }) => props.children, + toWorkspaceSelection: (metadata: { + id: string; + projectPath: string; + projectName: string; + namedWorkspacePath: string; + }) => ({ + workspaceId: metadata.id, + projectPath: metadata.projectPath, + projectName: metadata.projectName, + namedWorkspacePath: metadata.namedWorkspacePath, + }), + useWorkspaceMetadata: () => ({ + workspaceMetadata: mockWorkspaceMetadata, + loading: true, + }), + useWorkspaceActions: () => mockWorkspaceActions, + useWorkspaceContext: () => mockWorkspaceContextValue, + useOptionalWorkspaceContext: () => mockWorkspaceContextValue, +})); + +void mock.module("@/browser/stores/WorkspaceStore", () => ({ + WorkspaceStore: MockWorkspaceStore, + workspaceStore: mockWorkspaceStoreSingleton, + useWorkspaceState: () => ({}), + useWorkspaceStoreRaw: () => mockWorkspaceStoreRaw, + useWorkspaceRecency: () => ({}), + useWorkspaceSidebarState: () => ({}), + useBashToolLiveOutput: () => "", + useTaskToolLiveTaskId: () => null, + useLatestStreamingBashId: () => null, + useWorkspaceAggregator: () => null, + showAllMessages: noop, + addEphemeralMessage: noop, + removeEphemeralMessage: noop, + useWorkspaceUsage: () => ({}), + useWorkspaceStatsSnapshot: () => null, + useWorkspaceConsumers: () => ({}), +})); + +void mock.module("@/browser/stores/GitStatusStore", () => ({ + GitStatusStore: MockGitStatusStore, + useGitStatus: () => null, + useGitStatusRefreshing: () => false, + useGitStatusStoreRaw: () => mockGitStatusStoreRaw, + invalidateGitStatus: noop, +})); + +void mock.module("@/browser/stores/BackgroundBashStore", () => ({ + BackgroundBashStore: MockBackgroundBashStore, + useBackgroundBashStoreRaw: () => mockBackgroundBashStoreRaw, + useBackgroundProcesses: () => [], + useForegroundBashToolCallIds: () => new Set(), + useBackgroundBashTerminatingIds: () => new Set(), +})); + +void mock.module("@/browser/stores/PRStatusStore", () => ({ + PRStatusStore: MockPRStatusStore, + getPRStatusStoreInstance: () => mockPRStatusStoreInstance, + setPRStatusStoreInstance: (store: { setClient: () => void }) => { + mockPRStatusStoreInstance = store; + }, + useWorkspacePR: () => null, +})); + +void mock.module("@/browser/components/LoadingScreen/LoadingScreen", () => ({ + LoadingScreen: () => { + const { theme } = useTheme(); + return
{theme}
; + }, +})); + +void mock.module("@/browser/components/StartupConnectionError/StartupConnectionError", () => ({ + StartupConnectionError: (props: { error: string }) => ( +
{props.error}
+ ), +})); + +void mock.module("@/browser/components/AuthTokenModal/AuthTokenModal", () => ({ + // Note: Module mocks leak between bun test files. + // Export all commonly-used symbols to avoid cross-test import errors. + AuthTokenModal: (props: { error?: string | null }) => ( +
{props.error ?? "no-error"}
+ ), + getStoredAuthToken: () => null, + // eslint-disable-next-line @typescript-eslint/no-empty-function + setStoredAuthToken: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + clearStoredAuthToken: () => {}, +})); + +// eslint-disable-next-line no-restricted-syntax -- AppLoader imports react-dnd, which requires DOM globals during module evaluation. +const { AppLoader } = await import("./AppLoader"); + +describe("AppLoader", () => { + beforeEach(() => { + cleanupDom = installDom(); + }); + + afterEach(() => { + cleanup(); + cleanupDom?.(); + cleanupDom = null; + }); + + test("renders AuthTokenModal when API status is auth_required (before workspaces load)", () => { + apiStatus = "auth_required"; + apiError = "Authentication required"; + + const { getByTestId, queryByText } = render(); + + expect(queryByText("Loading Mux")).toBeNull(); + expect(getByTestId("AuthTokenModalMock").textContent).toContain("Authentication required"); + }); + + test("renders StartupConnectionError when API status is error (before workspaces load)", () => { + apiStatus = "error"; + apiError = "Connection error"; + + const { getByTestId, queryByTestId } = render(); + + expect(queryByTestId("LoadingScreenMock")).toBeNull(); + expect(queryByTestId("AuthTokenModalMock")).toBeNull(); + expect(getByTestId("StartupConnectionErrorMock").textContent).toContain("Connection error"); + }); + + test("wraps LoadingScreen in ThemeProvider", () => { + apiStatus = "connecting"; + apiError = null; + + const { getByTestId } = render(); + + // If ThemeProvider is missing, useTheme() will throw. + expect(getByTestId("LoadingScreenMock").textContent).toBeTruthy(); + }); +}); diff --git a/src/browser/components/AppLoader/AppLoader.tsx b/src/browser/features/AppShell/AppLoader/AppLoader.tsx similarity index 83% rename from src/browser/components/AppLoader/AppLoader.tsx rename to src/browser/features/AppShell/AppLoader/AppLoader.tsx index 0e1517e56c..868eb57917 100644 --- a/src/browser/components/AppLoader/AppLoader.tsx +++ b/src/browser/features/AppShell/AppLoader/AppLoader.tsx @@ -1,23 +1,23 @@ import { useState, useEffect } from "react"; import { AnimatePresence, motion } from "motion/react"; -import App from "../../App"; -import { AuthTokenModal } from "../AuthTokenModal/AuthTokenModal"; -import { ThemeProvider } from "../../contexts/ThemeContext"; -import { LoadingScreen } from "../LoadingScreen/LoadingScreen"; -import { StartupConnectionError } from "../StartupConnectionError/StartupConnectionError"; -import { useReducedMotion } from "../../hooks/useReducedMotion"; -import { useWorkspaceStoreRaw, workspaceStore } from "../../stores/WorkspaceStore"; -import { useGitStatusStoreRaw } from "../../stores/GitStatusStore"; -import { useBackgroundBashStoreRaw } from "../../stores/BackgroundBashStore"; -import { getPRStatusStoreInstance } from "../../stores/PRStatusStore"; -import { ProjectProvider, useProjectContext } from "../../contexts/ProjectContext"; +import App from "@/browser/App"; +import { AuthTokenModal } from "@/browser/components/AuthTokenModal/AuthTokenModal"; +import { ThemeProvider } from "@/browser/contexts/ThemeContext"; +import { LoadingScreen } from "@/browser/components/LoadingScreen/LoadingScreen"; +import { StartupConnectionError } from "@/browser/components/StartupConnectionError/StartupConnectionError"; +import { useReducedMotion } from "@/browser/hooks/useReducedMotion"; +import { useWorkspaceStoreRaw, workspaceStore } from "@/browser/stores/WorkspaceStore"; +import { useGitStatusStoreRaw } from "@/browser/stores/GitStatusStore"; +import { useBackgroundBashStoreRaw } from "@/browser/stores/BackgroundBashStore"; +import { getPRStatusStoreInstance } from "@/browser/stores/PRStatusStore"; +import { ProjectProvider, useProjectContext } from "@/browser/contexts/ProjectContext"; import { PolicyProvider, usePolicy } from "@/browser/contexts/PolicyContext"; import { PolicyBlockedScreen } from "@/browser/components/PolicyBlockedScreen/PolicyBlockedScreen"; import { APIProvider, useAPI, type APIClient } from "@/browser/contexts/API"; -import { WorkspaceProvider, useWorkspaceContext } from "../../contexts/WorkspaceContext"; -import { RouterProvider } from "../../contexts/RouterContext"; -import { TelemetryEnabledProvider } from "../../contexts/TelemetryEnabledContext"; -import { TerminalRouterProvider } from "../../terminal/TerminalRouterContext"; +import { WorkspaceProvider, useWorkspaceContext } from "@/browser/contexts/WorkspaceContext"; +import { RouterProvider } from "@/browser/contexts/RouterContext"; +import { TelemetryEnabledProvider } from "@/browser/contexts/TelemetryEnabledContext"; +import { TerminalRouterProvider } from "@/browser/terminal/TerminalRouterContext"; interface AppLoaderProps { /** Optional pre-created ORPC api?. If provided, skips internal connection setup. */ diff --git a/src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.tsx b/src/browser/features/AppShell/BackgroundProcessesBanner/BackgroundProcessesBanner.tsx similarity index 97% rename from src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.tsx rename to src/browser/features/AppShell/BackgroundProcessesBanner/BackgroundProcessesBanner.tsx index ede9c0dca4..75c71b5905 100644 --- a/src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.tsx +++ b/src/browser/features/AppShell/BackgroundProcessesBanner/BackgroundProcessesBanner.tsx @@ -1,8 +1,8 @@ import React, { useState, useCallback, useEffect } from "react"; import { Terminal, X, ChevronDown, ChevronRight, Loader2, FileText } from "lucide-react"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; import { cn } from "@/common/lib/utils"; -import { BackgroundBashOutputDialog } from "../BackgroundBashOutputDialog/BackgroundBashOutputDialog"; +import { BackgroundBashOutputDialog } from "@/browser/components/BackgroundBashOutputDialog/BackgroundBashOutputDialog"; import { formatDuration } from "@/browser/features/Tools/Shared/toolUtils"; import { useBackgroundBashTerminatingIds, diff --git a/src/browser/components/ConnectionStatusToast/ConnectionStatusToast.tsx b/src/browser/features/AppShell/ConnectionStatusToast/ConnectionStatusToast.tsx similarity index 100% rename from src/browser/components/ConnectionStatusToast/ConnectionStatusToast.tsx rename to src/browser/features/AppShell/ConnectionStatusToast/ConnectionStatusToast.tsx diff --git a/src/browser/components/WindowsToolchainBanner/WindowsToolchainBanner.tsx b/src/browser/features/AppShell/WindowsToolchainBanner/WindowsToolchainBanner.tsx similarity index 100% rename from src/browser/components/WindowsToolchainBanner/WindowsToolchainBanner.tsx rename to src/browser/features/AppShell/WindowsToolchainBanner/WindowsToolchainBanner.tsx diff --git a/src/browser/components/AIView/AIView.tsx b/src/browser/features/Chat/AIView/AIView.tsx similarity index 96% rename from src/browser/components/AIView/AIView.tsx rename to src/browser/features/Chat/AIView/AIView.tsx index bbd2644d78..029dbe50fa 100644 --- a/src/browser/components/AIView/AIView.tsx +++ b/src/browser/features/Chat/AIView/AIView.tsx @@ -6,7 +6,7 @@ import { ThinkingProvider } from "@/browser/contexts/ThinkingContext"; import { WorkspaceModeAISync } from "@/browser/components/WorkspaceModeAISync/WorkspaceModeAISync"; import { AgentProvider } from "@/browser/contexts/AgentContext"; import { BackgroundBashProvider } from "@/browser/contexts/BackgroundBashContext"; -import { WorkspaceShell } from "../WorkspaceShell/WorkspaceShell"; +import { WorkspaceShell } from "@/browser/features/Workspace/WorkspaceShell/WorkspaceShell"; interface AIViewProps { workspaceId: string; diff --git a/src/browser/components/AgentModePicker/AgentModePicker.test.tsx b/src/browser/features/Chat/AgentModePicker/AgentModePicker.test.tsx similarity index 99% rename from src/browser/components/AgentModePicker/AgentModePicker.test.tsx rename to src/browser/features/Chat/AgentModePicker/AgentModePicker.test.tsx index 570ecf9b74..bafe400d0d 100644 --- a/src/browser/components/AgentModePicker/AgentModePicker.test.tsx +++ b/src/browser/features/Chat/AgentModePicker/AgentModePicker.test.tsx @@ -1,7 +1,7 @@ import React from "react"; import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"; -import { installDom } from "../../../../tests/ui/dom"; +import { installDom } from "../../../../../tests/ui/dom"; import { AgentProvider } from "@/browser/contexts/AgentContext"; import { TooltipProvider } from "@/browser/components/Tooltip/Tooltip"; diff --git a/src/browser/components/AgentModePicker/AgentModePicker.tsx b/src/browser/features/Chat/AgentModePicker/AgentModePicker.tsx similarity index 100% rename from src/browser/components/AgentModePicker/AgentModePicker.tsx rename to src/browser/features/Chat/AgentModePicker/AgentModePicker.tsx diff --git a/src/browser/components/ChatPane/ChatPane.tsx b/src/browser/features/Chat/ChatPane/ChatPane.tsx similarity index 98% rename from src/browser/components/ChatPane/ChatPane.tsx rename to src/browser/features/Chat/ChatPane/ChatPane.tsx index 8e4cd0438d..83035c6eec 100644 --- a/src/browser/components/ChatPane/ChatPane.tsx +++ b/src/browser/features/Chat/ChatPane/ChatPane.tsx @@ -14,7 +14,10 @@ import { getTranscriptContextMenuText, } from "@/browser/utils/messages/transcriptContextMenu"; import { useContextMenuPosition } from "@/browser/hooks/useContextMenuPosition"; -import { PositionedMenu, PositionedMenuItem } from "../PositionedMenu/PositionedMenu"; +import { + PositionedMenu, + PositionedMenuItem, +} from "@/browser/components/PositionedMenu/PositionedMenu"; import { MessageListProvider } from "@/browser/features/Messages/MessageListContext"; import { cn } from "@/common/lib/utils"; import { MessageRenderer } from "@/browser/features/Messages/MessageRenderer"; @@ -24,7 +27,7 @@ import { InterruptedBarrier } from "@/browser/features/Messages/ChatBarrier/Inte import { EditCutoffBarrier } from "@/browser/features/Messages/ChatBarrier/EditCutoffBarrier"; import { StreamingBarrier } from "@/browser/features/Messages/ChatBarrier/StreamingBarrier"; import { RetryBarrier } from "@/browser/features/Messages/ChatBarrier/RetryBarrier"; -import { PinnedTodoList } from "../PinnedTodoList/PinnedTodoList"; +import { PinnedTodoList } from "@/browser/features/Workspace/PinnedTodoList/PinnedTodoList"; import { VIM_ENABLED_KEY } from "@/common/constants/storage"; import { ChatInput, type ChatInputAPI } from "@/browser/features/ChatInput/index"; import type { QueueDispatchMode } from "@/browser/features/ChatInput/types"; @@ -50,28 +53,28 @@ import { useWorkspaceStoreRaw, type WorkspaceState, } from "@/browser/stores/WorkspaceStore"; -import { WorkspaceMenuBar } from "../WorkspaceMenuBar/WorkspaceMenuBar"; +import { WorkspaceMenuBar } from "@/browser/features/Workspace/WorkspaceMenuBar/WorkspaceMenuBar"; import type { DisplayedMessage, QueuedMessage as QueuedMessageData } from "@/common/types/message"; import type { RuntimeConfig } from "@/common/types/runtime"; import { getRuntimeTypeForTelemetry } from "@/common/telemetry"; import { useAIViewKeybinds } from "@/browser/hooks/useAIViewKeybinds"; import { QueuedMessage } from "@/browser/features/Messages/QueuedMessage"; -import { CompactionWarning } from "../CompactionWarning/CompactionWarning"; -import { ContextSwitchWarning as ContextSwitchWarningBanner } from "../ContextSwitchWarning/ContextSwitchWarning"; -import { ConcurrentLocalWarning } from "../ConcurrentLocalWarning/ConcurrentLocalWarning"; -import { BackgroundProcessesBanner } from "../BackgroundProcessesBanner/BackgroundProcessesBanner"; +import { CompactionWarning } from "@/browser/components/CompactionWarning/CompactionWarning"; +import { ContextSwitchWarning as ContextSwitchWarningBanner } from "@/browser/components/ContextSwitchWarning/ContextSwitchWarning"; +import { ConcurrentLocalWarning } from "@/browser/features/Workspace/ConcurrentLocalWarning/ConcurrentLocalWarning"; +import { BackgroundProcessesBanner } from "@/browser/features/AppShell/BackgroundProcessesBanner/BackgroundProcessesBanner"; import { checkAutoCompaction } from "@/common/utils/compaction/autoCompactionCheck"; import { cancelCompaction } from "@/browser/utils/compaction/handler"; import type { ContextSwitchWarning } from "@/browser/utils/compaction/contextSwitchCheck"; import { useProviderOptions } from "@/browser/hooks/useProviderOptions"; -import { useAutoCompactionSettings } from "../../hooks/useAutoCompactionSettings"; +import { useAutoCompactionSettings } from "@/browser/hooks/useAutoCompactionSettings"; import { useContextSwitchWarning } from "@/browser/hooks/useContextSwitchWarning"; import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig"; import { useSendMessageOptions } from "@/browser/hooks/useSendMessageOptions"; import type { TerminalSessionCreateOptions } from "@/browser/utils/terminal"; import { useAPI } from "@/browser/contexts/API"; import { useReviews } from "@/browser/hooks/useReviews"; -import { ReviewsBanner } from "../ReviewsBanner/ReviewsBanner"; +import { ReviewsBanner } from "@/browser/components/ReviewsBanner/ReviewsBanner"; import type { ReviewNoteData } from "@/common/types/review"; import { useWorkspaceContext } from "@/browser/contexts/WorkspaceContext"; import { diff --git a/src/browser/features/ChatInput/CreationCenterContent.tsx b/src/browser/features/ChatInput/CreationCenterContent.tsx index a87d352dd3..744079ebbe 100644 --- a/src/browser/features/ChatInput/CreationCenterContent.tsx +++ b/src/browser/features/ChatInput/CreationCenterContent.tsx @@ -1,5 +1,5 @@ import { useTheme } from "@/browser/contexts/ThemeContext"; -import { Shimmer } from "@/browser/features/AIElements/Shimmer"; +import { Shimmer } from "@/browser/components/Shimmer/Shimmer"; import { LoadingAnimation } from "@/browser/components/LoadingAnimation/LoadingAnimation"; interface CreationCenterContentProps { diff --git a/src/browser/features/ChatInput/CreationControls.tsx b/src/browser/features/ChatInput/CreationControls.tsx index bc8062fb27..4e1dd24643 100644 --- a/src/browser/features/ChatInput/CreationControls.tsx +++ b/src/browser/features/ChatInput/CreationControls.tsx @@ -48,7 +48,7 @@ import { resolveCoderAvailability, type CoderAvailabilityState, type CoderControlsProps, -} from "../Runtime/CoderControls"; +} from "@/browser/components/CoderControls/CoderControls"; /** * Shared styling for inline form controls in the creation UI. diff --git a/src/browser/features/ChatInput/index.tsx b/src/browser/features/ChatInput/index.tsx index 5457909dd6..03ce3943a6 100644 --- a/src/browser/features/ChatInput/index.tsx +++ b/src/browser/features/ChatInput/index.tsx @@ -5,7 +5,7 @@ import { FILE_SUGGESTION_KEYS, } from "@/browser/features/ChatInput/CommandSuggestions"; import type { Toast } from "@/browser/features/ChatInput/ChatInputToast"; -import { ConnectionStatusToast } from "@/browser/components/ConnectionStatusToast/ConnectionStatusToast"; +import { ConnectionStatusToast } from "@/browser/features/AppShell/ConnectionStatusToast/ConnectionStatusToast"; import { ChatInputToast } from "@/browser/features/ChatInput/ChatInputToast"; import type { SendMessageError } from "@/common/types/errors"; import { createErrorToast } from "@/browser/features/ChatInput/ChatInputToasts"; @@ -65,7 +65,7 @@ import { type SlashSuggestion, } from "@/browser/utils/slashCommands/suggestions"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; -import { AgentModePicker } from "@/browser/components/AgentModePicker/AgentModePicker"; +import { AgentModePicker } from "@/browser/features/Chat/AgentModePicker/AgentModePicker"; import { ContextUsageIndicatorButton } from "@/browser/components/ContextUsageIndicatorButton/ContextUsageIndicatorButton"; import { useWorkspaceUsage } from "@/browser/stores/WorkspaceStore"; import { useProviderOptions } from "@/browser/hooks/useProviderOptions"; diff --git a/src/browser/components/CommandPalette/CommandPalette.test.ts b/src/browser/features/CommandPalette/CommandPalette/CommandPalette.test.ts similarity index 100% rename from src/browser/components/CommandPalette/CommandPalette.test.ts rename to src/browser/features/CommandPalette/CommandPalette/CommandPalette.test.ts diff --git a/src/browser/components/CommandPalette/CommandPalette.tsx b/src/browser/features/CommandPalette/CommandPalette/CommandPalette.tsx similarity index 100% rename from src/browser/components/CommandPalette/CommandPalette.tsx rename to src/browser/features/CommandPalette/CommandPalette/CommandPalette.tsx diff --git a/src/browser/features/Messages/CodeBlockSSR.tsx b/src/browser/features/Messages/CodeBlockSSR.tsx index 45e74a626a..964ea47734 100644 --- a/src/browser/features/Messages/CodeBlockSSR.tsx +++ b/src/browser/features/Messages/CodeBlockSSR.tsx @@ -5,7 +5,7 @@ */ import React from "react"; -import { CopyIcon } from "@/browser/components/icons/CopyIcon/CopyIcon"; +import { CopyIcon } from "@/browser/components/Icons/CopyIcon/CopyIcon"; interface CodeBlockSSRProps { code: string; diff --git a/src/browser/features/Messages/InitMessage.tsx b/src/browser/features/Messages/InitMessage.tsx index 54e7adbd3c..db7c53e5c0 100644 --- a/src/browser/features/Messages/InitMessage.tsx +++ b/src/browser/features/Messages/InitMessage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from "react"; import { cn } from "@/common/lib/utils"; import type { DisplayedMessage } from "@/common/types/message"; import { Loader2, Wrench, CheckCircle2, AlertCircle } from "lucide-react"; -import { Shimmer } from "../AIElements/Shimmer"; +import { Shimmer } from "@/browser/components/Shimmer/Shimmer"; import { formatDuration } from "@/common/utils/formatDuration"; interface InitMessageProps { diff --git a/src/browser/features/Messages/ReasoningMessage.tsx b/src/browser/features/Messages/ReasoningMessage.tsx index fb38d1603e..be067e0f51 100644 --- a/src/browser/features/Messages/ReasoningMessage.tsx +++ b/src/browser/features/Messages/ReasoningMessage.tsx @@ -4,7 +4,7 @@ import { MarkdownRenderer } from "./MarkdownRenderer"; import { TypewriterMarkdown } from "./TypewriterMarkdown"; import { normalizeReasoningMarkdown } from "./MarkdownStyles"; import { cn } from "@/common/lib/utils"; -import { Shimmer } from "../AIElements/Shimmer"; +import { Shimmer } from "@/browser/components/Shimmer/Shimmer"; import { Lightbulb } from "lucide-react"; interface ReasoningMessageProps { diff --git a/src/browser/components/BranchSelector/BranchSelector.tsx b/src/browser/features/Project/BranchSelector/BranchSelector.tsx similarity index 99% rename from src/browser/components/BranchSelector/BranchSelector.tsx rename to src/browser/features/Project/BranchSelector/BranchSelector.tsx index db00d87097..5d3ad1772f 100644 --- a/src/browser/components/BranchSelector/BranchSelector.tsx +++ b/src/browser/features/Project/BranchSelector/BranchSelector.tsx @@ -2,8 +2,8 @@ import React, { useState, useCallback, useEffect, useRef } from "react"; import { GitBranch, Loader2, Check, Copy, Globe, ChevronRight } from "lucide-react"; import { cn } from "@/common/lib/utils"; import { useAPI } from "@/browser/contexts/API"; -import { Popover, PopoverContent, PopoverTrigger } from "../Popover/Popover"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; +import { Popover, PopoverContent, PopoverTrigger } from "@/browser/components/Popover/Popover"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; import { useCopyToClipboard } from "@/browser/hooks/useCopyToClipboard"; import { invalidateGitStatus, useGitStatus } from "@/browser/stores/GitStatusStore"; import { createLRUCache } from "@/browser/utils/lruCache"; diff --git a/src/browser/components/DirectoryPickerModal/DirectoryPickerModal.tsx b/src/browser/features/Project/DirectoryPickerModal/DirectoryPickerModal.tsx similarity index 98% rename from src/browser/components/DirectoryPickerModal/DirectoryPickerModal.tsx rename to src/browser/features/Project/DirectoryPickerModal/DirectoryPickerModal.tsx index f316838310..8571f26b0c 100644 --- a/src/browser/components/DirectoryPickerModal/DirectoryPickerModal.tsx +++ b/src/browser/features/Project/DirectoryPickerModal/DirectoryPickerModal.tsx @@ -8,10 +8,10 @@ import { DialogFooter, } from "@/browser/components/Dialog/Dialog"; import { Button } from "@/browser/components/Button/Button"; -import { Checkbox } from "../Checkbox/Checkbox"; +import { Checkbox } from "@/browser/components/Checkbox/Checkbox"; import { Input } from "@/browser/components/Input/Input"; import type { FileTreeNode } from "@/common/utils/git/numstatParser"; -import { DirectoryTree } from "../DirectoryTree/DirectoryTree"; +import { DirectoryTree } from "@/browser/components/DirectoryTree/DirectoryTree"; import { useAPI } from "@/browser/contexts/API"; import { formatKeybind, isMac } from "@/browser/utils/ui/keybinds"; import { getErrorMessage } from "@/common/utils/errors"; diff --git a/src/browser/components/GitInitBanner/GitInitBanner.tsx b/src/browser/features/Project/GitInitBanner/GitInitBanner.tsx similarity index 100% rename from src/browser/components/GitInitBanner/GitInitBanner.tsx rename to src/browser/features/Project/GitInitBanner/GitInitBanner.tsx diff --git a/src/browser/components/GitStatusIndicator/GitStatusIndicator.tsx b/src/browser/features/Project/GitStatus/GitStatusIndicator/GitStatusIndicator.tsx similarity index 97% rename from src/browser/components/GitStatusIndicator/GitStatusIndicator.tsx rename to src/browser/features/Project/GitStatus/GitStatusIndicator/GitStatusIndicator.tsx index 00192bf965..798c8ad0ec 100644 --- a/src/browser/components/GitStatusIndicator/GitStatusIndicator.tsx +++ b/src/browser/features/Project/GitStatus/GitStatusIndicator/GitStatusIndicator.tsx @@ -8,7 +8,7 @@ import { GitStatusIndicatorView, type GitStatusIndicatorMode, } from "../GitStatusIndicatorView/GitStatusIndicatorView"; -import { useGitBranchDetails } from "@/browser/features/Hooks/useGitBranchDetails"; +import { useGitBranchDetails } from "@/browser/hooks/useGitBranchDetails"; interface GitStatusIndicatorProps { gitStatus: GitStatus | null; diff --git a/src/browser/components/GitStatusIndicatorView/GitStatusIndicatorView.tsx b/src/browser/features/Project/GitStatus/GitStatusIndicatorView/GitStatusIndicatorView.tsx similarity index 98% rename from src/browser/components/GitStatusIndicatorView/GitStatusIndicatorView.tsx rename to src/browser/features/Project/GitStatus/GitStatusIndicatorView/GitStatusIndicatorView.tsx index 40688ef480..3aab0b0565 100644 --- a/src/browser/components/GitStatusIndicatorView/GitStatusIndicatorView.tsx +++ b/src/browser/features/Project/GitStatus/GitStatusIndicatorView/GitStatusIndicatorView.tsx @@ -3,8 +3,16 @@ import type { GitStatus } from "@/common/types/workspace"; import type { GitCommit, GitBranchHeader } from "@/common/utils/git/parseGitLog"; import { cn } from "@/common/lib/utils"; import { stopKeyboardPropagation } from "@/browser/utils/events"; -import { ToggleGroup, ToggleGroupItem } from "../ToggleGroupPrimitive/ToggleGroupPrimitive"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../Dialog/Dialog"; +import { + ToggleGroup, + ToggleGroupItem, +} from "@/browser/components/ToggleGroupPrimitive/ToggleGroupPrimitive"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/browser/components/Dialog/Dialog"; import { BaseSelectorPopover } from "@/browser/features/RightSidebar/CodeReview/BaseSelectorPopover"; // Helper for indicator colors diff --git a/src/browser/components/ProjectSidebar/ProjectSidebar.tsx b/src/browser/features/Project/ProjectSidebar/ProjectSidebar.tsx similarity index 98% rename from src/browser/components/ProjectSidebar/ProjectSidebar.tsx rename to src/browser/features/Project/ProjectSidebar/ProjectSidebar.tsx index da5a89467a..5bb1c4b742 100644 --- a/src/browser/components/ProjectSidebar/ProjectSidebar.tsx +++ b/src/browser/features/Project/ProjectSidebar/ProjectSidebar.tsx @@ -51,13 +51,16 @@ import { getSectionTierKey, sortSectionsByLinkedList, } from "@/browser/utils/ui/workspaceFiltering"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; -import { SidebarCollapseButton } from "../SidebarCollapseButton/SidebarCollapseButton"; -import { ConfirmationModal } from "../ConfirmationModal/ConfirmationModal"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; +import { SidebarCollapseButton } from "@/browser/components/SidebarCollapseButton/SidebarCollapseButton"; +import { ConfirmationModal } from "@/browser/components/ConfirmationModal/ConfirmationModal"; import { useSettings } from "@/browser/contexts/SettingsContext"; -import { WorkspaceListItem, type WorkspaceSelection } from "../WorkspaceListItem/WorkspaceListItem"; -import { WorkspaceStatusIndicator } from "../WorkspaceStatusIndicator/WorkspaceStatusIndicator"; +import { + WorkspaceListItem, + type WorkspaceSelection, +} from "@/browser/features/Workspace/WorkspaceListItem/WorkspaceListItem"; +import { WorkspaceStatusIndicator } from "@/browser/features/Workspace/WorkspaceStatusIndicator/WorkspaceStatusIndicator"; import { TitleEditProvider, useTitleEdit } from "@/browser/contexts/WorkspaceTitleEditContext"; import { useConfirmDialog } from "@/browser/contexts/ConfirmDialogContext"; import { useProjectContext } from "@/browser/contexts/ProjectContext"; @@ -67,19 +70,19 @@ import { useWorkspaceActions } from "@/browser/contexts/WorkspaceContext"; import { useRouter } from "@/browser/contexts/RouterContext"; import { usePopoverError } from "@/browser/hooks/usePopoverError"; import { forkWorkspace } from "@/browser/utils/chatCommands"; -import { PopoverError } from "../PopoverError/PopoverError"; -import { SectionHeader } from "../SectionHeader/SectionHeader"; -import { AddSectionButton } from "../AddSectionButton/AddSectionButton"; -import { WorkspaceSectionDropZone } from "../WorkspaceSectionDropZone/WorkspaceSectionDropZone"; -import { WorkspaceDragLayer } from "../WorkspaceDragLayer/WorkspaceDragLayer"; -import { SectionDragLayer } from "../SectionDragLayer/SectionDragLayer"; -import { DraggableSection } from "../DraggableSection/DraggableSection"; +import { PopoverError } from "@/browser/components/PopoverError/PopoverError"; +import { SectionHeader } from "@/browser/components/SectionHeader/SectionHeader"; +import { AddSectionButton } from "@/browser/components/AddSectionButton/AddSectionButton"; +import { WorkspaceSectionDropZone } from "@/browser/components/WorkspaceSectionDropZone/WorkspaceSectionDropZone"; +import { WorkspaceDragLayer } from "@/browser/components/WorkspaceDragLayer/WorkspaceDragLayer"; +import { SectionDragLayer } from "@/browser/components/SectionDragLayer/SectionDragLayer"; +import { DraggableSection } from "@/browser/components/DraggableSection/DraggableSection"; import type { SectionConfig } from "@/common/types/project"; import { getErrorMessage } from "@/common/utils/errors"; import { getProjectWorkspaceCounts } from "@/common/utils/projectRemoval"; // Re-export WorkspaceSelection for backwards compatibility -export type { WorkspaceSelection } from "../WorkspaceListItem/WorkspaceListItem"; +export type { WorkspaceSelection } from "@/browser/features/Workspace/WorkspaceListItem/WorkspaceListItem"; // Draggable project item moved to module scope to avoid remounting on every parent render. // Defining components inside another component causes a new function identity each render, diff --git a/src/browser/features/Settings/Sections/RuntimesSection.tsx b/src/browser/features/Settings/Sections/RuntimesSection.tsx index 931f117e0a..cd85b2ecd1 100644 --- a/src/browser/features/Settings/Sections/RuntimesSection.tsx +++ b/src/browser/features/Settings/Sections/RuntimesSection.tsx @@ -4,7 +4,7 @@ import { AlertTriangle, Loader2 } from "lucide-react"; import { CoderWorkspaceForm, resolveCoderAvailability, -} from "@/browser/features/Runtime/CoderControls"; +} from "@/browser/components/CoderControls/CoderControls"; import { RuntimeConfigInput } from "@/browser/components/RuntimeConfigInput/RuntimeConfigInput"; import { Select, diff --git a/src/browser/features/SplashScreens/OnboardingWizardSplash.tsx b/src/browser/features/SplashScreens/OnboardingWizardSplash.tsx index ff4c3c29cd..82875b5c07 100644 --- a/src/browser/features/SplashScreens/OnboardingWizardSplash.tsx +++ b/src/browser/features/SplashScreens/OnboardingWizardSplash.tsx @@ -18,7 +18,7 @@ import { LocalIcon, SSHIcon, WorktreeIcon, -} from "@/browser/components/icons/RuntimeIcons/RuntimeIcons"; +} from "@/browser/components/Icons/RuntimeIcons/RuntimeIcons"; import { ProjectAddForm, type ProjectAddFormHandle, diff --git a/src/browser/features/Tools/Shared/ToolPrimitives.tsx b/src/browser/features/Tools/Shared/ToolPrimitives.tsx index 1ebcf4eef1..d6013124be 100644 --- a/src/browser/features/Tools/Shared/ToolPrimitives.tsx +++ b/src/browser/features/Tools/Shared/ToolPrimitives.tsx @@ -16,7 +16,7 @@ import { Square, Wrench, } from "lucide-react"; -import { EmojiIcon } from "@/browser/components/icons/EmojiIcon/EmojiIcon"; +import { EmojiIcon } from "@/browser/components/Icons/EmojiIcon/EmojiIcon"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; /** diff --git a/src/browser/features/Tools/SwitchAgentToolCall.tsx b/src/browser/features/Tools/SwitchAgentToolCall.tsx index 758f4a0574..1e93f38fe0 100644 --- a/src/browser/features/Tools/SwitchAgentToolCall.tsx +++ b/src/browser/features/Tools/SwitchAgentToolCall.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { formatAgentIdLabel } from "@/browser/components/AgentModePicker/AgentModePicker"; +import { formatAgentIdLabel } from "@/browser/features/Chat/AgentModePicker/AgentModePicker"; import { ToolContainer, ToolHeader, diff --git a/src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx b/src/browser/features/Workspace/ArchivedWorkspaces/ArchivedWorkspaces.tsx similarity index 98% rename from src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx rename to src/browser/features/Workspace/ArchivedWorkspaces/ArchivedWorkspaces.tsx index 976ae62f13..c89725415a 100644 --- a/src/browser/components/ArchivedWorkspaces/ArchivedWorkspaces.tsx +++ b/src/browser/features/Workspace/ArchivedWorkspaces/ArchivedWorkspaces.tsx @@ -7,10 +7,13 @@ import { usePersistedState } from "@/browser/hooks/usePersistedState"; import { getArchivedWorkspacesExpandedKey } from "@/common/constants/storage"; import { useAPI } from "@/browser/contexts/API"; import { ChevronDown, ChevronRight, Loader2, Search, Trash2 } from "lucide-react"; -import { ArchiveIcon, ArchiveRestoreIcon } from "../icons/ArchiveIcon/ArchiveIcon"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; -import { RuntimeBadge } from "../RuntimeBadge/RuntimeBadge"; -import { Skeleton } from "../Skeleton/Skeleton"; +import { + ArchiveIcon, + ArchiveRestoreIcon, +} from "@/browser/components/Icons/ArchiveIcon/ArchiveIcon"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; +import { RuntimeBadge } from "@/browser/components/RuntimeBadge/RuntimeBadge"; +import { Skeleton } from "@/browser/components/Skeleton/Skeleton"; import { Dialog, DialogContent, @@ -19,7 +22,7 @@ import { DialogDescription, DialogFooter, } from "@/browser/components/Dialog/Dialog"; -import { ForceDeleteModal } from "../ForceDeleteModal/ForceDeleteModal"; +import { ForceDeleteModal } from "@/browser/components/ForceDeleteModal/ForceDeleteModal"; import { Button } from "@/browser/components/Button/Button"; import type { z } from "zod"; import type { SessionUsageFileSchema } from "@/common/orpc/schemas/chatStats"; diff --git a/src/browser/components/ConcurrentLocalWarning/ConcurrentLocalWarning.tsx b/src/browser/features/Workspace/ConcurrentLocalWarning/ConcurrentLocalWarning.tsx similarity index 100% rename from src/browser/components/ConcurrentLocalWarning/ConcurrentLocalWarning.tsx rename to src/browser/features/Workspace/ConcurrentLocalWarning/ConcurrentLocalWarning.tsx diff --git a/src/browser/components/PinnedTodoList/PinnedTodoList.tsx b/src/browser/features/Workspace/PinnedTodoList/PinnedTodoList.tsx similarity index 96% rename from src/browser/components/PinnedTodoList/PinnedTodoList.tsx rename to src/browser/features/Workspace/PinnedTodoList/PinnedTodoList.tsx index ff8e387d8c..fca41c1d4b 100644 --- a/src/browser/components/PinnedTodoList/PinnedTodoList.tsx +++ b/src/browser/features/Workspace/PinnedTodoList/PinnedTodoList.tsx @@ -1,5 +1,5 @@ import React, { useSyncExternalStore } from "react"; -import { TodoList } from "../TodoList/TodoList"; +import { TodoList } from "@/browser/components/TodoList/TodoList"; import { useWorkspaceStoreRaw } from "@/browser/stores/WorkspaceStore"; import { usePersistedState } from "@/browser/hooks/usePersistedState"; import { cn } from "@/common/lib/utils"; diff --git a/src/browser/components/WorkspaceLinks/WorkspaceLinks.tsx b/src/browser/features/Workspace/WorkspaceLinks/WorkspaceLinks.tsx similarity index 86% rename from src/browser/components/WorkspaceLinks/WorkspaceLinks.tsx rename to src/browser/features/Workspace/WorkspaceLinks/WorkspaceLinks.tsx index 8700f6dcb7..2b5cffafa0 100644 --- a/src/browser/components/WorkspaceLinks/WorkspaceLinks.tsx +++ b/src/browser/features/Workspace/WorkspaceLinks/WorkspaceLinks.tsx @@ -4,7 +4,7 @@ */ import { useWorkspacePR } from "@/browser/stores/PRStatusStore"; -import { PRLinkBadge } from "../PRLinkBadge/PRLinkBadge"; +import { PRLinkBadge } from "@/browser/components/PRLinkBadge/PRLinkBadge"; interface WorkspaceLinksProps { workspaceId: string; diff --git a/src/browser/components/WorkspaceListItem/WorkspaceListItem.tsx b/src/browser/features/Workspace/WorkspaceListItem/WorkspaceListItem.tsx similarity index 96% rename from src/browser/components/WorkspaceListItem/WorkspaceListItem.tsx rename to src/browser/features/Workspace/WorkspaceListItem/WorkspaceListItem.tsx index b50036ffb5..f336f3cd62 100644 --- a/src/browser/components/WorkspaceListItem/WorkspaceListItem.tsx +++ b/src/browser/features/Workspace/WorkspaceListItem/WorkspaceListItem.tsx @@ -10,25 +10,33 @@ import type { FrontendWorkspaceMetadata } from "@/common/types/workspace"; import React, { useState, useEffect, useLayoutEffect, useRef, useCallback } from "react"; import { useDrag } from "react-dnd"; import { getEmptyImage } from "react-dnd-html5-backend"; -import { GitStatusIndicator } from "../GitStatusIndicator/GitStatusIndicator"; +import { GitStatusIndicator } from "@/browser/features/Project/GitStatus/GitStatusIndicator/GitStatusIndicator"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; -import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from "../Popover/Popover"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; +import { + Popover, + PopoverContent, + PopoverTrigger, + PopoverAnchor, +} from "@/browser/components/Popover/Popover"; import { useContextMenuPosition } from "@/browser/hooks/useContextMenuPosition"; -import { PositionedMenu, PositionedMenuItem } from "../PositionedMenu/PositionedMenu"; +import { + PositionedMenu, + PositionedMenuItem, +} from "@/browser/components/PositionedMenu/PositionedMenu"; import { Trash2, Ellipsis, Loader2, Sparkles } from "lucide-react"; import { WorkspaceStatusIndicator } from "../WorkspaceStatusIndicator/WorkspaceStatusIndicator"; -import { Shimmer } from "@/browser/features/AIElements/Shimmer"; -import { ArchiveIcon } from "../icons/ArchiveIcon/ArchiveIcon"; -import { WorkspaceTerminalIcon } from "../icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon"; +import { Shimmer } from "@/browser/components/Shimmer/Shimmer"; +import { ArchiveIcon } from "@/browser/components/Icons/ArchiveIcon/ArchiveIcon"; +import { WorkspaceTerminalIcon } from "@/browser/components/Icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon"; import { WORKSPACE_DRAG_TYPE, type WorkspaceDragItem, -} from "../WorkspaceSectionDropZone/WorkspaceSectionDropZone"; +} from "@/browser/components/WorkspaceSectionDropZone/WorkspaceSectionDropZone"; import { useLinkSharingEnabled } from "@/browser/contexts/TelemetryEnabledContext"; import { formatKeybind, KEYBINDS } from "@/browser/utils/ui/keybinds"; -import { ShareTranscriptDialog } from "../ShareTranscriptDialog/ShareTranscriptDialog"; -import { WorkspaceActionsMenuContent } from "../WorkspaceActionsMenuContent/WorkspaceActionsMenuContent"; +import { ShareTranscriptDialog } from "@/browser/components/ShareTranscriptDialog/ShareTranscriptDialog"; +import { WorkspaceActionsMenuContent } from "@/browser/components/WorkspaceActionsMenuContent/WorkspaceActionsMenuContent"; import { useAPI } from "@/browser/contexts/API"; export interface WorkspaceSelection { diff --git a/src/browser/components/WorkspaceMenuBar/WorkspaceMenuBar.tsx b/src/browser/features/Workspace/WorkspaceMenuBar/WorkspaceMenuBar.tsx similarity index 94% rename from src/browser/components/WorkspaceMenuBar/WorkspaceMenuBar.tsx rename to src/browser/features/Workspace/WorkspaceMenuBar/WorkspaceMenuBar.tsx index e83bd81b93..6357edd6a9 100644 --- a/src/browser/components/WorkspaceMenuBar/WorkspaceMenuBar.tsx +++ b/src/browser/features/Workspace/WorkspaceMenuBar/WorkspaceMenuBar.tsx @@ -10,13 +10,13 @@ import { getNotifyOnResponseKey, getNotifyOnResponseAutoEnableKey, } from "@/common/constants/storage"; -import { GitStatusIndicator } from "../GitStatusIndicator/GitStatusIndicator"; -import { RuntimeBadge } from "../RuntimeBadge/RuntimeBadge"; -import { BranchSelector } from "../BranchSelector/BranchSelector"; -import { WorkspaceMCPModal } from "../WorkspaceMCPModal/WorkspaceMCPModal"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; -import { Popover, PopoverTrigger, PopoverContent } from "../Popover/Popover"; -import { Checkbox } from "../Checkbox/Checkbox"; +import { GitStatusIndicator } from "@/browser/features/Project/GitStatus/GitStatusIndicator/GitStatusIndicator"; +import { RuntimeBadge } from "@/browser/components/RuntimeBadge/RuntimeBadge"; +import { BranchSelector } from "@/browser/features/Project/BranchSelector/BranchSelector"; +import { WorkspaceMCPModal } from "@/browser/components/WorkspaceMCPModal/WorkspaceMCPModal"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; +import { Popover, PopoverTrigger, PopoverContent } from "@/browser/components/Popover/Popover"; +import { Checkbox } from "@/browser/components/Checkbox/Checkbox"; import { formatKeybind, KEYBINDS, matchesKeybind } from "@/browser/utils/ui/keybinds"; import { useGitStatus } from "@/browser/stores/GitStatusStore"; import { useWorkspaceSidebarState } from "@/browser/stores/WorkspaceStore"; @@ -31,15 +31,15 @@ import { useOpenInEditor } from "@/browser/hooks/useOpenInEditor"; import { usePersistedState } from "@/browser/hooks/usePersistedState"; import { usePopoverError } from "@/browser/hooks/usePopoverError"; import { isDesktopMode, DESKTOP_TITLEBAR_HEIGHT_CLASS } from "@/browser/hooks/useDesktopTitlebar"; -import { DebugLlmRequestModal } from "../DebugLlmRequestModal/DebugLlmRequestModal"; +import { DebugLlmRequestModal } from "@/browser/components/DebugLlmRequestModal/DebugLlmRequestModal"; import { WorkspaceLinks } from "../WorkspaceLinks/WorkspaceLinks"; -import { ShareTranscriptDialog } from "../ShareTranscriptDialog/ShareTranscriptDialog"; -import { ConfirmationModal } from "../ConfirmationModal/ConfirmationModal"; -import { PopoverError } from "../PopoverError/PopoverError"; -import { WorkspaceActionsMenuContent } from "../WorkspaceActionsMenuContent/WorkspaceActionsMenuContent"; -import { WorkspaceTerminalIcon } from "../icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon"; +import { ShareTranscriptDialog } from "@/browser/components/ShareTranscriptDialog/ShareTranscriptDialog"; +import { ConfirmationModal } from "@/browser/components/ConfirmationModal/ConfirmationModal"; +import { PopoverError } from "@/browser/components/PopoverError/PopoverError"; +import { WorkspaceActionsMenuContent } from "@/browser/components/WorkspaceActionsMenuContent/WorkspaceActionsMenuContent"; +import { WorkspaceTerminalIcon } from "@/browser/components/Icons/WorkspaceTerminalIcon/WorkspaceTerminalIcon"; -import { SkillIndicator } from "../SkillIndicator/SkillIndicator"; +import { SkillIndicator } from "@/browser/components/SkillIndicator/SkillIndicator"; import { useAPI } from "@/browser/contexts/API"; import { useAgent } from "@/browser/contexts/AgentContext"; diff --git a/src/browser/components/WorkspaceShell/WorkspaceShell.tsx b/src/browser/features/Workspace/WorkspaceShell/WorkspaceShell.tsx similarity index 95% rename from src/browser/components/WorkspaceShell/WorkspaceShell.tsx rename to src/browser/features/Workspace/WorkspaceShell/WorkspaceShell.tsx index 997f09ed2b..1a60b0f20f 100644 --- a/src/browser/components/WorkspaceShell/WorkspaceShell.tsx +++ b/src/browser/features/Workspace/WorkspaceShell/WorkspaceShell.tsx @@ -1,21 +1,21 @@ import type { TerminalSessionCreateOptions } from "@/browser/utils/terminal"; import React, { useCallback, useRef } from "react"; import { cn } from "@/common/lib/utils"; -import { LoadingAnimation } from "../LoadingAnimation/LoadingAnimation"; +import { LoadingAnimation } from "@/browser/components/LoadingAnimation/LoadingAnimation"; import { RIGHT_SIDEBAR_WIDTH_KEY, getReviewImmersiveKey } from "@/common/constants/storage"; import { useResizableSidebar } from "@/browser/hooks/useResizableSidebar"; import { useResizeObserver } from "@/browser/hooks/useResizeObserver"; import { useOpenTerminal } from "@/browser/hooks/useOpenTerminal"; import { usePersistedState } from "@/browser/hooks/usePersistedState"; import { RightSidebar } from "@/browser/features/RightSidebar/RightSidebar"; -import { PopoverError } from "../PopoverError/PopoverError"; +import { PopoverError } from "@/browser/components/PopoverError/PopoverError"; import type { RuntimeConfig } from "@/common/types/runtime"; import { useBackgroundBashError } from "@/browser/contexts/BackgroundBashContext"; import { useWorkspaceState } from "@/browser/stores/WorkspaceStore"; import { useReviews } from "@/browser/hooks/useReviews"; import type { ReviewNoteData } from "@/common/types/review"; -import { ConnectionStatusToast } from "../ConnectionStatusToast/ConnectionStatusToast"; -import { ChatPane } from "../ChatPane/ChatPane"; +import { ConnectionStatusToast } from "@/browser/features/AppShell/ConnectionStatusToast/ConnectionStatusToast"; +import { ChatPane } from "@/browser/features/Chat/ChatPane/ChatPane"; // ChatPane uses tailwind `min-w-96`. const CHAT_PANE_MIN_WIDTH_PX = 384; diff --git a/src/browser/components/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx b/src/browser/features/Workspace/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx similarity index 95% rename from src/browser/components/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx rename to src/browser/features/Workspace/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx index 1497714eba..dcabce3801 100644 --- a/src/browser/components/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx +++ b/src/browser/features/Workspace/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx @@ -1,9 +1,9 @@ import { useWorkspaceSidebarState } from "@/browser/stores/WorkspaceStore"; import { ModelDisplay } from "@/browser/features/Messages/ModelDisplay"; -import { EmojiIcon } from "@/browser/components/icons/EmojiIcon/EmojiIcon"; +import { EmojiIcon } from "@/browser/components/Icons/EmojiIcon/EmojiIcon"; import { CircleHelp, ExternalLinkIcon, Loader2 } from "lucide-react"; import { memo } from "react"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; export const WorkspaceStatusIndicator = memo<{ workspaceId: string; diff --git a/src/browser/features/Hooks/useGitBranchDetails.ts b/src/browser/hooks/useGitBranchDetails.ts similarity index 100% rename from src/browser/features/Hooks/useGitBranchDetails.ts rename to src/browser/hooks/useGitBranchDetails.ts diff --git a/src/browser/hooks/useUnreadTracking.ts b/src/browser/hooks/useUnreadTracking.ts index 72e57b5e51..c384844f00 100644 --- a/src/browser/hooks/useUnreadTracking.ts +++ b/src/browser/hooks/useUnreadTracking.ts @@ -1,5 +1,5 @@ import { useEffect, useCallback, useRef } from "react"; -import type { WorkspaceSelection } from "@/browser/components/ProjectSidebar/ProjectSidebar"; +import type { WorkspaceSelection } from "@/browser/features/Project/ProjectSidebar/ProjectSidebar"; import { getWorkspaceLastReadKey } from "@/common/constants/storage"; import { readPersistedState, updatePersistedState } from "./usePersistedState"; diff --git a/src/browser/main.tsx b/src/browser/main.tsx index 287df55e93..96377a2b7f 100644 --- a/src/browser/main.tsx +++ b/src/browser/main.tsx @@ -1,7 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { installBrowserLogCapture } from "@/browser/utils/browserLog"; -import { AppLoader } from "@/browser/components/AppLoader/AppLoader"; +import { AppLoader } from "@/browser/features/AppShell/AppLoader/AppLoader"; import { initTelemetry, trackAppStarted } from "@/common/telemetry"; import { initTitlebarInsets } from "@/browser/hooks/useDesktopTitlebar"; diff --git a/src/browser/stories/App.readmeScreenshots.stories.tsx b/src/browser/stories/App.readmeScreenshots.stories.tsx index a93a7e5bc0..860b6ff40d 100644 --- a/src/browser/stories/App.readmeScreenshots.stories.tsx +++ b/src/browser/stories/App.readmeScreenshots.stories.tsx @@ -273,10 +273,10 @@ export const CodeReview: AppStory = { window.localStorage.setItem(RIGHT_SIDEBAR_WIDTH_KEY, "950"); window.localStorage.removeItem(getRightSidebarLayoutKey(workspaceId)); - const REVIEW_DIFF = `diff --git a/src/browser/components/WorkspaceShell.tsx b/src/browser/components/WorkspaceShell.tsx + const REVIEW_DIFF = `diff --git a/src/browser/features/Workspace/WorkspaceShell.tsx b/src/browser/features/Workspace/WorkspaceShell.tsx index aaa1111..bbb2222 100644 ---- a/src/browser/components/WorkspaceShell.tsx -+++ b/src/browser/components/WorkspaceShell.tsx +--- a/src/browser/features/Workspace/WorkspaceShell.tsx ++++ b/src/browser/features/Workspace/WorkspaceShell.tsx @@ -1,8 +1,18 @@ import React from 'react'; +import { useRightSidebarLayout } from '../Hooks/useRightSidebarLayout'; @@ -328,7 +328,7 @@ index 0000000..def5678 + return layout; +}`; - const REVIEW_NUMSTAT = `10\t2\tsrc/browser/components/WorkspaceShell.tsx + const REVIEW_NUMSTAT = `10\t2\tsrc/browser/features/Workspace/WorkspaceShell.tsx 12\t0\tsrc/browser/utils/layout.ts 18\t0\tsrc/browser/hooks/useRightSidebarLayout.ts`; @@ -354,7 +354,7 @@ index 0000000..def5678 toolCalls: [ createFileReadTool( "call-read-1", - "src/browser/components/WorkspaceShell.tsx", + "src/browser/features/Workspace/WorkspaceShell.tsx", 'import React from \'react\';\nimport { useRightSidebarLayout } from \'../hooks/useRightSidebarLayout\';\nimport { clamp } from \'../utils/layout\';\n\nexport function WorkspaceShell(props: WorkspaceShellProps) {\n const layout = useRightSidebarLayout(props.workspaceId);\n const sidebarWidth = clamp(layout.width, 200, 800);\n return (\n
\n
Mux
\n
\n
\n );\n}' ), createFileReadTool( diff --git a/src/browser/stories/App.sidebar.stories.tsx b/src/browser/stories/App.sidebar.stories.tsx index ec4d383e06..df37f596e4 100644 --- a/src/browser/stories/App.sidebar.stories.tsx +++ b/src/browser/stories/App.sidebar.stories.tsx @@ -88,7 +88,10 @@ function createGitStatusExecutor(gitStatus?: Map) { const dirtyFiles = dirtyCount > 0 - ? [" M src/App.tsx", " M src/browser/components/GitStatusIndicatorView.tsx"].join("\n") + ? [ + " M src/App.tsx", + " M src/browser/features/Project/GitStatus/GitStatusIndicatorView.tsx", + ].join("\n") : ""; return [ diff --git a/src/browser/stories/meta.tsx b/src/browser/stories/meta.tsx index 1be7f8e15a..1405cf93d2 100644 --- a/src/browser/stories/meta.tsx +++ b/src/browser/stories/meta.tsx @@ -8,7 +8,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import type { FC, ReactNode } from "react"; import { useRef } from "react"; -import { AppLoader } from "../components/AppLoader/AppLoader"; +import { AppLoader } from "../features/AppShell/AppLoader/AppLoader"; import { TooltipProvider } from "@/browser/components/Tooltip/Tooltip"; import type { APIClient } from "@/browser/contexts/API"; import { ThemeProvider } from "@/browser/contexts/ThemeContext"; diff --git a/src/browser/utils/runtimeUi.ts b/src/browser/utils/runtimeUi.ts index 08638e3e2c..e9ad53fdc2 100644 --- a/src/browser/utils/runtimeUi.ts +++ b/src/browser/utils/runtimeUi.ts @@ -7,7 +7,7 @@ import { DockerIcon, DevcontainerIcon, CoderIcon, -} from "@/browser/components/icons/RuntimeIcons/RuntimeIcons"; +} from "@/browser/components/Icons/RuntimeIcons/RuntimeIcons"; export interface RuntimeIconProps { size?: number; diff --git a/src/node/services/agentSkills/builtInSkillContent.generated.ts b/src/node/services/agentSkills/builtInSkillContent.generated.ts new file mode 100644 index 0000000000..90e16516ed --- /dev/null +++ b/src/node/services/agentSkills/builtInSkillContent.generated.ts @@ -0,0 +1,6205 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run: bun scripts/gen_builtin_skills.ts +// Source: src/node/builtinSkills/*.md and docs/ + +export const BUILTIN_SKILL_FILES: Record> = { + init: { + "SKILL.md": [ + "---", + "name: init", + "description: Bootstrap an AGENTS.md file in a new or existing project", + "---", + "", + "", + "Use your tools to create or improve an AGENTS.md file in the root of the workspace which will serve as a contribution guide for AI agents.", + "If an AGENTS.md file already exists, focus on additive improvement (preserve intent and useful information; refine, extend, and reorganize as needed) rather than replacing it wholesale.", + "Inspect the workspace layout, code, documentation and git history to ensure correctness and accuracy.", + "", + "Ensure the following preamble exists at the top of the file before any other sections. Do not include the surrounding code fence backticks; only include the text.", + "", + "```md", + "You are an experienced, pragmatic software engineering AI agent. Do not over-engineer a solution when a simple one is possible. Keep edits minimal. If you want an exception to ANY rule, you MUST stop and get permission first.", + "```", + "", + "Recommended sections:", + "", + "- Project Overview (mandatory)", + " - Basic details about the project (e.g., high-level overview and goals).", + " - Technology choices (e.g., languages, databases, frameworks, libraries, build tools).", + "- Reference (mandatory)", + " - List important code files.", + " - List important directories and basic code structure tips.", + " - Project architecture.", + "- Essential commands (mandatory)", + " - build", + " - format", + " - lint", + " - test", + " - clean", + " - development server", + " - other _important_ scripts (use `find -type f -name '*.sh'` or similar)", + "- Patterns (optional)", + " - List any important or uncommon patterns (compared to other similar codebases), with examples (e.g., how to authorize an HTTP request).", + " - List any important workflows and their steps (e.g., how to make a database migration).", + " - Testing patterns.", + "- Anti-patterns (optional)", + " - Search git history and comments to find recurring mistakes or forbidden patterns.", + " - List each pattern and its reason.", + "- Code style (optional)", + " - Style guide to follow (with link).", + "- Commit and Pull Request Guidelines (mandatory)", + " - Required steps for validating changes before committing.", + " - Commit message conventions (read `git log`, or use `type: message` by default).", + " - Pull request description requirements.", + "", + "You can add other sections if they are necessary.", + "If the information required for mandatory sections isn't available due to the workspace being empty or sparse, add TODO text in its place.", + "Optional sections should be scrapped if the information is too thin.", + "", + "Some investigation tips:", + "", + "- Read existing lint configs, tsconfig, and CI workflows to find any style or layout rules.", + '- Search for "TODO", "HACK", "FIXME", "don\'t", "never", "always" in comments.', + "- Examine test files for patterns.", + "- Read PR templates and issue templates if they exist.", + "- Check for existing CONTRIBUTING.md, CODE_OF_CONDUCT.md, or similar documentation files.", + "", + "Some writing tips:", + "", + '- Each "do X" should have a corresponding "don\'t Y" where applicable.', + "- Commands should be easily copy-pastable and tested.", + "- Terms or phrases specific to this project should be explained on first use.", + "- Anything that is against the norm should be explicitly highlighted and called out.", + "", + "Above all things:", + "", + "- The document must be clear and concise. Simple projects should need less than 400 words, but larger and more mature codebases will likely need 700+. Prioritize completeness over brevity.", + "- Don't include useless fluff.", + "- The document must be in Markdown format and use headings for structure.", + "- Give examples where necessary or helpful (commands, directory paths, naming patterns).", + "- Explanations and examples must be correct and specific to this codebase.", + "- Maintain a professional, instructional tone.", + "", + "If the workspace is empty or sparse, ask the user for more information. Avoid hallucinating important decisions. You can provide suggestions to the user for language/technology/tool choices, but always respect the user's decision.", + "", + "- Project description and goals.", + "- Language(s).", + "- Technologies (database?), frameworks, libraries.", + "- Tools.", + "- Any other questions as you deem necessary.", + "", + "For empty or sparse workspaces ONLY, when finished writing/updating AGENTS.md, ask the user if they would like you to do the following:", + "", + "- initialize git IF it's not already set up (e.g., `git init`, `git remote add`, etc.)", + "- write a concise README.md file", + "- generate the bare minimum project scaffolding (e.g., initializing the package manager, writing a minimal build tool config)", + " ", + "", + ].join("\n"), + }, + "mux-diagram": { + "SKILL.md": [ + "---", + "name: mux-diagram", + "description: Mermaid diagram best practices and text-based chart alternatives", + "---", + "", + "# Diagrams & Charts", + "", + "Use this skill when creating diagrams, flowcharts, or chart-like visualizations.", + "", + "## Mermaid (rendered)", + "", + "The app renders fenced `mermaid` code blocks as interactive diagrams.", + "", + "Best practices:", + "", + "- Avoid side-by-side subgraphs (they display too wide)", + "- For comparisons, use separate diagram blocks or single graph with visual separation", + "- When using custom fill colors, include contrasting color property (e.g., `style note fill:#ff6b6b,color:#fff`)", + "- Make good use of visual space: e.g. use inline commentary", + '- Wrap node labels containing brackets or special characters in quotes (e.g., `Display["Message[]"]` not `Display[Message[]]`)', + "", + "Supported diagram types: flowchart, sequence, class, state, ER, Gantt, pie, git graph, mindmap, timeline, sankey, and more. Choose the type that best fits the data.", + "", + "## Text-based alternatives (no rendering required)", + "", + "Not every visualization needs Mermaid. Prefer lightweight formats when they suffice:", + "", + "- **Markdown tables** — best for structured comparisons, feature matrices, or tabular data", + "- **Bulleted/numbered lists** — best for hierarchies, step sequences, or simple trees", + "- **Indented tree notation** — for directory structures or shallow hierarchies", + "- **ASCII/Unicode bars** — quick inline quantitative comparisons (e.g., `████░░ 67%`)", + "", + "Choose Mermaid when relationships, flow, or topology matter. Choose text when the data is tabular or sequential.", + "", + ].join("\n"), + }, + "mux-docs": { + "references/docs/AGENTS.md": [ + "---", + "title: AGENTS.md", + "description: Agent instructions for AI assistants working on the Mux codebase", + "---", + "", + "**Prime directive:** keep edits minimal and token-efficient—say only what conveys actionable signal.", + "", + "## Project Snapshot", + "", + "- `mux`: Electron + React desktop app for parallel agent workflows; UX must be fast, responsive, predictable.", + "- Minor breaking changes are expected, but critical flows must allow upgrade↔downgrade without friction; skip migrations when breakage is tightly scoped.", + "- **Before creating or updating any PR, commit, or public issue**, you **MUST** read the `pull-requests` skill (`agent_skill_read`) for attribution footer requirements and workflow conventions. Do not skip this step.", + "", + "## External Submissions", + "", + "- **Do not submit updates to the Terminal-Bench leaderboard repo directly.** Only provide the user with commands they can run themselves.", + "", + "## Repo Reference", + "", + "- Core files: `src/main.ts`, `src/preload.ts`, `src/App.tsx`, `src/config.ts`.", + "- Up-to-date model names: see `src/common/knownModels.ts` for current provider model IDs.", + "- Persistent data: `~/.mux/config.json`, `~/.mux/src//` (worktrees), `~/.mux/sessions//chat.jsonl`.", + "", + "## Documentation Rules", + "", + "- No free-floating Markdown. User docs live in `docs/` (read `docs/README.md`, add pages to `docs.json` navigation, use standard Markdown + mermaid). Developer notes belong inline as comments.", + " - Exception: the `rfc` folder contains human-written RFCs for implementation planning.", + "- For planning artifacts, use the `propose_plan` tool or inline comments instead of ad-hoc docs.", + "- Do not add new root-level docs without explicit request; during feature work rely on code + tests + inline comments.", + "- External API docs already live inside `/tmp/ai-sdk-docs/**.mdx`; never browse `https://sdk.vercel.ai/docs/ai-sdk-core` directly.", + "", + "### Code Comments", + "", + "- When delivering a user's request, leave their rationale in the code as comments.", + '- Generally, prefer code comments that explain the "why" behind a change.', + '- Still explain the "what" if the code is opaque, surprising, confusing, etc.', + "", + "## Key Features & Performance", + "", + "- Core UX: projects sidebar (left panel), workspace management (local git worktrees or SSH clones), config stored in `~/.mux/config.json`.", + "- Fetch bulk data in one IPC call—no O(n) frontend→backend loops.", + "- **React Compiler enabled** — auto-memoization handles components/hooks; do not add manual `React.memo()`, `useMemo`, or `useCallback` for memoization purposes. Focus instead on fixing unstable object references that the compiler cannot optimize (e.g., `new Set()` in state setters, inline object literals as props).", + "- **useEffect** — Before adding effects, consult the `react-effects` skill. Most effects for derived state, prop resets, or event-triggered logic are anti-patterns.", + "", + "## Tooling & Commands", + "", + "- Package manager: bun only. Use `bun install`, `bun add`, `bun run` (which proxies to Make when relevant). Run `bun install` if modules/types go missing.", + "- Makefile is source of truth (new commands land there, not `package.json`).", + "- Primary targets: `make dev|start|build|lint|lint-fix|fmt|fmt-check|typecheck|test|test-integration|clean|help`.", + "- Full `static-check` includes docs link checking via `mintlify broken-links`.", + "- `.mux/tool_env` is sourced before every `bash` tool call. Use `run_and_report ` when running multiple validation steps in one call.", + "- Do not pipe/redirect/wrap `run_and_report` output; keep helper markers intact so Mux can show clean step status.", + "- `./scripts/wait_pr_ready.sh ` is the preferred tail-end helper after local validation and after you've exhausted useful local work.", + "- `./scripts/wait_pr_checks.sh ` is the checks watcher; `wait_pr_ready.sh` must execute `wait_pr_checks.sh --once` on each loop iteration.", + "- `./scripts/wait_pr_codex.sh ` is the Codex gate used by `wait_pr_ready.sh`.", + "", + "## PR Workflow (Codex)", + "", + "- If a PR has Codex review comments, address + resolve them, then re-request review by commenting `@codex review` on the PR.", + "- Prefer `gh` CLI for GitHub interactions over manual web/curl flows.", + "- In Orchestrator mode, delegate implementation/verification commands to `exec` or `explore` sub-agents and integrate their patches; do not bypass delegation with direct local edits.", + "- In Orchestrator mode, route higher-complexity implementation tasks to `plan` sub-agents so they can research and produce a precise plan before auto-handoff to implementation.", + "", + "- User preference: when work is already on an open PR, push branch updates at the end of each completed change set so the PR stays current.", + '- **PR creation gate:** Do **not** open/create a pull request unless the user explicitly asks (e.g., "open a PR", "create PR", "submit this"). By default, complete local validation, commit/push branch updates as requested, and let the user review before deciding whether to open a PR.', + "", + "> PR readiness is mandatory. You MUST keep iterating until the PR is fully ready.", + '> A PR is fully ready only when: (1) Codex confirms approval (thumbs-up reaction on the PR description or an approval comment like "Didn\'t find any major issues"), (2) all Codex review threads are resolved, and (3) all required CI checks pass.', + "> You MUST NOT report success or stop the loop before these conditions are met.", + "", + "When a PR exists, you MUST remain in this loop until the PR is fully ready:", + "", + "1. Push your latest fixes.", + "2. Run local validation (`make static-check` and targeted tests as needed); in Orchestrator mode, delegate command execution to sub-agents.", + "3. Request review with `@codex review`.", + "4. Run `./scripts/wait_pr_ready.sh ` (which must execute `./scripts/wait_pr_checks.sh --once` while checks are pending).", + "5. If Codex leaves comments, address them (delegate fixes in Orchestrator mode), resolve threads with `./scripts/resolve_pr_comment.sh `, push, and repeat.", + "6. If checks/mergeability fail, fix issues locally (delegate fixes in Orchestrator mode), push, and repeat.", + "", + "The only early-stop exception is when the reviewer is clearly misunderstanding the intended change and further churn would be counterproductive. In that case, leave a clarifying PR comment and pause for human direction.", + "", + "## Testing: HistoryService", + "", + "HistoryService is pure local disk I/O with a single dependency (`getSessionDir`). **Always use a real instance** via `createTestHistoryService()` (`src/node/services/testHistoryService.ts`) rather than mocking.", + "", + "- Partial message lifecycle (`readPartial` / `writePartial` / `deletePartial` / `commitPartial`) is part of HistoryService; there is no separate PartialService.", + "- For pre-seeded data: call `historyService.appendToHistory()` in `beforeEach`", + '- For error injection: use real instance + `spyOn(historyService, "method").mockRejectedValueOnce(...)`', + '- For call tracking: `spyOn(historyService, "method")` without `mockImplementation` — real impl runs, calls are recorded', + "- For assertions: read history back with `getHistoryFromLatestBoundary()` or `getLastMessages()` instead of checking mock calls", + "", + "## Mobile Testing", + "", + "Mobile app tests live in `mobile/src/**/*.test.ts` and use Bun's built-in test runner (`bun test`).", + "", + "- Run mobile tests: `make test-mobile` or `bun run test:mobile`.", + "- The environment currently lacks native mobile testing tools (ADB, iOS Simulator), so focus on unit and integration tests that can run in a Node/Bun environment.", + "- Mobile components do not yet have automated UI tests.", + "", + "## Refactoring & Runtime Etiquette", + "", + "- Use `git mv` to retain history when moving files.", + "", + "## Self-Healing & Crash Resilience", + "", + "- Prefer **self-healing** behavior: if corrupted or invalid data exists in persisted state (e.g., `chat.jsonl`), the system should sanitize or filter it at load/request time rather than failing permanently.", + "- Never let a single malformed line in history brick a workspace—apply defensive filtering in request-building paths so the user can continue working.", + "- When streaming crashes, any incomplete state committed to disk should either be repairable on next load or excluded from provider requests to avoid API validation errors.", + "- **Startup-time initialization must never crash the app.** Wrap in try-catch, use timeouts, fall back silently.", + "", + "## Command Palette & UI Access", + "", + "- Open palette with `Cmd+Shift+P` (mac) / `Ctrl+Shift+P` (win/linux) / `F4`; quick toggle via `Cmd+P` / `Ctrl+P`.", + "- Palette covers workspace mgmt, navigation, chat utils, mode/model switches, slash commands (`/` for suggestions, `>` for actions).", + "", + "## Styling", + "", + "- Never use emoji characters as UI icons or status indicators; emoji rendering varies across platforms and fonts.", + "- Prefer SVG icons (usually from `lucide-react`) or shared icon components under `src/browser/components/Icons/`.", + "- For tool call headers, use `ToolIcon` from `src/browser/components/tools/shared/ToolPrimitives.tsx`.", + "- If a tool/agent provides an emoji string (e.g., `status_set` or `displayStatus`), render via `EmojiIcon` (`src/browser/components/Icons/EmojiIcon.tsx`) instead of rendering the emoji.", + "- If a new emoji appears in tool output, extend `EmojiIcon` to map it to an SVG icon.", + "- Colors defined in `src/browser/styles/globals.css` (`:root @theme` block). Reference via CSS variables (e.g., `var(--color-plan-mode)`), never hardcode hex values.", + "", + "## Security: Renderer HTML & XSS", + "", + "- Treat repo-controlled strings (file paths, diff content, branch names, commit messages) as attacker-controlled input.", + "- Never render attacker-controlled data through `dangerouslySetInnerHTML`, `innerHTML`, `outerHTML`, or `insertAdjacentHTML`.", + "- Prefer React element trees for highlighting (split + `` nodes) so React escaping stays in effect.", + "- If raw HTML/SVG rendering is unavoidable (e.g., Shiki/Mermaid), require explicit sanitization/hardening and document the trust boundary with a `SECURITY AUDIT` comment at the sink.", + "", + "## TypeScript Discipline", + "", + "- Ban `as any`; rely on discriminated unions, type guards, or authored interfaces.", + "- Use `Record` for exhaustive mappings to catch missing cases.", + "- Apply utility types (`Omit`, `Pick`, etc.) to build UI-specific variants of backend types, preventing unnecessary re-renders and clarifying intent.", + "- Let types drive design: prefer discriminated unions for state, minimize runtime checks, and simplify when types feel unwieldy.", + "- Use `using` declarations (or equivalent disposables) for processes, file handles, etc., to ensure cleanup even on errors.", + "- Centralize magic constants under `src/constants/`; share them instead of duplicating values across layers.", + "- Never repeat constant values (like keybinds) in comments—they become stale when the constant changes.", + "- **Avoid `void asyncFn()`** - fire-and-forget async calls hide race conditions. When state is observable by other code (in-memory cache, event emitters), ensure visibility order matches invariants. If memory and disk must stay in sync, persist before updating memory so observers see consistent state.", + "- **Avoid `setTimeout` for component coordination** - racy and fragile; use callbacks or effects.", + "- **Keyboard event propagation** - React's `e.stopPropagation()` only stops synthetic event bubbling; native `window` listeners still fire. Escape-to-interrupt is **safe-by-default** in editable elements (``, `