feat: redesign pane layout system (@superset/panes)#3088
Conversation
|
Important Review skippedToo many files! This PR contains 219 files, which is 69 over the limit of 150. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (219)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
8 issues found across 219 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx:15">
P2: Use a cross-platform basename helper (or `path.basename`) instead of splitting on "/" so Windows paths render correctly.
(Based on your team's feedback about using cross-platform path utilities instead of split.) [FEEDBACK_USED]</violation>
</file>
<file name="packages/panes/src/react/components/Workspace/Workspace.tsx">
<violation number="1" location="packages/panes/src/react/components/Workspace/Workspace.tsx:45">
P1: Handle `closeTab` promise rejections in bulk-close handlers instead of firing async calls without await/catch.
(Based on your team's feedback about handling async rejections with await/catch to avoid unhandled promise rejections.) [FEEDBACK_USED]</violation>
</file>
<file name="packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx">
<violation number="1" location="packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx:60">
P2: The add-tab button is rendered even when no add action exists, resulting in a visible but non-functional control.</violation>
</file>
<file name="plans/panes-v2-data-model-redesign.md">
<violation number="1" location="plans/panes-v2-data-model-redesign.md:432">
P2: The package rename step contains a no-op (`@superset/panes` → `@superset/panes`) and misses the real old package name, which can cause the migration to be implemented incorrectly.</violation>
<violation number="2" location="plans/panes-v2-data-model-redesign.md:434">
P2: The desktop import update step is a no-op and should reference the old package name as the source of the rename.</violation>
<violation number="3" location="plans/panes-v2-data-model-redesign.md:580">
P3: Verification steps still reference `packages/pane-layout` after the package rename, which makes the test command path inconsistent with the migration plan.</violation>
<violation number="4" location="plans/panes-v2-data-model-redesign.md:654">
P2: The README example uses `renderToolbarActions`, but the documented `PaneDefinition` interface only defines `renderToolbar`, so the sample API usage is incorrect.</violation>
</file>
<file name="packages/panes/src/react/components/Workspace/components/Tab/Tab.tsx">
<violation number="1" location="packages/panes/src/react/components/Workspace/components/Tab/Tab.tsx:79">
P3: Add a key to the fragment returned from the map so React can reconcile list items correctly (use a keyed Fragment instead of the shorthand).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| onCloseTab={closeTab} | ||
| onCloseOtherTabs={(tabId) => { | ||
| for (const tab of tabs) { | ||
| if (tab.id !== tabId) closeTab(tab.id); |
There was a problem hiding this comment.
P1: Handle closeTab promise rejections in bulk-close handlers instead of firing async calls without await/catch.
(Based on your team's feedback about handling async rejections with await/catch to avoid unhandled promise rejections.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/panes/src/react/components/Workspace/Workspace.tsx, line 45:
<comment>Handle `closeTab` promise rejections in bulk-close handlers instead of firing async calls without await/catch.
(Based on your team's feedback about handling async rejections with await/catch to avoid unhandled promise rejections.) </comment>
<file context>
@@ -0,0 +1,74 @@
+ onCloseTab={closeTab}
+ onCloseOtherTabs={(tabId) => {
+ for (const tab of tabs) {
+ if (tab.id !== tabId) closeTab(tab.id);
+ }
+ }}
</file context>
| PaneViewerData, | ||
| } from "../../types"; | ||
|
|
||
| function getFileTitle(filePath: string): string { |
There was a problem hiding this comment.
P2: Use a cross-platform basename helper (or path.basename) instead of splitting on "/" so Windows paths render correctly.
(Based on your team's feedback about using cross-platform path utilities instead of split.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx, line 15:
<comment>Use a cross-platform basename helper (or `path.basename`) instead of splitting on "/" so Windows paths render correctly.
(Based on your team's feedback about using cross-platform path utilities instead of split.) </comment>
<file context>
@@ -0,0 +1,96 @@
+ PaneViewerData,
+} from "../../types";
+
+function getFileTitle(filePath: string): string {
+ return filePath.split("/").pop() ?? filePath;
+}
</file context>
| </TooltipContent> | ||
| </Tooltip> | ||
| ); | ||
| return button; |
There was a problem hiding this comment.
P2: The add-tab button is rendered even when no add action exists, resulting in a visible but non-functional control.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx, line 60:
<comment>The add-tab button is rendered even when no add action exists, resulting in a visible but non-functional control.</comment>
<file context>
@@ -59,50 +46,46 @@ function AddRootButtonCell<TPaneData>({
- </TooltipContent>
- </Tooltip>
- );
+ return button;
}
</file context>
| return button; | |
| return null; |
|
|
||
| 1. Rename `packages/pane-layout/` → `packages/panes/` and `@superset/panes` → `@superset/panes` in `package.json` | ||
| 2. Delete all existing source files in `src/` (types, store, react components, tests) | ||
| 3. Update the import in `apps/desktop/package.json` from `@superset/panes` to `@superset/panes` |
There was a problem hiding this comment.
P2: The desktop import update step is a no-op and should reference the old package name as the source of the rename.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plans/panes-v2-data-model-redesign.md, line 434:
<comment>The desktop import update step is a no-op and should reference the old package name as the source of the rename.</comment>
<file context>
@@ -0,0 +1,961 @@
+
+1. Rename `packages/pane-layout/` → `packages/panes/` and `@superset/panes` → `@superset/panes` in `package.json`
+2. Delete all existing source files in `src/` (types, store, react components, tests)
+3. Update the import in `apps/desktop/package.json` from `@superset/panes` to `@superset/panes`
+4. Stub `src/index.ts` so the build doesn't break
+
</file context>
| 3. Update the import in `apps/desktop/package.json` from `@superset/panes` to `@superset/panes` | |
| 3. Update the import in `apps/desktop/package.json` from `@superset/pane-layout` to `@superset/panes` |
|
|
||
| ### Phase 1: Rename + gut the package | ||
|
|
||
| 1. Rename `packages/pane-layout/` → `packages/panes/` and `@superset/panes` → `@superset/panes` in `package.json` |
There was a problem hiding this comment.
P2: The package rename step contains a no-op (@superset/panes → @superset/panes) and misses the real old package name, which can cause the migration to be implemented incorrectly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plans/panes-v2-data-model-redesign.md, line 432:
<comment>The package rename step contains a no-op (`@superset/panes` → `@superset/panes`) and misses the real old package name, which can cause the migration to be implemented incorrectly.</comment>
<file context>
@@ -0,0 +1,961 @@
+
+### Phase 1: Rename + gut the package
+
+1. Rename `packages/pane-layout/` → `packages/panes/` and `@superset/panes` → `@superset/panes` in `package.json`
+2. Delete all existing source files in `src/` (types, store, react components, tests)
+3. Update the import in `apps/desktop/package.json` from `@superset/panes` to `@superset/panes`
</file context>
| 1. Rename `packages/pane-layout/` → `packages/panes/` and `@superset/panes` → `@superset/panes` in `package.json` | |
| 1. Rename `packages/pane-layout/` → `packages/panes/` and `@superset/pane-layout` → `@superset/panes` in `package.json` |
| renderPane: (ctx) => <CodeEditor file={ctx.pane.data.filePath} />, | ||
| getTitle: (ctx) => ctx.pane.data.filePath.split("/").pop(), | ||
| getIcon: (ctx) => <FileIcon />, | ||
| renderToolbarActions: (ctx) => ( |
There was a problem hiding this comment.
P2: The README example uses renderToolbarActions, but the documented PaneDefinition interface only defines renderToolbar, so the sample API usage is incorrect.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plans/panes-v2-data-model-redesign.md, line 654:
<comment>The README example uses `renderToolbarActions`, but the documented `PaneDefinition` interface only defines `renderToolbar`, so the sample API usage is incorrect.</comment>
<file context>
@@ -0,0 +1,961 @@
+ renderPane: (ctx) => <CodeEditor file={ctx.pane.data.filePath} />,
+ getTitle: (ctx) => ctx.pane.data.filePath.split("/").pop(),
+ getIcon: (ctx) => <FileIcon />,
+ renderToolbarActions: (ctx) => (
+ !ctx.pane.pinned && <PinButton onClick={() => ctx.actions.pin()} />
+ ),
</file context>
| renderToolbarActions: (ctx) => ( | |
| renderToolbar: (ctx) => ( |
|
|
||
| ## Verification | ||
|
|
||
| 1. `bun test` in `packages/pane-layout` — all tests pass |
There was a problem hiding this comment.
P3: Verification steps still reference packages/pane-layout after the package rename, which makes the test command path inconsistent with the migration plan.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plans/panes-v2-data-model-redesign.md, line 580:
<comment>Verification steps still reference `packages/pane-layout` after the package rename, which makes the test command path inconsistent with the migration plan.</comment>
<file context>
@@ -0,0 +1,961 @@
+
+## Verification
+
+1. `bun test` in `packages/pane-layout` — all tests pass
+2. `bun run typecheck` — all packages type-check
+3. `bun run lint:fix` — clean lint
</file context>
| 1. `bun test` in `packages/pane-layout` — all tests pass | |
| 1. `bun test` in `packages/panes` — all tests pass |
| {node.children.map((child, index) => { | ||
| const key = child.type === "pane" ? child.paneId : child.id; | ||
| return ( | ||
| <> |
There was a problem hiding this comment.
P3: Add a key to the fragment returned from the map so React can reconcile list items correctly (use a keyed Fragment instead of the shorthand).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/panes/src/react/components/Workspace/components/Tab/Tab.tsx, line 79:
<comment>Add a key to the fragment returned from the map so React can reconcile list items correctly (use a keyed Fragment instead of the shorthand).</comment>
<file context>
@@ -0,0 +1,123 @@
+ {node.children.map((child, index) => {
+ const key = child.type === "pane" ? child.paneId : child.id;
+ return (
+ <>
+ {index > 0 && <ResizableHandle key={`handle-${key}`} />}
+ <ResizablePanel key={key} defaultSize={percentages[index]}>
</file context>
…culotte # Conflicts: # bun.lock
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
…ease-age policy @mastra/core 1.17.0 → 1.16.0 (released 2026-03-24) @pierre/diffs 1.1.7 → 1.1.3 (released 2026-03-21)
…t link UI - Unify DevicePicker / ProjectPickerPill / CompareBaseBranchPicker to a shared FORM_PICKER_TRIGGER_CLASS: no background, h-[22px], text-[11px] text-muted-foreground, size-3 icons, align="start" dropdowns. Bump the project trigger thumbnail to size-4; drop the leftover `!` override (twMerge handles it). - DevicePicker: icon-only trigger (aria-label + title surface the name). - Rewrite IssueLinkCommand / PRLinkCommand / GitHubIssueLinkCommand to one codepath each: accept a button as `children`, wrap it in PopoverTrigger, own their open state internally. No more shared plusMenuRef, no more external open/onOpenChange/anchorRef coordination, no manual onPointerDownOutside anchor-guard — Radix handles toggle and dismiss natively so clicking a trigger while its popover is open closes it like every other picker. - v2 NewWorkspace PromptGroup: drop the three popover-open useStates + plusMenuRef + manual toggle handlers. AttachmentButtons becomes a layout shell that renders the three trigger elements as props; each wraps a shared LinkTrigger (tooltip + pill button). - Chat (v1 + v2) + v1 NewWorkspace PromptGroup: remove the link-issue popover wiring (IssueLinkCommand usage, ChatShortcuts' onLinkIssue callback). PlusMenu in chat collapses from a dropdown with attach/link options to a plain attachment button. - Temporarily disable v2 ChatPane render: it predates this PR and is missing ChatServiceProvider (introduced in PR #3088), so chatServiceTrpc has no context in TiptapPromptEditor. Replaced with a "Chat pane is temporarily disabled" placeholder; original render body commented out for quick restoration.
- Drop "previously this did X" / "introduced in PR #3088" / commented-out renderPane block in v2 usePaneRegistry chat pane. - Collapse JSDocs that only restated the function's name (ParentDirectoryPicker, AddRepositoryModals layout blurb, per-method docs on UseFolderFirstImportResult, persistLocalProject). - Tighten the explanatory comments that still earn their keep (pending PROJECT_NOT_SETUP interceptor, PinAndSetupModal conflict state, store onSuccess / forceRepoint prop docs).
* docs: v2 project create/import design + plan
Simplified redesign after PR review. Collapses the earlier three-signal
backing model (cloud + per-host cloud signal + local) into two signals
(cloud + local-only), removes the v2_host_projects cloud table and
Electric sync, drops per-row state decoration on the sidebar, and moves
backing checks to action time (workspace-create modal, error paths).
* feat(trpc): v2Projects.findByGitHubRemote + jwt-scoped create
Adds the cloud-side matcher used by host-service's folder-first import
flow: given a clone URL, returns candidate projects the user has access
to whose GitHub repo matches (case-insensitively). Named findByGitHubRemote
(not findByRemote) because the match is GitHub-specific.
v2Projects.create switches to jwtProcedure with an explicit
organizationId + repoCloneUrl, matching the shape host-service needs to
call from project.create. No existing callers.
parseGitHubRemote moves from packages/host-service to packages/shared so
both cloud tRPC and host-service consume the same implementation.
* feat(host-service): project.create / setup / list / findByPath / remove
Full create/import lifecycle in host-service:
- project.list — DB read of host-service.projects. Pure, no filesystem
probing. Stale paths surface via operation errors, not proactive checks.
- project.findByPath — validate git root, read remote, forward to cloud
v2Projects.findByGitHubRemote. Backs the folder-first import picker.
- project.create — discriminated-union mode (empty/clone/importLocal/
template); Phase 1 ships clone + importLocal only, empty and template
throw NOT_IMPLEMENTED.
- project.setup — discriminated-union mode (clone/import) with
acknowledgeWorkspaceInvalidation gate on the re-point case.
- project.remove — local worktree + repo dir teardown.
Cloud backing (v2_host_projects) is intentionally absent: there is no
per-host cloud signal in this design. Backing is a local-only concept,
checked at action time.
Adds ProjectNotSetupCause to the error formatter so the renderer can
catch throws from workspace.create (next commit) and open the Pin & Set
Up modal inline.
* feat(desktop): add-repository modals at dashboard layout level
Three flows for getting projects onto this device:
- New project — clone a GitHub URL into a chosen parent directory.
Drives project.create(mode=clone).
- Import existing folder — native picker → project.findByPath branches on
candidate count. 0 → name + create (importLocal). 1, not set up here →
auto-advance to project.setup. 1, already set up → destructive re-point
confirmation. >1 → picker modal.
- Pin & set up — clone an existing cloud project onto this device.
Drives project.setup(mode=clone), with forceRepoint entry for repair.
All three modals are mounted once at the dashboard layout level via
AddRepositoryModals, and opened through a small zustand store. Sidebar
header "Add repository" dropdown triggers New project / Import folder.
* feat(desktop): workspaces-tab Available section + folder-first import trigger
Lists cloud projects in the user's active org that aren't pinned
locally. Pin & set up per row runs project.setup. Header dropdown
("Add repository") mirrors the sidebar — "+ New project" +
"Import existing folder." Entry points route through the dashboard-level
AddRepositoryModals via the shared zustand store.
useAvailableV2Projects powers the section: antijoin
v2Projects ∖ v2SidebarProjects scoped to the active organization,
with the existing v2-workspaces search filter applied.
* feat(desktop): workspace-create inline setup + remote-device stub
- Host-service workspaceCreation.{create,checkout,adopt} throw
PROJECT_NOT_SETUP (PRECONDITION_FAILED + cause { kind, projectId })
when this host has no local project row. No more silent auto-clone
into ~/.superset/repos/ — the user explicitly picks where to clone.
- Pending workspace-create page intercepts data.projectNotSetup on the
error, opens the Pin & set up modal pre-filled with the project, and
registers a one-shot onSuccess callback to retry the original intent
once setup resolves. The pending row stays in "creating" through the
modal so the UI doesn't flicker to failed.
- Clicking a remote-device workspace row lands on the new
WorkspaceNotOnThisHostState stub: explains the workspace lives on
another host, offers "Set up here" (opens Pin & set up for the
project) or "Browse workspaces." V2 workspace page checks
host.machineId via live query and renders the stub before mounting
the pane tree, which would otherwise crash on a foreign worktree.
* Fix infinite import
* fix: pre-existing notification test, a11y labels, design doc shape
- notification-manager.test: update expected strings to match source
(strings changed in #3039; test wasn't updated, CI was red on main too)
- DashboardSidebarHeader: aria-label="Add repository" on icon-only
dropdown triggers so screen readers announce them (tooltips don't
count as accessible names)
- docs/design/v2-project-create-import: correct v2Projects.create input
shape (jwt-scoped { organizationId, name, slug, repoCloneUrl })
* feat(desktop): unify new-workspace pickers + link popovers, strip chat link UI
- Unify DevicePicker / ProjectPickerPill / CompareBaseBranchPicker to a
shared FORM_PICKER_TRIGGER_CLASS: no background, h-[22px], text-[11px]
text-muted-foreground, size-3 icons, align="start" dropdowns. Bump the
project trigger thumbnail to size-4; drop the leftover `!` override
(twMerge handles it).
- DevicePicker: icon-only trigger (aria-label + title surface the name).
- Rewrite IssueLinkCommand / PRLinkCommand / GitHubIssueLinkCommand to
one codepath each: accept a button as `children`, wrap it in
PopoverTrigger, own their open state internally. No more shared
plusMenuRef, no more external open/onOpenChange/anchorRef coordination,
no manual onPointerDownOutside anchor-guard — Radix handles toggle and
dismiss natively so clicking a trigger while its popover is open
closes it like every other picker.
- v2 NewWorkspace PromptGroup: drop the three popover-open useStates +
plusMenuRef + manual toggle handlers. AttachmentButtons becomes a
layout shell that renders the three trigger elements as props; each
wraps a shared LinkTrigger (tooltip + pill button).
- Chat (v1 + v2) + v1 NewWorkspace PromptGroup: remove the link-issue
popover wiring (IssueLinkCommand usage, ChatShortcuts' onLinkIssue
callback). PlusMenu in chat collapses from a dropdown with
attach/link options to a plain attachment button.
- Temporarily disable v2 ChatPane render: it predates this PR and is
missing ChatServiceProvider (introduced in PR #3088), so
chatServiceTrpc has no context in TiptapPromptEditor. Replaced with a
"Chat pane is temporarily disabled" placeholder; original render body
commented out for quick restoration.
* feat(desktop): host-scoped project picker with Available / Needs setup sections
The v2 new-workspace picker was listing every cloud project the user
had access to, regardless of whether it was set up on the selected
device. That produced the PROJECT_NOT_SETUP error path on submit —
reviewers flagged the pending-row-stuck-in-creating fallout as a P1.
Root-cause fix: split the project list by selected-host availability.
- `useHostProjectIds(hostTarget)` queries host-service `project.list`
on the chosen device (local via activeHostUrl, remote via relay) and
returns the set of set-up project IDs.
- PromptGroup splits `recentProjects` into `availableProjects` +
`needSetupProjects` using that set; changing the device refetches.
- ProjectPickerPill renders two CommandGroup sections: Available (click
selects) and Needs setup (click opens Pin & set up for that project).
- Pin & set up already invalidates `["project", "list", activeHostUrl]`
on success, so after setup the project flips to Available — user
picks it and continues normally.
While `project.list` is loading or errors, everything falls back to
Available — picker stays usable; any real failure surfaces via the
existing workspace-create error path.
* lint
* fix(desktop): IssueLinkCommand uncontrolled close + PlusMenu aria-label
- IssueLinkCommand: the refactored popover-trigger API made `open` and
`onOpenChange` optional so callers (v2 PromptGroup) could let Radix
manage state. But `handleSelect` only fired the optional controlled
callback, so in uncontrolled mode the popover never closed after
picking an issue. Track state ourselves via a controllable-state
pattern: internal `useState` when the prop is absent, caller's value
when passed. `setOpen` always writes through, so close-on-select
works in both modes.
- PlusMenu: add aria-label="Add attachment" to the icon-only trigger.
Radix Tooltip sets aria-describedby on the trigger, not
aria-labelledby, so screen readers previously announced it as an
unlabeled button.
* refactor(desktop): drop controlled-open props from IssueLinkCommand; extend aria-label fix
- IssueLinkCommand: only caller passes `onSelect + children`, so the
optional open/onOpenChange pass-through was dead code. Simplify to
always-internal state. Radix Popover has no imperative close from
inside its content — owning state is the canonical shadcn/cmdk
pattern, not scaffolding.
- AttachmentButtons (v2): add aria-label to the shared LinkTrigger (so
Link issue / Link GitHub issue / Link pull request all announce a
name) and to the paperclip. Same fix as PlusMenu — Radix Tooltip
sets aria-describedby on the trigger, not aria-labelledby, so
tooltip-only buttons read as unlabeled to screen readers.
* fix(trpc): scope v2Project.findByGitHubRemote + modal picker to active org
host-service is pinned to a single organization at boot (env.ORGANIZATION_ID);
its local projects table has no orgId column. Project discovery was leaking
across orgs:
- v2Project.findByGitHubRemote used ctx.organizationIds (plural, all
accessible orgs). The folder-first picker would surface candidates from
orgs the current host can't set up, producing a confusing NOT_FOUND when
host-service then called v2Project.get with its own org.
- DashboardNewWorkspaceModalContent queried collections.v2Projects with no
org filter. Same over-fetch, same downstream failure.
Align both with the rest of the codebase (v2Project.get / create,
useAvailableV2Projects, useWorkspaceHostOptions) which take/filter by an
explicit active orgId:
- findByGitHubRemote: add organizationId input, authorize it against
ctx.organizationIds (same shape as get/create), filter candidates by it.
- host-service project.findByPath: pass ctx.organizationId through.
- DashboardNewWorkspaceModalContent: .where(eq(projects.organizationId,
activeOrganizationId)) on the live query, matching useAvailableV2Projects.
* Lint
* fix(host-service): clone-then-cloud in project.createFromClone, rollback on cloud failure
Matches the local-first-then-cloud pattern already used by
workspace.create (workspace-creation.ts:860-918, which git-worktree-adds
first then registers cloud with a rollback on failure).
Previously createFromClone called v2Project.create before cloneRepoInto,
so any clone failure (network, bad URL, auth, dir collision) left a cloud
v2_projects row with nothing local backing it on any host. Retrying the
flow with corrected input accumulated more orphans.
Reorder: clone first, register cloud in try/catch, rmSync the freshly-
created clone if cloud-create or persistLocalProject throws.
* fix(host-service): move project.create visibility into GitHub-provisioning modes
Only empty + template modes provision a new GitHub repo and need to tell
the GitHub App whether it should be private or public. clone +
importLocal reuse an existing remote where visibility is already set —
the top-level field was required but ignored for those two paths.
Move `visibility: z.enum(["private", "public"])` into the empty and
template variants of the discriminated union. Drop it from clone/
importLocal callers. Update design doc to match.
* refactor(desktop): use Radix composition for link-command tooltips, drop dead chat-link wiring
Responds to saddlepaddle + Kitenite reviews on PR #3566.
- IssueLinkCommand / PRLinkCommand / GitHubIssueLinkCommand now own the
Popover + Tooltip composition internally via
`PopoverTrigger asChild > TooltipTrigger asChild`. Callers pass a plain
PromptInputButton + a tooltipLabel prop. Removes the LinkTrigger
forwardRef + `{...rest}` spread trick that was sneaking Popover props
through an intermediate Tooltip wrapper.
- Delete the misleading JSDoc at IssueLinkCommand claiming Radix can't be
closed imperatively — PopoverClose exists; the controlled-open pattern
we use is just shadcn's canonical combobox.
- Drop orphaned `_issueLinkOpen` / `_addLinkedIssue` + the
`setIssueLinkOpen` prop threaded through ChatShortcuts in both v1 and
v2 chat, plus the same dead state in v1 NewWorkspaceModal PromptGroup.
- Retire the CHAT_LINK_ISSUE hotkey entry — its only consumer was the
dead setIssueLinkOpen toggle.
* chore: trim past-state narration + what-describing comments
- Drop "previously this did X" / "introduced in PR #3088" / commented-out
renderPane block in v2 usePaneRegistry chat pane.
- Collapse JSDocs that only restated the function's name (ParentDirectoryPicker,
AddRepositoryModals layout blurb, per-method docs on UseFolderFirstImportResult,
persistLocalProject).
- Tighten the explanatory comments that still earn their keep (pending
PROJECT_NOT_SETUP interceptor, PinAndSetupModal conflict state, store
onSuccess / forceRepoint prop docs).
* refactor(desktop): strip v2 discovery/recovery surface to MVP
Two rules for v1:
- Sidebar = pinned projects.
- Workspaces tab = every workspace in the user's active org.
Code deletes:
- V2AvailableProjectsSection, useAvailableV2Projects, useHostProjectIds.
- v2UsersHosts innerJoin in useAccessibleV2Workspaces — tab no longer
drops rows when host access changes.
- Available-section wiring in V2WorkspacesList + v2-workspaces/page.tsx.
- Available / Needs-setup split in ProjectPickerPill and the
openPinAndSetup bridge in PromptGroup. Also removes the wrong-host
bug (cubic AF_o, saddlepaddle CXVQ) as dead code.
- PROJECT_NOT_SETUP recovery loop in the pending page — failure is a
plain toast now.
Docs realigned:
- design/v2-project-create-import.md opens with the two rules and
moves Available / inline setup / backing signals to an explicit
"Out of scope for v1" block.
- plans/20260417-v2-project-create-import-impl.md mirrors the same
deferrals; Phase-1 checklist is now all checked.
Net −537 lines. Typecheck + lint clean.
* refactor(desktop): open remote-host workspaces without gating
Previously any workspace whose hostMachineId didn't match the local
machine landed on a WorkspaceNotOnThisHostState stub. That hid the
workspace from the user entirely when the whole point is to let them
see it. Delete the gate, delete the stub component, and let the
workspace page render for any host. Operations that assume local
filesystem (terminal spawn, local git) fail at the point they run.
Also slims the page's live query — projectGithubOwner, projectName,
hostMachineId etc. were only fed into the stub.
Design doc + plan updated to reflect the no-gating posture.
Resolves saddlepaddle CTvC.
* refactor(desktop): extract FormPickerTrigger component
Addresses saddlepaddle CWlq — the shared style for the three top-of-modal
pickers (Device / Project / Branch) lived as a string constant in
types.ts, which is an odd place for a className and doesn't compose.
Promote it to a named FormPickerTrigger component that encapsulates the
base button styles and accepts extra className + native button props.
The three call sites lose their raw <button type="button"> +
backtick-composed classNames.
Drops FORM_PICKER_TRIGGER_CLASS from types.ts.
* refactor(desktop): remove dead PinAndSetupModal + async-hygiene sweep
PinAndSetupModal had zero remaining callers after the MVP cut — the
pending-page PROJECT_NOT_SETUP interceptor and the Available-section
"Pin & set up" button were the only two. Delete the whole modal,
its store action, useOpenPinAndSetupModal hook, PinAndSetupTarget
type, and the forceRepoint plumbing that existed only to support it.
Also addresses the async-hygiene nits on the surviving surfaces:
- useFolderFirstImport.start wraps selectDirectory.mutateAsync in
try/catch → reportError (coderabbit nmS, cubic op5).
- ParentDirectoryPicker.handleBrowse wraps the same (cubic op8).
- AddRepositoryModals effect adds .catch on startRef.current()
(cubic oqE).
- FolderFirstImportModal keys CandidatePickerContent on repoPath so
selectedId resets per import (coderabbit nmM).
Docs + plan updated to reflect the removed modal + ENOENT recovery
deferral.
Net −205 lines. Typecheck + lint clean.
* refactor(host-service): reject re-pointing instead of confirming it
v1 has no re-point UX. project.setup now treats an existing row as:
- same resolved path → no-op success (idempotent; fixes the false
CONFLICT that cubic/coderabbit flagged on same-path setup).
- different path → CONFLICT with the existing path in the message,
no escape hatch. User must project.remove first if they genuinely
want to move the project.
Drops `acknowledgeWorkspaceInvalidation` from the input, the ack
branch of the CONFLICT guard, and the setupFromClone/setupFromImport
helpers + SetupContext type in handlers.ts (the setup path is small
enough to inline).
Client drops the confirm-repoint state, confirmRepoint method,
ConfirmRepointContent component, and the conflict branch in
SetupInvokeResult — none of which have anything to retry against.
Also fixes the TOCTOU race in cloneRepoInto: replaces
existsSync + rmSync-on-error with mkdirSync (atomic claim) +
rmSync-on-error, so clone failure can't delete a directory this
process didn't create.
Resolves coderabbit nmb, nmd, and cubic oqN.
* fix(desktop): unify workspaces-tab empty state
The onboarding "No workspaces yet" check was reading already-filtered
pinned/others counts, so a search that matched nothing landed on the
onboarding copy instead of the clear-filters UI.
Collapse to a single !hasAnyMatches branch that picks copy + icon
based on hasActiveFilters. Drops the bogus hasAnyWorkspaces check.
Resolves cubic CrwE.
* revert queries
* Clean up dead code
* refactor(desktop): port v1 new-project UI into NewProjectModal
Replace the bespoke name + clone-URL + parent-picker form with v1's
new-project page layout: a Location row (text input + browse button),
three mode tiles (Clone/Empty/Template), and a per-mode form. Only
Clone is wired up; Empty + Template carry "(coming soon)" since v2
project.create throws NOT_IMPLEMENTED for them.
Location auto-populates to ~/.superset/projects via window.getHomeDir.
Project name is derived from the clone URL's last segment so the form
matches v1 (no explicit name field).
ParentDirectoryPicker deleted — the inline Input + folder button
replaces it and there's no other caller.
* feat(db/trpc): decouple v2 projects from GitHub App installs
v2Projects previously required a non-null githubRepositoryId, which
gated project creation on the org having installed the repo via the
GitHub App. Cloning any other repo (public, not installed, or non-
matching) failed at the cloud step after a successful local clone.
Changes:
- githubRepositoryId becomes nullable with ON DELETE SET NULL,
matching v1's projects table.
- repoCloneUrl is added as the canonical source of truth for the
remote URL. Also nullable so empty-mode / local-only projects
without a remote can coexist.
- UNIQUE(organization_id, lower(repo_clone_url)) prevents two
projects from claiming the same repo in one org. NULLs don't
collide, so URL-less projects still work.
- v2Project.create accepts an optional repoCloneUrl, canonicalizes
via parseGitHubRemote, and links a matching github_repositories
row case-insensitively when one exists. Unique-violation (23505)
surfaces as CONFLICT with per-constraint messaging.
- v2Project.findByGitHubRemote matches on v2Projects.repoCloneUrl
directly instead of joining through the installation table, so
unlinked projects are discoverable.
- v2Project.get drops the derived repoCloneUrl — consumers read the
stored column or the joined githubRepository directly.
Migration 0034 bundles all five schema changes. Nullable-safe: no
backfill required for existing rows.
* No candidate thing
* feat(desktop): flag projects not set up on selected host in new-workspace modal
After picking a host in DevicePicker, each project in ProjectPickerPill
shows an amber warning triangle when that host doesn't have the project
set up locally. A matching "Project needs to be set up" note appears
next to the ⌘↵ hint when the currently-selected project needs setup,
so the user sees the blocker before submitting.
Setup state comes from a per-host project.list query (re-added to the
host-service router). The RPC is resolved through the standard
getHostServiceClientByUrl path — local uses activeHostUrl, remote/cloud
goes through the relay. If the host is unreachable we treat setup as
unknown and hide the indicator rather than falsely flagging everything.
Submit path is unchanged: picking a not-set-up project still fires
workspace.create, which throws PROJECT_NOT_SETUP and surfaces as the
existing toast. Inline setup UX is still deferred.
* chore: biome format fix + sync design doc to workspaces-tab filter
- git.ts: biome wants the ghMsg ternary wrapped; main's 27e243b added
the catch block and the CI biome check caught it post-merge.
- design doc: the workspaces tab code filters to hosts the user is
linked to via v2_users_hosts, not every workspace in the org. Update
wording to match what shipped; note teammate workspaces on unshared
hosts are not surfaced in v1.
* docs: move v2 project create/import plan to plans/done
Plan is shipped — move per AGENTS.md rule 7 and drop the rewrite/history
notes in both plan and design doc since the PR body is the canonical
record of what was cut.
* docs: drop rewrite/history notes from plan and design doc
Captured in PR body instead.
* chore: trim restating/navigational comments in project handlers
Summary
@superset/pane-layoutto@superset/panesreact-resizable-panelsfor resize UIPaneActionConfig(split + close with hotkey tooltips)renderTitle,renderHeaderExtras,renderToolbar(full eject) onPaneDefinitionPaneContextandTabContexttypes with enriched layout info (parentDirection,position)Test plan
bun testinpackages/panes→ 57 tests passbun run typecheckinpackages/panes→ cleanSummary by cubic
Redesigned the pane layout to a simpler Tab → Split → Pane model using the new
@superset/panespackage. Integrated into the desktop v2 workspace with weighted splits, resize, a streamlined Add Tab menu (with Show Preset Bar toggle), and a cleaner empty state.New Features
react-resizable-panelswith resize/equalize.PaneDefinitionand configurable headers viaPaneActionConfigwith hotkey tooltips.Refactors
PaneViewer; empty state renamed toWorkspaceEmptyState.ChatPane.@superset/pane-layoutto@superset/panes; host-service CORS now respectsDESKTOP_VITE_PORTforlocalhost/127.0.0.1; downgraded@mastra/core(1.17.0 → 1.16.0) and@pierre/diffs(1.1.7 → 1.1.3) to satisfy CI policy.Written for commit 4d23d5f. Summary will update on new commits.