From 7db57daa1485fe026aa1fc1759a2361b7fdf7b4a Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Fri, 12 Jun 2026 23:23:36 -0700 Subject: [PATCH] Seed workspace creation from task source context Co-authored-by: Orca --- .../host-context-implementation-checklist.md | 3 +- ...poserState-host-context-boundaries.test.ts | 42 ++++++++++++++++++ src/renderer/src/hooks/useComposerState.ts | 44 +++++++++++++++++-- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index 5b8f982078b..ae5199d20b8 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -57,7 +57,7 @@ Done means: users can tell where Tasks are being fetched from, and changing exec ## 4. New Workspace Modal, Start Work, And Workspace Creation - [x] Split “start from task/issue/PR” source context from workspace run target in New Workspace modal state. -- [~] When opening the New Workspace modal from Tasks, preselect the logical project and a reasonable run host without treating the source repo as the only execution target. +- [x] When opening the New Workspace modal from Tasks, preselect the logical project and a reasonable run host without treating the source repo as the only execution target. - [x] Ensure pending worktree/workspace creation persists both the source task context and selected run host. - [x] Make “create workspace on host” only show hosts where the project has a ProjectHostSetup. - [x] Keep “add project to host” as the path for making a project available on another host; do not present workspace creation as if Orca is responsible for bootstrapping repos/dependencies. @@ -189,6 +189,7 @@ Done means: reviewers and future agents can understand the vision, implementatio - [x] 2026-06-12: Added Linear/Jira Settings account-scope diagnostics showing whether provider credentials and account checks are owned by the local desktop client or the selected remote server; verified with provider account-scope tests and web typecheck. - [x] 2026-06-12: Added repo-backed Tasks empty-state copy that distinguishes missing project sources from no matching GitHub/GitLab work, then verified with focused empty-state tests and web typecheck. - [x] 2026-06-12: Scoped Jira Tasks drawer/list cache reconciliation by Jira task source context and site id so same-key issues from different hosts/accounts cannot replace each other; verified with focused cache selector tests, targeted oxlint, and full typecheck. +- [x] 2026-06-12: Seeded New Workspace run-target selection from the incoming task source context so Linear/Jira starts preselect the logical project/source host instead of the ambient active repo; verified with a focused composer host-boundary test, targeted oxlint, and full typecheck. - [x] 2026-06-12: Fed Tasks source-context diagnostics from the shared execution-host registry so the source chip now reports SSH availability and blocked/incompatible remote-server hosts; verified with summary tests and web typecheck. - [x] 2026-06-12: Extended Linear/Jira Tasks source diagnostics to include the account-owning host plus blocked remote-server availability, keeping account source separate from workspace run host; verified with summary tests and web typecheck. - [x] 2026-06-12: Started the user-facing Host copy audit by replacing stale “remote project” and “Remote SSH” UI labels with project-on-host / SSH-host language; verified with web typecheck. diff --git a/src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts b/src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts index 799f81df760..4fb71781705 100644 --- a/src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts +++ b/src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs' import { join } from 'node:path' import { describe, expect, it } from 'vitest' +import { resolveInitialWorkspaceRunSeed } from './useComposerState' const HOOK_SOURCE = readFileSync(join(__dirname, 'useComposerState.ts'), 'utf8') @@ -38,4 +39,45 @@ describe('useComposerState host-context boundaries', () => { expect(section).toContain('repoId: runRepo.id') expect(section).not.toContain('repoId: repoForItem.id') }) + + it('seeds initial workspace run target from the task source context', () => { + expect( + resolveInitialWorkspaceRunSeed({ + initialTaskSourceContext: { + projectId: 'logical-project', + hostId: 'ssh:builder', + projectHostSetupId: 'setup-builder' + } + }) + ).toEqual({ + projectId: 'logical-project', + hostId: 'ssh:builder', + projectHostSetupId: 'setup-builder' + }) + + expect( + resolveInitialWorkspaceRunSeed({ + draftProjectId: 'draft-project', + draftHostId: 'local', + draftProjectHostSetupId: 'setup-local', + initialTaskSourceContext: { + projectId: 'logical-project', + hostId: 'ssh:builder', + projectHostSetupId: 'setup-builder' + } + }) + ).toEqual({ + projectId: 'draft-project', + hostId: 'local', + projectHostSetupId: 'setup-local' + }) + + const section = sourceBetween(HOOK_SOURCE, 'const initialRunSeed', 'const [internalRepoId') + + expect(section).toContain('resolveInitialWorkspaceRunSeed') + expect(section).toContain('initialTaskSourceContext') + expect(section).toContain('projectId: initialRunSeed.projectId') + expect(section).toContain('hostId: initialRunSeed.hostId') + expect(section).toContain('projectHostSetupId: initialRunSeed.projectHostSetupId') + }) }) diff --git a/src/renderer/src/hooks/useComposerState.ts b/src/renderer/src/hooks/useComposerState.ts index fe012f66a67..5319df6119b 100644 --- a/src/renderer/src/hooks/useComposerState.ts +++ b/src/renderer/src/hooks/useComposerState.ts @@ -103,6 +103,7 @@ import { type NewWorkspaceProjectOption } from '@/lib/new-workspace-project-options' import { buildExecutionHostRegistry } from '../../../shared/execution-host-registry' +import { normalizeExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' import { getHostDisplayLabelOverrides } from '../../../shared/host-setting-overrides' import { queueNewWorkspaceTerminalFocus } from '@/lib/new-workspace-terminal-focus' import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner' @@ -281,6 +282,34 @@ export type UseComposerStateResult = { createDisabled: boolean } +export type InitialWorkspaceRunSeedInput = { + draftProjectId?: string | null + draftHostId?: string | null + draftProjectHostSetupId?: string | null + initialTaskSourceContext?: Pick< + TaskSourceContext, + 'projectId' | 'hostId' | 'projectHostSetupId' + > | null +} + +export function resolveInitialWorkspaceRunSeed({ + draftProjectId, + draftHostId, + draftProjectHostSetupId, + initialTaskSourceContext +}: InitialWorkspaceRunSeedInput): { + projectId: string | null + hostId: ExecutionHostId | null + projectHostSetupId: string | null +} { + return { + projectId: draftProjectId ?? initialTaskSourceContext?.projectId ?? null, + hostId: normalizeExecutionHostId(draftHostId ?? initialTaskSourceContext?.hostId), + projectHostSetupId: + draftProjectHostSetupId ?? initialTaskSourceContext?.projectHostSetupId ?? null + } +} + // Why: both the full-page TaskPage composer and the Cmd+J modal can be // mounted simultaneously. Without instance scoping, a single native file // drop fires every subscriber and duplicates attachments/prompt edits across @@ -363,6 +392,15 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS const draftProjectHostSetupId = persistDraft ? (newWorkspaceDraft?.projectHostSetupId ?? null) : null + // Why: Tasks can start work from Linear/Jira source contexts that are not + // repo-backed. Seed the run target from the logical project/source host so + // the modal does not silently fall back to the ambient active repo. + const initialRunSeed = resolveInitialWorkspaceRunSeed({ + draftProjectId, + draftHostId, + draftProjectHostSetupId, + initialTaskSourceContext + }) const resolvedInitialWorkspaceStatus = useMemo( () => initialWorkspaceStatus && isWorkspaceStatusId(initialWorkspaceStatus, workspaceStatuses) @@ -378,9 +416,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS draftRepoId, initialRepoId, activeRepoId, - projectId: draftProjectId, - hostId: draftHostId, - projectHostSetupId: draftProjectHostSetupId, + projectId: initialRunSeed.projectId, + hostId: initialRunSeed.hostId, + projectHostSetupId: initialRunSeed.projectHostSetupId, focusedHostScope: workspaceHostScope })