Skip to content

feat(desktop): choose a Host for new tasks - #3122

Merged
M4n5ter merged 8 commits into
mainfrom
feat/desktop-host-aware-new-task
Aug 17, 2026
Merged

feat(desktop): choose a Host for new tasks#3122
M4n5ter merged 8 commits into
mainfrom
feat/desktop-host-aware-new-task

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Aug 16, 2026

Copy link
Copy Markdown
Member
English

Summary

  • Let the new-task composer choose a Project from any ready Runtime Host
  • Bind model discovery, readiness, Skills, mentions, drafts, Session creation, and the first send to that exact Host and Project
  • Keep the default Host and existing Sessions unchanged; only non-Local selections receive a compact Host badge

Refs #2522

Verification

  • Desktop test suite: 892 passed
  • Desktop typecheck, renderer production build, and changed-file Biome checks passed
  • Manual Desktop smoke test with Local and a remote Runtime Host completed a Host-side task without changing the default Host
中文

概要

  • 新任务编辑器可以选择任意已就绪 Runtime Host 上的 Project
  • 模型发现、readiness、Skills、mentions、草稿、Session 创建和首次发送始终绑定同一个 Host 与 Project
  • 不修改默认 Host,也不影响已有 Session;仅非 Local 选择显示紧凑的 Host 徽标

关联 #2522

验证

  • Desktop 测试 892 项全部通过
  • Desktop typecheck、renderer 生产构建及变更文件 Biome 检查通过
  • 使用 Local 与远程 Runtime Host 完成 Desktop 实机验证,远程任务正常完成且默认 Host 未改变

Visual comparison

Before: one Project list After: grouped Hosts and an empty Local Host with bootstrap actions
Before: single-Host Project picker After: multi-Host Project picker

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: OpenAI Codex assisted with implementation, tests, and verification under the maintainer's direction

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary

This PR lets users select a Project from any ready Runtime Host in the desktop new-task composer. The selected Host and Project control model discovery, readiness, Skills, mentions, file search, session creation, and the first message. Existing Sessions and the default Host remain unchanged. Non-Local selections show a Host badge.

Design assessment

The PR extends the existing new-task, session, Runtime Host, project-management, and preload bridge paths. It does not create a parallel session flow. Existing-session operations continue to use session-scoped APIs.

The new catalog, target, and draft-key state isolates unsaved composers by Host and Project. The grouped workspace picker supports Projects across multiple Hosts. These changes appear necessary for the requested behavior and form the smallest coherent solution.

No safe deletion or simplification is evident from the current diff. The tests preserve coverage for target forwarding, permission modes, stale results, unresolved draft handoff, cleanup, failure feedback, project-selection side effects, and existing-session behavior.

Validation

Reported validation includes:

  • 880 desktop tests passed.
  • Desktop typecheck passed.
  • Renderer production build passed.
  • Biome passed for all 18 changed files.
  • Manual testing confirmed remote Project model discovery, task completion, and preservation of the default Host when switching drafts.

The supplied repository search found no direct check-result records. Required checks therefore remain unverified here.

Review-relevant risks

  • The preload bridge adds the public maka.newTasks API and exported target and catalog types. Material public-contract changes require independent human review under repository policy.
  • The composer changes user-visible Host, Project, readiness, relinking, no-project, and no-model behavior. Material user-visible behavior changes require independent human review under repository policy.
  • Project-management changes alter whether adding a Project updates Host selection. Material state-management behavior changes require independent human review under repository policy.
  • No protected-area effect on security, licensing, releases, or governance was identified in the current diff.
  • The person performing the merge must review the final diff. A maintainer makes the final determination.

Walkthrough

New-task creation now targets a selected Runtime Host and project. The preload bridge exposes host-scoped APIs. Renderer state, workspace selection, readiness, composer routing, drafts, models, permissions, and tests use target-specific context.

Changes

Host-scoped new-task targeting

Layer / File(s) Summary
Bridge contracts and workspace resolution
apps/desktop/src/preload/*, apps/desktop/src/main/runtime-host-boot.ts, apps/desktop/src/main/runtime-host-skills-ipc-main.ts, apps/desktop/src/main/workspace-search-ipc-main.ts
The bridge exposes typed new-task APIs. IPC resolves explicit projects, no-project selections, and local or remote hosts.
Target state and workspace picker
apps/desktop/src/renderer/use-new-task-target.ts, apps/desktop/src/renderer/use-shell-connections.ts, apps/desktop/src/renderer/app-shell.tsx, packages/ui/src/workspace-picker.tsx
The renderer loads host catalogs, tracks targets, manages projects, routes connection snapshots, and renders grouped workspace choices.
New-task creation and routing
apps/desktop/src/renderer/app-shell-chat-actions.ts, apps/desktop/src/renderer/app-shell-session-start-actions.ts, apps/desktop/src/renderer/use-task-submission-readiness.ts, apps/desktop/src/renderer/use-composer-mentions.ts, apps/desktop/src/main/__tests__/*
Creation, readiness, skill lookup, file search, and mention subscriptions use the selected target. Tests cover forwarding and cleanup.
Target-specific drafts and composer state
apps/desktop/src/renderer/chat-composer-region.tsx, apps/desktop/src/renderer/new-task-reload-intent.ts, apps/desktop/src/renderer/use-shell-chat-model.ts, apps/desktop/src/renderer/app-shell.tsx, packages/ui/src/composer.tsx, packages/ui/src/use-composer-draft.ts
Draft keys scope persistence, imports, models, thinking levels, permissions, and host-specific model guidance.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: 🟡 Moderate · up to cf678

The change can lose a persisted draft during host/project handoff, leave model discovery unresolved when host state changes repeatedly, and route readiness recovery to the wrong host. These are bounded but concrete correctness and availability risks in the current implementation, so merge should wait for fixes or explicit owner acceptance.

Possibly related PRs

Suggested reviewers: astro-han

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Ai Use Disclosure ✅ Passed The PR selects substantive AI use and names OpenAI Codex with scope; all five introduced commits contain valid, consistent Generated-by: OpenAI Codex trailers.
Title check ✅ Passed The title clearly and concisely describes the main change: Host selection for new tasks.
Description check ✅ Passed The description includes the required summary, issue reference, verification, AI use, checklist, behavior change, and supporting visual evidence.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-host-aware-new-task

Comment @coderabbitai help to get the list of available commands.

@M4n5ter
M4n5ter marked this pull request as ready for review August 16, 2026 13:26
@M4n5ter
M4n5ter force-pushed the feat/desktop-host-aware-new-task branch from f409a92 to e0683c3 Compare August 16, 2026 13:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/desktop/src/main/__tests__/runtime-host-skills-ipc-main.test.ts (1)

47-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the explicit no-project selection.

The test covers a string projectId. It does not cover projectId: null, which normalizeNewSessionProjectId preserves as a distinct value and which resolveNewSessionWorkspaceTarget in apps/desktop/src/main/runtime-host-boot.ts maps to a host_path target for local hosts and to undefined for remote hosts. That branch is the one most likely to regress into undefined.

Assert that resolvedProjectIds receives null when the new-session context carries projectId: null.

apps/desktop/src/renderer/use-new-task-target.ts (1)

91-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared project-mutation flow.

addProject and relinkProject differ only in the bridge call. The capability guard, pending handling, selection update, refresh(), and the error toast are identical. One helper that takes the bridge call keeps the two paths from drifting.

♻️ Proposed extraction
+  async function commitProjectSelection(
+    host: ReadyHost,
+    run: () => Promise<{ ok: true; project: ProjectRecord } | { ok: false; reason: 'cancelled' }>,
+  ): Promise<void> {
+    if (!host.capabilities.chooseClientDirectory || pending) return;
+    setPending(true);
+    try {
+      const result = await run();
+      if (!result.ok) return;
+      setSelectedProfileId(host.profile.id);
+      setProjectSelections((current) =>
+        new Map(current).set(host.profile.id, result.project.id),
+      );
+      await refresh();
+    } catch (error) {
+      options.toastApi.error(
+        copy.selectDirectoryFailedTitle,
+        localizedShellErrorMessage(error, copy.readPathFailedFallback, options.uiLocale),
+      );
+    } finally {
+      setPending(false);
+    }
+  }
+
+  const addProject = (host: ReadyHost): Promise<void> =>
+    commitProjectSelection(host, () =>
+      window.maka.newTasks.addProject({ profileId: host.profile.id, hostId: host.hostId }),
+    );
+
+  const relinkProject = (host: ReadyHost, projectId: string): Promise<void> =>
+    commitProjectSelection(host, () =>
+      window.maka.newTasks.relinkProject(
+        { profileId: host.profile.id, hostId: host.hostId },
+        projectId,
+      ),
+    );
apps/desktop/src/renderer/use-shell-connections.ts (1)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the snapshot key once, and use useEffect for the refresh.

The key expression options.newTaskHost?.hostId ?? (options.defaultHost ? DEFAULT_HOST_KEY : NO_HOST_KEY) appears at Line 42 and Line 65, and Line 66 restates the same "no host context" condition a third time. A single helper keeps the three in step.

The refresh at Line 52 does not need layout timing. useEffect avoids running an IPC dispatch in the pre-paint phase.

♻️ Proposed consolidation
+  const hostKeyFor = (sessionId?: string): string | undefined =>
+    sessionId
+      ? parseDesktopSessionKey(sessionId).hostId
+      : options.newTaskHost?.hostId ??
+        (options.defaultHost ? DEFAULT_HOST_KEY : undefined);
+
-  const snapshotKey = options.activeSessionId
-    ? parseDesktopSessionKey(options.activeSessionId).hostId
-    : options.newTaskHost?.hostId ?? (options.defaultHost ? DEFAULT_HOST_KEY : NO_HOST_KEY);
+  const snapshotKey = hostKeyFor(options.activeSessionId) ?? NO_HOST_KEY;
@@
-  useLayoutEffect(() => {
+  useEffect(() => {
     if (!options.activeSessionId && (options.newTaskHost || options.defaultHost)) {
       void refreshConnections();
     }
   }, [options.activeSessionId, options.newTaskHost?.hostId, options.defaultHost]);
@@
   async function refreshConnections(sessionId?: string) {
-    const key = sessionId
-      ? parseDesktopSessionKey(sessionId).hostId
-      : options.newTaskHost?.hostId ?? (options.defaultHost ? DEFAULT_HOST_KEY : NO_HOST_KEY);
-    if (!sessionId && !options.newTaskHost && !options.defaultHost) return;
+    const key = hostKeyFor(sessionId);
+    if (!key) return;

Also applies to: 52-56, 62-66

apps/desktop/src/main/__tests__/app-shell-first-send-cleanup.test.ts (1)

174-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add one case for a missing new-task target.

send gains a new early return when there is no active session and newTaskTarget is undefined (apps/desktop/src/renderer/app-shell-chat-actions.ts lines 306-309). That branch is the only guard that stops a send with no Host. No test covers it, so a later refactor can drop it silently.

Assert that send returns false and that newTasks.create is never called.

💚 Proposed test
it('does not create a session when no new-task target is selected', async () => {
  let created = false;
  const restoreWindow = installWindow({
    newTasks: {
      create: async () => {
        created = true;
        return { id: 'session-1' };
      },
    },
    sessions: {},
  });
  try {
    const deps = { ...createActionsDeps(), newTaskTarget: undefined };
    assert.equal(await createAppShellChatActions(deps).send('hello'), false);
  } finally {
    restoreWindow();
  }
  assert.equal(created, false);
});

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b319ef8c-545d-4ec5-803f-cfbb085a6214

📥 Commits

Reviewing files that changed from the base of the PR and between 0dc3e5e and f409a92.

📒 Files selected for processing (18)
  • apps/desktop/src/main/__tests__/app-shell-first-send-cleanup.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-skills-ipc-main.test.ts
  • apps/desktop/src/main/runtime-host-boot.ts
  • apps/desktop/src/main/runtime-host-skills-ipc-main.ts
  • apps/desktop/src/main/workspace-search-ipc-main.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/app-shell-chat-actions.ts
  • apps/desktop/src/renderer/app-shell-session-start-actions.ts
  • apps/desktop/src/renderer/app-shell.tsx
  • apps/desktop/src/renderer/locales/shell-copy.ts
  • apps/desktop/src/renderer/styles/composer.css
  • apps/desktop/src/renderer/use-composer-mentions.ts
  • apps/desktop/src/renderer/use-new-task-target.ts
  • apps/desktop/src/renderer/use-shell-connections.ts
  • apps/desktop/src/renderer/use-task-submission-readiness.ts
  • apps/desktop/stories/app-shell.stories.tsx
  • packages/ui/src/workspace-picker.tsx

Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.

Comment thread apps/desktop/src/preload/preload.ts
Comment thread apps/desktop/src/renderer/app-shell.tsx Outdated
@M4n5ter
M4n5ter force-pushed the feat/desktop-host-aware-new-task branch from d9ae67d to 712372a Compare August 16, 2026 14:23
Allow the session-less composer to select a Project from any connected Runtime Host without changing the default Host. Bind model discovery, readiness, Skills, mentions, Session creation, and the first send to the same selected target.

Generated-by: OpenAI Codex
Keep drafts, model and permission choices, connection projections, and recovery actions scoped to the selected Host and Project. Catalog failures now remain recoverable without borrowing or mutating the default Host's state.

Generated-by: OpenAI Codex
@M4n5ter
M4n5ter force-pushed the feat/desktop-host-aware-new-task branch from 712372a to 8e15584 Compare August 16, 2026 14:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/desktop/src/main/__tests__/app-shell-busy-race-settlement.test.ts (1)

91-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the obsolete newChatProjectId fixture field.

createAppShellChatActions receives newTaskTarget. The unused newChatProjectId field suggests that this test still covers the removed contract. Delete it.

As per path instructions, “Flag concrete cases where code can be deleted or simplified.”

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 51170732-db43-4d5b-92ec-d01c7f7ecc88

📥 Commits

Reviewing files that changed from the base of the PR and between e0683c3 and 8e15584.

📒 Files selected for processing (23)
  • apps/desktop/src/main/__tests__/app-shell-busy-race-settlement.test.ts
  • apps/desktop/src/main/__tests__/app-shell-first-send-cleanup.test.ts
  • apps/desktop/src/main/__tests__/app-shell-session-settings-actions.test.ts
  • apps/desktop/src/main/__tests__/bootstrap-selection-lease.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-skills-ipc-main.test.ts
  • apps/desktop/src/main/runtime-host-skills-ipc-main.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/app-shell-chat-actions.ts
  • apps/desktop/src/renderer/app-shell-command-actions.ts
  • apps/desktop/src/renderer/app-shell-session-settings-actions.ts
  • apps/desktop/src/renderer/app-shell-session-start-actions.ts
  • apps/desktop/src/renderer/app-shell.tsx
  • apps/desktop/src/renderer/chat-composer-region.tsx
  • apps/desktop/src/renderer/locales/shell-copy.ts
  • apps/desktop/src/renderer/new-task-reload-intent.ts
  • apps/desktop/src/renderer/styles/composer.css
  • apps/desktop/src/renderer/use-composer-mentions.ts
  • apps/desktop/src/renderer/use-new-task-target.ts
  • apps/desktop/src/renderer/use-shell-chat-model.ts
  • apps/desktop/src/renderer/use-shell-connections.ts
  • packages/ui/src/composer.tsx
  • packages/ui/src/workspace-picker.tsx
🚧 Files skipped from review as they are similar to previous changes (10)
  • apps/desktop/src/main/tests/runtime-host-skills-ipc-main.test.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/renderer/styles/composer.css
  • apps/desktop/src/renderer/use-new-task-target.ts
  • apps/desktop/src/main/tests/app-shell-first-send-cleanup.test.ts
  • apps/desktop/src/renderer/app-shell-session-start-actions.ts
  • apps/desktop/src/renderer/app-shell-chat-actions.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/use-composer-mentions.ts
  • packages/ui/src/workspace-picker.tsx

Included review availability: Your plan includes up to 3 reviews per rolling hour; 0 remain after this review.

Comment thread apps/desktop/src/renderer/app-shell.tsx Outdated
Keep the existing new-chat race coverage on the target-scoped Session creation path introduced by multi-Host task selection.

Generated-by: OpenAI Codex
Keep target-specific model, thinking, permission, and reload draft choices when switching Hosts or resolving startup catalog state. Seed the default Host connection surface from onboarding so startup never presents a false empty model list.

Generated-by: OpenAI Codex
@M4n5ter
M4n5ter force-pushed the feat/desktop-host-aware-new-task branch from 8e15584 to e0bdb8c Compare August 16, 2026 15:14
Keep draft Project selection independent from the Host's persisted current Project and preserve the unresolved new-task draft when catalog discovery settles behind another Session. Make empty ready Hosts enterable without moving the selected Host's fixed actions into the project scroller.

Generated-by: OpenAI Codex

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 99481d52-8341-4606-a89f-0518b9c8763f

📥 Commits

Reviewing files that changed from the base of the PR and between e0bdb8c and cf6780d.

📒 Files selected for processing (13)
  • apps/desktop/src/main/__tests__/chat-composer-region-draft-handoff.test.tsx
  • apps/desktop/src/main/__tests__/new-session-project.test.ts
  • apps/desktop/src/main/__tests__/project-management-service.test.ts
  • apps/desktop/src/main/app-ipc-main.ts
  • apps/desktop/src/main/new-session-project.ts
  • apps/desktop/src/main/project-management-service.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/app-shell-chat-actions.ts
  • apps/desktop/src/renderer/chat-composer-region.tsx
  • apps/desktop/stories/app-shell.stories.tsx
  • packages/ui/src/composer.tsx
  • packages/ui/src/use-composer-draft.ts
  • packages/ui/src/workspace-picker.tsx
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/app-shell-chat-actions.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/renderer/chat-composer-region.tsx
  • apps/desktop/src/preload/preload.ts
  • packages/ui/src/workspace-picker.tsx

Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.

Comment thread packages/ui/src/use-composer-draft.ts
Express the renderer regression with createElement so it remains a standard Desktop test entry and is included by the repository's unused-file check.

Generated-by: OpenAI Codex
Let the draft owner hydrate a requested inactive slot from its persistence adapter when memory has no value. This preserves an unresolved new-task draft even when the renderer starts on an existing Session.

Generated-by: OpenAI Codex
Keep the default Host connection projection independent from multi-Host catalog availability. Resolve implicit Project selection from the Host's configured and current preferences, and keep the picker usable during background refreshes.

Generated-by: OpenAI Codex
@M4n5ter

M4n5ter commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

I have completed the full verification, and after assessment, I believe this change has minimal impact on the existing system. I take full responsibility for this and decide to merge it.

shaokeyibb added a commit to shaokeyibb/maka that referenced this pull request Aug 22, 2026
…hanges

Draft text, staged attachments and staged quotes are all keyed by (profileId,
hostId, projectId) since apache#3122, and the workspace picker that changes the
project part sits directly under the composer — so choosing a Project re-keyed
all three mid-composition and what the user was writing dropped out of view.

Carry them to the target the user selects. The buckets stay keyed per target,
so apache#3122's Host-scoped new-task state is unchanged; they move with the
selection instead of staying behind under the key the user navigated away from.

The move is unconditional: the target arrived at holds what was brought to it
and nothing else. Carrying only into an empty target would leave copies under
every key the composer passed through, and one would resurface later — send the
task, come back to an empty composer, pick another target, and the text just
sent would reappear as that target's own draft.

The carry keys on the new-task target rather than on the composer's active key,
so a Session keeps its own draft and attachments when the target moves behind
it. Leaving the unresolved slot keeps its existing reload-lease guard: that
transition is startup settling, not a choice, and its draft may belong to one
specific target.

Fixes apache#3408

Generated-by: Claude Code (Claude Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
shaokeyibb added a commit to shaokeyibb/maka that referenced this pull request Aug 22, 2026
Review found two ways pending content could escape the owner an in-flight
operation had captured.

Staged files and quotes no longer follow the workspace picker at all. They are
in-memory intent, not Host state — nothing persists them and nothing restores
them per target — so they go back to one key that never moves, as they were
before apache#3122. `send()` captures the key it submitted from and clears exactly
that key once it resolves, so a key that followed the picker left the files it
had already delivered staged under the new target, ready to be sent a second
time. A key that cannot go stale needs no re-keying rule to keep it honest,
which removes rekeyPending and the carry hook with it.

The draft text keeps its target-scoped key, because its reload lease belongs to
one specific target. ChatComposerRegion therefore defers its carry while a send
is settling: the submission owns the text it submitted until the completion
clears the key it captured, and text typed after that send is carried once it
does.

pickAttachments resolves its owner after the native dialog closes rather than
capturing it before opening. The surface can change while a dialog is up, and
files the user just chose belong in the composer they are looking at, not in a
bucket they have left where the files are invisible but still sendable.

Generated-by: Claude Code (Claude Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Astro-Han pushed a commit that referenced this pull request Aug 22, 2026
…hanges (#3432)

* fix(desktop): keep the new-task composer's contents when the target changes

Draft text, staged attachments and staged quotes are all keyed by (profileId,
hostId, projectId) since #3122, and the workspace picker that changes the
project part sits directly under the composer — so choosing a Project re-keyed
all three mid-composition and what the user was writing dropped out of view.

Carry them to the target the user selects. The buckets stay keyed per target,
so #3122's Host-scoped new-task state is unchanged; they move with the
selection instead of staying behind under the key the user navigated away from.

The move is unconditional: the target arrived at holds what was brought to it
and nothing else. Carrying only into an empty target would leave copies under
every key the composer passed through, and one would resurface later — send the
task, come back to an empty composer, pick another target, and the text just
sent would reappear as that target's own draft.

The carry keys on the new-task target rather than on the composer's active key,
so a Session keeps its own draft and attachments when the target moves behind
it. Leaving the unresolved slot keeps its existing reload-lease guard: that
transition is startup settling, not a choice, and its draft may belong to one
specific target.

Fixes #3408

Generated-by: Claude Code (Claude Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(desktop): give the new-task composer's staged content one owner

Review found two ways pending content could escape the owner an in-flight
operation had captured.

Staged files and quotes no longer follow the workspace picker at all. They are
in-memory intent, not Host state — nothing persists them and nothing restores
them per target — so they go back to one key that never moves, as they were
before #3122. `send()` captures the key it submitted from and clears exactly
that key once it resolves, so a key that followed the picker left the files it
had already delivered staged under the new target, ready to be sent a second
time. A key that cannot go stale needs no re-keying rule to keep it honest,
which removes rekeyPending and the carry hook with it.

The draft text keeps its target-scoped key, because its reload lease belongs to
one specific target. ChatComposerRegion therefore defers its carry while a send
is settling: the submission owns the text it submitted until the completion
clears the key it captured, and text typed after that send is carried once it
does.

pickAttachments resolves its owner after the native dialog closes rather than
capturing it before opening. The surface can change while a dialog is up, and
files the user just chose belong in the composer they are looking at, not in a
bucket they have left where the files are invisible but still sendable.

Generated-by: Claude Code (Claude Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant