Skip to content

fix(web,mobile): remove the Purpose control from the composer - #540

Merged
lukemaj merged 3 commits into
mainfrom
fix/remove-purpose-chip
Nov 5, 2026
Merged

lukemaj merged 3 commits into
mainfrom
fix/remove-purpose-chip

Conversation

@lukemaj

@lukemaj lukemaj commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What: The Purpose control ("Purpose: Unscoped" and the "Running: … Send queues the selected purpose" line) is gone from the web/desktop composer and the mobile thread and new-task screens.
Why: The user will never pick a purpose by hand; purpose is set automatically and stored per run, so the control was noise on every thread.
So what: The developer approved merging; it merges once required CI is green and the independent review passes.

Rebased onto current main (no merge commits). Run purpose is still admitted on the server and stored per run exactly as before: web sends record the thread's stored selection (default unscoped) or inherit/target for continuations and Steer/Restart; mobile new-task sends record unscoped; agents still pass purposeIntent through launch, delegate, send and watch tools. Scheduled task forms keep their purpose field (#377).

Changes:

  • apps/web/src/components/chat/useStartTurnWithPurpose.ts (new fork module, with test): one purpose-aware startTurn. It resolves purpose from the request's destination thread, never the viewed one: its stored selection, unscoped for new threads, inherit/target for continuations and Steer/Restart, and legacy for a thread the client has not loaded so a stored selection is never overwritten. ChatView, composer, multiple-model, plan follow-up, implement-plan and goal Resume/Clear sends bind it (review rounds 1 and 2: these paths recorded legacy unscoped, and a delayed send could take the viewed thread's purpose).
  • apps/web/src/components/ChatView.tsx: drops ExecutionPurposeControl, its draft state and change handler; its inline purpose wrapper is replaced by the hook above.
  • apps/mobile/.../ThreadDetailScreen.tsx: drops ThreadExecutionPurposeControl (file deleted).
  • apps/mobile/.../NewTaskDraftScreen.tsx: drops ExecutionPurposePicker and its state; sends record unscoped. The picker stays for scheduled tasks.
  • docs/user/thread-sidebar.md, docs/fork-features.md updated.

Proof: tsc --noEmit passes for apps/web and apps/mobile; vp lint and vp fmt clean on changed files; bash scripts/fork-check.sh --upstream upstream OK. vp test run apps/web/src/components/chat/useStartTurnWithPurpose.test.ts passes (3 tests: destination selection, unloaded thread, new/continuation/Steer). No browser or device pass and no before/after screenshots: dev servers and devices were not authorized in this session.

Requirements and who asked: The user asked to remove the composer Purpose control everywhere and keep purpose automatic and stored per run.
Deleted: The web composer control, mobile thread control, mobile new-task picker, their draft state and change handlers, and ChatView's viewed-thread purpose wrapper.
Bottleneck: This PR sat unreviewed and went stale against main; review and merge approval are the bottleneck.
Checked myself: Searched web and mobile for ExecutionPurposeControl, ThreadExecutionPurposeControl, ExecutionPurposePicker, "Send queues the selected purpose" and "legacy caller"; only scheduled-task uses remain. Ran the typechecks, lint and fork check above.

Opus 5.5 via Claude Code in T3 Code.

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Oct 19, 2026
@lukemaj

lukemaj commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

What: No blocking defects found; PASS for exact head 87b94493b298e077b0b0f0fda58f498d733ae0dd.
Why: Composer removal preserves purpose admission and pending-task submission, and the remaining pickers belong to scheduled-task forms.
So what: The implementation passes independent review; the documentation has a nonblocking wording nit below.

Reviewed against base e5936c25383afe05638b8e6390f5363513a3f7b3 using the complete PR diff and read-only source inspection.

  • Web: apps/web/src/components/ChatView.tsx:2142-2164 retains the stored selection revision for ordinary server-thread sends and a saved scope intent for drafts. Manual continuation uses inherit; Steer/Restart use target; explicit intents and older-server capability handling remain intact. buildLocalDraftThread supplies no purpose selection, so draft sends resolve to the deliberate unscoped default. The removed identifiers have no remaining references in ChatView; updateThreadMetadata still has other callers.
  • Mobile: apps/mobile/src/features/threads/NewTaskDraftScreen.tsx:320-335 still initializes and resynchronizes purpose state from the pending task, including delayed pending-task loading. Submission at :1322-1330 still converts that scope into the saved intent. Removing the picker does not remove its reference-bearing state. Existing-thread submission still uses the stored selection revision in apps/mobile/src/state/use-thread-composer-state.ts:691-700.
  • Surface audit: the only remaining consumers of ExecutionPurposeControl and ExecutionPurposePicker are apps/web/src/components/settings/ScheduledTasksSettings.tsx:1336 and apps/mobile/src/features/settings/SettingsScheduledTasksRouteScreen.tsx:771, as intentionally required for schedules. Desktop shares the web composer. No remaining composer renders either control. Server persistence, contracts, and MCP purpose forwarding are unchanged.
  • Docs: the feature manifest correctly removes the deleted file and retains the two picker components still used by schedules. Nonblocking nit: docs/user/thread-sidebar.md:54 still says a child's follow-up uses its "visible selection", and :57-58 says legacy callers remain "visibly unscoped". The composer no longer exposes that selection or run-purpose display. Describe the stored selection and recorded unscoped purpose instead. This wording does not affect runtime behavior.

Validation: source review plus git diff --check e5936c2538 87b94493b2 (pass). Worktree HEAD and live PR head matched the requested SHA; the worktree remained clean. I did not rerun the author's typechecks/lint or perform browser/device verification. No repository files were edited, committed, or pushed.

Verdict: PASS. No blocking findings.

Reviewer: GPT-6.1-sol, Codex harness, medium review scope.

@lukemaj

lukemaj commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

What: Round 2 PASS with no findings at exact head 4c16c6fcfc97e0cd1472daac1d4b9f8976edc95b.
Why: Both documentation replacements accurately describe the existing code, and nothing else changed since Round 1.
So what: The Round 1 wording nit is resolved; independent review passes for this head.

Reviewed git diff 87b94493b2 4c16c6fcfc in full. The only new commit has Round 1 head 87b94493b298e077b0b0f0fda58f498d733ae0dd as its sole parent. Its entire delta is two line replacements in docs/user/thread-sidebar.md, with no code or other file changes.

  • Line 54, "uses the child's stored purpose": accurate. Ordinary follow-ups submit the stored selection revision in apps/web/src/components/ChatView.tsx:2142-2164 and apps/mobile/src/state/use-thread-composer-state.ts:691-700. The selection branch in apps/server/src/orchestration-v2/ExecutionPurpose.ts:84-98 resolves that stored selection to the run purpose.
  • Line 58, "are recorded as unscoped": accurate. Omitted intent resolves to LEGACY_EXECUTION_PURPOSE in ExecutionPurpose.ts:73-75; packages/contracts/src/executionPurpose.ts:203-207 defines that as unscoped with reason legacy_no_intent. Saved schedules use the same resolution in apps/server/src/scheduledTasks/ScheduledTaskService.ts:848-860. Schedule forms retain their purpose handling.

Validation: exact-SHA source inspection, full delta and commit-parent inspection, and git diff --check 87b94493b2 4c16c6fcfc (pass). Live PR head matched the requested SHA immediately before posting. No runtime tests or browser/device verification were needed for this prose-only delta. No local files were written, and no checkout, edit, commit, push, reset, switch, or pull was performed.

Requirements and who asked: User requested a read-only Round 2 review of the two-line documentation correction and an exact-head verdict.
Deleted: No additional implementation or review scope; Round 1 implementation findings remain outside this delta review.
Bottleneck: Accuracy of the two revised statements against the unchanged code.
Checked myself: Read the exact diff, parent, relevant web/mobile submission code, purpose resolver and schedule path; confirmed only one documentation file changed.

Reviewer: GPT-6.1-sol through the Codex harness in T3 Code; requested effort low.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

⚠️ The latest CI run did not produce a thread transfer result for 4c16c6f.

This comment will update automatically after the next completed run.

Purpose stays recorded per run; agents set it through their launch,
delegate, send and watch calls, and composer sends record the thread's
stored selection (new threads default to unscoped). The web composer,
mobile thread and mobile new-task screens no longer show a purpose
control or the "Running: ... Send queues the selected purpose" line.
Scheduled task forms keep their purpose field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj
lukemaj force-pushed the fix/remove-purpose-chip branch from 4c16c6f to 3512537 Compare November 5, 2026 13:58
@lukemaj lukemaj changed the title fix(web,mobile): remove the Purpose chip from the composer fix(web,mobile): remove the Purpose control from the composer Nov 5, 2026
@lukemaj

lukemaj commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

What: Changes requested for one P2 acceptance failure at exact head 3512537f9f032e86c164475f001afd2e718725a0.
Why: Ordinary web/desktop composer sends bypass the purpose wrapper and omit purposeIntent, so an idle scoped thread's next run loses its stored Issue/PR attribution.
So what: Wire automatic purpose into the actual composer send paths, then review the replacement head against check (2).

P2: Automatic purpose does not reach ordinary web/desktop composer sends.

The changed fallback in apps/web/src/components/ChatView.tsx:1241 belongs to the local startThreadTurn wrapper at lines 1221-1243. Its only caller is sendAppMessage at line 1891. The actual composer uses useComposerSend at ChatView line 2890, and that hook creates its own raw threadEnvironment.startTurn binding at useComposerSend.ts:336. Its ordinary/new-thread dispatch at useComposerSend.ts:1284 through line 1310 supplies no purposeIntent; the Promachos wrapper only adds prismRole.

Trigger: send an ordinary follow-up to an idle thread whose stored purpose selection is an Issue or PR. packages/client-runtime/src/operations/commands.ts:764 preserves the omission, and ExecutionPurpose.ts:73 resolves it to LEGACY_EXECUTION_PURPOSE, rather than the stored selection. Ordinary new-thread launches likewise omit the automatic intent. This fails explicit check (2) and contradicts the updated claim at docs/user/thread-sidebar.md:60. Plan and multiple-model send hooks have the same bypass.

This omission is also present in base 45ab51f58362dbf86e1f2c12c2fb9b60c30e9d99: it is an inherited integration gap against this review's explicit acceptance requirement, not a regression introduced by deleting the controls. Resume, Steer and Restart retain server-side source/target inheritance; omission alone does not demonstrate lost purpose on those paths.

Other requested checks:

  • Searches for ExecutionPurposeControl, ThreadExecutionPurposeControl, ExecutionPurposePicker, Send queues the selected purpose, and legacy caller found no remaining composer/thread controls. The surviving web control is used only by ScheduledTasksSettings with runs=[]; the mobile picker remains in SettingsScheduledTasksRouteScreen.
  • Mobile NewTaskDraftScreen sends explicit unscoped intent on capable environments; existing mobile composer sends selection/target intent, and the outbox preserves it.
  • Removed imports, draft state, handlers and the deleted mobile control have no dangling consumers. Server admission, MCP purpose fields and scheduled-task purpose fields remain intact.
  • The deleted file was removed from the registry; changed upstream files are already allowlisted and registered. The exact candidate has the stated base as its sole parent, and GitHub's Fork Stack Model checks passed.
  • Documentation matches the UI removal; the stored-purpose follow-up claim is inaccurate because of the finding above.

What I tried and sources checked: read the complete gh pr diff 540 --repo toolboxmd/chromeria; inspected exact-head and base source with git -C /home/luke/.t3/worktrees/chromeria/t3-3fed63d9 show, grep, and log; traced ChatView, extracted composer hooks, command bindings, Promachos wrapping, client-runtime serialization, mobile outbox delivery, and server purpose admission; read AGENTS.md, docs/fork.md, the registry, allowlist, and both changed docs. A second read-only reviewer independently checked the candidate and confirmed the same finding. Static review only: no tests, dev servers, browser/device sessions, local writes or live userdata access.

Verdict: changes requested.

Reviewed by gpt-6.1-sol through the Codex harness in T3 Code.

The composer, multiple-model, plan follow-up and implement-plan sends
called startTurn directly and omitted purposeIntent, so the server
recorded legacy unscoped instead of the thread's stored selection. They
now all start turns through useStartTurnWithPurpose, the same rule
ChatView's app-message sends already used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

What: Round 2 requests changes for two P2 purpose-attribution defects.
Why: Delayed sends can adopt another thread's purpose, and goal actions still bypass automatic purpose.
So what: Correct these paths and review the replacement head.

Reviewed head: acf4653d16007d43c2945092b3bed17a8925b4ac
Base: origin/main, 45ab51f58362dbf86e1f2c12c2fb9b60c30e9d99

  1. P2: Delayed sends can overwrite the destination thread's purpose with the currently viewed thread's purpose.

Location: useStartTurnWithPurpose.ts:36-40.

Trigger: start an ordinary send with a pending attachment upload in idle server thread A, then navigate to server thread B with a different stored purpose before the upload finishes.

ThreadRouteView.tsx:21 deliberately reuses the same unkeyed ChatView between server threads. useStableCallback.ts:10-18 updates the shared callback to the latest committed render. Meanwhile, useComposerSend.ts:914 awaits uploads and later dispatches the captured thread A request at line 1286; navigation does not cancel this ordinary send (the route guards at lines 975 and 1072 concern approval handling).

The shared callback now sees B. Because A's request does not match B's ID, line 40 emits a reference/unscoped intent from B's selection. This both misattributes A's new run and overwrites A's stored selection: ExecutionPurpose.ts:76-82 returns select for these intents, and Orchestrator.ts:5915-5941 persists it.

Keep purpose resolution bound to the intended thread/send across asynchronous preparation. A controlled delayed-send regression test is meaningful here: start in A, commit navigation to B, release the pending operation, and verify A retains its purpose. This exercises state lifetime and attribution, beyond callback wiring.

  1. P2: Goal Resume/Clear still create legacy-unscoped runs on scoped threads.

Location: useThreadLifecycle.tsx:511-520.

The remaining raw binding at line 171 serves /goal resume and /goal clear, through ThreadActivityBanners.tsx:139 and line 148. These requests omit both purposeIntent and manualContinuationOfRunId.

Trigger: on an idle scoped Codex thread with a paused goal, click the goal row's Resume action. The request reaches ordinary dispatch with no inheritance source or active target, so ExecutionPurpose.ts:73-74 resolves it to LEGACY_EXECUTION_PURPOSE. Server manual-resume inheritance does not cover this goal command: Orchestrator.ts:14949-14957 lists the actual continuation fields, goal-command handling happens after purpose admission at lines 6081-6119, and the new run stores the resolved purpose at line 7523.

This bypass also exists in the stated base. It remains an acceptance gap in automatic attribution across entry points, rather than a regression caused by removing the UI. Route these actions through purpose-aware dispatch while preserving their existing command behavior.

Checklist and cleared concerns:

  • Round 1's identified ordinary/new-thread, compact, multiple-model, plan-follow-up and implement-plan bindings now receive the shared purpose-aware callback. ChatView app messages also retain it.
  • Promachos wrapping still adds prismRole before invoking the purpose wrapper; the wrapper preserves the rest of the request. No separate Promachos regression found.
  • Multiple-model bootstrap and implement-plan new-thread requests receive explicit saved purpose under a stable route; their asynchronous route lifetime remains subject to finding 1.
  • Manual interrupted-run Resume supplies manualContinuationOfRunId; server source inheritance remains intact. Explicit Steer/Restart retain target-purpose handling.
  • Searches found no remaining composer Purpose controls on web/desktop, mobile thread or mobile new-task. Web ScheduledTasksSettings retains ExecutionPurposeControl with runs=[]; mobile scheduled-task settings retain ExecutionPurposePicker.
  • Mobile new-task sends explicit unscoped intent on capable servers. Existing mobile composer selection/target intent and outbox preservation remain intact.
  • No dangling consumers of the deleted mobile control or removed imports/state were found. Server admission, MCP launch/delegate/send/watch purpose fields and scheduled-task fields are unchanged.
  • Fork registration includes the new hook, both newly carried upstream hook files, shared composer ownership, and removal of the deleted mobile module. Documentation accurately describes the UI removal, but its stored-purpose guarantee remains incomplete because of the findings above.

What was tried and sources checked: complete cumulative gh pr diff 540 --repo toolboxmd/chromeria; exact-head/base source and history; ChatView, ThreadRouteView, composer hooks, useStableCallback, Promachos wrapping, client-runtime serialization, mobile composer/outbox delivery and server admission. Applicable AGENTS.md, docs/fork.md, registry, allowlist, changed documentation, operations review/verification references and the Great Library README were read. The coordinating reviewer independently confirmed both findings against the exact-head source and rechecked the live PR head/base before posting.

Proof limits: static review only. No tests, cache-writing checks, dev servers, browser/device sessions, repository writes or live userdata access. The worktree remained clean. This comment and the associated review status are the only review publication writes.

Requirements and who asked: The user requested removal of manual composer Purpose UI while retaining automatic purpose attribution and per-run storage.
Deleted: Manual composer controls and their state/handlers were removed; scheduled-task controls remain required.
Bottleneck: Purpose must stay attached to the intended execution across every send entry point and asynchronous route change.
Checked myself: Read the cumulative diff and traced delayed sends, route reuse, goal actions, client serialization and server purpose admission at the exact candidate.

Verdict: CHANGES REQUESTED.

Reviewed by gpt-6.1-sol through the Codex harness in T3 Code.

A delayed send finished after navigating away read the viewed thread's
purpose and could overwrite the destination's selection. Purpose now
comes from the request's own thread: its stored selection, unscoped for
new threads, and nothing for a thread the client has not loaded. Every
web send hook binds this command itself, and goal Resume/Clear use it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

What: Independent read-only review round 3 approves exact head 7db4b27cbdd3c18d8e3aa96a8b3b77bb9537f5a5; no actionable defects found.
Why: Both round-2 purpose-routing defects are resolved, and the inspected callers preserve automatic purpose admission after removing the composer controls.
So what: The independent review passes for this head; this is not merge approval or runtime verification.

Reviewed against base 45ab51f58362dbf86e1f2c12c2fb9b60c30e9d99, including all three commits and the complete gh pr diff 540 --repo toolboxmd/chromeria output. Rechecked the live PR base/head immediately before publication.

Findings: none. Optional nits: none.

Cleared prior findings:

  • R2 P2-1: apps/web/src/components/chat/useStartTurnWithPurpose.ts:43-60 reads the shell using the request's environment and destination thread at dispatch time. The helper at lines 21-35 uses that shell's revision, does not capture the viewed thread, and omits intent when the destination is unavailable. ExecutionPurpose.ts:85-99 rejects stale selection revisions; a selection intent does not write a new selection. Delayed upload/navigation sends therefore cannot copy the newly viewed thread's scope into the original destination.
  • R2 P2-2: apps/web/src/components/chat/useThreadLifecycle.tsx:172 binds useStartTurnWithPurpose; the goal command path at lines 510-520 dispatches through it.

Checks and sources inspected:

  • Full diff, project AGENTS.md, docs/fork.md, docs/fork-features.md, scripts/fork-upstream-edits.txt, and docs/user/thread-sidebar.md.
  • Composer ordinary/delayed sends, compact-before-send, ChatView command sends, plan follow-up, implement-plan in a new thread, multi-model bootstrap sends, and goal Resume/Clear.
  • withPromachosStart preserves the request while adding the Promachos role. Actual Steer/Restart dispatch values match the helper's target branches. The raw useComposerResume path supplies manualContinuationOfRunId, with server-side inheritance retained.
  • New bootstrap threads explicitly start unscoped; separately created implementation threads use their own shell's selection or the permitted legacy fallback if the shell has not arrived. No source-thread scope is copied.
  • Mobile thread/new-task UI and outbox purpose handling; remaining purpose-control references are scheduled-task settings, which the brief requires retaining. MCP/server purpose admission and scheduled-task purpose fields remain intact.
  • The three-case helper test was inspected, including destination revision/default, unloaded destination, bootstrap, continuation and Steer branches. No tests, dev servers, browsers or devices were run under the read-only constraint. No workspace files were changed; status remained clean.

Verdict: approve.

Review performed through Codex with gpt-6.1-sol; delegated reviewer also used Codex with gpt-6.1-sol at high effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant