feat(desktop): clone V1 new-workspace composer onto V2 modal#3302
feat(desktop): clone V1 new-workspace composer onto V2 modal#3302
Conversation
Replaces the tab-based V2 create-workspace modal with a clone of the
battle-tested V1 composer, rewiring only the backend boundaries.
Backend boundary changes (V1 → V2):
- Project list: electronTrpc.projects.getRecents → v2Projects +
githubRepositories collections
- Branch list: electronTrpc.projects.getBranches* →
workspaceCreation.searchBranches on host-service
- Create action: 4 V1 mutations (create/createFromPr/openTracked/
openExternal) → single workspaceCreation.create on host-service
- GitHub issues/PRs list + content: electronTrpc.projects.{listIssues,
listPullRequests, searchPullRequests, getIssueContent} →
workspaceCreation.{searchGitHubIssues, searchPullRequests,
getGitHubIssueContent} on host-service (Octokit via ctx.github())
- Navigation: navigateToWorkspace → navigateToV2Workspace
V2 additions:
- DevicePicker in the composer footer for host target selection; on
host change, compareBaseBranch resets
- hostTarget field in the draft context
Intentionally dropped for Phase 1 (deferred to Phase 2):
- Branch prefix feature (projects.get, getGitAuthor, settings.
getBranchPrefix, settings.getGitInfo, resolveBranchPrefix) — crosses
V2 host boundary, needs host-aware prefix in Phase 2
- Worktree preflight UI (getExternalWorktrees, getWorktreesByProject,
resolveOpenableWorktrees, worktree badges/filter tab) — host-service
workspaceCreation.create handles tracked/external/adopt server-side
Host-service endpoints added:
- workspaceCreation.getContext — project + default branch
- workspaceCreation.searchBranches — git branches with hasWorkspace
- workspaceCreation.create — semantic create with outcome resolution
(created_workspace / opened_existing_workspace / opened_worktree /
adopted_external_worktree) and path-traversal guard on branchName
- workspaceCreation.searchGitHubIssues — Octokit issue list/search
- workspaceCreation.searchPullRequests — Octokit PR list/search
- workspaceCreation.getGitHubIssueContent — Octokit issue body fetch
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReworks the new-workspace modal into a prompt-first flow: centralized draft context, PromptGroup prompt UI (attachments, linked issues/PRs, inline pickers, AI branch naming), a renderer-side createWorkspace hook, and a new host-service workspaceCreation TRPC router; removes legacy tab/list components and related hooks. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as PromptGroup (renderer)
participant Draft as Draft Context
participant Host as Host Service (workspaceCreation)
participant GitHub as GitHub API
participant Local as Local Git & DB
participant Cloud as Cloud Workspace Service
User->>UI: enter prompt, select project/branch/device, attach/link issues/PRs
UI->>Draft: updateDraft(workspaceName, linkedIssues, linkedPR, compareBaseBranch, runSetupScript)
User->>UI: submit
UI->>UI: validate, convert blobs to data URLs
alt linked GitHub item present
UI->>Host: request getGitHubIssueContent / search endpoints
Host->>GitHub: fetch issue/PR data
GitHub-->>Host: issue/PR content
Host-->>UI: content
end
UI->>Host: workspaceCreation.create({ projectId, names, composer, linkedContext, runSetupScript })
Host->>Local: ensure local repo (clone if missing), compute defaultBranch
Host->>Local: check/create worktree (git worktree add or -b from compareBaseBranch)
Local-->>Host: worktree created / error
alt worktree created
Host->>Cloud: create cloud workspace record
Cloud-->>Host: workspace created
Host->>Local: insert workspace DB row, optionally run setup script
Host-->>UI: return created_workspace
else failure
Host->>Local: rollback (git worktree remove) best-effort
Host-->>UI: error
end
UI->>UI: revoke blobs, clear pending, navigate to workspace or show error
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
Greptile SummaryThis PR replaces the V2 tab-based create-workspace modal with a clone of the battle-tested V1 Key points:
Confidence Score: 4/5Safe to merge after fixing the sanitizeText corruption bug; the git leading-dash guard is a best-practice hardening that can follow quickly. The clone-and-rewire approach is sound, the path-traversal guard is in place, and V1 UX behaviour is preserved. One clear functional bug (HTML-encoding Markdown content sent to the AI) will corrupt issue context for every linked GitHub issue. The git leading-dash gap is a minor hardening issue. Both are small, targeted fixes; everything else reviewed is correct.
|
| Filename | Overview |
|---|---|
| packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts | New tRPC router with 6 procedures; good path-traversal guard via safeResolveWorktreePath but compareBaseBranch is passed to git without a leading-dash check, risking flag injection. |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx | 1103-line V1-clone composer with rewired V2 data sources; contains a bug where sanitizeText (HTML encoding) is applied to a Markdown issue body, corrupting blockquotes, code, and arrows before they reach the AI agent. |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext.tsx | Draft context restructured to track linkedIssues/linkedPR instead of tab state; resetKey added for PromptInputProvider sync; no-op guard removed from updateDraft causes spurious re-renders. |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts | Simplified to a thin callback over workspaceCreation.create; isPending state and ensureWorkspaceInSidebar removed intentionally (navigation handles sidebar update in V2). |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx | New content pane resolving V2 project list from v2Projects + githubRepositories live collections and delegating composer to PromptGroup; pre-selection logic looks correct. |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceModal.tsx | Modal shell gains PromptInputProvider wrapper and PromptInputResetSync helper; onFocusOutside prevention added; overall clean structural change. |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts | New hook wrapping workspaceCreation.searchBranches via react-query; correct query key including hostUrl ensures cache separation per host. |
| packages/host-service/src/trpc/router/router.ts | Minimal change: registers the new workspaceCreationRouter under the workspaceCreation key. |
Sequence Diagram
sequenceDiagram
participant UI as PromptGroup (renderer)
participant Draft as DraftContext
participant HS as host-service (workspaceCreation)
participant Git as local git
participant Cloud as Cloud API
UI->>Draft: updateDraft(prompt/branch/linkedIssue)
UI->>UI: handleCreate()
UI->>UI: generateBranchName (electronTrpc — local)
UI->>HS: workspaceCreation.getGitHubIssueContent (if linked GH issues)
HS-->>UI: issue markdown
UI->>Draft: closeAndResetDraft()
UI->>HS: workspaceCreation.create(projectId, names, composer, linkedContext)
HS->>Git: worktree add / worktree add -b
HS->>Cloud: device.ensureV2Host + v2Workspace.create
Cloud-->>HS: cloudRow
HS->>HS: insert local workspace row
HS-->>UI: {outcome, workspace}
UI->>UI: navigateToV2Workspace(workspace.id)
Reviews (1): Last reviewed commit: "feat(desktop): clone V1 new-workspace co..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (6)
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts (1)
48-51: ExtracthostTarget→hostUrlresolution into one helper.This same branch now exists in several new paths (
useBranchContext,GitHubIssueLinkCommand,PRLinkCommand, and here). Pulling it into a shared helper will keep future host-routing changes from drifting across create/search flows.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts` around lines 48 - 51, Extract the hostTarget → hostUrl resolution into a shared helper (e.g., resolveHostUrl) and replace the duplicated logic in useCreateDashboardWorkspace (inside useCreateDashboardWorkspace.ts where hostUrl is computed), useBranchContext, GitHubIssueLinkCommand, and PRLinkCommand with calls to that helper; the helper should accept the hostTarget object and the activeHostUrl (and access env.RELAY_URL or accept it as a param) and return either activeHostUrl for kind==="local" or `${RELAY_URL}/hosts/${hostId}` for remote hosts so all call sites use the single function.apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx (3)
600-622: Consider extracting inline sanitization functions.The
sanitizeTextandsanitizeUrlfunctions are defined inside themapcallback, which means they're recreated for each issue. For readability and slight performance improvement, consider extracting them outsidehandleCreateor at least outside the loop.♻️ Extract sanitization helpers
// At module level or inside PromptGroupInner (outside handleCreate): const sanitizeText = (str: string) => str.replace(/[&<>"']/g, (char) => { const entities: Record<string, string> = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }; return entities[char] || char; }); const sanitizeUrl = (url: string) => { try { const parsed = new URL(url); if (!["http:", "https:"].includes(parsed.protocol)) { return "#invalid-url"; } return url; } catch { return "#invalid-url"; } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx` around lines 600 - 622, sanitizeText and sanitizeUrl are currently declared inside the map callback within handleCreate (in PromptGroup / PromptGroupInner), causing them to be recreated per iteration; move these helpers out of the loop by extracting them to module scope or to the PromptGroup/PromptGroupInner outer scope so the map callback can call the single shared functions sanitizeText and sanitizeUrl instead of redefining them each time, preserving their implementation and return semantics (including the "#invalid-url" fallback).
446-459: Consider memoizing serialized hostTarget for comparison.The
JSON.stringify(hostTarget)on every render could be optimized, though the impact is minimal given the object's small size. The reset logic correctly clearscompareBaseBranchwhen project or host changes, as specified in PR objectives.♻️ Optional: Memoize hostTarget serialization
+ const serializedHostTarget = useMemo(() => JSON.stringify(hostTarget), [hostTarget]); const previousProjectIdRef = useRef(projectId); - const previousHostRef = useRef(JSON.stringify(hostTarget)); + const previousHostRef = useRef(serializedHostTarget); useEffect(() => { - const nextHost = JSON.stringify(hostTarget); if ( previousProjectIdRef.current !== projectId || - previousHostRef.current !== nextHost + previousHostRef.current !== serializedHostTarget ) { previousProjectIdRef.current = projectId; - previousHostRef.current = nextHost; + previousHostRef.current = serializedHostTarget; updateDraft({ compareBaseBranch: null }); } - }, [projectId, hostTarget, updateDraft]); + }, [projectId, serializedHostTarget, updateDraft]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx` around lines 446 - 459, The JSON.stringify(hostTarget) is being called on every render; memoize the serialized hostTarget with useMemo and use that memoized string for previousHostRef initialization and in the useEffect comparison to avoid repeated serialization. Update references to use the memoized value (e.g., const memoHost = useMemo(() => JSON.stringify(hostTarget), [hostTarget])) and then use previousHostRef, previousProjectIdRef, the useEffect, and updateDraft as before but comparing against memoHost instead of calling JSON.stringify inline.
849-853: Potential slug collision when removing linked issues.The
removeLinkedIssuefunction filters byslug, but both internal issues (e.g., "SUP-123") and GitHub issues (e.g., "#123") could theoretically have overlapping slug formats in edge cases. While the current slug formats appear distinct, using a unique identifier would be more robust.♻️ Use composite key or unique ID for removal
const removeLinkedIssue = (slug: string) => { updateDraft({ - linkedIssues: linkedIssues.filter((issue) => issue.slug !== slug), + linkedIssues: linkedIssues.filter( + (issue) => (issue.url ?? issue.slug) !== (linkedIssues.find(i => i.slug === slug)?.url ?? slug) + ), }); };Or pass the full issue object / URL to
removeLinkedIssuefor unambiguous matching.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx` around lines 849 - 853, The removeLinkedIssue function currently removes items by comparing slug which can collide across internal and GitHub issues; update removeLinkedIssue to accept an unambiguous identifier (preferably the issue's unique id or full URL) or the full issue object and filter linkedIssues using that unique field (e.g., issue.id || issue.url) instead of slug; update callers to pass the chosen unique identifier/object and ensure updateDraft({ linkedIssues: linkedIssues.filter(i => i.id !== id) }) (or equivalent URL/object equality) is used for reliable removal.apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext.tsx (2)
153-186: Context value memoization depends on entire state object.The
useMemodependency array includesstate, which changes on every draft update. This means the context value is recreated on every state change. While this is functionally correct (the value should change when state changes), the manual spreading of individual state properties in lines 155-167 suggests an intent to optimize.Consider whether the memoization provides value here, or if the explicit property spreading is sufficient.
♻️ Alternative: Depend on specific state properties
If fine-grained memoization is desired:
const value = useMemo<DashboardNewWorkspaceDraftContextValue>( () => ({ // ... same object construction }), [ closeAndResetDraft, createWorkspace, onClose, resetDraft, runAsyncAction, - state, + state.selectedProjectId, + state.hostTarget, + state.prompt, + state.compareBaseBranch, + state.runSetupScript, + state.workspaceName, + state.workspaceNameEdited, + state.branchName, + state.branchNameEdited, + state.linkedIssues, + state.linkedPR, + state.draftVersion, + state.resetKey, updateDraft, ], );Or simply remove
useMemosince the value needs to update on every state change anyway.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext.tsx` around lines 153 - 186, The useMemo block for value currently depends on the entire state object causing unnecessary recomputations; update the dependency array of the useMemo (the value created in useMemo) to list the specific state properties used (e.g., state.selectedProjectId, state.hostTarget, state.prompt, state.compareBaseBranch, state.runSetupScript, state.workspaceName, state.workspaceNameEdited, state.branchName, state.branchNameEdited, state.linkedIssues, state.linkedPR, state.draftVersion, state.resetKey) instead of state, and keep the other function deps (closeAndResetDraft, createWorkspace, onClose, resetDraft, runAsyncAction, updateDraft); alternatively remove useMemo entirely if you want the context to update on every state change.
13-28: Consider stricter typing for LinkedIssue source and LinkedPR state.
LinkedIssue.sourceis optional but used as a discriminator inPromptGroup.tsx(e.g.,issue.source === "github"). Similarly,LinkedPR.stateis a plainstringrather than a union type.♻️ Tighten type definitions
export type LinkedIssue = { slug: string; // "#123" for GitHub, "SUP-123" for internal title: string; - source?: "github" | "internal"; + source: "github" | "internal"; url?: string; // GitHub issue URL taskId?: string; // Internal task ID for navigation number?: number; // GitHub issue number state?: "open" | "closed"; }; export type LinkedPR = { prNumber: number; title: string; url: string; - state: string; + state: "open" | "closed" | "merged"; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext.tsx` around lines 13 - 28, The types are too loose: make LinkedIssue.source a required discriminant and narrow LinkedPR.state to a union of known values; specifically update the LinkedIssue type (symbol LinkedIssue) to have source: "github" | "internal" (remove the optional flag) so consumers like PromptGroup.tsx can safely discriminate, and change LinkedPR (symbol LinkedPR) state: "open" | "closed" | "merged" (or the exact set your app uses) instead of string; after changing the types, run TS checks and adjust any call sites that relied on source being undefined or used other PR state strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx`:
- Around line 102-110: The PopoverContent uses onFocusOutside={(e) =>
e.preventDefault()} which blocks Tab navigation from leaving the popover; remove
that onFocusOutside handler so focus can move out naturally and rely on existing
handlers onPointerDownOutside and onEscapeKeyDown to close the popover. Update
the same pattern in the PRLinkCommand component as well (remove the
preventDefault in its PopoverContent onFocusOutside) so keyboard users can
escape via Tab as expected.
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/LinkedGitHubIssuePill/LinkedGitHubIssuePill.tsx`:
- Around line 35-47: LinkedGitHubIssuePill renders a removal Button that's
hidden via opacity/pointer-events on hover-only, but remains focusable via Tab;
update the Button in LinkedGitHubIssuePill to also reveal itself when
keyboard-focused by adding focus-visible (and focus) utility classes so it
becomes visible and interactive on focus (e.g., add
focus-visible:pointer-events-auto focus-visible:opacity-100 and optionally
focus:pointer-events-auto focus:opacity-100 to the existing className), ensuring
the button is both visually exposed and operable when focused via keyboard.
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx`:
- Around line 676-689: The call to buildLaunchRequest currently discards its
AgentLaunchRequest | null return, so create explicit handling: call
buildLaunchRequest(trimmedPrompt, convertedFiles.length > 0 ? convertedFiles :
undefined) and if it returns null, treat that as a validation failure
(clearPendingWorkspace(pendingWorkspaceId), show a toast.error with a clear
message) or, if null is acceptable, add a comment documenting that null means
"no agent selected" and do not abort; ensure the catch block remains for thrown
errors but also handle the null branch so a workspace isn't created silently
without a valid launch request when one is required.
In
`@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts`:
- Around line 267-270: Defaulting both branchName and workspaceName to the
static string "workspace" causes collisions; change the fallback logic in
workspace-creation.ts where branchName and workspaceName are assigned so each
missing name gets a unique default (e.g., use a timestamp/UUID: `branchName =
input.names.branchName || 'branch-' + Date.now()` and `workspaceName =
input.names.workspaceName || 'workspace-' + Date.now()` or similar unique
generator), or alternatively document the intentional behavior; ensure the two
fallbacks are generated independently so they don't both resolve to the same
static value and update any related tests or callers that assume the previous
static default.
- Around line 291-306: There is a race when ensuring the local clone at
repoPath: concurrent requests can both observe !existsSync(repoPath) and start
mkdirSync/clone; wrap the clone logic in a lock and a post-check to avoid
duplicate work and partial clones — e.g., acquire a per-project lock (file lock
or in-memory mutex keyed by input.projectId), re-check existsSync(repoPath)
after acquiring the lock, perform clone into a temp directory and atomically
rename to repoPath or use try/catch to delete partial clones on failure, then
release the lock; also add a short comment near the simpleGit() call explaining
why simpleGit() is used instead of ctx.git(localProject.repoPath) because
ctx.git expects an existing repo.
---
Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx`:
- Around line 600-622: sanitizeText and sanitizeUrl are currently declared
inside the map callback within handleCreate (in PromptGroup / PromptGroupInner),
causing them to be recreated per iteration; move these helpers out of the loop
by extracting them to module scope or to the PromptGroup/PromptGroupInner outer
scope so the map callback can call the single shared functions sanitizeText and
sanitizeUrl instead of redefining them each time, preserving their
implementation and return semantics (including the "#invalid-url" fallback).
- Around line 446-459: The JSON.stringify(hostTarget) is being called on every
render; memoize the serialized hostTarget with useMemo and use that memoized
string for previousHostRef initialization and in the useEffect comparison to
avoid repeated serialization. Update references to use the memoized value (e.g.,
const memoHost = useMemo(() => JSON.stringify(hostTarget), [hostTarget])) and
then use previousHostRef, previousProjectIdRef, the useEffect, and updateDraft
as before but comparing against memoHost instead of calling JSON.stringify
inline.
- Around line 849-853: The removeLinkedIssue function currently removes items by
comparing slug which can collide across internal and GitHub issues; update
removeLinkedIssue to accept an unambiguous identifier (preferably the issue's
unique id or full URL) or the full issue object and filter linkedIssues using
that unique field (e.g., issue.id || issue.url) instead of slug; update callers
to pass the chosen unique identifier/object and ensure updateDraft({
linkedIssues: linkedIssues.filter(i => i.id !== id) }) (or equivalent URL/object
equality) is used for reliable removal.
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext.tsx`:
- Around line 153-186: The useMemo block for value currently depends on the
entire state object causing unnecessary recomputations; update the dependency
array of the useMemo (the value created in useMemo) to list the specific state
properties used (e.g., state.selectedProjectId, state.hostTarget, state.prompt,
state.compareBaseBranch, state.runSetupScript, state.workspaceName,
state.workspaceNameEdited, state.branchName, state.branchNameEdited,
state.linkedIssues, state.linkedPR, state.draftVersion, state.resetKey) instead
of state, and keep the other function deps (closeAndResetDraft, createWorkspace,
onClose, resetDraft, runAsyncAction, updateDraft); alternatively remove useMemo
entirely if you want the context to update on every state change.
- Around line 13-28: The types are too loose: make LinkedIssue.source a required
discriminant and narrow LinkedPR.state to a union of known values; specifically
update the LinkedIssue type (symbol LinkedIssue) to have source: "github" |
"internal" (remove the optional flag) so consumers like PromptGroup.tsx can
safely discriminate, and change LinkedPR (symbol LinkedPR) state: "open" |
"closed" | "merged" (or the exact set your app uses) instead of string; after
changing the types, run TS checks and adjust any call sites that relied on
source being undefined or used other PR state strings.
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts`:
- Around line 48-51: Extract the hostTarget → hostUrl resolution into a shared
helper (e.g., resolveHostUrl) and replace the duplicated logic in
useCreateDashboardWorkspace (inside useCreateDashboardWorkspace.ts where hostUrl
is computed), useBranchContext, GitHubIssueLinkCommand, and PRLinkCommand with
calls to that helper; the helper should accept the hostTarget object and the
activeHostUrl (and access env.RELAY_URL or accept it as a param) and return
either activeHostUrl for kind==="local" or `${RELAY_URL}/hosts/${hostId}` for
remote hosts so all call sites use the single function.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dbdf8e32-0d38-4629-98a0-39949c0dbc5f
📒 Files selected for processing (43)
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceModal.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/DashboardNewWorkspaceForm.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/LinkedGitHubIssuePill/LinkedGitHubIssuePill.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/LinkedGitHubIssuePill/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/LinkedPRPill/LinkedPRPill.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/LinkedPRPill/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/BranchesGroup/BranchesGroup.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/BranchesGroup/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceFormHeader/DashboardNewWorkspaceFormHeader.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceFormHeader/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceListTabContent/DashboardNewWorkspaceListTabContent.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceListTabContent/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspacePromptTabContent/DashboardNewWorkspacePromptTabContent.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspacePromptTabContent/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/IssuesGroup/IssuesGroup.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/IssuesGroup/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/ProjectSelector/ProjectSelector.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/ProjectSelector/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PromptGroup/PromptGroup.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PromptGroup/components/PromptGroupAdvancedOptions/PromptGroupAdvancedOptions.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PromptGroup/components/PromptGroupAdvancedOptions/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PullRequestsGroup/PullRequestsGroup.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PullRequestsGroup/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useDashboardNewWorkspaceProjectSelection/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useDashboardNewWorkspaceProjectSelection/useDashboardNewWorkspaceProjectSelection.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useResolvedLocalProject/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useResolvedLocalProject/useResolvedLocalProject.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsxapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/index.tsapps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.tspackages/host-service/src/trpc/router/router.tspackages/host-service/src/trpc/router/workspace-creation/index.tspackages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
💤 Files with no reviewable changes (23)
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceFormHeader/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/BranchesGroup/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PromptGroup/components/PromptGroupAdvancedOptions/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useResolvedLocalProject/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspacePromptTabContent/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceListTabContent/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/ProjectSelector/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspacePromptTabContent/DashboardNewWorkspacePromptTabContent.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/IssuesGroup/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceFormHeader/DashboardNewWorkspaceFormHeader.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/IssuesGroup/IssuesGroup.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useResolvedLocalProject/useResolvedLocalProject.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DashboardNewWorkspaceListTabContent/DashboardNewWorkspaceListTabContent.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PromptGroup/components/PromptGroupAdvancedOptions/PromptGroupAdvancedOptions.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PromptGroup/PromptGroup.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/BranchesGroup/BranchesGroup.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PullRequestsGroup/PullRequestsGroup.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/DashboardNewWorkspaceForm.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/PullRequestsGroup/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/ProjectSelector/ProjectSelector.tsx
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useDashboardNewWorkspaceProjectSelection/index.ts
- apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useDashboardNewWorkspaceProjectSelection/useDashboardNewWorkspaceProjectSelection.ts
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
There was a problem hiding this comment.
8 issues found across 43 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx:677">
P1: The built `AgentLaunchRequest` is unused, so the selected agent/context never gets handed off after workspace creation.</violation>
<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx:723">
P1: Send the effective base branch here. Right now the UI shows the default branch, but create falls back to `HEAD`, so new workspaces can fork from the wrong branch.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx:44">
P2: This no longer preserves V1's `getRecents` behavior: `recentProjects` is an unordered map of all `v2Projects`, so the picker and fallback selection can default to an arbitrary project instead of a recent one.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsx:76">
P2: Parse pasted GitHub PR URLs before calling `searchPullRequests`. The new endpoint searches `in:title`, so sending the raw URL makes pasted PR links stop resolving.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts:36">
P2: `searchBranches` cannot populate branch context on a host that has not cloned the project yet, so the remote-host branch picker comes up empty on first use.</violation>
</file>
<file name="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts">
<violation number="1" location="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts:296">
P2: Handle concurrent clone attempts for the same repo path; the current check-then-clone sequence can race and fail when requests arrive at the same time.</violation>
<violation number="2" location="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts:339">
P1: Verify an existing path is a real git worktree before adopting it as a workspace.</violation>
<violation number="3" location="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts:369">
P1: Roll back the newly created worktree when host registration fails.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
After merging main, v2Project.get and v2Workspace.create were converted to jwtProcedure and now require organizationId in their input. The host-service workspace.create endpoint was already updated in main, but our new workspaceCreation router still had four call sites missing it.
New route: /v2-workspace/pending/$pendingId - Reads pending workspace from pendingWorkspaces collection via useLiveQuery - Polls workspaceCreation.getProgress every 500ms for step-by-step progress - Shows: workspace name, branch name, step checklist (ensuring_repo → creating_worktree → registering) - On succeeded: auto-navigates to /v2-workspace/$workspaceId, cleans up pending row - On failed: shows error message with Retry + Dismiss buttons - Retry resets status to creating (full re-fire is a follow-up TODO) - Dismiss deletes pending row + clears IndexedDB attachments
- Replace single zustand pendingWorkspace with useLiveQuery on pendingWorkspaces collection (supports multiple concurrent creates) - Pending sidebar items are now clickable — navigate to /v2-workspace/pending/$pendingId to view progress - Add "failed" status to creationStatus type across sidebar components (types, icon, collapsed button, status text helper) - Succeeded pending workspaces are filtered out (replaced by real workspace from Electric sync)
PromptGroup.tsx: 890 → 441 lines (UI render only) Extracted: - components/AttachmentButtons/ (74 lines) - components/ProjectPickerPill/ (79 lines) - components/CompareBaseBranchPicker/ (134 lines) - hooks/useHandleCreate/ (181 lines) — full create orchestration - types.ts (15 lines) — shared types + constants Existing sub-components unchanged: - GitHubIssueLinkCommand, LinkedGitHubIssuePill, LinkedPRPill, PRLinkCommand
- Failed: red triangle icon + red "Failed" text - Creating: keeps spinner + gray "Creating..." text
Host-service getProgress now returns fully resolved steps with id, label, and status (pending/active/done). The renderer maps over the array directly — no STEP_LABELS, no STEP_ORDER, no string matching. If the host-service adds/changes steps, the UI updates automatically.
Was returning "now" for everything under 60s. Now returns "now" for <5s, then "5s", "30s", etc. Pending page consumes it for the elapsed timer instead of a custom formatter.
- Delete dev-seed-pending-workspace.ts and DevSeedPendingWorkspace.tsx - Remove stash restore effect from DashboardNewWorkspaceModalContent (draft recovery now handled by pending page retry, not modal reopen) - Remove DevSeedPendingWorkspace from layout - Leave zustand store untouched (V1 still uses it)
The pending page was under /v2-workspace/pending/$pendingId which caused the v2-workspace layout to render bare <Outlet /> during route transitions. This removed the WorkspaceTrpcProvider from the tree while TerminalPane was still mounted → crash. Moved to /_dashboard/pending/$pendingId — completely outside the v2-workspace layout. The layout reverts to main's version unchanged.
…ayout bug - Dismiss button always visible on pending page (not just when stale) - Added TODO comment on v2-workspace layout explaining the bare Outlet bug that strips WorkspaceTrpcProvider during transitions - Removed instrumentation console.logs
Create flow uses `git worktree add -b` directly. No try/catch fallback to checkout. If branch exists despite dedup, the git command errors and the create fails cleanly. Checking out existing branches is a separate intent (createFromPr). Updated decisions doc (decision #8).
Replace workspace-${uuid} with friendly-words pattern (e.g.
"cheerful-umbrella"). Generate once, use for both branch name and
workspace name when user typed neither. New shared util at
shared/utils/friendly-branch-name.ts.
- Extract useRetryCreate hook that re-fires createWorkspace with draft data from the pending collection row + attachments from IndexedDB - Retry button calls the hook instead of inline logic - Move elapsed timer to right-aligned end of status line
V1 uses "compareBaseBranch" because it serves double duty as the git fork point AND the diff comparison base. For V2's create flow it's just the fork point — "baseBranch" is clearer. The workspace view can derive its own compare base independently. Renamed in: draft context, PromptGroup, useHandleCreate, useCreateDashboardWorkspace, host-service create input schema, pendingWorkspace collection schema, pending page retry. V1 code untouched.
…UserBranchName Two clearly different operations: - slugifyForBranch: turns arbitrary text (prompts) into a branch slug. Lowercases, strips special chars, collapses spaces to dashes. - sanitizeUserBranchName: strips only what git forbids from a user-typed branch name. Preserves case, slashes, underscores. Host-service no longer sanitizes — it only validates (non-empty) and deduplicates. The renderer owns all sanitization/slugification. Removed sanitizeBranchNameWithMaxLength from host-service utils.
…unctions - resolveNames(draft) — pure. Computes branch + workspace names from draft state. User-typed → sanitizeUserBranchName, prompt → slugifyForBranch, empty → friendlyBranchName. - mapLinkedContext(draft) — pure. Maps linked issues/PR to API payload shape. - useSubmitWorkspace(projectId) — the hook. Orchestrates resolve → store → insert → close → navigate → fire create. Calls the pure functions. Dependency array simplified: just draft object instead of 12 individual fields.
Truncates base name upfront to 94 chars (reserving 6 for suffix like -99999) before appending dedup suffixes. Strips trailing .- after truncation. Last-resort fallback uses base36 timestamp instead of decimal for shorter output.
Summary
Replaces the tab-based V2 create-workspace modal with a clone of the battle-tested V1 composer, rewiring only the backend boundaries. This is the "revert + clone + rewire" approach agreed on after the hand-rewritten V2 modal (#3204) kept accumulating subtle regressions flagged by reviewers.
Supersedes #3204 (now POC).
What changes
Backend boundary swaps (V1 electronTrpc → V2 host-service):
electronTrpc.projects.getRecents→v2Projects+githubRepositoriescollectionselectronTrpc.projects.getBranches*→workspaceCreation.searchBranchescreate/createFromPr/openTrackedWorktree/openExternalWorktree) → singleworkspaceCreation.createwith server-side outcome resolutionelectronTrpc.projects.listIssues→workspaceCreation.searchGitHubIssues(Octokit)electronTrpc.projects.listPullRequests/searchPullRequests→workspaceCreation.searchPullRequests(Octokit)electronTrpc.projects.getIssueContent→workspaceCreation.getGitHubIssueContent(Octokit)navigateToWorkspace→navigateToV2WorkspaceV2 additions:
DevicePickerin the composer footer for host target selectionRetained app-local `electronTrpc` calls (not GH/FS/git, explicit decision):
Intentionally dropped for Phase 1 (deferred to Phase 2):
New host-service endpoints
All under `workspaceCreation.*`:
Why clone + rewire instead of rewrite
The V1 `PromptGroup` has years of accumulated UX polish (draft preservation on failure, pill key handling, keyboard shortcut routing, attachment lifecycle, animation, agent launch request building, etc.) that a reimplementation kept missing. Cloning V1 and surgically swapping the data boundaries is a smaller diff surface and inherits all of that behavior for free.
Test plan
Summary by cubic
Cloned the V1 new‑workspace composer into the V2 modal and wired it to
workspaceCreation.*on the host‑service, adding a pending workspace page with live progress, retry, and clickable sidebar entries. Naming now resolves in the renderer; the server only deduplicates and always creates a new worktree/branch.New Features
workspaceCreation.create: always new branch usinggit worktree add -b; server dedupes the branch (truncates base for suffix); optional setup script; returns the workspace, warnings, andinitialCommands./_dashboard/pending/$pendingIdthat pollsworkspaceCreation.getProgress; auto‑navigate on success; retry re‑runs create using the pending row + IndexedDB attachments; dismiss on failure; elapsed timer shows “now” for <5s, then seconds; staleness detection; attachments cleared on success; sidebar items are clickable and show “failed” when appropriate.Refactors
v2Projects+githubRepositories; branches viaworkspaceCreation.searchBranches. Renderer computes names (branch = user input viasanitizeUserBranchName> prompt slug viaslugifyForBranch> friendly two‑word; workspace = input > prompt > branch). Host only deduplicates.baseBranch; pending state sourced frompendingWorkspaces; attachments persisted in IndexedDB; host calls includeorganizationId; improved error handling with worktree rollback; composer split into UI vs orchestration; extractedresolveNames,mapLinkedContext,useSubmitWorkspace;formatRelativeTimeshows seconds; pending page moved outside the V2 workspace layout.Written for commit 6ad0bb9. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation