Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/desktop/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
For Electron interprocess communication, ALWAYS use trpc as defined in `src/lib/trpc`
Please use alias as defined in `tsconfig.json` when possible

## Window-drag regions: `drag` on empty leaves only

Never mark a container with interactive children as `drag` and carve the children out with `no-drag`: Chromium loses the carve-outs when they sit inside masked, scrollable, or CSS-zoomed wrappers (OverflowFadeContainer, ZoomStable), which silently deadens every control under the bar. Instead, put `drag` only on dedicated empty leaf elements — traffic-light spacers and flex fillers (see TopBar, DashboardSidebarHeader, packages/panes TabBar). The worst failure mode then is "empty area not draggable" instead of "chrome swallows clicks".

Comment on lines +5 to +8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document agent responsibilities and capabilities.

This adds interaction guidance, but does not identify agent responsibilities or capabilities as required for desktop AGENTS.md. As per coding guidelines, apps/desktop/**/AGENTS.md must “Document agent responsibilities, capabilities, and interaction patterns in AGENTS.md”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/AGENTS.md` around lines 5 - 8, Expand the desktop AGENTS.md
guidance to document the agent’s responsibilities, capabilities, and interaction
patterns, while preserving the existing window-drag rule for empty leaf
elements. Include actionable expectations for how agents handle desktop UI
interactions and avoid weakening the current Chromium carve-out warning.

Source: Coding guidelines

## Error text must be selectable

The renderer sets `user-select: none` on `body`, so rendered errors need explicit `select-text cursor-text` classes — otherwise users can't copy them into bug reports. (Sonner toasts are exempt; they manage selection themselves.)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,13 +555,15 @@ export function TiptapPromptEditor({
return false;
},
keydown: (_view, event) => {
// Keep bare Cmd/Ctrl+Arrow line-nav inside the editor, but let chords
// that resolve to a real hotkey (e.g. ⌘⌥←/→ = prev/next tab) bubble to
// Keep Cmd/Ctrl+Arrow line-nav inside the editor. Bare chords (incl.
// Shift selection) never bubble — app hotkeys fire in contenteditable
// and would preventDefault the cursor move — while Alt chords that
// resolve to a real hotkey (e.g. ⌘⌥←/→ = prev/next tab) bubble to
// react-hotkeys-hook instead of the editor swallowing them.
if (
(event.key === "ArrowLeft" || event.key === "ArrowRight") &&
(event.metaKey || event.ctrlKey) &&
resolveHotkeyFromEvent(event) === null
(!event.altKey || resolveHotkeyFromEvent(event) === null)
) {
event.stopPropagation();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {

if (!isHydrated) {
return (
<div className="flex h-screen w-screen items-center justify-center bg-background">
<div className="relative flex h-screen w-screen items-center justify-center bg-background">
<div className="drag absolute inset-x-0 top-0 h-12" />
<SupersetLogo className="h-8 w-auto" gradient />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,21 +290,22 @@ export function DashboardSidebarHeader({
pinned row height it matches is Mac-only; elsewhere the row height (h-8)
scales with zoom, so the controls should scale with it. */}
<div
className="drag -mx-3 flex h-8 items-center gap-1.5 pr-3"
style={
isMac
? {
paddingLeft: `${80 / zoomFactor}px`,
height: `${32 / zoomFactor}px`,
}
: { paddingLeft: "8px" }
}
// Window-drag regions live on the empty spacer + filler leaves, never
// on this row: `no-drag` carve-outs under a `drag` ancestor are lost
// inside zoomed wrappers like ZoomStable, deadening the controls.
className="-mx-3 flex h-8 items-center pr-3"
style={isMac ? { height: `${32 / zoomFactor}px` } : undefined}
>
<div
className="drag h-full shrink-0"
style={{ width: isMac ? `${80 / zoomFactor}px` : "8px" }}
/>
<ZoomStable enabled={isMac} className="flex items-center gap-1.5">
<SidebarToggle />
<NavigationControls />
<ResourceConsumption surface="v2" />
</ZoomStable>
<div className="drag h-full min-w-0 flex-1" />
</div>

<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,18 @@ export function TopBar() {

return (
<div
className="drag gap-2 h-12 w-full flex items-center justify-between bg-muted/45 relative dark:bg-muted/35"
// Window-drag regions live on the empty leaf elements (traffic-light
// spacer + title filler), never on this container: `no-drag` carve-outs
// under a `drag` ancestor are lost inside zoomed/masked/scrollable
// wrappers, which makes the whole bar swallow clicks.
className="gap-2 h-12 w-full flex items-center justify-between bg-muted/45 relative dark:bg-muted/35"
style={barStyle}
>
<div
className="flex items-center h-full"
style={{ paddingLeft: trafficLightInset }}
>
<div className="flex items-center h-full">
<div
className="drag h-full shrink-0"
style={{ width: trafficLightInset }}
/>
{!sidebarHostsChrome && (
<ZoomStable enabled={isMac} className="flex items-center gap-1.5">
<SidebarToggle />
Expand All @@ -67,15 +72,15 @@ export function TopBar() {
)}
</div>

<div className="flex min-w-0 flex-1 items-center justify-start">
<div className="drag flex h-full min-w-0 flex-1 items-center justify-start">
{isV2WorkspaceRoute && v2WorkspaceId && (
<V2WorkspaceTitle workspaceId={v2WorkspaceId} />
)}
</div>
Comment on lines +75 to 79

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the v2 title outside the drag region.

V2WorkspaceTitle is rendered inside the drag element, so this is not an empty leaf. Make the title a normal sibling and place a separate empty flex filler beside it.

Proposed fix
-			<div className="drag flex h-full min-w-0 flex-1 items-center justify-start">
+			<div className="flex h-full min-w-0 flex-1 items-center justify-start">
 				{isV2WorkspaceRoute && v2WorkspaceId && (
 					<V2WorkspaceTitle workspaceId={v2WorkspaceId} />
 				)}
+				<div className="drag h-full min-w-0 flex-1" />
 			</div>

As per coding guidelines, apply drag only to dedicated empty leaf elements.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="drag flex h-full min-w-0 flex-1 items-center justify-start">
{isV2WorkspaceRoute && v2WorkspaceId && (
<V2WorkspaceTitle workspaceId={v2WorkspaceId} />
)}
</div>
<div className="flex h-full min-w-0 flex-1 items-center justify-start">
{isV2WorkspaceRoute && v2WorkspaceId && (
<V2WorkspaceTitle workspaceId={v2WorkspaceId} />
)}
<div className="drag h-full min-w-0 flex-1" />
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsx`
around lines 75 - 79, Move V2WorkspaceTitle outside the div with the drag class
in the TopBar layout. Keep it as a normal sibling, and add a separate empty flex
filler element with the drag class beside it so drag applies only to a dedicated
empty leaf while preserving the existing spacing and alignment.

Source: Coding guidelines


<div className="flex items-center gap-3 h-full pr-4 shrink-0">
{!isOnline && (
<div className="no-drag flex items-center gap-1.5 text-xs text-muted-foreground bg-muted px-2 py-1 rounded">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground bg-muted px-2 py-1 rounded">
<HiOutlineWifi className="size-3.5" />
<span>Offline</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export function PRActionHeader({

return (
<div className="@container flex h-12 shrink-0 items-center gap-2 bg-muted/45 px-2 dark:bg-muted/35">
<div className="ml-auto flex items-center gap-2">
<div className="drag h-full min-w-0 flex-1" />
<div className="flex items-center gap-2">
<V2WorkspaceOpenInButton workspaceId={workspaceId} />
<ActionSlot
variant={action}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { WindowControls } from "renderer/routes/_authenticated/_dashboard/compon
import { CommandPalette } from "renderer/screens/main/components/CommandPalette";
import { ResizablePanel } from "renderer/screens/main/components/ResizablePanel";
import { getV2NotificationSourcesForTab } from "renderer/stores/v2-notifications";
import { StateScreenShell } from "../components/StateScreenShell";
import { useWorkspace } from "../providers/WorkspaceProvider";
import { AddTabMenu } from "./components/AddTabMenu";
import { BackgroundTerminalsButton } from "./components/BackgroundTerminalsButton";
Expand Down Expand Up @@ -91,16 +92,18 @@ function V2WorkspacePage() {

if (workspaceStatusQuery.data?.worktreeExists === false) {
return (
<WorkspaceMissingWorktreeState
workspaceId={workspace.id}
workspaceName={workspace.name}
branch={workspace.branch}
worktreePath={workspaceStatusQuery.data?.worktreePath}
onRefresh={() => {
void workspaceStatusQuery.refetch();
}}
isRefreshing={workspaceStatusQuery.isFetching}
/>
<StateScreenShell>
<WorkspaceMissingWorktreeState
workspaceId={workspace.id}
workspaceName={workspace.name}
branch={workspace.branch}
worktreePath={workspaceStatusQuery.data?.worktreePath}
onRefresh={() => {
void workspaceStatusQuery.refetch();
}}
isRefreshing={workspaceStatusQuery.isFetching}
/>
</StateScreenShell>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ReactNode } from "react";

/**
* Wraps full-pane workspace state screens (not-found, creating, host
* incompatible, missing worktree, loading). On the v2 workspace route with an
* expanded sidebar the TopBar is hidden and the pane tab bar — normally the
* window-drag region — isn't rendered either, so these screens must provide
* their own drag strip across the top. Overlaid (not in flow) so the centered
* state content keeps its layout; the strip only covers empty background.
*/
export function StateScreenShell({ children }: { children: ReactNode }) {
return (
<div className="relative h-full w-full">
<div className="drag absolute inset-x-0 top-0 h-12" />
{children}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { StateScreenShell } from "./StateScreenShell";
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/u
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
import { useHostWorkspaces } from "renderer/routes/_authenticated/providers/HostWorkspacesProvider";
import { useWorkspaceTransactionsStore } from "renderer/stores/workspace-creates";
import { StateScreenShell } from "./components/StateScreenShell";
import { WorkspaceCreateErrorState } from "./components/WorkspaceCreateErrorState";
import { WorkspaceCreatingState } from "./components/WorkspaceCreatingState";
import { WorkspaceHostIncompatibleState } from "./components/WorkspaceHostIncompatibleState";
Expand Down Expand Up @@ -76,37 +77,49 @@ function V2WorkspaceLayout() {
const hostStatus = useRemoteHostStatus(workspace);

if (!workspaceId || (!workspace && !isReady)) {
return <div className="flex h-full w-full" />;
return <StateScreenShell>{null}</StateScreenShell>;
}

if (!workspace) {
if (failedEntry) {
return <WorkspaceCreateErrorState entry={failedEntry} />;
return (
<StateScreenShell>
<WorkspaceCreateErrorState entry={failedEntry} />
</StateScreenShell>
);
}
return <WorkspaceNotFoundState workspaceId={workspaceId} />;
return (
<StateScreenShell>
<WorkspaceNotFoundState workspaceId={workspaceId} />
</StateScreenShell>
);
}

if (isCreatePending) {
return (
<WorkspaceCreatingState
name={workspace.name}
branch={workspace.branch}
startedAt={new Date(workspace.createdAt).getTime()}
/>
<StateScreenShell>
<WorkspaceCreatingState
name={workspace.name}
branch={workspace.branch}
startedAt={new Date(workspace.createdAt).getTime()}
/>
</StateScreenShell>
);
}

if (hostStatus.status === "incompatible") {
return (
<WorkspaceHostIncompatibleState
hostName={hostStatus.hostName}
hostVersion={hostStatus.hostVersion}
minVersion={hostStatus.minVersion}
/>
<StateScreenShell>
<WorkspaceHostIncompatibleState
hostName={hostStatus.hostName}
hostVersion={hostStatus.hostVersion}
minVersion={hostStatus.minVersion}
/>
</StateScreenShell>
);
}
if (hostStatus.status === "loading") {
return <div className="flex h-full w-full" />;
return <StateScreenShell>{null}</StateScreenShell>;
}

return (
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src/renderer/routes/_authenticated/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ function AuthenticatedLayout() {
// across re-renders loops the router until the renderer OOMs (#5729).
if (isAuthPending) {
return (
<div className="flex h-screen w-screen flex-col items-center justify-center gap-4 bg-background">
<div className="relative flex h-screen w-screen flex-col items-center justify-center gap-4 bg-background">
<div className="drag absolute inset-x-0 top-0 h-12" />
<Spinner className="size-8" />
{authPendingTimedOut && (
<>
Expand Down Expand Up @@ -235,7 +236,8 @@ function AuthenticatedLayout() {

if (!isSignedIn && hasLocalToken && !isOnline) {
return (
<div className="flex h-screen w-screen flex-col items-center justify-center gap-4 bg-background">
<div className="relative flex h-screen w-screen flex-col items-center justify-center gap-4 bg-background">
<div className="drag absolute inset-x-0 top-0 h-12" />
<HiOutlineWifi className="size-12 text-muted-foreground" />
<div className="text-center">
<h2 className="text-lg font-medium">You're offline</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,13 @@ function SettingsLayout() {

return (
<div className="flex flex-col h-screen w-screen bg-tertiary">
<div
className="drag flex h-12 w-full items-center gap-1.5 bg-tertiary"
style={{
paddingLeft: isMac ? "96px" : "8px",
}}
>
<div className="flex h-12 w-full items-center bg-tertiary">
<div
className="drag h-full shrink-0"
style={{ width: isMac ? "96px" : "8px" }}
/>
<NavigationControls />
<div className="drag h-full min-w-0 flex-1" />
</div>

<div className="flex flex-1 overflow-hidden bg-background">
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/renderer/routes/create-organization/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ export function CreateOrganization() {

return (
<div className="relative flex min-h-screen items-center justify-center bg-background p-4">
{/* Stops short of the top-right Cancel/Sign Out button. */}
<div className="drag absolute left-0 right-32 top-0 h-12" />
<div className="absolute top-4 right-4">
{hasActiveOrganization ? (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,39 +165,18 @@ export function TabBar<TData>({

const insertLineLeft = insertIndex !== null ? insertIndex * TAB_WIDTH : null;

if (tabs.length === 0) {
return (
<div
ref={setRootRef}
// No shade or bottom border with zero tabs: the empty bar blends into
// the content below. `drag`: the bar doubles as the Electron
// window-drag region (empty areas only — interactive clusters opt out
// with `no-drag`).
className="drag group/root-tabs flex h-10 min-w-0 shrink-0 items-stretch"
>
<div className="no-drag flex h-full w-10 shrink-0 items-center justify-center">
<AddTabButton renderAddTabMenu={renderAddTabMenu} />
</div>
<div className="flex min-w-0 flex-1 items-stretch" />
{renderTabBarTrailing && (
<div className="no-drag flex h-full shrink-0 items-center px-1">
{renderTabBarTrailing()}
</div>
)}
</div>
);
}

return (
<div
ref={setRootRef}
// The bottom border is drawn on the bar's leaf elements (inactive tabs,
// the add-button cluster, the trailing region, and the flex filler) rather
// than the root, so the active tab can leave a real 1px gap and flow into
// the content below. `drag`: the bar doubles as the Electron window-drag
// region (empty areas only — the tabs track and button clusters opt out
// with `no-drag`).
className="drag group/root-tabs flex h-10 min-w-0 shrink-0 items-stretch bg-border/30"
// the content below. Only the empty filler right of the tabs is an
// Electron window-drag region — marking the whole bar `drag` and carving
// children out with `no-drag` loses the carve-outs once they sit inside
// the masked/scrollable OverflowFadeContainer, which made the entire bar
// swallow clicks.
className="group/root-tabs flex h-10 min-w-0 shrink-0 items-stretch bg-border/30"
>
<OverflowFadeContainer
observeChildren
Expand Down Expand Up @@ -243,7 +222,7 @@ export function TabBar<TData>({
)}
{/* Carries the bar's bottom border across the empty space to the
right of the tabs (collapses to 0 when the tabs overflow). */}
<div className="h-full flex-1 border-b border-border" />
<div className="drag h-full flex-1 border-b border-border" />
</div>
</OverflowFadeContainer>
{hasHorizontalOverflow && (
Expand Down
Loading