upstream 取り込み PR #2b: v2 project create + big features / 8 commits + drizzle 0071#406
upstream 取り込み PR #2b: v2 project create + big features / 8 commits + drizzle 0071#406
Conversation
) * 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 superset-sh#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 superset-sh#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 superset-sh#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 superset-sh#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
…superset-sh#3602) * fix(desktop): stop spurious folder picker on settings → dashboard nav The folder-first import flow was plumbed through a zustand counter plus a useEffect in AddRepositoryModals. The counter outlives component mounts, but the effect runs on every mount — so once a user ever clicked "Import existing folder," every subsequent remount of the dashboard layout (including the one that happens when navigating back from Settings) re-ran the effect and re-opened the native directory picker. Remove the indirection. DashboardSidebarHeader now owns useFolderFirstImport and renders FolderFirstImportModal itself; the dropdown item calls folderImport.start() directly on click. No store pulse, no effect. * fix(desktop): drop dead promise-rejection catch on folderImport.start
…3605) * feat(desktop): infer project name from folder, skip naming modal on import When a folder import has no matching cloud project, infer the project name from the folder basename and create the project directly instead of opening a modal asking the user to type a name. Mirrors v1's upsertProject behavior. Users can still rename after import. Removes FolderFirstImportModal and its no-match state machine. * refactor(desktop): extract getBaseName helper with tests Promote the inline inferProjectName in useFolderFirstImport and the duplicate getBaseName in FilesView/utils into a shared helper at renderer/lib/pathBasename. Fixes the trailing-separator edge case in the old FilesView impl (/foo/ now returns "foo" instead of ""). Add 27 unit tests covering posix, Windows, UNC, trailing/consecutive separators, dotfiles, unicode, emoji, and degenerate inputs. * fix(desktop): drop FolderFirstImportModal usage in sidebar header Main added a FolderFirstImportModal reference in DashboardSidebarHeader after this branch removed the modal. Remove the stale import and the two renders — the hook now triggers import directly via start().
…-sh#3606) * feat(desktop): v2 project settings with setup/relocate path Adds a settings surface for cloud-synced (v2) projects so users can point an existing v2 project at a local clone, or move it to a new folder when the original location was wrong. The shared projects list now shows both v1 and v2 entries; v2 rows route to the new dedicated settings tree. - host-service: `project.setup` accepts `allowRelocate` on import mode; when the cloud project has no `repoCloneUrl`, reads the local origin and backfills via a new cloud `v2Project.linkRepoCloneUrl` procedure. - Adds `project.get` + `project.findBackfillConflict` (precheck that surfaces when another org project already owns the same repo). - UI: new `/settings/v2-project/$projectId/general` page with a "Project Location" section that handles both initial setup and relocation (with a confirm dialog warning about orphaned worktrees). On conflict, offers to redirect to the existing project. * fix(v2): decouple slug from project rename Renaming a v2 project previously auto-derived slug from the new name and sent both to v2Project.update, hitting v2_projects_org_slug_unique when another project in the org already owned the derived slug. Only update name; slug stays stable from create time (edit as a separate action if/when needed). Also drops the ad-hoc 23505 handling in v2Project.{create,update, linkRepoCloneUrl} — the real conflict paths all pre-check, so the only writes that can still violate a unique index are rare races where the generic error surface is acceptable. * refactor(desktop): split-pane settings/projects with unified detail route Collapses the separate /settings/project and /settings/v2-project trees into a single /settings/projects layout. Left pane lists v1 + v2 projects (deduped as before); right pane renders the selected project's detail at /settings/projects/\$projectId. The detail route branches on v2 cloud-collection membership and renders the existing ProjectSettings or V2ProjectSettings component. Also removes the in-page "Back to Projects" button from the project settings header — the sidebar already provides navigation. Repoints every caller (workspace sidebar, dashboard sidebar, workspace run button, v2 conflict redirect, settings-sidebar matchRoute) to the new URL. Old route files deleted; cloud/secrets tree left in place as orphan (no external callers). * refactor(desktop): split sidebar into Cloud + Local sections * refactor(desktop): label projects sidebar sections v1/v2, drop icons * refactor(desktop): hide v1/v2 section headers when only one group exists * refactor(desktop): clean up v1 project settings page - Drop icons from every SettingsSection; title alone carries meaning - Collapse redundant labels/descriptions (Branch Prefix, Base Branch) - Remove the Terminal Presets section (it only redirects to /settings/terminal) - Consolidate the three Scripts editors into tabs (Setup/Teardown/Run) - Tighten the header: project name + muted path subtitle * fix(desktop): drop stale title reference in ScriptsEditor drop zone * refactor(desktop): drop dividers, tighten Appearance row - Remove per-section border-t; rely on wider vertical spacing (space-y-8) - Inline Appearance color + icon upload + Hide image into one flex row - Drop redundant TabsContent top margin in ScriptsEditor * refactor(desktop): clean up v2 project settings, add delete action - Strip the self-styled wrapper from ProjectLocationSection; reuse the shared SettingsSection from v1 so layout and spacing match. - Add Danger Zone section with a DeleteProjectSection that calls v2Project.delete (with AlertDialog confirm). On success navigates back to /settings/projects. - Show repoCloneUrl as a muted subtitle in the v2 project header. * fix(desktop): correct relative import path in V2ProjectSettings * refactor(desktop): only show ClickablePath underline + external icon on hover * feat(v2): editable repoCloneUrl in project settings - Extend v2Project.update to accept repoCloneUrl (canonicalize, auto-link github_repositories row, clear via null). - Add a Repository section with an Input + Save button; replaces the read-only subtitle in the header. * Lint * refactor(desktop): show repo URL as text, reveal input only on Edit * refactor(desktop): rename to Host Service Location, show host name Row is structured as [host label | path | action] so we can iterate to show every connected host once multi-host support lands. * style(desktop): biome import reorder * feat(desktop): offer Clone + Import when host needs setup Not-set-up rows now show two actions: Clone here (picks a parent dir and runs setup mode=clone) and Import existing (picks a repo path). Clone is disabled when the project has no linked repoCloneUrl. * fix: PR review feedback for v2 settings - Sidebar dedup was built from v1 groups, hiding any linked v1 row even when its v2 twin wasn't loaded; build set from v2Projects. - project.setup: move rejectIfRepoint above linkRepoCloneUrl so a rejected repoint doesn't permanently link the cloud project to the candidate folder's origin. - v2Project.linkRepoCloneUrl: make the update atomic via 'isNull(repoCloneUrl) AND org match' in WHERE; treat zero rows as CONFLICT to close the two-concurrent-imports race. - ProjectLocationSection: mirror the backfill-conflict precheck on the relocate path, and keep isSubmitting true while the conflict dialog is open.
…#3596) * feat(desktop): v1 review comments open in a pane like v2 Clicking a review comment in v1's ReviewPanel now opens it as a full pane rendering the comment body (markdown, code blocks, tables), instead of opening the GitHub URL. The "Open on GitHub" action remains as a separate icon in the comment row. Adds a "comment" pane type to the v1 tabs store (shared types, createCommentPane/createCommentTabWithPane helpers, openCommentPane action that reuses an existing comment pane per workspace) and a new CommentPane view wired into TabView alongside file/chat/browser panes. * feat(desktop): comment pane toolbar shows avatar + GitHub link * fix(desktop): sync tab name when reusing comment pane + correct section comment
* feat(desktop): port v1 projects + workspaces + sidebar state into v2 First-time v2 launch now migrates the user's v1 local data into v2 cloud + local stores and surfaces a branded summary modal. Covers projects (dedup via GitHub remote, link-or-create), worktrees (adopt into v2_workspaces with legacy-path support), sections (including empty ones), and sidebar ordering with v1→v2 normalization. Idempotent across runs via a new v1_migration_state table. Followups tracked: SUPER-469, SUPER-470, SUPER-471. * fix(desktop): address CodeRabbit feedback on v1→v2 migration - Scope v1_migration_state PK to (organization_id, v1_id, kind) so migrating the same v1 row under different orgs keeps independent state. Regenerated 0041 in place (pre-ship, no chained migration needed). - Reuse v1 section id as v2 section id — deterministic mapping makes the rerun guard in writeV2SidebarState actually dedup; prior crypto.randomUUID would silently duplicate sections on every rerun. - Add sr-only DialogTitle + DialogDescription to V1MigrationSummaryModal for assistive tech; keep the visual welcome header as-is. - Stable React keys in summary entry lists (include array index). - Log warning when v2Project.findByGitHubRemote returns multiple candidates so the constraint-slip case is diagnosable post-hoc.
…eep only schema The upstream cherry-pick added 0041_v1_migration_state.sql and journal entry, but fork already has idx 0041–0070. Remove the SQL file and journal entry; schema change (v1MigrationState table) is kept for drizzle-kit generate later.
* fix(desktop): v2 file-open honors CMD+O editor choice v2 click-to-open-externally (FilesTab tree, DiffPane entries, terminal links, file pane header) was always hitting Cursor regardless of the editor the user picked in the v2 Open In dropdown. Root cause: the server-side `resolveDefaultEditor` only consults v1 localDb tables, so it never saw the v2 choice (which lives client-side in tanstack-db `v2SidebarProjects.defaultOpenInApp`). The hook now forwards that choice to `openFileInEditor` as an explicit `app` override. Also fixes a silent path-resolution bug that produced doubled paths like `<worktree>/apps/desktop/apps/desktop/...` when relative diff paths were opened without a cwd: `resolvePath` now throws `RelativePathWithoutCwdError` instead of falling back to Electron's `process.cwd()`, and the tRPC input field is renamed `cwd` → `worktreePath` so the intent is explicit. Extracted `useV2ProjectDefaultApp(projectId)` as the single source of truth for reading/writing the v2 preference — used by both `V2OpenInMenuButton` (write on open) and `useOpenInExternalEditor` (read on open). * docs(desktop): mention worktreePath not cwd in withResolveGuard comment * lint
…sh#3683) * feat(desktop): v2 Changes file list shift/cmd-click policy Mirror the v2 Files tab click policy on the Changes sidebar: plain click reuses an existing diff pane (or smart-places into the active tab), shift+click opens the diff in a new tab, and cmd/ctrl+click opens the file in the external editor. * refactor(desktop): centralize v2 sidebar modifier-click intent Extract the meta/ctrl -> editor, shift -> new tab, else -> select dispatch into a shared getSidebarClickIntent() helper so the Files tab tree item and the Changes file row resolve modifiers the same way. * refactor(desktop): use path aliases for deep-relative imports * feat(desktop): right-click menu + hover tooltip for v2 sidebar items Surface the click/shift-click/cmd-click actions on v2 file and diff rows so they stay discoverable: add a hover tooltip that lists the modifier shortcuts, and a right-click menu with Open / Open in New Tab / Open in Editor (with shortcut hints) plus the existing path actions. Changes rows get the menu for the first time; Files rows gain explicit Open / Open in New Tab / Open in Editor entries in place of the unwired "Open to the Side" placeholder. * fix(desktop): nest Tooltip inside ContextMenuTrigger so right-click works * fix(desktop): gate cmd-click to files only in v2 Files sidebar
PR #2b 8 cherry-pick 後の typecheck エラーを修正:
- TiptapPromptEditor / QuestionInputOverlay モジュールを upstream から補完
(cherry-pick で取りこぼされた新規ファイル群)
- settings/project/$projectId/cloud/{page,secrets/page}.tsx の route
string を fork の実 route に合わせて修正
- ChatInputFooter 型 props の不整合解消
- V2WorkspacesList の prop 型調整
- ReviewPanel の upstream comment pane 追加 API を fork の 19 procedure と
併存させる形で統合
- packages/local-db: drizzle-kit generate で 0071_v1_migration_state
migration SQL と snapshot を生成 (upstream 0041 は fork 最大 idx 70 と
衝突するため、schema.ts の重複 export を削除して drizzle-kit で再生成)
- packages/ui/src/components/ai-elements/prompt-input.tsx の型拡張
bun run typecheck: 全 27/27 pass
bun run lint: pass
fork 固有機能 (19 procedure, TERMINAL_OPTIONS, SUPERSET_WORKSPACE_NAME,
dmg.size 4g, 依存) 全健在
|
Important Review skippedToo many files! This PR contains 152 files, which is 2 over the limit of 150. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (152)
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 |
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
…uperset-sh#3039 test が期待する upstream 新仕様 ("Awaiting Response" / "is waiting for your reply") に実装を合わせる。cherry-pick 取り込み時に test のみ upstream 側に置き換わり、実装が fork 旧仕様 ("Input Needed" / "needs your attention") のままだった不整合を修正。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 80639ed104
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex final review で HIGH 指摘された branch-type workspace の migration skip 問題を修正。 upstream の migrate.ts は `workspace.worktreeId` がある場合のみ `v1WorktreePath` を算出していたため、fork 独自の v1 `type="branch"` workspace (worktreeId: null) では adopt() に worktreePath が渡らず、 host-service 側の listWorktreeBranches() が primary working tree を skip する仕様と重なり、"worktree_not_registered" で失われていた。 branch-type では project.mainRepoPath を明示的に worktreePath として 渡すことで、adopt() が primary 側の checked-out branch を正しく解決 できるようにする。
P1: v2Projects.get に repoCloneUrl fallback を復元 - migration 0034 は repo_clone_url を nullable で追加したが既存 row を backfill しないため、pre-migration projects は repoCloneUrl = null のまま。下流の clone/setup パスが BAD_REQUEST で失敗する問題。 - githubRepository.fullName から `https://github.com/<fullName>.git` を導出する fallback を追加。 P2: external.openFileInEditor の no-editor fallback でエラー surface - shell.openPath は失敗時に throw せず非空文字列を返す仕様。 - その戻り値をチェックして TRPCError を throw、上流にエラー通知。
Summary
upstream 取り込み PR #2b: 大物 feature / DB migration バッチ 8 commits + 修復。Codex 事前調査を経て scheduled agent runs (superset-sh#3576) と link-click (superset-sh#3600) は別 PR #2c に分離。
v2 project create (superset-sh#3566) は 59 files / +7314 lines の最大規模 cherry-pick で、fork 固有機能 (19 procedure, TERMINAL_OPTIONS, SUPERSET_WORKSPACE_NAME など) を全て維持しつつ取り込み。
取り込み内容(8 commits)
2b7c6119cfeat(v2): minimal project create/import for workspaces (feat(v2): minimal project create/import for workspaces superset-sh/superset#3566)cb6036dfdfix(desktop): stop spurious folder picker (fix(desktop): stop spurious folder picker on settings → dashboard nav superset-sh/superset#3602) ← PR fix(desktop): prevent browser webview reload on tab/workspace switch #2 から送り5d895f12cfeat(desktop): infer project name from folder on import (feat(desktop): infer project name from folder on import superset-sh/superset#3605) ← PR fix(desktop): prevent browser webview reload on tab/workspace switch #2 から送り1141a7164feat(desktop): v2 project settings with setup/relocate path (feat(desktop): v2 project settings with setup/relocate path superset-sh/superset#3606)4b8887284feat(desktop): v1 review comments open in a pane like v2 (feat(desktop): v1 review comments open in a pane like v2 superset-sh/superset#3596)5aead3361feat(desktop): port v1 projects + workspaces into v2 (feat(desktop): port v1 projects + workspaces into v2 superset-sh/superset#3670)37a47ed26fix(desktop): v2 file-open honors CMD+O editor choice (fix(desktop): v2 file-open honors CMD+O editor choice superset-sh/superset#3674) ← PR feat(desktop): AIコミットメッセージ自動生成 #4 から送りa510c187bfeat(desktop): v2 Changes file list shift/cmd-click policy (feat(desktop): v2 Changes file list shift/cmd-click policy superset-sh/superset#3683)追加 fix commits(3 件)
683ab6f41chore(local-db): remove upstream 0041_v1_migration_state migration (number collision)(本 commit)fix(desktop): resolve PR #2b typecheck errors post cherry-pick99a1ca66f(Chat UX Enhancements superset-sh/superset#3039 Chat UX) からの必要ファイル補完 (TiptapPromptEditor, QuestionInputOverlay 等 9 ファイル)/settings/project/→/settings/projects/)neonProjectIdprojection 追加drizzle-kit generateで0071_v1_migration_stateを再生成PR #2c へ分離(別 PR で処理)
Codex 事前調査で分離判定:
e2b9f42aa996(feat(automations): scheduled agent runs (end-to-end) superset-sh/superset#3576 scheduled agent runs) — 189 files / +13131 lines、API/CLI/TRPC/workflows 全部d006f60a0c82(feat(desktop): configurable link-click behavior in v2 superset-sh/superset#3600 configurable link-click v2) — MarkdownEditor rename が feat(automations): scheduled agent runs (end-to-end) superset-sh/superset#3576 と絡むため #2c へFork 側のコンフリクト解決
v2 project create (superset-sh#3566) の 9 files conflict 解消:
ChatInputFooter.tsx(v1/v2): TiptapPromptEditor/QuestionInputOverlay 採用layout.tsx:AddRepositoryModalsとKeepAliveWorkspaces両方維持V2WorkspacesList.tsx/v2-workspaces/page.tsx: upstream 版 (pinned/others 分離) 採用error-types.ts:ProjectNotSetupCauseコメント追加project.ts/v2-project.ts: upstream 方式採用packages/shared/package.json:simple-git-unsafeエクスポート + dedupe 維持v1 review pane (superset-sh#3596): fork 固有 19 GitHub 拡張 mutations を全て維持 +
openCommentPane/handleOpenCommentを追加v1→v2 migration (superset-sh#3670):
apps/desktop/src/lib/trpc/routers/index.ts:migration: createMigrationRouter()を既存 router に追加packages/local-db/src/schema/schema.ts:v1MigrationStateテーブル定義を 1 箇所に集約0041_v1_migration_state.sqlと snapshot/journal entry を削除bunx drizzle-kit generate --name="v1_migration_state"で 0071_v1_migration_state として再生成v2 file-open (superset-sh#3674):
external/index.ts:withResolveGuardパターン +nodePath.isAbsoluteチェックusePathActions/FileSearchResultItem/FileTreeItem:cwd→worktreePath変更 (fork のbranch/copySupersetLinkパラメータ維持)v2 Changes file list (superset-sh#3683):
electronTrpcimport を維持しつつ新規 hooks imports を統合Fork 固有機能ヘルスチェック
baseline 比較で 全項目健在:
ansi_up/@vscode/ripgrep/@xyflow/react依存TERMINAL_OPTIONS,SUPERSET_WORKSPACE_NAME,moonshot-ai.kimi-code1.5.10(PR feat(desktop): ポートリストのリサイズ・フィルタ機能 #6 で bump 済み)dmg.size="4g"(electron-builder.ts)predev,patch-dev-protocol.ts,build:browser-mcpTest plan
bun install正常完了bun run typecheckグリーン (27/27)bun run lintグリーンbun run --filter @superset/desktop buildexit 0 成功 (dmg/zip/blockmap 生成)workspaces.githubExtended配下で健在次の PR
e2b9f42scheduled agent runs +d006f60link-click → これで upstream 0 commits 達成 (完遂目標)