From ade4354ddd60cb7d7c4953d88b9fd0028ce067ac Mon Sep 17 00:00:00 2001
From: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Date: Wed, 5 Aug 2026 02:34:13 -0700
Subject: [PATCH] Open GitHub items in workspace composer with issue automation
(#12653)
* Open TaskPage GitHub items directly in workspace composer
Remove background creation indirection. The composer prefills with
issue/pull-request metadata from the GitHub work item.
* Generalize workspace composer tests to support multiple sources
Rename test from task-page-github-composer-boundary to task-page-workspace-composer-boundary. Add test verifying Linear items open directly in the workspace composer, expanding beyond GitHub-specific routing.
* Support issue command automation in workspace composer
- Pass GitHub work items through composer for issue context
- Quick create resolves commands at workspace creation time
- Extract command building and trust logic to dedicated module
* Derive composer hooks by host context for React Doctor
Key loaded hooks and issue commands by execution-host context so
repo/host switches no longer reset derived state in effects (the
static-analysis gate). Also wire SSH-aware workspace targets and
cancel-safe submit settlement for workspace creation.
* Support duplicate repo IDs across hosts in workspace creation
When the same repo exists on multiple hosts (local and SSH), resolve the
workspace creation target to the ready setup on the preferred host instead of
failing closed. Also add resilience to hook checks by clearing the cache on
transient IPC failures.
---
.../components/NewWorkspaceComposerModal.tsx | 58 +-
src/renderer/src/components/TaskPage.tsx | 13 +-
...-github-background-create-boundary.test.ts | 44 --
...k-page-workspace-composer-boundary.test.ts | 127 +++++
...poserState-host-context-boundaries.test.ts | 56 +-
.../useComposerState-host-retarget.test.ts | 62 ++
src/renderer/src/hooks/useComposerState.ts | 534 ++++++++++++++----
.../src/lib/composer-issue-command.test.ts | 30 +
.../src/lib/composer-issue-command.ts | 37 ++
.../lib/composer-submit-cancellation.test.ts | 44 ++
.../src/lib/composer-submit-cancellation.ts | 18 +
.../src/lib/ensure-hooks-confirmed.test.ts | 98 +++-
.../src/lib/ensure-hooks-confirmed.ts | 173 +++++-
.../src/lib/new-workspace-create-gates.ts | 3 +-
.../src/lib/pending-worktree-creation.test.ts | 99 ++++
.../src/lib/pending-worktree-creation.ts | 23 +
.../lib/project-host-workspace-target.test.ts | 84 +++
.../src/lib/project-host-workspace-target.ts | 72 ++-
.../lib/worktree-creation-flow-dedupe.test.ts | 110 ++++
.../src/lib/worktree-creation-flow-startup.ts | 50 ++
.../src/lib/worktree-creation-flow.ts | 73 +--
21 files changed, 1514 insertions(+), 294 deletions(-)
delete mode 100644 src/renderer/src/components/task-page-github-background-create-boundary.test.ts
create mode 100644 src/renderer/src/components/task-page-workspace-composer-boundary.test.ts
create mode 100644 src/renderer/src/hooks/useComposerState-host-retarget.test.ts
create mode 100644 src/renderer/src/lib/composer-issue-command.test.ts
create mode 100644 src/renderer/src/lib/composer-issue-command.ts
create mode 100644 src/renderer/src/lib/composer-submit-cancellation.test.ts
create mode 100644 src/renderer/src/lib/composer-submit-cancellation.ts
create mode 100644 src/renderer/src/lib/pending-worktree-creation.test.ts
create mode 100644 src/renderer/src/lib/worktree-creation-flow-dedupe.test.ts
create mode 100644 src/renderer/src/lib/worktree-creation-flow-startup.ts
diff --git a/src/renderer/src/components/NewWorkspaceComposerModal.tsx b/src/renderer/src/components/NewWorkspaceComposerModal.tsx
index f04a8f80d..aa46a10f7 100644
--- a/src/renderer/src/components/NewWorkspaceComposerModal.tsx
+++ b/src/renderer/src/components/NewWorkspaceComposerModal.tsx
@@ -1,4 +1,4 @@
-import React, { Suspense, useCallback, useEffect, useMemo, useState } from 'react'
+import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useAppStore } from '@/store'
import { lazyWithRetry } from '@/lib/lazy-with-retry'
import {
@@ -20,6 +20,7 @@ import type { LinkedWorkItemSummary } from '@/lib/new-workspace'
import { shouldAllowComposerEnterSubmitTarget } from '@/lib/new-workspace-enter-guard'
import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut'
import type {
+ GitHubWorkItem,
TuiAgent,
WorkspaceCreateTelemetrySource,
WorkspaceStatus
@@ -41,9 +42,11 @@ type ComposerModalData = {
initialEphemeralVmRecipeId?: string
initialProjectGroupId?: string
linkedWorkItem?: LinkedWorkItemSummary | null
+ initialGitHubWorkItem?: GitHubWorkItem | null
taskSourceContext?: TaskSourceContext | null
initialBaseBranch?: string
initialWorkspaceStatus?: WorkspaceStatus
+ enableIssueAutomation?: boolean
/** Telemetry surface that opened the composer. Set by each
* `openModal('new-workspace-composer', ...)` site so
* `workspace_created.source` carries the right value. Falls back to
@@ -58,42 +61,29 @@ export default function NewWorkspaceComposerModal(): React.JSX.Element | null {
const modalData = useAppStore((s) => s.modalData as ComposerModalData | undefined)
const closeModal = useAppStore((s) => s.closeModal)
- // Why: Dialog open-state transitions must be driven by the store, not a
- // mirror useState, so palette/open-modal calls feel instantaneous and the
- // modal doesn't linger with stale data after close.
- const handleOpenChange = useCallback(
- (open: boolean) => {
- if (!open) {
- closeModal()
- }
- },
- [closeModal]
- )
-
if (!visible) {
return null
}
- return (
-
- )
+ return
}
function ComposerModalBody({
modalData,
- onClose,
- onOpenChange
+ onClose
}: {
modalData: ComposerModalData
onClose: () => void
- onOpenChange: (open: boolean) => void
}): React.JSX.Element {
+ const submitCancelledRef = useRef(false)
+ const handleDismiss = useCallback(() => {
+ submitCancelledRef.current = true
+ onClose()
+ }, [onClose])
+ const isSubmissionCancelled = useCallback(() => submitCancelledRef.current, [])
+
return (
-