Mobile fullscreen input: API-backed Paste/Submit buttons - #935
Merged
selfcontained merged 4 commits intoAug 11, 2026
Merged
Conversation
The fullscreen keyboard input had three buttons (header Send, "Send + Enter", "Send Raw") that all wrote raw bytes straight to the pty over the terminal WebSocket — none of them reliably submitted. Replace with exactly two buttons under the textarea: - Paste (left): injects the text without a trailing return - Submit (right): injects the text with a trailing return Both now go through a new server endpoint, POST /api/v1/agents/:id/terminal/inject-text, which mirrors the existing inject-phrase/inject-pin routes — tmux paste-buffer delivery via the injection coordinator instead of a raw pty write, so multi-line text lands as a single paste. Style: bigger tap targets (h-16) matching the keyboard control bar, with an exaggerated outside-bottom-corner radius (rounded-bl/br-[28px]) so the buttons are easy to hit and don't clip on curved screens. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Match the intended layout: Cancel stays top-left, Paste and Submit sit top-right in the header, and the separate button row under the textarea is gone — the textarea now fills the remaining space. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Now that the buttons sit in the header instead of the bottom of the screen, the curved-screen corner treatment doesn't apply — plain button rounding matches the rest of the header controls. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Preserve the draft: sendFullscreenInput no longer clears the textarea/closes the overlay until the inject-text request succeeds. On failure the overlay stays open, the text is untouched, and focus returns to the textarea so the user can retry. - Give Cancel a real touch target: swap the bare text <button> for the shared Button component at h-11, matching Paste/Submit sizing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
selfcontained
deleted the
agt_6f78aaff3ef5/build-mobile-fullscreen-submit-paste-buttons
branch
August 11, 2026 13:42
selfcontained added a commit
that referenced
this pull request
Aug 12, 2026
Worktrees deep-dive against workspace-prep.ts, shared/git/worktree.ts, tmux/setup-script.ts, and archive.ts: - "When creation fails" claimed the card shows an Attention badge. That badge renders only for status `error`; a worktree failure goes through markSetupFailed, which sets `stopped` — and the reconciler never revisits it, since its query only selects running/stopping/creating/ archiving. What users actually see is the status line flipping to Blocked with the git error, so say that. - Both launch paths fetch the starting branch from origin and fork the new branch from `origin/<branch>`; checking out the starting branch directly uses the local copy. Neither was documented. - Removing a worktree also deletes the branch Dispatch created for it, and "Archive and remove worktree" force-deletes it (`branch -D`) including unpushed commits. The section only described the directory. - The tmux setup script skips the dependency install for terminal-type agents. Also from the diff since the last audit: the shortcut-pin `disabled` state (#937) in the Pins paragraph, the two terminal injection routes inject-text (#935) and inject-pin (#930) in the api-spec Terminal table, and a new ambient tip for shortcut pins deep-linked to a new media#media-sidebar anchor. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
selfcontained added a commit
that referenced
this pull request
Aug 13, 2026
* Cover the mobile terminal toolbar's input contracts The toolbar is the only input surface on mobile and had zero unit tests after #935 reworked it with API-backed Paste/Submit. Almost everything it does is a side effect on something outside its own markup — the escape sequence handed to the terminal socket, the ref the terminal's onData handler reads to fold in the ctrl modifier, and the inject-text POST that deliberately does not go over the terminal WS — so none of it was observable from the existing E2E check. Mounts the real tree (including the copy-mode banner) with only the HTTP seam, the toaster and the audio cue mocked, and pins: the exact escape sequence for all seven shortcut keys; the ctrl modifier arming/disarming ctrlPendingRef and clearing on the terminal's ctrl-consumed announcement without writing the ref back; the 420ms press flash and its next-frame re-arm; the copy-mode banner appearing for copy/exiting but not for the "unknown" state the terminal reports before its first poll; the sound-cue short-circuit; and the composer's inject-text payload, empty-draft short-circuit, no-agent guard, in-flight gating, and failure path keeping the draft, the overlay and focus. 33 of 35 mutants killed. The two survivors are diagnosed, not gaps: the !isConnected guard inside sendKey is redundant with the button's disabled attribute (mutating the pair together dies), and the onSuccess draft clear is unobservable because the overlay unmounts on close. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Sharpen the ctrl-flash assertion to target the ctrl button Review catch: the test named "does not flash the ctrl toggle" only asserted on the other controls, so it stayed green if the ctrl button started rendering the flash class or the ctrl handler started arming a flash. Now it asserts the ctrl button carries neither the flash token nor the animation class, and — since the flash slot is shared and triggerFlash blanks it first — that arming ctrl mid-flash leaves the key the user just pressed still lit. That last assertion is what makes a flashing ctrl handler observable at all: adding flashButtonClass("ctrl") to the button alone is inert, because nothing ever sets a flash keyed "ctrl". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The mobile fullscreen keyboard input had three buttons (header Send, "Send + Enter", "Send Raw") that all wrote raw bytes straight to the pty over the terminal WebSocket — none of them reliably submitted.
Replaces them with exactly two buttons in the header, top-right (Cancel stays top-left):
Both go through a new server endpoint,
POST /api/v1/agents/:id/terminal/inject-text, mirroring the existinginject-phrase/inject-pinroutes: delivery via the injection coordinator + tmux paste-buffer (TmuxTerminal.pasteText/sendCommand) instead of a raw pty write, so multi-line text lands as a single paste.Style
Bigger tap targets (44px) matching the mobile keyboard control bar, with an exaggerated outside-top-corner radius on Submit so it's easy to hit and doesn't clip on curved-screen devices.
Testing
pnpm run check— cleanpnpm run finalize:web— clean buildpnpm --filter @dispatch/server test— 155 suites / 2644 tests pass (addedapps/server/test/inject-text-route.test.tscovering the new route's validation/auth/409 boundaries)pnpm run test:e2e— 179 passed / 12 skipped (pre-existing live-terminal skips)🤖 Generated with Claude Code