chore: sync with upstream#2
Conversation
There was a problem hiding this comment.
1 issue found across 351 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/scripts/copy-native-modules.ts">
<violation number="1" location="apps/desktop/scripts/copy-native-modules.ts:185">
P1: `copyExactModuleVersion` is called with a semver range (`dependencyRange`) but it expects an exact version. When the Bun store lookup falls through, `fetchNpmPackage` constructs an invalid npm tarball URL (e.g. `pkg-^4.0.0.tgz`) that always 404s. The `findBunStoreFolderName` fallback may also silently copy a version that doesn't satisfy the range. Consider resolving the range to an exact installed version before passing it here.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| console.log( | ||
| ` ${dependencyName}: top-level version missing; materializing ${dependencyRange} at the workspace root`, | ||
| ); | ||
| copyExactModuleVersion( |
There was a problem hiding this comment.
P1: copyExactModuleVersion is called with a semver range (dependencyRange) but it expects an exact version. When the Bun store lookup falls through, fetchNpmPackage constructs an invalid npm tarball URL (e.g. pkg-^4.0.0.tgz) that always 404s. The findBunStoreFolderName fallback may also silently copy a version that doesn't satisfy the range. Consider resolving the range to an exact installed version before passing it here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/scripts/copy-native-modules.ts, line 185:
<comment>`copyExactModuleVersion` is called with a semver range (`dependencyRange`) but it expects an exact version. When the Bun store lookup falls through, `fetchNpmPackage` constructs an invalid npm tarball URL (e.g. `pkg-^4.0.0.tgz`) that always 404s. The `findBunStoreFolderName` fallback may also silently copy a version that doesn't satisfy the range. Consider resolving the range to an exact installed version before passing it here.</comment>
<file context>
@@ -110,6 +111,119 @@ function copyModuleIfSymlink(
+ console.log(
+ ` ${dependencyName}: top-level version missing; materializing ${dependencyRange} at the workspace root`,
+ );
+ copyExactModuleVersion(
+ nodeModulesDir,
+ dependencyName,
</file context>
…uperset-sh#3392) * fix(desktop): enable modal focus trap on v1 + v2 workspace dialogs Applies the same fix as superset-sh#3276 to both v1 (NewWorkspaceModal) and v2 (DashboardNewWorkspaceModal) Dialogs so alt-tabbing back restores focus to the modal. * Lint
superset-sh#3398) * feat(desktop): modifier+click in file tree opens external editor / new tab - Meta/Ctrl+click: open in external editor (works on files and folders) - Shift+click: open file in a new internal tab - Plain click: unchanged (preview pane / toggle folder) Adds the behavior to both v1 (FileTreeItem, FileSearchResultItem) and v2 (WorkspaceFilesTreeItem) file trees. v2 guards the external-editor path with an isLocalWorkspace check so remote hosts toast instead of silently calling local electron IPC against a path that does not exist. * fix(desktop): v2 file-tree meta+click reads defaultOpenInApp from v2SidebarProjects Align with the per-project v2 "Open In" pattern established in superset-sh#3393: resolve the editor from collections.v2SidebarProjects.defaultOpenInApp (falling back to finder) and call electronTrpc.external.openInApp — instead of openFileInEditor, which does v1-era backend resolution off the projects.defaultApp column that v2 doesn't write to. * fix(desktop): address PR review on v2 file-tree meta+click - Gate the isLocal check on the host live query actually resolving, so a meta+click before the collections query settles silently no-ops instead of toasting a misleading "only supported on local workspaces" error (cubic/greptile/coderabbit). - Use a separator-safe split when deriving the new-tab title from a file path so Windows host paths (backslash-separated) don't produce a full path as the tab title (cubic).
… tRPC batch (superset-sh#3400) The workspace tRPC client used httpBatchLink, which returns a batched response only when every procedure in the batch resolves. On workspace switch, FilesTab's filesystem.listDirectory (ms) was batched with WorkspaceSidebar's git.getStatus (5-10s on a large monorepo), so the file tree stalled behind git for every switch. Swap to httpBatchStreamLink so responses stream as each procedure completes, allow the trpc-accept header through CORS on the host service, and drop a now-unnecessary staleTime:0 override on listDirectory.fetch.
…3401) * feat(desktop): focus neighbor workspace after v2 delete When the active v2 workspace is deleted, navigate to the previous workspace in visual order, falling back to the next, then to / if no neighbors exist. Mirrors v1 behavior (without the wrap-around). Also switches the delete flow to toast.promise for feedback. * fix(desktop): address PR feedback on v2 delete focus - Isolate navigation from toast.promise so a router rejection after a successful delete doesn't surface a misleading "Failed to delete" toast (greptile). - Break sort ties in getFlattenedV2WorkspaceIds with section-before- workspace to match the sidebar's ordering when tabOrder collides (cubic).
…on, terminal override respect (superset-sh#3391) * attempt fix * Test and doc * fix(desktop): terminal & migration respect hotkey overrides - Rebuild the hotkey reverse index on override store changes so the terminal forwards the user's current bindings instead of frozen defaults. Fixes swallowed-keystroke on rebound-away defaults and dead-binding on new overrides. - Sanitize migrated overrides: canonicalize and drop malformed strings (`ctrl+control`, `ctrl+shift+@`, `meta+[`) that the pre-fix recorder could produce. - Document the meta-on-non-Mac policy (Windows OS intercept, Linux WM ownership). * feat(desktop): allow meta (Win/Super) bindings on non-Mac with OS warning Drop the blanket reject and extend OS_RESERVED for Windows shell intercepts (meta+d/e/l/r/tab) so users get a warning instead of a silent block. Linux WM configs vary too much to predict — trust the user and let them rebind if a chord doesn't fire. * refactor(desktop): unify event↔chord matching via shared helpers Audit against react-hotkeys-hook and internal usages found two more consumers comparing via event.key, which breaks the same punctuation / layout / rebind cases fixed in the recorder: - utils/utils.ts isTerminalReservedEvent had its own event.key-based TERMINAL_RESERVED set with ctrl+\\. - Terminal/helpers.ts matchesKey used event.key to check the CLEAR_TERMINAL rebind — silently wrong for any punctuation rebind. Consolidation: - Export eventToChord and new matchesChord(event, chord) as the single canonical event↔chord matcher. - Export TERMINAL_RESERVED_CHORDS as the single source of truth. - Rewrite isTerminalReservedEvent around them. - Replace matchesKey() with matchesChord(). - Remove duplicated TERMINAL_RESERVED from useRecordHotkeys.ts. Also adds tests for eventToChord, matchesChord, and isTerminalReservedEvent parity. Updates plan doc with the cleanup. * docs(desktop): rewrite hotkey fix plan for concise review * chore(desktop/hotkeys): deslop — tighten comments, remove dead wrappers - Merge canonicalizeChord / normalizeChord into one exported function (the wrapper was pointless indirection). - Drop section-banner comments (`// Helpers`, `// Hook`) and comments that restated code (`// Must include ctrl or meta…`). - Tighten JSDocs to convey intent in one line where possible. - display.ts: drop duplicate short arrow entries (`up: "↑"`) — the normalizeToken call aliases them to canonical names already. Extract isModifier type guard to kill the cast repetition. Net -35 LOC, same behavior, same 62 tests passing. * fix(desktop/hotkeys): canonicalize reserved-chord tables + review cleanups Addresses PR review feedback: - Bug: OS_RESERVED[\"windows\"] had \"ctrl+alt+delete\" which never matched because canonicalization sorts modifiers alphabetically. Wrap both OS_RESERVED and TERMINAL_RESERVED_CHORDS in .map(canonicalizeChord) at build time so multi-modifier entries can't silently miss the reserved warning. OS_RESERVED values switched from array to Set. - Extract sanitizeOverride into utils/sanitizeOverride.ts so the test imports the real implementation instead of a near-copy. - Test stub: preserve explicit \`code: undefined\` so the synthetic-event guard in captureHotkeyFromEvent is actually exercised (not the empty-string branch). - Resolver tests: resolve a sample hotkey once at describe scope and throw if HOTKEYS has no bound defaults, instead of each test silently no-op-ing via \`if (!def) return\`. - Add regression test asserting canonicalizeChord(\"ctrl+alt+delete\") sorts to \"alt+ctrl+delete\". * fix(desktop/hotkeys): re-sanitize localStorage for users who migrated pre-fix The original guard short-circuited whenever \`hotkey-overrides\` existed in localStorage, which meant any user who ran the migration before the Bug 5 sanitizer shipped would keep their corrupt entries (\`ctrl+control\`, \`ctrl+shift+@\`, \`meta+[\`) forever. Gate migration/sanitization on a separate \`hotkey-overrides-sanitized-v1\` marker instead: - No marker + no store → tRPC migration with sanitizer. - No marker + has store → in-place re-sanitization of existing entries. - Marker present → done, skip. The marker is only set once per user; new bindings written by the fixed recorder can't be corrupt so no further passes are needed. * fix(desktop/hotkeys): bump migration marker key so pre-sanitizer users re-migrate Use a dedicated `hotkey-overrides-migrated-v2` marker separate from the Zustand persist key. Users who ran the migration on the pre-sanitizer build (~1 day window) will re-run once and get their corrupt entries (`ctrl+control`, `ctrl+shift+@`, `meta+[`) dropped. Mark set only on success paths (or when there's nothing to migrate), not in the catch branch — transient tRPC failures at boot retry next launch instead of silently giving up on the user's legacy bindings.
…3415) * Fix code TUI copy * Handle more keyboard shortcuts * fix(desktop): match VS Code terminal clipboard handling
* fix v1 split pane startup sizing * Handle background
…h#3403) * feat(desktop): directional pane focus via Cmd+Alt+Arrow (v2) Adds spatial pane navigation to v2 workspaces: Cmd+Alt+Arrow jumps focus to the visually adjacent pane in that direction (no wrap at edges). Reclaims Cmd+Alt+Arrow from the retired PREV/NEXT_TAB and PREV/NEXT_WORKSPACE shortcuts — tabs still cycle via Ctrl+Tab and both tabs and workspaces keep Cmd+Alt+1..9 jump-to-N. The spatial neighbor util walks up the LayoutNode path to find the deepest ancestor split whose axis matches the arrow, then descends into the sibling subtree picking the near-edge leaf. * fix(panes): preserve cross-axis alignment during spatial neighbor descent findEdgePaneId previously fell through to node.first on any perpendicular split encountered while descending into the sibling subtree, losing the source pane's row/column position. In a 2x2 grid this caused the directional focus move to land on the wrong pane depending on how the grid was grouped in the layout tree (e.g. in a rows-first 2x2, down from top-right landed on bottom-left). Track the source pane's path below the pivot ancestor as an alignment path and consume one entry per perpendicular-split descent, so the descent mirrors the source's cross-axis choices. Adds unit tests for getSpatialNeighborPaneId covering single pane, simple horizontal/vertical splits, edge no-wrap, and both groupings of the 2x2 grid. * refactor(desktop): drop linear PREV/NEXT_PANE now that directional nav exists The 4-way FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} shortcuts supersede linear pane cycling. Removing PREV/NEXT_PANE also frees ctrl+shift+alt+Arrow on Windows/Linux, which dodges the Intel HD Graphics screen-rotation driver shortcut that steals ctrl+alt+Arrow at the OS level. - Remove PREV_PANE/NEXT_PANE from the hotkey registry and both v1/v2 handler sites. - Remap FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} on Windows/Linux from ctrl+alt+Arrow to ctrl+shift+alt+Arrow. - Delete now-unused getNextPaneId/getPreviousPaneId helpers from renderer/stores/tabs/utils.ts. Users who relied on linear cycling can re-add it via settings once the unbound-default hotkey support lands as a follow-up.
…perset-sh#3421) The hover icon swap was silently broken because isOpen was read via a one-shot collections.get() call instead of a live query, so the component never re-rendered when rightSidebarOpen changed.
…ne title resolution (superset-sh#3420) * feat(desktop): v2 diff viewer opens in its own tab + pane-derived tab titles openDiffPane now scans all tabs for an existing diff pane (focus + scroll) and falls back to addTab, so clicking a file in the Changes sidebar never hijacks the focused editor tab. Collapses tab/pane title resolution onto a single canonical field: PaneDefinition.getTitle is tightened to (pane) => string, file's rich JSX moves into the existing renderTitle hook, and a new resolveTabTitle helper powers both the tab bar and the "Move to Tab" context menu. tab.titleOverride is reserved for user renames; every auto-default caller is stripped and multi-pane tabs fall back to "Tab N" instead of "tab-<uuid>". * feat(desktop): pane-derived tab titles reserve tab.titleOverride for user renames Preset execution and workspace bootstrap were baking preset.name / terminal.label onto tab.titleOverride, which meant those names persisted misleadingly after a tab was split and couldn't be distinguished from a real user rename. Move both to the pane's titleOverride instead, and teach resolveTabTitle to read pane.titleOverride before falling through to getTitle() for single-pane tabs. tab.titleOverride is now written only by the tab-bar rename action; splitting a named tab flips the label to "Tab N" while the pane keeps its name in its header, and user renames still win over everything. * fix(desktop): browser.getTitle falls back to "Browser" for about:blank Unnavigated browser panes had their pane header fall through to pane.id (a raw UUID) because getTitle returned undefined for about:blank and the old titleOverride: "Browser" default was removed along with the other auto-default titleOverride writes. * fix(desktop): browser tab title uses URL.host to preserve port URL.hostname drops the port, so localhost:3000 and localhost:4000 both rendered as "localhost" in the tab bar. URL.host keeps the port when one is explicitly set.
…ditor) (superset-sh#3418) react-hotkeys-hook skips events whose target is contentEditable unless enableOnContentEditable is set. CodeMirror 6 renders its editor as a contenteditable element, so app chords like cmd+w stopped firing when focus was in the v1 file editor — meta+w never reached the CLOSE_TERMINAL handler that closes the focused pane. Default enableOnContentEditable: true in useHotkey, mirroring the existing enableOnFormTags default. Callers can still opt out via options.
…kspace (superset-sh#3422) * feat(desktop/hotkeys): allow unbound defaults; restore PREV/NEXT tab+workspace Widen PlatformKey and HotkeyDefinition so hotkey entries can register with a null chord per platform. Downstream consumers were already null-safe from superset-sh#3391 (useBinding, buildRegisteredAppChords, formatHotkeyDisplay, sanitizeOverride, HotkeyMenuShortcut), so the schema widening is the only structural change needed. Re-introduce PREV_TAB, NEXT_TAB, PREV_WORKSPACE, NEXT_WORKSPACE as registered-but-unbound entries so users who want tab/workspace neighbor navigation can rebind them in Settings → Keyboard. PR superset-sh#3403 removed these to free Cmd+Alt+Arrow for directional pane focus; this restores the hotkey IDs (and their v1/v2 handlers) without claiming any default chord. Users with pre-superset-sh#3403 overrides for these IDs will transparently get their bindings back since the override is preserved in localStorage. - Null-guard canonicalizeChord(defaultKey) in useRecordHotkeys so recording a new chord for an unbound hotkey no longer throws. - Replace the force-cast in resolveHotkeyFromEvent.test.ts sample picker with a type predicate so sampleDef.key narrows to string honestly instead of lying about the widened schema. * fix(desktop/hotkeys): restore prevIndex in v2 PREV_WORKSPACE handler
…rs selectable (superset-sh#3432) Global `user-select: none` on body blocks copying error text on full-page error states. Matches the pattern already used by v1 WorkspaceInitializingView.
superset-sh#3457) Gate `onFileLinkClick` behind metaKey/ctrlKey in v1 helpers so file-path links once again require cmd/ctrl+click, matching pre-superset-sh#3382 behavior. The shared `LinkDetectorAdapter` (used by v2) still activates on plain click and is untouched.
…et-sh#3458) Replace the 5s tray polling interval with event-driven refresh: `mouse-enter` + host-service status-changed trigger an async rebuild. Drop the hostInfoCache Map — updateTrayMenu is now async and fetches host.info inline with a 2s AbortController timeout, so there is no stale state to manage. Unwrap the superjson envelope (`result.data.json`) that the original polling code missed, which was causing every org to render as a UUID slice (previously) or "Loading…" (after the first pass of this fix). Closes superset-sh#3454
…uperset-sh#3397) * Branch discovery * UI * branch discovery * feat(desktop): paginated branch picker with tabs, checkout, and open actions Rework the v2 new-workspace modal's branch picker to handle thousands of branches and surface existing workspaces directly. - Host-service searchBranches: cursor pagination, refresh flag gated by 30s TTL, server-side filter (branch/worktree), reflog-based recency, git-worktree-derived worktreePath, isCheckedOut flag. - New checkout procedure: git worktree add <path> <existing-branch> (no -b, no dedup). Auto-resolves local vs origin/<branch>. - Renderer: useInfiniteQuery + IntersectionObserver for infinite scroll; tabs for Branch vs Worktree filters; hover-reveal action buttons — Check out on Branch tab (disabled when isCheckedOut), Open on Worktree tab (navigates to existing workspace). Click row body still sets base branch for the prompt-driven fork flow. * Concise doc * fix(desktop): remote-only checkout tracking, host-scoped open, a11y - Remote-only Check out: use `git worktree add --track -b <branch>` so the worktree lands on a proper local tracking branch instead of detached HEAD. Auto-track only fires for the short name; passing `origin/<branch>` is treated as a commit-ish. - Open existing: scope the branch→workspace map by host id so the Worktree tab doesn't navigate to a workspace on the wrong host when the same branch exists on multiple hosts. - Picker a11y: reveal row action buttons on focus-within, not just hover. Swap the disabled span for a focusable button with aria-disabled so keyboard users can reach the "already checked out" tooltip. * fix(desktop): fall back to branch-only workspace lookup when host id unresolved The previous host-scoped filter returned an empty map when targetHostId was null (collections still loading, or no v2_hosts row matched the renderer's machineId). That broke Open on the Worktree tab in the common single-host case. Keep the host filter as a soft constraint — apply it only when a host id resolves; otherwise match by (projectId, branch). * feat(desktop): adopt orphan worktrees on Worktree-tab Create A `.worktrees/<branch>` directory can exist on disk without a matching `workspaces` row (older flows, partial failures, manual creation). The Worktree tab now surfaces these explicitly: rows with a workspace row show "Open" and navigate; rows without one show "Create" and adopt the worktree by registering cloud + local workspace rows, then navigate. - Host-service: `searchBranches` emits `hasWorkspace` per row (joined against workspaces table for this project on this host). - Host-service: new `adopt` procedure — no git ops, just cloud + local DB registration over an existing worktree path. Idempotent. - Renderer: Worktree-tab button label + handler switch on `hasWorkspace`. New `useAdoptWorktree` hook wraps the mutation. - Docs: Worktree-tab row-state table updated, procedure list updated. * Style * Handle git ref * doc change * refactor(host-service): discriminated ResolvedRef for git refs Closes a class of bugs where ref kind was inferred from short-name prefix (`startsWith("origin/")`) — fragile because a local branch can legitimately be named `origin/foo` and would be misclassified as a remote-tracking ref. - New `runtime/git/refs.ts`: `ResolvedRef` discriminated union (local / remote-tracking / tag / head) with template-literal `fullRef` types. `resolveRef` probes against full refnames, classification is unambiguous. Helpers: `asLocalRef`, `asRemoteRef`, `resolveDefaultBranchName`. - `resolveStartPoint` now returns `ResolvedRef`. `create` and `checkout` switch on `kind` instead of inspecting ref strings. Fixes the `origin/foo` local-branch misclassification in `checkout` and the spurious-fetch case in `create`. - `searchBranches` and `listBranchNames` parsing: derive isLocal/isRemote and the user-facing name from the FULL refname's structural prefix (`refs/heads/`, `refs/remotes/origin/`) — not from the short form. - New regression test: local branch named `origin/foo` resolves as `local`, not `remote-tracking`. - New lint script `scripts/check-git-ref-strings.sh` blocks `.startsWith("origin/")` and `.replace("origin/", ...)` outside of `runtime/git/refs.ts`. V1 desktop tRPC routers excluded with a doc'd follow-up — they have pre-existing instances of the bug class. - Pattern documented in `packages/host-service/GIT_REFS.md` along with prior art (GitHub Desktop, VSCode). * fix(host-service, desktop): resolveRef remote-prefix handling + a11y/UX cleanups Addresses review carryovers and a real-bug new finding in the ref helper. - resolveRef: accept `<remote>/<branch>` shortform input. Previously `resolveRef("origin/foo")` probed `refs/remotes/origin/origin/foo` (double-prefix) and returned null. Strip the remote prefix on the remote-probe path only — local probe is unchanged so a local branch literally named `origin/foo` still wins. New refs.test.ts covers all documented input shapes (12 tests). - listWorktreeBranches: drop dead `currentPath === worktreesRoot` branch (a Superset worktree is always a child, never the root). Replace silent catch with console.warn so a failed `git worktree list` is observable. - Picker action buttons: drop native `disabled` attribute on the "Check out" disabled state; keep aria-disabled. Native disabled buttons block pointer events so the Tooltip explaining "already checked out elsewhere" never opens. - IntersectionObserver guard: track an in-flight flag inside the callback so a re-mounted observer can't cascade-load all remaining pages when the sentinel stays in the root margin after a page loads. - BranchRow type: import from useBranchContext barrel instead of re-declaring locally — keeps single source of truth from inferRouterOutputs. - Picker actions (Create, Check out): respect `draft.workspaceName` when set instead of hardcoding `branchName`. Falls back to branchName. * fix(host-service): resolveStartPoint prefers local over remote-tracking A workspace branch like `agreeable-ermine` is local-only. If a stale `refs/remotes/origin/agreeable-ermine` cached ref exists (one-off push, missed prune), the previous remote-first logic silently picked it for the worktree start point — and `git worktree add ... origin/<branch>` failed with "fatal: invalid reference" when the cached ref no longer resolved. Local-first matches user intent: they pick branches from a list of refs they can see locally — they expect to fork from that exact local state. Remote freshness is bounded by the picker's refresh-on-modal-open already triggering `git fetch --prune`. Remote-tracking is still the fallback when local doesn't exist (the remote-only branch case that `Check out` handles correctly). * feat(workspace-creation): plumb baseBranchSource hint from picker to server The picker already knows whether the row the user clicked was local or remote-only (`isLocal` / `isRemote`). Carry that fact through the create flow as `baseBranchSource: "local" | "remote-tracking"` instead of re-resolving server-side. The local-first fix in resolveStartPoint stays as a safer default, but the hint removes the guesswork entirely: - The server can't be misled by a stale cached `refs/remotes/origin/<x>` because it doesn't probe — it uses what the picker said. - The user's intent (the row they actually saw + clicked) is what gets forked from. No silent priority inversion between client and server. Plumbing: - Server: `composer.baseBranchSource?: "local" | "remote-tracking"`. When set, `buildStartPointFromHint` constructs a ResolvedRef directly, skipping `resolveStartPoint`. Without it, falls back to probing (preserves behavior for non-picker callers). - Picker `onSelectCompareBaseBranch` now takes `(name, source)`. Source is `branch.isLocal ? "local" : "remote-tracking"`. - Draft + pending-workspace schema gain `baseBranchSource` (nullable for legacy rows). Pending page retry path passes the field through. * feat(workspace-creation): unify fork/checkout/adopt under pending-page flow Three buttons (Submit / Check out / Create) now share one path: the modal inserts a pendingWorkspaces row tagged with `intent` and navigates to /pending/<id>; the page owns the host-service mutation, the loading/error UX, and retry. Previously only fork went through this path — checkout and adopt were fire-and-forget and silently failed when the slow paths (clone, fetch, setup script) errored. - Schema: pendingWorkspaceSchema gains `intent: "fork" | "checkout" | "adopt"` discriminator and `warnings: string[]`. Fork-only fields (prompt, baseBranch, linkedIssues, linkedPR, attachmentCount) are default-empty for checkout/adopt rows. v2 isn't released — no migration concerns. - Pending page: `useFireIntent` switches on intent and calls the right mutation (createWorkspace / checkoutWorkspace / adoptWorktree). Fires once on first mount via a ref guard; same hook drives Retry. Adopt has no host-side progress steps so the page shows a generic spinner for that intent. Warnings array surfaced on success (especially useful for checkout's "setup terminal failed" case). - PromptGroup: handleCheckout / handleAdoptWorktree now insert a pending row + navigate via shared `insertPendingAndNavigate`. The fire-and-forget mutation calls in this component are gone; so are the local imports of useCheckoutDashboardWorkspace / useAdoptWorktree. - useSubmitWorkspace: drops the post-navigate fire path. It's pure "insert + navigate" now, matching checkout/adopt. mapLinkedContext is inlined into the page where the linkedContext is built per intent. - Doc: PENDING_FLOW.md captures the design — three intents, one path, the schema rationale, retry semantics, and the explicit decisions on adopt UX and warnings display. * fix(desktop): use client collection as source of truth for workspace existence After deleting a workspace, reopening the modal and clicking the branch on the Worktree tab surfaced "Could not find existing workspace for this branch." Root cause: two disagreeing sources: - Server-side `hasWorkspace` (host-service local `workspaces` table): still true, because the cloud delete doesn't cascade to the host DB. - Client `workspaceByBranch` (v2Workspaces collection, cloud-synced): false, because the cloud row was soft-deleted. Picker rendered "Open" from the server snapshot → click hit the client lookup → miss → error toast. Fix: pass `hasWorkspaceForBranch(name)` into the picker from PromptGroup, computed against the cloud-synced collection. The client is authoritative for "does a workspace row currently exist?" — the server field is a host-side cache that can be stale after deletion. Side benefit: an orphan worktree (disk dir without a cloud row) now correctly gets the "Create" button, and clicking it adopts the existing worktree into a fresh workspace. Deleted-but-left-on-disk workspaces become resurrectable with one click. Host-side cleanup (removing the local `workspaces` row + the worktree directory when a workspace is deleted) is a separate concern — flagged for a follow-up. * docs: add workspace delete design (host-service orchestrates cleanup) Captures the design for full-resource workspace deletion — worktree on disk, optional branch, cloud row, local host-DB row. Host-service orchestrates in that order (hard-to-reverse side first). Covers failure modes, UX decisions (delete-branch opt-in, confirm only when dirty), and what replaces the current cloud-only v2Workspace.delete. Not implemented here — separate PR picks this up. * fix(host-service): adopt always creates a fresh cloud row The old short-circuit returned `existingLocal.id` whenever a local `workspaces` row matched the (project, branch) pair, without calling cloud. After a prior workspace was hard-deleted in the cloud, the host sqlite row leaked — so re-adopting the worktree echoed a phantom id whose cloud row no longer existed. Renderer navigated to it, Electric never synced anything back, sidebar/target page showed "not found" indefinitely. Fix: drop the local-idempotency shortcut. Always call `v2Workspace.create`; the new cloudRow.id is authoritative. If a stale local row exists for this (project, branch), replace it with the fresh mapping. Also: pending page now waits for the newly-created cloud row to sync into the Electric-backed `v2Workspaces` collection before navigating, with a 3s fallback. Fast intents (adopt) were beating the sync to the punch and landing on /v2-workspace/<id> before the row was visible. Docs: replaced the draft delete design with the canonical version — v2 delete unifies through `workspaceCleanup.destroy`; implementation owned by a follow-up PR. * fix: reset pending-page refs on navigation, clear progress on early throws, harden lint script Three PR-review follow-ups: - Pending page: reset `firedRef` / `navigatedRef` when `pendingId` changes under a mounted component. Previously, navigating from one pending page to another kept the flags stuck at true → the second page never fired its mutation and sat in "creating" forever. - Checkout procedure: `clearProgress(pendingId)` on the empty-branch and `safeResolveWorktreePath` error paths. Without this, a stale pending row lingered until the 5-minute sweeper removed it. - check-git-ref-strings.sh: distinguish ripgrep exit codes. Exit 1 (no matches) still passes; exit 2+ (unreadable file, bad regex) now fails loudly. `2>/dev/null` previously masked real scan failures as clean passes. * refactor(desktop): tighten pending-row schema, extract + test intent payload builders Addresses two code-smell findings from the PR review: - pendingWorkspaceSchema had `hostTarget`, `linkedIssues`, `linkedPR` as `z.unknown()`, forcing `as`-casts at every read site and hiding malformed rows until a consumer crashed. Replaced with structured zod shapes (discriminatedUnion for hostTarget, typed object schemas for linkedIssues/linkedPR). Drops the casts in the pending page and the submit hook, and any malformed row now fails zod parsing instead of reaching the dispatch. - Intent dispatch logic in useFireIntent (the switch that translates pending-row state into mutation inputs per intent) was untested and tangled up with IO. Extracted the payload-building into pure helpers in buildIntentPayload.ts and added a contract suite (11 tests, all passing) covering: - mapLinkedContextFromPending: internal filtering, github filtering, skip-missing-fields, linkedPR passthrough, empty-input - buildForkPayload: full fork shape, empty-prompt → undefined, attachments plumbing, host-tracking hostTarget survives - buildCheckoutPayload: branch + runSetupScript only - buildAdoptPayload: minimal shape Pending page's useFireIntent becomes a thin wrapper: build payload, call mutation, update collection. Bonus cleanup: the `hostUrl` derivation block collapses from a nested ternary with 3 string casts to a plain discriminant switch now that the type is known. * docs: consolidate branch discovery, pending flow, and delete designs Merges three separate design docs in the modal directory into one `DESIGN.md` covering the full v2 workspace-creation architecture: - Branch discovery (data shape, server flow, client flow) — was BRANCH_DISCOVERY_DESIGN.md - Actions per row (Fork / Check out / Open / Create, authority for hasWorkspace) - Workspace creation flow (three intents, pending-row schema, pending-page dispatch, baseBranchSource plumbing, per-mutation details) — was PENDING_FLOW.md - Workspace delete (follow-up PR spec with host-service ownership principle) — was WORKSPACE_DELETE_DESIGN.md - Invariants + enforcement (authority decisions, tests, lint, type guarantees) Cross-links to `packages/host-service/GIT_REFS.md` which stays separate — it's a cross-cutting pattern doc, not modal-specific. Code-comment pointers updated from the old filenames to the new consolidated DESIGN.md with section references. * docs: rename DESIGN.md → V2_WORKSPACE_CREATION.md, move to apps/desktop root The doc covers a multi-module subsystem (modal + pending page + host-service procedures) — it doesn't belong nested deep inside the modal directory where 'DESIGN.md' is also too generic to be findable. Moved to apps/desktop/V2_WORKSPACE_CREATION.md, parallel to AGENTS.md and CLAUDE.md, where system-level architecture docs live in this repo. Name says what it covers. Code-comment pointers updated to use the new path. Internal cross-link to GIT_REFS.md fixed for the new depth. * docs: fold WORKSPACE_CREATION_FALLBACK research into V2_WORKSPACE_CREATION The fallback doc was the original design proposal for resolveStartPoint + --no-track + targeted single-ref fetch. All shipped in this PR. The spec/diff sections are now stale — implementation lives in code. Kept the prior-art comparison (VS Code, T3Code, GitHub Desktop, v1) as an appendix in V2_WORKSPACE_CREATION.md — that's the part future contributors might re-derive without reference. Updated the comparison table to reflect what we actually shipped (local-first instead of remote-first; targeted single-ref fetch). Added the rationale for each rejection (why not VS Code's upstream lookup, why not T3Code's gh-merge-base, etc.). Removed packages/host-service/WORKSPACE_CREATION_FALLBACK.md. * refactor(desktop): extract picker controller + linked-context hooks from PromptGroup PromptGroup was 592 lines mixing form state, agent prefs, branch picker state + handlers, link handlers, hotkey wiring, and JSX. Split the picker controller and link handlers into co-located hooks; PromptGroup is now 360 lines focused on form composition. - useBranchPickerController: owns picker state (search, filter), the branch-context query, host-id resolution + workspace lookup, and the three per-row action handlers (Open / Check out / Adopt). Returns a `pickerProps` bag the caller spreads into <CompareBaseBranchPicker>. Hides ~70 lines of plumbing behind one hook call. - useLinkedContext: bundles addLinkedIssue / addLinkedGitHubIssue / removeLinkedIssue / setLinkedPR / removeLinkedPR. Pure delegation over updateDraft. - Collapse the PromptGroup / PromptGroupInner indirection. There was no error boundary, provider, or conditional in the wrapper — just a pass-through. Removed. Behavior unchanged. Typecheck + lint clean; 11 pending-page tests pass.
…h#3459) MCP clients on spec 2025-06-18 send resource=<mcp-url> in authorize/token requests. better-auth 1.5.6 validates that against validAudiences and our allowlist only contained the bare API origin, so tokens were rejected with "requested resource invalid". Add the MCP endpoint to validAudiences and to the JWT verifier's audience list.
…rride migrator (superset-sh#3460) * feat(desktop/hotkeys): directional pane focus for v1 + Mac alt modifier Restores keyboard pane navigation to v1 workspaces with the same Cmd+Alt+Arrow chords v2 uses, via a standalone spatial neighbor walker over v1's MosaicNode<string> tree (no cross-package coupling to v2). Also lets the recorder accept alt-only chords on Mac and warns when bound alt-only chords could mask typing special characters. * feat(desktop/hotkeys): best-effort migrator for v1 event.key overrides The v1 recorder stored chords from event.key (layout-dependent raw glyphs like "meta+,", "ctrl+shift+@", "meta+alt+ª"), while the new store matches on event.code names. Extends sanitizeOverride with a token rewrite pre-pass covering US-ANSI punctuation, shifted glyphs, and macOS Option dead-keys; gates the Option dead-key table behind a navigator.keyboard-based US-layout probe so non-US Mac users aren't silently rebound to the wrong physical key.
…perset-sh#3461) * chore(desktop): auto-restart host-service on bundle change in dev Watches the built host-service.js in NODE_ENV=development and restarts running instances via the coordinator when electron-vite rewrites the bundle. Fast edit→reload loop for packages/host-service and src/main/host-service without restarting Electron. Not true HMR — in-memory host-service state (PTYs, watchers, chat streams) is torn down on each reload. * fix(desktop): wait for host-service bundle to stabilize before reload Rollup rewrites host-service.js via unlink+rewrite, so the fs.watch fires while the file is missing or partially written. The coordinator now polls statSync until size is non-zero and stable for 150ms (5s deadline) before calling restartAll, avoiding MODULE_NOT_FOUND on respawn.
…erset-sh#3464) The v2-workspace layout rendered child routes without WorkspaceTrpcProvider during the tick between workspaceId changing and the useLiveQuery join resolving. If the outgoing page hadn't finished unmounting, its hooks (TerminalPane, useGitChangeEvents, etc.) crashed with "useWorkspaceClient must be used within WorkspaceClientProvider". Hold the render until useLiveQuery reports isReady so the new workspace mounts into a valid provider on the same tick, and show an explicit "Workspace not found" state when the collection has hydrated but the id doesn't resolve.
* Delete
* plans(workspace-delete): refine design per review
- Drop unsupported claim about v2 branch ephemerality; deleteBranch
defaults off, no persisted preference
- Add Host ownership section: v2Workspaces.hostId is 1:1 so destroy
is host-local; FORBIDDEN from other hosts
- Replace "remote-host cleanup" with "abandoned-host cleanup" as the
real follow-up (cross-device delete isn't a thing under the schema)
* feat(host-service): workspaceCleanup.destroy
Adds a single unified delete path for v2 workspaces. Sequences:
terminals → teardown → worktree → branch → cloud → host sqlite.
- runTeardown silently executes .superset/teardown.sh with a 60s
timeout, SIGKILL on timeout, 4KB output tail capture; returns
status + exitCode + tail.
- disposeSessionsByWorkspaceId kills all active PTYs for a workspace
(releases file locks before worktree remove / teardown).
- Typed errors: CONFLICT for dirty worktrees (prompt force: true),
INTERNAL_SERVER_ERROR with cause.kind=TEARDOWN_FAILED surfaced to
the client via errorFormatter so the renderer can show outputTail.
- deleteBranch opts in (default false); force also upgrades
`git branch -d` to `-D`.
- Cloud failure is swallowed as a warning — disk is already clean,
cloud self-heals on next sync.
* feat(desktop): wire v2 sidebar delete through workspaceCleanup.destroy
- useDestroyWorkspace hook resolves the workspace's host-service URL
and calls workspaceCleanup.destroy, normalizing TRPC errors into a
typed union (conflict / teardown-failed / unknown) for the dialog.
- DashboardSidebarDeleteDialog becomes self-contained: owns the
mutation, the "delete local branch" checkbox (off by default), and
renders the force-retry UI for dirty worktrees and teardown
failures (with outputTail preview).
- useDashboardSidebarWorkspaceItemActions drops the direct
apiTrpcClient.v2Workspace.delete call; post-success cleanup moves
to an onDeleted callback (sidebar removal + focus navigation).
Path B (renderer → cloud v2Workspace.delete) is now unused in desktop.
* fix(workspace-delete): review follow-ups
- Run teardown script via createTerminalSessionInternal (same PTY
primitive v2 uses for interactive terminals) so the script inherits
the user's shell environment — login rcfiles, PATH, nvm/rbenv, etc.
Silent: session is transient and never surfaced as a visible pane.
Output captured via pty.onData; timeout via pty.kill() so behavior
is cross-platform (no process-group SIGKILL on Windows).
- Guard the timeout callback with `if (settled) return;` to prevent
the event-loop race where a successful 60s exit is misreported as
a timeout.
- disposeSession now always marks the DB row disposed even when the
in-memory session is missing, so zombie `active` rows left from
crashes get reconciled during bulk workspace cleanup.
- Export TEARDOWN_TIMEOUT_MS from host-service; renderer uses it in
the dialog copy instead of a literal `60`. Renderer also runs the
output tail through strip-ansi (host-service returns raw PTY bytes;
sanitizing is a presentation concern).
- TeardownFailureCause.signal is now `number | null` (Unix signal
number from node-pty) rather than a bogus NodeJS.Signals cast.
- JSDoc: INTERNAL_ERROR → INTERNAL_SERVER_ERROR on the workspace-
cleanup error contract.
- Tag the audit doc's ASCII-diagram fence as `text` for markdownlint.
- biome lint:fix.
* Refactor
* fix(workspace-delete): optimistic-close UX (no in-dialog wait)
Mirrors v1's pattern: the destroy runs in the background under a
toast.loading → success/error, the dialog closes immediately on
confirm, and only re-opens when the user has a decision to make.
- Confirm/force-retry close the dialog optimistically; mutation
continues with a toast for feedback. No more frozen "Deleting..."
for the up-to-60s teardown duration.
- CONFLICT and TEARDOWN_FAILED reopen the dialog in the matching
error pane so the user can force-retry with full context. The
branch opt-in is preserved across the reopen.
- Unknown errors fall through to toast.error — no reopen.
- isPending state and "Deleting..." button copy dropped from all
panes (never visible since the dialog closes optimistically).
* fix(workspace-delete): move TEARDOWN_TIMEOUT_MS to @superset/shared
The renderer used to value-import TEARDOWN_TIMEOUT_MS from
@superset/host-service, which dragged node-pty (and transitively
@parcel/watcher's native .node binary) into the renderer bundle
— esbuild had no loader for .node and failed the build.
@superset/shared is renderer-safe. Put the constant there so both
host-service and the renderer import the same single source of
truth without crossing the bundler boundary.
* fix(workspace-delete): auth + tolerate missing host-sqlite row
Two bugs unblocking the v2 delete flow:
1. v2Workspace.delete cloud procedure used protectedProcedure
(session), so host-service's JWT auth returned 401. Switch to
jwtProcedure (mirrors v2Workspace.create) with an org-membership
check derived from the workspace's organizationId. Returns
alreadyGone: true idempotently when the row is missing.
2. workspaceCleanup.destroy threw NOT_FOUND when the host-sqlite
row was missing, even though the workspace exists in the cloud
and belongs to this host. That state happens when the workspace
was created via a flow that didn't register it locally or the
host DB was reset — the user has no way to delete without this
fix. Now each disk step is gated on having the local row + a
resolvable project; missing rows surface as warnings and cloud
cleanup still proceeds.
Also makes ctx.api absence a warning instead of an upfront
PRECONDITION_FAILED, so local-only cleanup still completes.
* plans(workspace-delete): cloud-as-commit-point redesign
Reshape destroy as a linear preflight → teardown → cloud → local-
cleanup saga. No tombstones, no reconciler, no persistent state.
- Any failure before the cloud step leaves the workspace untouched.
- Any failure after is a warning (local orphans are cheap).
- Force skips preflight + teardown; cleanup is always --force past
the commit point.
- Three phases cleanly separated so future changes (auto-retry,
cross-device reconcile) land at a single seam.
Also updates the teardown contract to reflect the PTY-via-
createTerminalSessionInternal approach (env parity w/ v2 setup;
cross-platform timeout via pty.kill) and pins TEARDOWN_TIMEOUT_MS
to @superset/shared so the renderer doesn't drag node-pty into
its bundle.
* fix(workspace-delete): cloud is the commit point
Reorders workspaceCleanup.destroy into three phases so the failure
semantics match the plan doc:
0. Preflight (dirty worktree) — throws CONFLICT if !force
1. Teardown script — throws TEARDOWN_FAILED if !force
2. Cloud delete ← commit point; throws passthrough on failure
3. Local cleanup (PTYs, worktree, branch, host sqlite) — warnings only
Any failure in phases 0–2 leaves the workspace fully intact. The
user retries when they've addressed the cause (committed work, fixed
teardown, reconnected cloud, re-authed). Phase 3 is best-effort; local
orphans are cheap and surface as warnings.
No tombstones, no reconciler, no persistent state. Step 3b always
uses --force since we're past the commit point regardless of the
input flag.
* fix(workspace-delete): preserve TeardownFailureCause fields over wire
tRPC's `new TRPCError({ cause })` runs non-Error causes through
getCauseFromUnknown() which wraps them in a synthetic
UnknownCauseError (Error subclass) while copying fields as own
properties. Superjson's transformer recognises it as Error and
serialises only { name, message, stack } — our { kind, exitCode,
signal, timedOut, outputTail } fields were being dropped on the wire,
so the renderer saw an Error with no teardown metadata and
TeardownFailedPane crashed on stripAnsi(undefined).
The errorFormatter now rebuilds a plain object from the cause fields
so superjson serialises it as an object and the renderer gets the
full TeardownFailureCause.
Also defensive outputTail ?? "" in the pane so stripAnsi never
sees undefined.
* feat(ui/alert-dialog): make content text selectable
Desktop globals set user-select: none on html/body for a native feel.
Alert dialogs carry user-facing messages (error details, teardown
output tails, descriptions) that users need to copy — e.g. paste an
error into chat or grep a log snippet.
Applied at the component level so every AlertDialog in the app
benefits, not just the workspace-delete error panes.
* chore(workspace-delete): tighten stale comments + copy
- TeardownFailedPane: drop the defensive-round-trip note now that the
errorFormatter reliably serializes the cause as a plain object;
keep just the `?? ""` coalesce and the WHY of stripAnsi.
- ConflictPane: was phrased as "uncommitted or unlocked work" when
CONFLICT is now only thrown by the preflight dirty check (locked-
worktree cases fall to warnings). Rewrites copy + JSDoc to match.
- teardown.ts: timeout message said SIGKILL, but `pty.kill()` with no
args sends SIGHUP on Unix. Drop the false specificity.
* chore(workspace-delete): dev-only hook to simulate cloud failure
SUPERSET_DEBUG_FAIL_CLOUD_DELETE makes workspaceCleanup.destroy throw
at phase 2 (cloud delete) without needing to stop the cloud API dev
server. Used to verify the destroy saga's passthrough-on-cloud-fail
behavior end-to-end.
No-op when unset; guarded solely by env-var presence (truthy).
* Revert "chore(workspace-delete): dev-only hook to simulate cloud failure"
This reverts commit 5c88d88.
* fix(workspace-delete): review sweep
Addresses open PR comments:
- branchDeleted (P1, cubic): track via local flag inside the try
block instead of computing from preconditions, so the field reports
whether git branch actually succeeded.
- Teardown timeout can hang (coderabbit major): if pty.kill() doesn't
cause onExit to fire (zombie PTY), settle the promise directly after
a 2s grace window so workspaceCleanup.destroy never blocks forever.
- formatTeardownReason signal-terminated (coderabbit minor): handle
`exitCode === null && signal !== null && !timedOut` with a dedicated
"terminated by signal N" branch instead of falling through to
"failed to start".
- Duplicate run calls (coderabbit major): in-flight ref guard in
useDestroyDialogState.run so a rapid second click (same pane or
re-opened error pane) can't fire the mutation twice before the
first resolves.
- Global checkbox id (coderabbit minor): DestroyConfirmPane uses
useId() so the "Also delete local branch" label targeting doesn't
collide across dialog instances.
- Audit doc tense (cubic + coderabbit): v1-v2-delete-patterns-audit.md
now explicitly labeled as a pre-unification snapshot.
- Plan doc delivered-vs-follow-up split (coderabbit): the UI section
and work order clearly mark what landed in this PR vs. what's
follow-up (hotkey, EmptyTabView, V2WorkspaceRow affordance).
…set-sh#3462) * fix(desktop): use native clipboard for copy path in v2 sidebar navigator.clipboard.writeText silently fails when focus is elsewhere (e.g., terminal), so the toast showed but nothing was actually copied. Switch to useCopyToClipboard which routes through Electron's native clipboard via tRPC, matching the v1 pathway. * feat(desktop): wire up Copy Path in v2 dashboard sidebar The workspace item context menu's Copy Path just toasted "coming soon". Fetch the worktreePath from the local host service for local workspaces, copy via electronTrpc.external.copyPath (native clipboard), and show a proper success toast. Non-local workspaces fall back to an explanatory error since the path only exists on the owning machine. * feat(desktop): wire up Open in Finder in v2 dashboard sidebar Share the local worktreePath resolver between Copy Path and Open in Finder, and route Open in Finder through electronTrpc.external.openInFinder instead of toasting "coming soon". * refactor(desktop): extract shared PathActionsMenuItems, wrap copy in try/catch Addresses review feedback on PR superset-sh#3462: - Pulled the Reveal in Finder / Copy Path / Copy Relative Path items shared between FileContextMenu and FolderContextMenu into a single PathActionsMenuItems component. - Wrap useCopyToClipboard calls in try/catch so IPC failures surface a proper error toast instead of failing silently. * refactor(desktop): hide Open in Finder & Copy Path for non-local workspaces - Gate the Open in Finder / Copy Path items in the dashboard sidebar workspace context menu behind isLocalWorkspace so they only appear for workspaces on the active machine. - Drop the now-redundant hostType guard from the actions hook. - Wrap openInFinder in try/catch in PathActionsMenuItems so native action failures surface a toast (addresses coderabbit/cubic P2 on superset-sh#3462).
Press Escape on any settings page to navigate back to where settings was opened from. Skips when focus is in a form field, contenteditable, or any open Radix layer (dialog, popover, dropdown).
…uperset-sh#3472) Flip Cmd+Alt+Arrow (mac) / Ctrl+Shift+Alt+Arrow (win/linux) back to the pre-superset-sh#3403 behavior: - Cmd+Alt+Left / Right → Previous / Next Tab - Cmd+Alt+Up / Down → Previous / Next Workspace FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} remain registered but are now unbound by default — users who want directional pane focus can rebind them in Settings → Keyboard. PREV_TAB_ALT / NEXT_TAB_ALT (Ctrl+Tab / Ctrl+Shift+Tab) are unchanged. Also move SCROLL_TO_BOTTOM on Windows/Linux from ctrl+shift+alt+down to ctrl+end to avoid the silent collision with NEXT_WORKSPACE that this restoration would otherwise introduce. buildRegisteredAppChords uses a Map keyed by canonical chord, so duplicate chords silently clobber each other in the reverse lookup. mac SCROLL_TO_BOTTOM (meta+shift+down) is unchanged. Registered users who previously overrode any of these IDs keep their overrides — defaults are just fallbacks.
…uperset-sh#3463) * feat(desktop): add Review tab to v2 workspace sidebar Port the v1 Review tab functionality into the v2 workspace sidebar, replacing the "Checks — Coming soon" stub. Uses v2-native host-service endpoints (git.getPullRequest, git.getPullRequestThreads) instead of v1 electron endpoints. Features: - PR header with title, state icon, review decision badge - Collapsible CI checks section with status icons and links - PR comments with avatars, timestamps, and preview text - Copy individual comments or copy all to clipboard - Comment pane: click a comment to view rendered markdown in a tab - GitHub link in pane header to open comment on GitHub - Copyable tables in rendered markdown * fix(desktop): address review tab PR feedback - Fix memory leaks: add timeout cleanup refs in CommentPane and CopyableTable, clear previous timeout on rapid clicks - Add error state: show "Unable to load review status" when tRPC query fails instead of infinite loading spinner - Fix getIcon fallback: show MessageSquare icon when avatarUrl is missing instead of rendering <img src={undefined}> - Add aria-label to comment open button for screen readers - Add aria-hidden to decorative arrow icon in PRHeader - Add group-focus-visible:opacity-100 to PRHeader arrow for keyboard nav * fix(desktop): add .catch() to all clipboard promise chains Prevents unhandled promise rejections in the renderer if electronTrpcClient.external.copyText.mutate() fails. * fix(desktop): guard state updates after unmount in CommentPane Add isMountedRef to both CommentPane and CopyableTable to prevent setCopied calls after unmount when the async clipboard IPC resolves after the component is gone. * fix(desktop): fix comments not loading until refresh The threads query had staleTime: 30_000 which cached empty results when the query fired before the host-service had PR data synced. Remove staleTime so every mount/refocus fetches fresh data (background polling at 30s still runs via refetchInterval). Also tighten the enabled condition to use prQuery.isSuccess instead of !!prQuery.data for clearer intent, and use composite key for CheckRow to handle duplicate check names. * fix(desktop): unmount guard in CommentsSection + single-pass check status - Add isMountedRef to CommentsSection so markCopied doesn't set state after unmount (same pattern as CommentPane/CopyableTable) - Rewrite computeChecksStatus as single loop instead of 3 passes (filter + some(failure) + some(pending) each called coerceCheckStatus) * feat(desktop): render mermaid diagrams + syntax highlighting in comment pane Use the existing CodeBlock component which handles mermaid via @streamdown/mermaid and syntax highlighting via react-syntax-highlighter. * fix(desktop): lighten mermaid diagram background in comment pane * fix(desktop): hide mermaid label + lighten actual diagram background - Hide the "mermaid" label and action buttons on the block wrapper - Strip the outer border/padding so it blends with the page - Target the SVG element itself to lighten its background color * fix(desktop): revert hacky mermaid overrides, use CSS text color instead Revert the custom Streamdown/SyntaxHighlighter approach. Go back to using the shared CodeBlock component. Instead of changing the mermaid canvas background, override the SVG text fill and node/edge label colors via CSS to use --foreground, making them readable on both light and dark themes. * fix(desktop): use mermaid themeVariables for light text in dark mode Use Streamdown directly with custom themeVariables to set light text colors (primaryTextColor, nodeTextColor, labelTextColor, etc.) on the dark mermaid theme. Removes the CSS hacks that couldn't override mermaid's inline SVG styles. * fix(desktop): mermaid dark theme contrast + hide label/wrapper chrome - Use theme: "base" with full dark color palette so themeVariables actually control node fills (dark gray nodes, light text) - Hide "mermaid" label and action buttons via CSS - Strip background/border from outer block and inner wrapper so diagram sits directly on the page background * fix(desktop): extract CommentCodeBlock as standalone component Move code block rendering out of useMemo into a proper React component so the component identity is stable across renders. Prevents unmount/ remount of Streamdown/SyntaxHighlighter when the theme changes. Mermaid theme variable objects extracted as module-level constants.
… modal (superset-sh#3844) * feat(desktop): persist last project + base branch in v2 workspace modal Remember the user's most recent project and per-project base branch in localStorage so the new-workspace modal pre-fills them on next open. Uses the same plain localStorage pattern as useAgentLaunchPreferences. * feat(desktop): also persist last selected device in v2 workspace modal * refactor(desktop): use zustand persist store for v2 workspace defaults Switches the persistence layer to a zustand persist store, matching the pattern used by v2-project-local-meta and other stores in renderer/stores. Also clears the per-project base-branch default when the user explicitly clears the picker, so a stale default doesn't re-appear on next open. * fix(desktop): shape-validate persisted lastHostTarget before applying
…et-sh#3811) * fix(host-service): isolate subsystem crashes from main thread Subsystem throws (timer callbacks, EventEmitter listeners, pty data/exit handlers, harness subscribe callback) used to escape into the process and could take the whole host-service down. Now they're contained per-subsystem and a process-level safety net catches anything that still slips through. - Add safety.ts with installProcessSafetyNet (uncaughtException + unhandledRejection log without exiting) and safeSync/safeAsync helpers for wrapping callbacks. - Wrap hot async entry points in EventBus, GitWatcher, PullRequestRuntimeManager, ChatRuntimeManager, and terminal PTY callbacks. - Surface the previously-silent malformed-message swallow in EventBus.parseClientMessage. Startup exit on init failure is intentionally preserved so the coordinator / external supervisor can restart with a clean process. * refactor(host-service): address review feedback on safety wrappers - Install process safety net only after server is listening so startup throws still reach main().catch and exit non-zero. - Rename misleading `safeSync` local in pull-requests.ts to `runBranchSync`/`runProjectRefresh` — it wraps an async fn. - Hoist `safeSync(event-bus:send, sendMessage)` to module scope to avoid per-broadcast-per-socket closure allocation. - Use `safeAsync` in git-watcher rescan for consistency with the pull-requests interval pattern. - Drop low-value `promise` field from unhandledRejection log. * refactor(host-service): simplify crash isolation to process-net + fan-out guards Drop safeSync/safeAsync wrappers entirely. The Node process-level handlers already catch throws from setInterval/setTimeout bodies, EventEmitter listeners, native pty callbacks, and orphaned promise continuations — so wrapping each one individually was redundant. The two cases that genuinely need inline error handling are loops over multiple subscribers, where a throw skips the rest of the iteration: - EventBus.broadcast — wrap per-socket send and drop dead sockets, matching the opencode pty fan-out pattern. Converts "log forever per broadcast" into "one log + clean state". - GitWatcher.scheduleFlush listener loop — inline try/catch so one bad subscriber can't skip siblings. Aligns with the field norm (opencode, tabby, hyper, mastra-code-ui all rely on process-level handlers + targeted inline guards rather than per-seam wrappers). Net -68 lines. * chore(host-service): drop redundant label arg, restore one-liner setInterval - installProcessSafetyNet had a default label arg that both call sites passed identically; drop the param and inline "host-service" in the logs. - git-watcher.ts start() reverts to its original setInterval one-liner shape (the multi-line form was leftover from an earlier refactor).
…review (superset-sh#3850) * feat(paywall): gate relay + automations + remote workspaces, dither preview Tightens the Pro paywall around three free-leaking surfaces and refreshes the paywall preview shader. UI gates - Relay (Settings → Security): turning on "Allow remote workspaces to access this device" now goes through gateFeature(REMOTE_WORKSPACES). Off-toggling stays ungated and keeps the new on/off confirm dialog. - Automations: sidebar entry is visible to free users (was hidden); click routes through gateFeature(AUTOMATIONS). Direct URLs to /automations redirect via a new layout-level guard. - Remote / cloud workspaces in the v2-workspaces list: open + add-to-sidebar on remote-device / cloud rows are gated. Local rows are unaffected. The workspace page itself is intentionally not gated — server-side host.checkAccess already returns paidPlan: false, so unpaid users hit a data-layer block instead of an accidental modal lock-out. Server-side defense (zero new round-trips) - findOrgMembership now LEFT JOINs subscriptions in the same statement, so callers gating on plan don't pay an extra query. Adds requireActiveOrgMembershipWithSubscription sibling. - automation.create / setEnabled / runNow refuse non-paid orgs. - host.checkAccess returns { allowed, paidPlan }; relay's existing 5-min LRU caches the combined boolean — no per-request API spam. - dispatchAutomation joins subscriptions into resolveTargetHost and short-circuits unpaid runs with a recorded skip. Paywall preview - Replaces MeshGradient with a DitheredBackground (shaders-react Dithering warp/4x4) ported from marketing, matching the new visual direction. - Adds AutomationsDemo + RemoteWorkspacesDemo; drops obsolete CloudWorkspacesDemo / IntegrationsDemo. - Reorders PRO_FEATURES to remote-workspaces → automations → team-collaboration → tasks → mobile-app and tightens the remote-workspaces description so feature-switching no longer shifts the modal height. * refactor(trpc): split membership-with-subscription into its own helper Restores the original `findOrgMembership` / `verifyOrgMembership` shapes so non-gating callers (organization, integration utils) keep their old return types, and moves the subscription join into dedicated siblings used only by plan-gating procedures. - findOrgMembershipWithSubscription / verifyOrgMembershipWithSubscription are the new joined helpers. - requireActiveOrgMembershipWithSubscription routes through the new verify*WithSubscription helper. - Reverts incidental rename churn in organization.ts. * fix(automations,paywall): address PR review - automation_runs: add `skipped_unpaid` status so paywall blocks don't inflate the `skipped_offline` metric. `recordSkipped` now takes the status as a parameter; dispatchAutomation uses it for the unpaid path and returns a matching `skipped_unpaid` outcome (was: persisting `skipped_offline` while returning `dispatch_failed` — both wrong). Includes drizzle migration 0040. - findOrgMembershipWithSubscription: deterministic ORDER BY (active before trialing, then most recent) so a brief active+trialing overlap doesn't return a non-deterministic row. - automations layout: useRef one-shot guard + replace navigation so the paywall + redirect don't re-fire on rerender (gateFeature isn't memoized) and don't stack history entries. * perf(automations): drop unpaid orgs at the cron, not in dispatch Filters automation evaluate-cron to skip rows whose org isn't on a paid plan, so unpaid automations never enqueue, never enter dispatch, and never write a run row. This is the primary fix; the dispatch-side `skipped_unpaid` path stays as defense-in-depth (the subscription join is already paid for by host resolution, so it's free). Also simplifies the deterministic ORDER BY across the three subscription joins (membership / dispatch / host.checkAccess) to plain `desc(subscriptions.createdAt)` — pick the newest matching row, drop the contrived case-expression preference. * refactor(automations): drop skipped_unpaid enum, bail silently in dispatch Now that the cron evaluate-step filters unpaid orgs out of the enqueue batch, the dispatch-side unpaid check only fires in a tiny race window between SELECT and dispatch. That doesn't justify a new DB enum + migration: defense-in-depth bails before any work, but no longer writes a run row. - Reverts enum + 0040 migration. - Reverts recordSkipped to its single-status form. - Unpaid path returns { status: "skipped_unpaid", runId: null } without a DB write (status mirrors the runtime-only "conflict" outcome). * refactor(billing): use ACTIVE_SUBSCRIPTION_STATUSES constant in subscription joins Replaces 5 inline ["active", "trialing"] arrays with the shared constant from @superset/shared/billing so adding a new active-equivalent status (e.g. past_due_grace) only needs one edit instead of grepping across queries.
…erset-sh#3856) `disposeSessionsByWorkspaceId` filtered by `status = "active"`, so any session whose row had drifted to `exited` (e.g. via `pty.onExit`) was skipped on workspace deletion — leaving the in-memory `sessions` Map entry behind. Widen to `status != "disposed"` so zombie rows get disposed too. `disposeSession` is already idempotent against a dead PTY.
The v2 ChatPaneInterface root sat inside PaneContent's row-direction flex with no width directive, so it sized to its intrinsic content and the chat appeared smushed. Add w-full to match every other v2 pane; also restore w-full on the SessionSelector trigger button.
…set-sh#3858) The v2 diff file header used `justify-between` with `flex-wrap`, so when the action cluster wrapped to a second row at narrow widths, the remaining chevron, status indicator, and file badge spread out across the first row with large gaps between them. Removing `justify-between` keeps those items tight on the left while `ml-auto` on the action div still anchors it to the right when it wraps.
…S failure (superset-sh#3861) When the laptop wakes from sleep and DNS hasn't recovered, the tunnel's auto-reconnect path calls getAuthToken() which fetches api.superset.sh. The fetch throws ENOTFOUND, the rejection escapes via `void this.connect()`, and the host-service process crashes — orphaning every PTY. The coordinator respawns on a new port, but the renderer keeps reconnecting to the dead port until it gives up. Wrap connect()'s body in try/catch and route any throw back through scheduleReconnect, so transient network failures behave the same as WebSocket socket-level errors.
…uperset-sh#3852) * feat(desktop): v2 workspaces list filters, project icons, PR hover - Replace device tab toggle with a Select. Drop the never-populated "cloud" option, default to "This device", and list each remote host (with online dot + per-host count) below a separator. - Add a project filter Select, keyed by project, using actual GitHub org avatars (https://github.com/{owner}.png) by leftJoining v2Projects to githubRepositories. Falls back to first letter on missing-owner / load error. - Group rows by project under sticky section headers (h-8 column header flush with top-8 project header to avoid sub-pixel gap). Section header shows the project icon, name, and workspace count. - Surface PR data per row as a small pill (PR icon + #NNN + checks dot) that opens a HoverCard mirroring v1's PR detail layout (status badge, review status, additions/deletions, title, checks summary + collapsible list, "View on GitHub"). Presentational helpers are duplicated locally rather than imported from the v1 hover-card tree. - Replace the WebKit native search clear "x" with InputGroupButton + LuX via type="text" on the search input. - Caddyfile setup heredoc now emits the global { auto_https disable_redirects } block so Caddy doesn't try to bind port 80 on dev. - github_pull_requests.updatedAt now mirrors GitHub's PR.updated_at: drop Drizzle's $onUpdate hook on the column, populate from pr.updated_at in sync, initial-sync, and webhook upsert paths. Check-run webhooks no longer touch updatedAt (they don't bump GitHub's PR.updated_at). Schema-only Drizzle change — no SQL migration generated; drizzle-kit generate should be a no-op. * feat(desktop): collapsible project sections + review fixes Address PR review feedback and add a collapse toggle on each project section in the v2 workspaces list view. - Detect merged PRs from `mergedAt` instead of `state === "merged"`. GitHub's PR state is only "open"|"closed", so the old branch was dead code and every merged PR was being ranked as "closed". - Map remaining check_run conclusions explicitly: `neutral` → success, `stale` and `startup_failure` → failure. Previously fell through to "pending" so completed checks rendered with a spinner. - Project + device filter triggers fall back to a project/host lookup built from pre-search-and-device-filter data, so the trigger no longer collapses to "All projects" / "Unknown device" when the active selection is filtered out by search. - V2WorkspaceProjectIcon: track failed owner instead of bare boolean, so switching to a different (working) owner re-tries the image. - Hover card: show review status badge and checks block for draft PRs too (previously gated on state === "open" only). - Project sections are now collapsible — chevron toggle in the section header, state persisted via the existing v2-project-local-meta store. * fix(desktop,trpc): address review comments on v2 workspaces filters PR - Fix @superset/trpc test: add `verifyOrgMembershipWithSubscription` to the `../integration/utils` module mock so bun can resolve the named export pulled in transitively via `requireActiveOrgMembership`. The paywall PR added that export but the test mock wasn't updated, which was failing CI on main. - Drop the auto-stamp narrative comment on github_pull_requests.updatedAt. - Don't blank the list while machineId is unresolved: treat "this-device" as no-op until machineId is known. - Auto-expand a project section when one of its workspaces is the active route, even if the user previously persisted it as collapsed. - Split the inline helper components out of V2WorkspacePrHoverCardContent and V2WorkspacesHeader into one-component-per-folder modules per AGENTS.md.
…t-sh#3867) Renames Cloud → Remote workspaces, swaps the (Coming Soon) text for proper Beta/Coming-soon Badges, adds an Automations row (Pro+), and reorders Features so they read top-to-bottom by surface (workspaces → automations → mobile → integrations → collab). The marketing /pricing page mirrors the desktop billing comparison.
…et-sh#3876) Previously, machineId was sourced from `hostServiceCoordinator.getConnection`, which returns null whenever no host-service instance is running. After macOS sleep killed the detached host-service child, every v2 workspace failed the `workspace.hostId === machineId` check and got routed through the relay as if it were remote, causing data not to load until app restart. machineId is a deterministic per-device value (`getHostId()`), so expose it via a dedicated `device.getMachineId` tRPC query and gate provider render on it so the context value is non-nullable. Note: this only fixes the misclassification. Reviving the host-service on wake (e.g. via `powerMonitor`) is a separate change.
Switch host-service-client from httpBatchLink to httpLink so calls to the local host service no longer wait on the slowest procedure in a batch.
Mirrors the host-service v2 path so chat TUIs (claude-code et al.) parse kitty CSI-u sequences from xterm.js. v1 already enables kittyKeyboard in the renderer; the env claim makes consumers honor it.
…uperset-sh#3869) * fix(agents): correct copilot flag order and mastracode prompt mode - copilot: reorder `promptCommand` from `copilot -i --allow-tool=write` to `copilot --allow-tool=write -i`. With the old order, the rendered shell command landed as `copilot -i --allow-tool=write "PROMPT"`, which commander.js parsed as `-i=--allow-tool=write` and rejected the prompt with `error: too many arguments`. - mastracode: add `promptCommand: "mastracode --prompt"`. The previous default-from-`command` rendered `mastracode "PROMPT"`, but mastracode's TUI silently drops positional args (only the headless `--prompt`/`-p` path actually executes the input). Trade-off: prompt-mode now runs headless since upstream has no `interactive + auto-execute` flag like copilot's `-i` or gemini's `--prompt-interactive`. - bump `mastracode` desktop dep `0.15.0-alpha.3` → `0.16.0` to match the current published release. * fix(agents): keep mastracode interactive after handling prompt Chain headless prompt execution with a TUI relaunch so the user lands in an interactive session on the same thread the prompt seeded. Without the suffix, `mastracode --prompt` executed and exited, breaking the expected "interactive + handles prompt" UX. The TUI auto-resumes the most recent thread (per mastracode 0.13+ behavior), so chaining `; mastracode` after the headless run drops the user back into the conversation populated by the prompt. * fix(agents): fix copilot flag order in legacy permissions migration The migration backfill restored `copilot -i --allow-all` for users seeded before superset-sh#3546, which has the same flag-ordering bug as the registry: `-i` consumes `--allow-all` as its prompt value and the real prompt heredoc errors with `too many arguments`. Reorder to `copilot --allow-all -i` so the prompt lands directly after `-i`. The yolo permissions intent is preserved via the unchanged suffix. * fix(desktop): revert internal mastracode bump to align workspace versions sherif flagged the workspace mismatch — packages/chat and packages/host-service still pin 0.15.0-alpha.3, so bumping desktop alone broke multi-version consistency. The runtime upgrade is already covered by the user-installed CLI; the internal dep just needs to track the rest of the workspace.
* feat(web): show Pro badge in account dropdown Adds a `billing.activePlan` tRPC query and renders a Pro/Enterprise badge next to the user's name in the AgentsHeader dropdown and mobile drawer when the active org has a paid subscription. * feat(desktop): show Pro badge in OrganizationDropdown Renders a Pro/Enterprise badge next to the org name in the desktop OrganizationDropdown trigger (topbar and expanded sidebar variants), using the existing useCurrentPlan hook. * fix: address PR review comments - billing.activePlan: drop redundant status check; the WHERE clause already restricts to ACTIVE_SUBSCRIPTION_STATUSES. - AgentsHeader: derive planLabel only when on a paid tier so a stale "Pro" string can't surface if loading-state logic changes.
…set-sh#3881) * fix(desktop): show local diff stats in v1 workspace hover card The v1 workspace hover modal was rendering pr.additions/pr.deletions from the GitHub PR snapshot, while the sidebar row showed local working-tree stats — so the two surfaces could disagree and the modal LOC appeared to flicker as PR data refetched. Centralize via a new useLocalDiffStats hook (wraps useGitChangesStatus) and use it in both surfaces. Drop the PR-stats fallback in the sidebar row so before-hover and after-hover stay consistent. * refactor(desktop): pass diffStats into hover card as a prop Lift the LOC source up to the parent (WorkspaceListItem already computes it for the inline +/-) and pass the same value into the hover card, so the modal renders whatever it's handed instead of fetching its own copy. Also render the diff stats in the no-PR and no-remote branches — they were previously gated behind PR presence. Reverts the worktreePath addition to getWorktreeInfo since the hover card no longer needs it. * fix(desktop): remove flickery PR-based diff stats from workspace list view The workspace list row was reading additions/deletions from the GitHub PR snapshot, which can drift from the local working tree and refetches on its own cadence — values appeared to flicker. List rows don't need LOC, so drop the display (and the now-unused githubStatus prefetch). * refactor(desktop): inline diff stats reducer back into list item The useLocalDiffStats hook was only called in one place; the other three imports were just for the LocalDiffStats type on a prop. Inline the useMemo back into WorkspaceListItem and use a structural type on the prop instead — drops the hook file and its index export.
…rset-sh#3846) * feat(desktop): add Create/Import project to v2 workspace picker - Add "Create new project" and "Import project" entries to the v2 ProjectPickerPill in the New Workspace modal; wired to the same flows used by the sidebar Add Repository button. - Move v2 sidebar Add Repository button next to New Workspace; rename dropdown items to "Create new project" / "Import project". - Refactor: openNewProject() and folderImport.start() now return promises (resolved by the modal/flow) instead of callbacks crossing a zustand store; consumers await and react in normal React flow. - Extract useFinalizeProjectSetup() and useHostProjectIds (with exported queryKey) into renderer/react-query/projects/. After a project is created/imported the cached host project list is invalidated so needsSetup re-evaluates correctly. * fix(desktop): address review feedback on workspace picker PR - Picker import toast: "Project imported and selected." (no longer tells user to open from sidebar — project is already selected here). - useHostProjectIds: log host fetch errors instead of swallowing. - openNewProject: JSDoc the single-in-flight semantics. - Drop redundant JSDoc / dead type re-export from earlier refactor. * fix(desktop): pin Create/Import actions below scrolling project list Move the action group out of CommandList so it stays visible when the project list overflows, matching v1 behavior. Tightened max-height to 280px to leave room for the footer. * fix(desktop): gate workspace modal init on org context being ready If session is still loading, activeOrganizationId is null and the v2Projects query filters to []. Initializing the selection in that state would lock in null and skip the real project list once the session resolves. Wait until activeOrganizationId is non-null before marking initialized. * refactor(desktop): drop reportError wrapper and use shared ProjectSetupResult - useFolderFirstImport: inline onError?.(message) — the wrapper added a hop without buying memoization (onError was already its only dep). - NewProjectModal: use ProjectSetupResult from react-query/projects instead of an inline result shape, avoiding type drift.
…erset-sh#3884) * fix(host-service): dedupe PR refresh calls with repo-keyed cache Multiple projects targeting the same GitHub repo each fired their own GraphQL query every 10s, and force=true paths bypassed the per-project debounce entirely. Replace with a single repo-keyed response cache so N projects on the same repo collapse to 1 call, branch-sync/tRPC bursts share the in-flight promise, and the polling cadence drops to 20s. * fix(host-service): cache PR detail fetches to skip repeat gh pr view The PR detail panel re-invokes `gh pr view` on every mount, so re-renders, tab switches, and click-back patterns each shell out fresh. Wrap the procedure in a 30s TTL cache keyed on `owner/name#prNumber`, sharing the in-flight promise across concurrent callers and evicting on failure so transient errors don't poison subsequent reads. * fix(host-service): bypass repo cache for explicit PR refreshes The branch-sync follow-up and the `refreshByWorkspaces` tRPC mutation both fire when external state has just changed (local SHA moved, or a PR was just merged) — exactly the case where the 10s settled cache returns stale data. Thread a bypassCache option from those call sites down to the cache lookup so explicit refreshes always fetch fresh while the polling tick keeps deduping. Bypass paths still write to the cache so subsequent polls benefit.
…d git status (superset-sh#3838) * fix(desktop): make file tree resilient to slow directory loads - Time out listDirectory after 5s and retry up to 3x with linear backoff - Abort in-flight requests on unmount and workspace/root change - Show a loading spinner in FilesTab while the workspace query resolves - Enable abortOnUnmount globally on the workspace tRPC client * refactor(desktop): simplify useFileTree cancellation, drop FilesTab workspace name - Use AbortController membership in the active set as the "still relevant" check, replacing isMountedRef + activeContextRef + isRequestCurrent - Inline single-use helpers (markDirectoryLoading, clearDirectoryLoading, shouldRetryListDirectory, getListDirectoryRetryDelayMs) - Drop the workspaceName prop from FilesTab/WorkspaceSidebar/WorkspaceContent; the files header is just "Explorer" * fix(desktop): only show files-tab spinner on initial load, not refetches * feat(host-service): plumb AbortSignal through listDirectory When the renderer aborts a listDirectory query (timeout or workspace switch), the host-service was running fs.readdir + per-symlink fs.stat to completion and discarding the result. Node's fs API doesn't honor AbortSignal, but we can short-circuit between operations — useful in symlink-heavy directories (node_modules) where the per-entry stat loop dominates. * refactor(host-service): batch listDirectory stat calls for cancellable abort Process per-entry symlink stats in batches of 16 with a signal check between batches. With Promise.all-over-everything, all stats kick off in the same microtask and an in-flight abort can't interrupt any of them. Batching bounds the zombie work to one batch (~16 stats) per abort. * feat(host-service): tRPC query timeout middleware + retry on TIMEOUT Hung host-service IPC (slow git, slow filesystem ops) was leaving the renderer spinning indefinitely. Replace the bespoke per-hook retry/timeout in useFileTree with a single server-side middleware that bounds every query procedure. Server (queryProcedure builder + middleware): - t.middleware races next() against a per-procedure timeout, rejecting with a TRPCError({ code: "TIMEOUT" }). Default 5s, override via `.meta({ timeoutMs })` on procedures that legitimately take longer (search, listCommits, getDiff, getStatus, getBranchSyncStatus, getPullRequestThreads, readFile). - Switch all query procedures in filesystem and git routers to queryProcedure; mutations remain on protectedProcedure. Client (workspace-client QueryClient): - defaultOptions.queries.retry retries TIMEOUT errors up to 2 times with linear backoff (300ms, 600ms). Other errors keep the previous single retry. useFileTree: - Drop withAbortableTimeout, ListDirectoryTimeoutError, retry-timer set, retry recursion, loadDirectoryRef, activeLoadAbortControllersRef. - Just await utils.filesystem.listDirectory.fetch(input). React Query's retry policy handles TIMEOUT; abortOnUnmount cancels in-flight on unmount/workspace switch. ~120 lines removed. Side benefit: git.getStatus / listBranches / listCommits etc. all gain hung-IPC protection automatically. The Changes tab no longer spins forever on a slow `git status`. * docs(host-service): add QUERY_TIMEOUTS.md reference Documents the queryProcedure / timeoutMiddleware pattern: where it lives, how to set per-procedure budgets via .meta({ timeoutMs }), the current budget table, and what timeouts do (and don't) interrupt.
… file (superset-sh#3885) * feat(desktop): persist large-diff expand state, auto-expand on file click Lift `showFullDiff` out of `DiffFileEntry` local state into a persisted `expandedFiles` array on `DiffPaneData`, synced through the existing paneLayout collection. Clicking a file from the v2 changes tab now adds its path to `expandedFiles` (and clears it from `collapsedFiles`), so a large file auto-renders its diff instead of showing the deferred placeholder, and the decision survives navigation/reload. * fix(desktop): guard collapsedFiles in openDiffPane against legacy panes Older persisted DiffPane snapshots may not have collapsedFiles set; the existing setCollapsed helper already uses `?? []` for the same reason. Match the defensive read here so reusing an existing diff pane doesn't TypeError on undefined.filter.
…og (superset-sh#3888) Reconnect/close/error chatter no longer streams into xterm scrollback. The transport now buffers up to 200 log entries and exposes them via the runtime registry; a pane-header button reveals a popover with the log and a clear action, and only appears when there's something to show. Removes the move-to-background button from the v2 terminal pane header.
* feat(api): backend prereqs for CLI v1 Lands the cloud + desktop changes the new CLI depends on, ahead of the CLI itself, so the next CLI PR can stack cleanly: - task: list filters/pagination, byIdOrSlug, list+create kept under prior `task.all` / `task.createFromUi` paths so the shipped CLI on main keeps compiling against this backend during the rollout - task.update: accept-but-ignore deprecated `branch` field for the same reason - automation: list/create/update/delete + run-now coverage CLI uses - host: list/checkAccess/setOnline shape CLI talks to - v2-workspace: workspace.list + create entry points - web: cli/authorize and oauth/consent flows; proxy preserves original search params on the sign-in redirect (path + search round-trips through `?redirect=` directly — no cookie stash) - desktop/mobile: drop unused sessionHosts collection registrations - host-service: project router shape adjustments CLI sources land in a follow-up PR on top of this one. * fix(api): tighten cli-authorize redirect_uri, jwt fallback, task search Address bot review on PR superset-sh#3889: - cli/authorize: parse redirect_uri with `new URL()` and check the parsed `hostname` + empty userinfo. The earlier prefix-match accepted `http://127.0.0.1:80@evil.example` as a loopback callback. - trpc.jwtProcedure: re-throw TRPCError instances from verifyJWT so an explicitly-rejected token (revoked/forged) doesn't silently fall through to the session cookie path. Non-TRPC parse errors still fall through (covers expired/missing tokens for desktop's session caller). - task.list: escape `%` and `_` in the search input before interpolating into the ilike pattern. * fix(api): validate automation v2ProjectId/workspace consistency PR superset-sh#3889 follow-up: - automation.create: when both `v2ProjectId` and `v2WorkspaceId` are supplied, require them to agree. Always derive the stored `v2ProjectId` from the workspace, since the workspace is the ground truth. - automation.create: when only `v2ProjectId` is supplied (no workspace), verify the project belongs to the active organization. Previously a caller-supplied id was inserted as-is.
* chore(marketing): update trusted by section * chore(marketing): tweak trusted by section * chore(marketing): tweak trusted by section
…uperset-sh#3895) Better-auth's apiKey plugin (`enableSessionForAPIKeys: true`) populates ctx.session for x-api-key requests, but jwtProcedure was throwing on the very first line if the request didn't carry an Authorization Bearer header — so any procedure marked jwtProcedure rejected api-key auth before reaching the session fallback. The CLI's `superset hosts`, `workspaces.list`, etc. all 401'd with `--api-key sk_live_…`. Accept any of: a verifiable Bearer JWT, a successful Bearer JWT fallback, or an x-api-key-derived session. Throw only if none of those produce identity. The TRPCError re-throw on explicit JWT rejection is preserved. Trailing error message updated to reflect the broader contract.
…perset-sh#3897) Each file in the v2 changes sidebar now reveals a more-actions dropdown on hover, with Open Diff / Open Diff in New Tab / Open File / Open File in New Tab / Open in Editor (mirrors the right-click menu, plus new Open File entries that route through the existing file-open pane). Click-modifier shortcut hints (⇧/⌘) are shown next to the actions that have them.
…-lines strip (superset-sh#3899) - Match ChangesHeader chrome on each file header (border-y + bg-muted/30) - Split filename into dir + basename so the basename always stays visible on narrow widths instead of being truncated behind ellipsis - Add a Copy path button next to the filename, drop Copy file contents - Move StatusIndicator next to the +/- diff stats - Blend diff body with the terminal pane surface color - Flatten the "N unmodified lines" expander flush to the pane edges (kills pierre/diffs' wrapper / content / expand-button rounding + inline gaps for both line-info and line-info-basic separators) - Standardize icon-button padding, drop the inline DiffViewModeToggle in favour of a co-located DiffPaneHeaderExtras component
Latest Upstream Release: Superset Desktop desktop-v1.7.2
Tag:
desktop-v1.7.2Published: 2026-04-29T01:29:22Z
Release Notes
What's Changed
Full Changelog: superset-sh/superset@desktop-v1.7.1...desktop-v1.7.2
396 new commits from upstream.
This PR is automatically created and updated daily to keep the fork in sync with superset-sh/superset.