feat(desktop): Ports セクションのワークスペース名をワークツリー名に改善#8
Conversation
同名ワークスペース(例: 全て "default")が複数ある場合に区別がつかない問題を修正。 getAllGrouped のキャッシュからワークツリーパスの basename を取得し表示名に使用。
📝 WalkthroughWalkthroughThe PR enhances workspace naming in the Ports section by deriving display names from worktree directory basenames. It replaces the flat workspace query with a grouped one and implements a helper function to construct disambiguated display names combining both directory and workspace information. Changes
Sequence DiagramsequenceDiagram
participant Component as PortsList Component
participant Hook as usePortsData Hook
participant Query as electronTrpc.workspaces.getAllGrouped
participant Helper as buildWorkspaceDisplayNames
participant Result as Display Names
Component->>Hook: Call usePortsData()
Hook->>Query: Query getAllGrouped()
Query-->>Hook: Return grouped workspaces & sections
Hook->>Helper: Process groups with buildWorkspaceDisplayNames
Helper->>Helper: Extract worktree directory basename
Helper->>Helper: Construct display name (basename + name if different)
Helper-->>Result: Return workspace display name mapping
Result-->>Component: Provide disambiguated names for rendering
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 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 |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts (1)
36-36: Consider cross-platform path handling.Using
split("/")assumes Unix-style paths. If Windows support is planned, this won't correctly extract the basename from backslash-delimited paths.♻️ Suggested refactor for cross-platform compatibility
- const basename = ws.worktreePath.split("/").pop() || ws.name; + // Handle both Unix (/) and Windows (\) path separators + const basename = ws.worktreePath.split(/[/\\]/).pop() || ws.name;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts` at line 36, The basename computation uses split("/") which fails on Windows paths; update the logic in usePortsData (where basename is computed from ws.worktreePath) to use a proper path utility instead of string splitting — e.g., import and call Node's path.basename (or the appropriate cross-platform path helper available in your environment) to compute the basename from ws.worktreePath and fall back to ws.name if empty; also ensure ws.worktreePath is validated before calling the path helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts`:
- Line 36: The basename computation uses split("/") which fails on Windows
paths; update the logic in usePortsData (where basename is computed from
ws.worktreePath) to use a proper path utility instead of string splitting —
e.g., import and call Node's path.basename (or the appropriate cross-platform
path helper available in your environment) to compute the basename from
ws.worktreePath and fall back to ws.name if empty; also ensure ws.worktreePath
is validated before calling the path helper.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2cccd4b0-3a51-4450-8b54-8534b2103b2f
📒 Files selected for processing (2)
README.mdapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
…t-sh#3302) * feat(desktop): clone V1 new-workspace composer onto V2 modal 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 * fix: pass organizationId to cloud API calls in workspaceCreation router 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. * chore: add instrumentation to workspace creation flow Temporary console.log at key points to debug "workspace created" toast showing but workspace not appearing: - useCreateDashboardWorkspace: log input + result - PromptGroup: log create result + navigation - workspaceCreation.create: log resolved names, each outcome path * Update docs * docs: clean up plan files, keep only final decisions + scenario analysis Remove superseded docs (v2-create-fix-plan, v2-create-decisions, v1-workspace-creation-logic). Keep v1-create-scenario-analysis as V1 behavior reference and v2-create-decisions-final as the 13 implementation decisions. * feat: implement v2 create decisions — always create, never collide Host-service workspaceCreation.create rewrite: - Strip collision detection (no opened_existing_workspace / opened_worktree / adopted_external_worktree). Create always creates. - Sanitize + deduplicate branch name server-side. If branch exists, append -2, -3, etc. Renderer sends best-effort name. - Simplified return: { workspace, warnings } — no outcome field. - Add setup script execution (runs .superset/setup.sh in worktree, blocks until done, non-fatal on failure). - Remove source and behavior fields from input schema. - Add sanitize-branch utils (copied from shared/utils/branch.ts). Renderer PromptGroup rewrite: - Remove AI branch gen (electronTrpc.workspaces.generateBranchName) — boundary violation, needs V1 project ID. - Renderer computes branch name: user-typed > prompt slug > random UUID. - Renderer computes workspace name: user-typed > prompt > branch name. - Single pending phase: "creating" (no "generating-branch" or "preparing"). - Remove buildLaunchRequest, hostUrl resolution, agentConfigsById — dead code after removing AI gen and collision paths. Per decisions in apps/desktop/plans/v2-create-decisions-final.md. * feat: draft stash for failure recovery On create submit: snapshot the draft into a zustand atom before closing the modal. On success: clear the stash. On failure: restore the stash and reopen the modal so the user can retry with their prompt, attachments, and linked context intact. - Added stashedDraft / stashDraft / clearStashedDraft / restoreStashedDraft to the new-workspace-modal zustand store - PromptGroup.handleCreate stashes before closeAndResetDraft, restores on catch - DashboardNewWorkspaceModalContent applies stash on reopen via useEffect that reads from the zustand store - Replaced runAsyncAction with direct try/catch for clearer success/failure handling * chore: add logging + error handling to cloud API calls in create The create flow was hanging silently when ensureV2Host or v2Workspace.create failed (e.g. Neon DB schema mismatch). Now both calls log before + catch errors explicitly, rollback the worktree on failure, and throw with a descriptive message instead of hanging. * docs: finalize v2 workspace creation status + pending workspace design - pendingWorkspaces local collection (localStorage-backed via @tanstack/react-db) holds full draft data for retry on failure - Attachments stored as raw blobs in IndexedDB (no compression — files are already compressed, IndexedDB has no size limit) - EventBus workspace:creating events for live step-by-step progress - /v2-workspace/pending/$pendingId route shows progress, error + retry - Sidebar renders pending workspaces as clickable skeletons - Multiple concurrent creates supported - Host-service returns initialCommands, setup dispatched to terminal pane * docs: switch from EventBus to polling for create progress - Host-service writes to in-memory Map during create mutation - New getProgress query endpoint, pending page polls every 500ms - PromptGroup owns the create promise (fire-and-forget, survives unmount), updates pendingWorkspaces collection on resolve/reject - Pending page is purely a progress viewer, not an owner - Sidebar reads collection via useLiveQuery, no polling - Removed all EventBus references from the design * docs: add TODO for cleaning up stale createProgress map entries * chore: add TODO to migrate chat pane uploads to IndexedDB blob pattern * chore: move IndexedDB migration TODO to ChatLaunchConfig where base64 blobs are stored * feat: add create progress infrastructure Host-service: - In-memory progress map with TTL sweep for stale entries - getProgress query endpoint (polled by pending page at 500ms) - Create mutation writes step progress (ensuring_repo → creating_worktree → registering) - Clears progress on success or failure - Returns initialCommands instead of running execSync (setup dispatched to terminal pane) - Accepts pendingId for progress correlation Renderer: - pendingWorkspaces local collection (localStorage-backed via @tanstack/react-db) with full draft data for retry on failure - pending-attachment-store.ts: IndexedDB wrapper for attachment blobs (store/load/clear keyed by pendingId) - useCreateDashboardWorkspace accepts + forwards pendingId * feat: rewrite PromptGroup submit to use pendingWorkspaces collection + IndexedDB - Insert full draft into pendingWorkspaces collection before closing modal - Store attachment blobs in IndexedDB (keyed by pendingId/blobUUID) - Navigate to /v2-workspace/pending/$pendingId immediately - Fire-and-forget createWorkspace — closure survives modal unmount - On success: update collection row to succeeded, clear IndexedDB blobs - On failure: update collection row to failed (draft preserved for retry) - Removed zustand stashedDraft/pendingWorkspace hooks (replaced by collection) - Removed inline convertBlobUrlToDataUrl (moved to pending-attachment-store) * feat: add pending workspace page with live progress polling 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 * feat: sidebar renders pending workspaces from collection, clickable - 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) * refactor: split PromptGroup into focused files 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 * fix: sidebar pending workspace visual states - Failed: red triangle icon + red "Failed" text - Creating: keeps spinner + gray "Creating..." text * fix: center pending workspace page layout * fix: pending page centered on page, left-aligned text * fix: pending page takes full width of content area * refactor: host-service defines step labels, renderer just renders 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. * fix: pending page top-aligned with padding, update dev seed scripts * feat: elapsed timer + staleness detection on pending page * fix: coerce createdAt to timestamp regardless of string/Date type * fix: timer inline before status text * fix: formatRelativeTime shows seconds under 1 minute 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. * chore: remove dev seed files and dead stash restore code - 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) * fix: move pending page out of v2-workspace layout 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. * docs: add comment explaining why pending page lives outside v2-workspace * chore: remove instrumentation console.logs from workspaceCreation.create * fix: always show dismiss on creating page, add TODO on v2-workspace layout 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 * fix: always create new branch, never try checkout existing 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). * feat: use friendly two-word names for fallback branch/workspace names 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. * feat: wire up retry from pending page, move timer to end - 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 * refactor: rename compareBaseBranch to baseBranch in V2 create flow 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. * refactor: split branch name handling into slugifyForBranch + sanitizeUserBranchName 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. * refactor: rename useHandleCreate → useSubmitWorkspace, extract pure functions - 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. * fix: dedup suffix can no longer exceed max branch length 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. * lint
…t-sh#3302) * feat(desktop): clone V1 new-workspace composer onto V2 modal 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 * fix: pass organizationId to cloud API calls in workspaceCreation router 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. * chore: add instrumentation to workspace creation flow Temporary console.log at key points to debug "workspace created" toast showing but workspace not appearing: - useCreateDashboardWorkspace: log input + result - PromptGroup: log create result + navigation - workspaceCreation.create: log resolved names, each outcome path * Update docs * docs: clean up plan files, keep only final decisions + scenario analysis Remove superseded docs (v2-create-fix-plan, v2-create-decisions, v1-workspace-creation-logic). Keep v1-create-scenario-analysis as V1 behavior reference and v2-create-decisions-final as the 13 implementation decisions. * feat: implement v2 create decisions — always create, never collide Host-service workspaceCreation.create rewrite: - Strip collision detection (no opened_existing_workspace / opened_worktree / adopted_external_worktree). Create always creates. - Sanitize + deduplicate branch name server-side. If branch exists, append -2, -3, etc. Renderer sends best-effort name. - Simplified return: { workspace, warnings } — no outcome field. - Add setup script execution (runs .superset/setup.sh in worktree, blocks until done, non-fatal on failure). - Remove source and behavior fields from input schema. - Add sanitize-branch utils (copied from shared/utils/branch.ts). Renderer PromptGroup rewrite: - Remove AI branch gen (electronTrpc.workspaces.generateBranchName) — boundary violation, needs V1 project ID. - Renderer computes branch name: user-typed > prompt slug > random UUID. - Renderer computes workspace name: user-typed > prompt > branch name. - Single pending phase: "creating" (no "generating-branch" or "preparing"). - Remove buildLaunchRequest, hostUrl resolution, agentConfigsById — dead code after removing AI gen and collision paths. Per decisions in apps/desktop/plans/v2-create-decisions-final.md. * feat: draft stash for failure recovery On create submit: snapshot the draft into a zustand atom before closing the modal. On success: clear the stash. On failure: restore the stash and reopen the modal so the user can retry with their prompt, attachments, and linked context intact. - Added stashedDraft / stashDraft / clearStashedDraft / restoreStashedDraft to the new-workspace-modal zustand store - PromptGroup.handleCreate stashes before closeAndResetDraft, restores on catch - DashboardNewWorkspaceModalContent applies stash on reopen via useEffect that reads from the zustand store - Replaced runAsyncAction with direct try/catch for clearer success/failure handling * chore: add logging + error handling to cloud API calls in create The create flow was hanging silently when ensureV2Host or v2Workspace.create failed (e.g. Neon DB schema mismatch). Now both calls log before + catch errors explicitly, rollback the worktree on failure, and throw with a descriptive message instead of hanging. * docs: finalize v2 workspace creation status + pending workspace design - pendingWorkspaces local collection (localStorage-backed via @tanstack/react-db) holds full draft data for retry on failure - Attachments stored as raw blobs in IndexedDB (no compression — files are already compressed, IndexedDB has no size limit) - EventBus workspace:creating events for live step-by-step progress - /v2-workspace/pending/$pendingId route shows progress, error + retry - Sidebar renders pending workspaces as clickable skeletons - Multiple concurrent creates supported - Host-service returns initialCommands, setup dispatched to terminal pane * docs: switch from EventBus to polling for create progress - Host-service writes to in-memory Map during create mutation - New getProgress query endpoint, pending page polls every 500ms - PromptGroup owns the create promise (fire-and-forget, survives unmount), updates pendingWorkspaces collection on resolve/reject - Pending page is purely a progress viewer, not an owner - Sidebar reads collection via useLiveQuery, no polling - Removed all EventBus references from the design * docs: add TODO for cleaning up stale createProgress map entries * chore: add TODO to migrate chat pane uploads to IndexedDB blob pattern * chore: move IndexedDB migration TODO to ChatLaunchConfig where base64 blobs are stored * feat: add create progress infrastructure Host-service: - In-memory progress map with TTL sweep for stale entries - getProgress query endpoint (polled by pending page at 500ms) - Create mutation writes step progress (ensuring_repo → creating_worktree → registering) - Clears progress on success or failure - Returns initialCommands instead of running execSync (setup dispatched to terminal pane) - Accepts pendingId for progress correlation Renderer: - pendingWorkspaces local collection (localStorage-backed via @tanstack/react-db) with full draft data for retry on failure - pending-attachment-store.ts: IndexedDB wrapper for attachment blobs (store/load/clear keyed by pendingId) - useCreateDashboardWorkspace accepts + forwards pendingId * feat: rewrite PromptGroup submit to use pendingWorkspaces collection + IndexedDB - Insert full draft into pendingWorkspaces collection before closing modal - Store attachment blobs in IndexedDB (keyed by pendingId/blobUUID) - Navigate to /v2-workspace/pending/$pendingId immediately - Fire-and-forget createWorkspace — closure survives modal unmount - On success: update collection row to succeeded, clear IndexedDB blobs - On failure: update collection row to failed (draft preserved for retry) - Removed zustand stashedDraft/pendingWorkspace hooks (replaced by collection) - Removed inline convertBlobUrlToDataUrl (moved to pending-attachment-store) * feat: add pending workspace page with live progress polling 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 * feat: sidebar renders pending workspaces from collection, clickable - 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) * refactor: split PromptGroup into focused files 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 * fix: sidebar pending workspace visual states - Failed: red triangle icon + red "Failed" text - Creating: keeps spinner + gray "Creating..." text * fix: center pending workspace page layout * fix: pending page centered on page, left-aligned text * fix: pending page takes full width of content area * refactor: host-service defines step labels, renderer just renders 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. * fix: pending page top-aligned with padding, update dev seed scripts * feat: elapsed timer + staleness detection on pending page * fix: coerce createdAt to timestamp regardless of string/Date type * fix: timer inline before status text * fix: formatRelativeTime shows seconds under 1 minute 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. * chore: remove dev seed files and dead stash restore code - 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) * fix: move pending page out of v2-workspace layout 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. * docs: add comment explaining why pending page lives outside v2-workspace * chore: remove instrumentation console.logs from workspaceCreation.create * fix: always show dismiss on creating page, add TODO on v2-workspace layout 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 * fix: always create new branch, never try checkout existing 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). * feat: use friendly two-word names for fallback branch/workspace names 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. * feat: wire up retry from pending page, move timer to end - 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 * refactor: rename compareBaseBranch to baseBranch in V2 create flow 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. * refactor: split branch name handling into slugifyForBranch + sanitizeUserBranchName 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. * refactor: rename useHandleCreate → useSubmitWorkspace, extract pure functions - 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. * fix: dedup suffix can no longer exceed max branch length 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. * lint
…uperset-sh#3517) * remove 7 day rule * Upgrade mastra * upgrade ai * Ad mastra * refactor(desktop): remove dead provider-diagnostics plumbing The provider-diagnostics store was fed by callSmallModel's per-attempt reporting, which was removed when small-model tasks moved to direct AI-SDK + mastracode's AuthStorage. Nothing writes to the issue map anymore, so the clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in ModelsSettings were all no-ops. Settings still surfaces "Session expired / Reconnect" via auth-status alone. ProviderIssue type collapsed from 8 codes to just "expired" to match. * fix(auth): auto-refresh expired Anthropic OAuth tokens Anthropic credentials were read via authStorage.get() everywhere, so mastracode's built-in refresh flow never ran. Once the 1-hour access token expired, status flipped to "Reconnect" and users had to do a full PKCE re-auth, even though a valid refresh token was already stored. Resolvers now call authStorage.getApiKey() for oauth creds on expiry, which triggers refreshToken() and persists the refreshed credential. getAnthropicAuthStatus does the same before declaring issue: "expired". Mirrors the pattern already used for OpenAI small-model auth. * review: address PR feedback from cubic + coderabbit + greptile - host-service ai-branch-name: run trailing-trim after slice so a 100-char truncation can't re-introduce a bare "." or "-" that git rejects as an invalid ref (coderabbit / cubic #2, #7). - host-service workspace-creation.generateBranchName: reuse the existing listBranchNames helper instead of the inline git walk, which classified off the short refname and could conflate a local "origin/foo" with refs/remotes/origin/foo (coderabbit #3). - packages/chat shared/small-model: drop the unused hasSmallModelCredentials export; only a test mock consumed it (greptile #4). - resolveAnthropicCredential: on refresh failure, return null instead of kind:"oauth" with a stale expiresAt so callers fall back cleanly (cubic #8). - chat-service.getAnthropicAuthStatus: log context when refresh throws instead of silently swallowing (cubic #9). * fix(chat): read auth.json directly instead of importing mastracode Importing createAuthStorage from mastracode loads the entire CLI tree (fastembed → onnxruntime-node's 208 MB native binary) via eager top-level requires in mastracode's CJS entry. This crashed electron-vite bundling and bloated the get-small-model chunk. getSmallModel now reads mastracode's auth.json file directly using the same path resolution logic (~/Library/Application Support/mastracode/ on macOS). Zero mastracode import, zero bundle impact. The chunk stays at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai). Production build verified: compile:app succeeds, Electron main process boots with no onnxruntime error. * docs(desktop): add manual testing plan for PR superset-sh#3517 * fix api key storage slot * fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now use authStorage.setStoredApiKey() (writes to "apikey:<provider>") instead of authStorage.set() (writes to the main "<provider>" slot shared with OAuth). This way connecting/disconnecting OAuth doesn't overwrite or delete a stored API key. resolveAuthMethodForProvider falls back to hasStoredApiKey() after checking the main slot, so status correctly reports authenticated when only an API key is stored. * fix(auth): backup/restore API keys across OAuth connect/disconnect mastracode's resolveModel only reads API keys from the main authStorage slot (authStorage.get("anthropic")). OAuth login overwrites this slot, and disconnect removes it — losing any previously saved API key. Fix: backup the API key to the dedicated apikey: slot before OAuth connect, restore it after disconnect. setApiKeyForProvider now writes to both slots (main for resolveModel compatibility, apikey: for backup). resolveAuthMethodForProvider checks both. Applies to both Anthropic and OpenAI providers. * chore: add upstream PR reference to auth workaround Point to mastra-ai/mastra#15483 so the backup/restore code can be removed once upstream lands and we bump mastracode. * refactor(desktop): derive settings provider action from status Replace the cascade of if/else + canDisconnect flag with a single getProviderAction(status) → connect | reconnect | logout | null. Fixes "Active" badge + "Connect" button showing simultaneously when authenticated via API key. * fix(desktop): always show Logout when provider is active Active providers now always show a Logout button. Clears OAuth or API key depending on authMethod — no more "Active" badge with no way to disconnect. * fix(desktop): simplify OpenAI OAuth dialog + auto-open browser Match Anthropic dialog's layout: remove the raw OAuth URL display and "Tip" block, auto-open the browser on OAuth start. Change "Back" to "Cancel" for consistency. * refactor(desktop): unify OAuth dialogs into shared OAuthDialog Extract shared OAuthDialog component with provider config object. AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers that pass provider-specific labels and options. * fix(desktop): show 'Copied!' feedback on Copy URL button * refactor(desktop): merge provider account + API key into single card Each provider section now renders AccountCard + ConfigRow inside one rounded card with a divider, instead of two separate cards. Removes the standalone "API Keys" collapsible section. * refactor(desktop): compact OAuth row in provider settings card OAuth row is now a single inline row (label + status + action) instead of a stacked AccountCard. Both providers share the same 2-row card layout: OAuth row + API key row with divider. * fix(desktop): contextual buttons in provider settings Connect is now primary (filled). Save only shows when there's input. Clear only shows when a key is saved. Removes visual noise from empty-state provider cards. * ui(desktop): add provider icons to settings section headers * ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers * ui: remove redundant disconnected subtitle * ui: remove subtitle text from OAuth rows * chore: remove dead AccountCard + getProviderSubtitle * docs: update test plan to match current UI * chore: move shipped plans to done/ --------- Co-authored-by: AviPeltz <aj.peltz@gmail.com>
…uperset-sh#3517) * remove 7 day rule * Upgrade mastra * upgrade ai * Ad mastra * refactor(desktop): remove dead provider-diagnostics plumbing The provider-diagnostics store was fed by callSmallModel's per-attempt reporting, which was removed when small-model tasks moved to direct AI-SDK + mastracode's AuthStorage. Nothing writes to the issue map anymore, so the clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in ModelsSettings were all no-ops. Settings still surfaces "Session expired / Reconnect" via auth-status alone. ProviderIssue type collapsed from 8 codes to just "expired" to match. * fix(auth): auto-refresh expired Anthropic OAuth tokens Anthropic credentials were read via authStorage.get() everywhere, so mastracode's built-in refresh flow never ran. Once the 1-hour access token expired, status flipped to "Reconnect" and users had to do a full PKCE re-auth, even though a valid refresh token was already stored. Resolvers now call authStorage.getApiKey() for oauth creds on expiry, which triggers refreshToken() and persists the refreshed credential. getAnthropicAuthStatus does the same before declaring issue: "expired". Mirrors the pattern already used for OpenAI small-model auth. * review: address PR feedback from cubic + coderabbit + greptile - host-service ai-branch-name: run trailing-trim after slice so a 100-char truncation can't re-introduce a bare "." or "-" that git rejects as an invalid ref (coderabbit / cubic #2, #7). - host-service workspace-creation.generateBranchName: reuse the existing listBranchNames helper instead of the inline git walk, which classified off the short refname and could conflate a local "origin/foo" with refs/remotes/origin/foo (coderabbit #3). - packages/chat shared/small-model: drop the unused hasSmallModelCredentials export; only a test mock consumed it (greptile #4). - resolveAnthropicCredential: on refresh failure, return null instead of kind:"oauth" with a stale expiresAt so callers fall back cleanly (cubic #8). - chat-service.getAnthropicAuthStatus: log context when refresh throws instead of silently swallowing (cubic #9). * fix(chat): read auth.json directly instead of importing mastracode Importing createAuthStorage from mastracode loads the entire CLI tree (fastembed → onnxruntime-node's 208 MB native binary) via eager top-level requires in mastracode's CJS entry. This crashed electron-vite bundling and bloated the get-small-model chunk. getSmallModel now reads mastracode's auth.json file directly using the same path resolution logic (~/Library/Application Support/mastracode/ on macOS). Zero mastracode import, zero bundle impact. The chunk stays at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai). Production build verified: compile:app succeeds, Electron main process boots with no onnxruntime error. * docs(desktop): add manual testing plan for PR superset-sh#3517 * fix api key storage slot * fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now use authStorage.setStoredApiKey() (writes to "apikey:<provider>") instead of authStorage.set() (writes to the main "<provider>" slot shared with OAuth). This way connecting/disconnecting OAuth doesn't overwrite or delete a stored API key. resolveAuthMethodForProvider falls back to hasStoredApiKey() after checking the main slot, so status correctly reports authenticated when only an API key is stored. * fix(auth): backup/restore API keys across OAuth connect/disconnect mastracode's resolveModel only reads API keys from the main authStorage slot (authStorage.get("anthropic")). OAuth login overwrites this slot, and disconnect removes it — losing any previously saved API key. Fix: backup the API key to the dedicated apikey: slot before OAuth connect, restore it after disconnect. setApiKeyForProvider now writes to both slots (main for resolveModel compatibility, apikey: for backup). resolveAuthMethodForProvider checks both. Applies to both Anthropic and OpenAI providers. * chore: add upstream PR reference to auth workaround Point to mastra-ai/mastra#15483 so the backup/restore code can be removed once upstream lands and we bump mastracode. * refactor(desktop): derive settings provider action from status Replace the cascade of if/else + canDisconnect flag with a single getProviderAction(status) → connect | reconnect | logout | null. Fixes "Active" badge + "Connect" button showing simultaneously when authenticated via API key. * fix(desktop): always show Logout when provider is active Active providers now always show a Logout button. Clears OAuth or API key depending on authMethod — no more "Active" badge with no way to disconnect. * fix(desktop): simplify OpenAI OAuth dialog + auto-open browser Match Anthropic dialog's layout: remove the raw OAuth URL display and "Tip" block, auto-open the browser on OAuth start. Change "Back" to "Cancel" for consistency. * refactor(desktop): unify OAuth dialogs into shared OAuthDialog Extract shared OAuthDialog component with provider config object. AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers that pass provider-specific labels and options. * fix(desktop): show 'Copied!' feedback on Copy URL button * refactor(desktop): merge provider account + API key into single card Each provider section now renders AccountCard + ConfigRow inside one rounded card with a divider, instead of two separate cards. Removes the standalone "API Keys" collapsible section. * refactor(desktop): compact OAuth row in provider settings card OAuth row is now a single inline row (label + status + action) instead of a stacked AccountCard. Both providers share the same 2-row card layout: OAuth row + API key row with divider. * fix(desktop): contextual buttons in provider settings Connect is now primary (filled). Save only shows when there's input. Clear only shows when a key is saved. Removes visual noise from empty-state provider cards. * ui(desktop): add provider icons to settings section headers * ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers * ui: remove redundant disconnected subtitle * ui: remove subtitle text from OAuth rows * chore: remove dead AccountCard + getProviderSubtitle * docs: update test plan to match current UI * chore: move shipped plans to done/ --------- Co-authored-by: AviPeltz <aj.peltz@gmail.com>
概要
Ports セクションのワークスペース名表示を改善し、ワークツリーのディレクトリ名(basename)を使用するようにしました。
背景
複数のワークツリーが同じワークスペース名(例: 全て「default」)を持つ場合、Ports セクションのグループヘッダーが全て同じ名前になり、どのワークツリーのポートか区別がつかない問題がありました。
変更内容
usePortsData.tsのみ(1ファイル)workspaces.getAll→workspaces.getAllGroupedに変更(サイドバーで既にキャッシュ済みのため追加コストなし)getAllGroupedのworktreePathから basename を取得し表示名に使用{ディレクトリ名} ({ワークスペース名})(例:ai-zyusetu-1 (default))コンフリクトリスク
変更は
usePortsData.tsの1ファイルのみ。バックエンドや型定義は変更なし。テスト計画
Summary by CodeRabbit