fix(mobile): support /, $ and @ in the new-task composer - #7523
fix(mobile): support /, $ and @ in the new-task composer#7523kirill-markin wants to merge 1 commit into
Conversation
The new-task screen mounted a bare editor, so the first message of a thread could not invoke a skill, a slash command, or a file mention. The trigger menu existed only in the thread composer, which meant sending a throwaway first message and using the second one to reach a skill. Extracts the trigger and menu logic out of ThreadComposer into a shared pure module plus a hook, and mounts ComposerCommandPopover on the new-task screen as well. Path search is scoped to the directory the task will actually run in. The thread composer keeps its previous behavior exactly. Fixes pingdotgg#7498 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
| !flow.submitting && | ||
| !(flow.workspaceMode === "worktree" && !flow.selectedBranchName); | ||
| // A share transfer locks the editor, so its rows must not be tappable. | ||
| const showComposerTriggerMenu = |
There was a problem hiding this comment.
🟡 Medium threads/NewTaskDraftScreen.tsx:847
When an @ search is loading or returns no matches, the trigger menu disappears without showing ComposerCommandPopover, so users receive no loading or empty-results feedback. The items.length > 0 guard makes the popover's isLoading and empty-state rendering unreachable; render it whenever a trigger is active.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/NewTaskDraftScreen.tsx around line 847:
When an `@` search is loading or returns no matches, the trigger menu disappears without showing `ComposerCommandPopover`, so users receive no loading or empty-results feedback. The `items.length > 0` guard makes the popover's `isLoading` and empty-state rendering unreachable; render it whenever a trigger is active.
There was a problem hiding this comment.
Checked this against origin/main: the gate is pre-existing, not introduced here.
ThreadComposer.tsx:725 on main already reads composerTrigger && composerMenuItems.length > 0, so ComposerCommandPopover's empty-state branch — and the emptyText / "Searching files…" copy at ComposerCommandPopover.tsx:89-91,177 — was already unreachable on mobile before this PR. This patch keeps the same gate on both call sites so the two surfaces behave identically, which is what the "thread composer keeps its previous behavior exactly" constraint requires.
Relaxing it is a real improvement, but it is a deliberate UX change to both surfaces (a popover that appears on a bare @ and shows a spinner), so it belongs in its own PR rather than riding along here. Happy to send that follow-up if you want it — the one-line change is the items.length > 0 half of the condition at both call sites.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds new trigger menu capability to the new-task draft composer and refactors shared logic into reusable modules. The introduction of new user-facing behavior (command/skill/path selection) warrants review. There is also an unresolved Medium-severity finding about missing loading/empty-state feedback in the popover. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
|
Status, so this does not read as abandoned — the blocking finding is answered in the review thread: #7523 (comment) Short version: the Everything else is green: Cursor Bugbot, Macroscope Effect Service Conventions, and the label checks. The Vercel marketing deploy fails with "Authorization required to deploy", which is the standard restriction on fork PRs rather than anything in this branch. I also verified this locally instead of leaning on CI: 20/20 unit tests for the extracted logic, clean |
|
Reopening: #8587 added the draft menus, but this PR also suppresses the nonfunctional /model row on drafts and resets selection synchronously when drafts change. Those details are not in the merged implementation. My initial closure missed that remaining scope. |
Problem
On mobile, the composer on the new-task screen mounted a bare editor, so the very first message of a thread could not invoke a skill (
$), a slash command (/), or a file mention (@). The trigger menu lived only inThreadComposer, which is reachable once the thread exists. In practice you had to send a throwaway first message, wait for the thread, and only then invoke the skill in message two — which is exactly when the skill matters least.Reported in #7498.
Fix
Extracted the trigger and menu logic out of
ThreadComposerinto a shared pure module plus a hook, and mountedComposerCommandPopoveron the new-task screen as the second call site.composer-trigger-menu.ts— pure menu building, insertion semantics, and the caret rule, with a colocated*.test.ts(matches the local convention ofnew-task-project-selection.ts,thread-settings-options.ts).use-composer-trigger-menu.ts— the React wiring: caret state, trigger detection, path search, memoized items, select handler.ThreadComposer.tsxloses ~240 lines and keeps its previous behavior exactly; it passes noresetKey, so the new caret-reset branch never runs there.NewTaskDraftScreen.tsxmounts the popover and passesresetKey: flow.draftKey, because that screen stays mounted across draft switches — without it a carried-over caret offset can open the popover with no user input.Path search is scoped to the directory the task will actually run in (
selectedWorktreePath || workspaceRootin local mode,workspaceRootin worktree mode), mirroringhandleStart, so@never searches the server's startup directory.About the
size:XLlabelThe label counts lines, not new behavior. The breakdown:
composer-trigger-menu.test.tscomposer-trigger-menu.tsThreadComposeruse-composer-trigger-menu.tsThreadComposerThreadComposer.tsxNewTaskDraftScreen.tsxSo ~376 lines are code moved verbatim out of
ThreadComposer(which shrinks by 244), 313 are tests, and the new surface itself is ~60 lines. Happy to split the extraction and the new call site into two PRs if that reviews better, though the line count would be the same across the pair.Before / after
Same screen, same input
$d, Android emulator, identical disposable environment.@mentions on the same screen, scoped to the selected project:Verification
vp test run apps/mobile/src/features/threads/composer-trigger-menu.test.ts— 20/20 green.tsc --noEmitinapps/mobile— clean.$dlists provider skills,@srclists project files, and tapping a row inserts[greet.ts](src/greet.ts). That last one also settles the one thing a static read cannot: the popover both renders and receives taps through the zero-height anchor insideKeyboardStickyView.Deliberately out of scope
/modelon the draft screen. On mobile the/modelrow only inserts literal text: nothing underapps/mobile/srcconsumes theslash-modeltrigger it becomes. Adding a real model picker to the trigger menu is a separate feature, so the draft screen omits the row.ThreadDetailScreen.tsx:731. An existing thread resolves@mentions against the project root even when it runs in a worktree. Pre-existing, untouched here, but the two surfaces now resolve mention cwd by different rules — worth a deliberate call rather than drift.apps/swift-ios. The Swift rebuild already wiresslashCommands,skillsandsearchPathsinto its new-thread composer, so this gap does not exist there. This PR is for the app that ships today and for Android, which keeps this code either way. Happy to close it if you would rather let the rewrite carry it.Model: Claude Opus 5 (1M context), harness: Claude Code.
Note
Low Risk
Localized mobile composer UX refactor with preserved thread behavior, explicit per-screen command gating, and unit tests on the extracted logic; no auth, payments, or server changes.
Overview
Brings the thread composer’s
$///@trigger popover to new-task draft so the first message can use skills, slash commands, and file mentions without starting a thread first.Refactor: Inline trigger/caret/menu logic is pulled out of
ThreadComposerinto sharedcomposer-trigger-menu.ts(menu building, insertion, caret rules) plususeComposerTriggerMenu(caret state, detection, path search, select handler).ThreadComposerdelegates to the hook and should behave the same; tests cover the pure helpers.New-task behavior:
NewTaskDraftScreenmountsComposerCommandPopover, wires controlled selection, and scopes@search to the directory the task will run in (worktree vs local). Built-ins areplan/defaultonly when plan mode is on—/modelis omitted on this screen.resetKey: flow.draftKeyre-parks the caret when switching drafts so a stale offset doesn’t open the menu; the popover is hidden while an incoming share locks the editor.Reviewed by Cursor Bugbot for commit ce5d27c. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add
/,$, and@trigger menus to the mobile new-task composerThreadComposerinto a shareduseComposerTriggerMenuhook, then wires it into bothThreadComposerandNewTaskDraftScreen./(slash commands),$(skills), and@(path mentions) triggers, builds ranked menu items viabuildComposerMenuItems, and handles insertion and interaction-mode switching viaresolveComposerCommandSelection.plananddefault;modelis only available inThreadComposer.📊 Macroscope summarized ce5d27c. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.