Repository navigation
feat(android): native phone app for Remote Access - #297
Conversation
A Kotlin/Compose client for the Connect Phone API in android/. - Connect by QR code or link, pair with the desktop PIN, and link several computers, each with its own pairing - Agent list with minimized tasks, usage meters (when the desktop serves /api/mobile/usage), pull to refresh, and a home-screen widget - Swipe sideways between open tasks, with the position in the header - Terminal: desktop colors, lazy rendering, reliable follow (optionally always jump to the latest), quick keys, quick replies, voice input, and an opt-in mode that sizes the PTY to the phone - Changes tab with per-file diffs, notes, new tasks, and closing tasks with the desktop's warnings - Built-in chat tasks: transcript, approvals and questions, send / stop - Optional notifications when an agent needs input, errors or finishes - Obsidian dark and light themes, splash, themed icon, predictive back, animations and haptics; release builds shrink with R8 Desktop support: - POST /api/mobile/tasks/:id/close (paired) and GET /api/mobile/tasks/:id/diff (view-only and paired), renderer-backed - `view-size` socket message: a paired phone may size a terminal while viewing it; the desktop's size returns on release or disconnect - Subscribing sends a rendered snapshot with up to 1000 lines of history (@xterm/addon-serialize over the existing headless mirror) - Minimized tasks appear in the phone's agent list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings -> Widget now sets how opaque the home-screen widget's card is (100/75/50/25%) and what color it is (Obsidian, Slate, Light). The fill and border are one color faded together, and each card carries text colors that stay readable, so a light card gets dark text. Both settings push to placed widgets immediately instead of waiting for the next publish, and Settings previews the real card at the chosen transparency. RemoteViews can only set a background from a resource, so the stops are shape drawables: three colors by four stops. A contrast test keeps each palette's text on its own card at or above the 4.5:1 WCAG AA floor; it caught Slate's timestamp color at 4.1:1, now lightened.
Add a Diff tab to the phone UI and merge/close actions behind a read-only readiness dialog. Diffs come from the renderer's own loadTaskDiff rather than the MCP coordinator: Coordinator.tasks only tracks delegated sub-tasks, so the coordinator route would 404 for phone-created and ordinary desktop tasks. Readiness reuses the desktop's pure buildMergeReadiness so both surfaces agree on what blocks a merge. Read routes (diff, readiness) follow notes and accept the view-only token; merge and close run real git and require the PIN-elevated paired token. All task ids are rejected at the boundary when they name a prototype-chain key.
Add a Merge action beside Close on the agent screen, opening a dialog that renders the desktop's own merge-readiness checks read-only and gates the confirm button on them. A blocked verdict disables merging; a warning does not, so someone away from their desk can still merge with eyes open. canMerge defaults to false when the field is missing, so an unreadable reply can never enable a git write.
The phone app now offers the same 15 looks as the desktop instead of its own Obsidian/Light pair. Appearance in Settings picks a look with a live swatch, and the theme (follow system, always dark, always light) chooses the tone, with the dark and light look remembered separately so a theme switch brings the matching look with it. LookPalettes.kt is generated from the sources the desktop already keeps its looks in: src/lib/look.ts for ids, labels, order and tone, src/styles.css for the colors and radius scale, and src/lib/theme.ts for the terminal ANSI palettes and look pairings. The generator resolves each preset's full cascade (no phone-side fallbacks), flattens radial-gradients to their middle stop, and falls back to the muted Noir ANSI set for dark looks the desktop leaves on xterm defaults. LookPresetsTest pins the default Obsidian values so a new desktop theme cannot quietly change the out-of-the-box appearance. npm run generate:android-looks rewrites the file and npm run check:android-looks fails when it drifts; the Kotlin tests run the same check.
…/android-phone-app Brings the desktop's 15 look presets to the phone (Settings → Appearance), with LookPalettes.kt generated from src/lib/look.ts, src/styles.css and src/lib/theme.ts so the two stay in step. Touches Screens.kt alongside the phone diff review and task lifecycle work already on this branch.
A task with git isolation 'none' edits the project folder in place, so it has no branch or worktree of its own. handleGetDiff fell through to loadTaskDiff, which diffed the project folder against the base branch: the phone either showed a git error or, worse, listed unrelated work already sitting in the repo as if the task had made it. Answer with an empty diff and an `unsupported` flag instead, and have both phone clients say why there is nothing to compare. The flag is optional, so an older desktop that omits it still renders its diff. The Kotlin client read the response as a Pair, which had no room for a third field; fetchDiff now returns a TaskDiff parsed in one place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Header Switch menu plus latency in the Agents subtitle when multiple computers are saved. - Terminal Stop button, Material icons for stop/history/mic and larger arrow quick keys (-> takes Tab's slot, / restored). - Send-quick-replies-immediately setting and per-agent local prompt history with a Recent-messages dialog. - Custom connection labels, editable in Settings > Computers.
… full test suite load
Add a Home Wi-Fi setting under "Wait for VPN". When the phone is on that network, connect without waiting for a VPN. Reading the Wi-Fi name needs location access, which the app requests when the name is set. Rename VpnMonitor to NetworkMonitor, which now also tracks the Wi-Fi name, and consolidate the VPN wait check into waitsForVpn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test and build the app when android/ changes. Pushing an android-v* tag publishes a signed APK as a GitHub release, separate from desktop v* releases and never marked latest. Release signing and the version come from the environment, so the keystore stays out of the repo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Warm npm run check drops from ~37s to ~5s: type checks run incremental, lint and format checks use caches, and check type-checks the backend without emitting (compile still emits for dev and build). Add test:changed for tests affected by uncommitted changes. Claude Code truncates MCP server instructions at 2048 characters, and the role-specific session guidance started at character 2038, so agents never saw it. Order instructions most specific first.
Coordinator - Report idle only after the assigned prompt is delivered and the queue is empty; re-check startup blockers at delivery. - A body whose Enter failed is submitted on the next ready prompt instead of having the next queued prompt typed onto it. - wait_for_signal_done: a retried requestId takes over the orphaned waiter's slot; timeouts clamped; waiters resolved on remove/close. - Close during creation, concurrent cleanup and stale exits handled. MCP server and client - Legacy waits bounded below Node fetch's ~300s header timeout. - Tool arguments validated; enforced tool set matches advertised. - Codex coordinator gets its token via a 0600 --token-file. Remote API - Bearer tokens only from the Authorization header on /api. - Malformed and non-object JSON bodies rejected with 400. Permissions and delegation - Children that do not inherit skip-permissions get bypass flags stripped from inherited args (value-aware, codex/claude/gemini/ copilot, best effort; limits documented). - Peer-message quotas per sender, per sender-recipient pair and globally. Preamble - Symlinked instruction files skipped; idempotent injection; CRLF preserved; restores go through atomic writes.
Child task diffs could list large amounts of inherited parent or main work, or nothing at all, depending on how the branches moved: - parent amended, squashed or conflict-resolved commits the child holds: use the child's creation commit when it was a parent tip - child merged main: fold the merged-in upstream into a synthetic base - child fast-forwarded or pull --rebase'd onto main: treat the recorded target like a rebase target - child rebased onto main: drop rebased copies of parent commits - parent branch deleted: compare against the main branch
* task/for-child-tasks-especially-sometimes-893236: fix(git): show only a subtask's own work in its diff
* task/detect-if-a-remote-pr-was-merged-on-7a00c3: feat(pr-checks): show merged PR state in purple
Add a Commit button next to Merge on a task. Its dialog lists the uncommitted files and which are staged, offers Stage all and Unstage all, and commits what is staged with a message, through the desktop's /api/mobile/tasks/:taskId/commit route. Reading works with the view-only token; staging and committing need pairing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The main-process screen mirror kept only 1000 lines of scrollback, and phones opening an agent terminal are sent that mirror, so long sessions showed a truncated history. Match the desktop renderer's 10k lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lines still too wide for the phone at the smallest font now wrap at word boundaries, so the agent terminal no longer needs horizontal panning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oesn-t-fully-populate-f4255f Phone terminal: full history and word wrap
The task pager kept syncing its settled page into the screen while it animated out, so the list change from the close sent it to a neighbouring task's terminal. Only follow the pager while an agent screen is open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save computers, settings and prompt history to a zip, optionally encrypted with a password (AES-256-GCM, PBKDF2 key). Restore from Settings or the connect screen; it validates the whole file before writing and restarts the app. Automatic backups write daily or weekly to a picked folder, keep the latest 10, and hold their password sealed by an Android Keystore key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The widget sums agents from every saved computer, polling the others'
agent lists once a minute over HTTP while the app is connected, and
says how many answered ("3 working · 2 comps"). It also gains the app
icon by its name, a status dot colored by what needs you most, and a
divider above the usage meters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Start tasks from GitHub issues or PRs via a picker; PR tasks check out the PR head from the remote matching the PR repo. - PR dialog shows state, checks and review decision; stages "fix CI" and "address review" prompts and merges with head-commit matching, reporting merge-queue enqueues as pending. - GitHub content in agent prompts is fenced and marked untrusted.
- Build evidence packages for task handoff: scan changes, run project checks in parallel, and track verification in one check list - Build evidence in the background after agent turns and mark it outdated on edits or config changes - Review workflow with preserved draft comments and clear next actions - Suggest verify command and checks with AI - Replace merge and push with a finish dialog: blockers and changes first, base-branch sync (rebase or merge), change tour, named target - Fold verify and evidence badges into task status - Pin dialog header and footer; keep lifecycle controls visible in Noir
* task/concept-for-evidence-packages-4e2539: feat(evidence): add evidence packages and a task finish dialog
* task/add-a-better-github-integration-taking-e223c6: feat(github): add issue/PR tasks, CI fixes and merge flow
…le-changes-probably-f6fffb' * task/tour-feature-should-not-create-many-cards-for-simple-changes-probably-f6fffb: fix(tour): keep simple change tours concise
…6f4' * task/fix-enterprise-github-prs-not-being-recognized-0036f4: fix(github): recognize enterprise pull requests
|
🔧 Needs changes – labelled Thanks for the PR! This adds a native Android client for Remote Access plus the desktop endpoints it needs (task actions, view sizing, rendered snapshots). The desktop side is solid overall, but a few things need fixing, and the branch currently conflicts with Should fix before merge
Nits
Questions
Automated review pass (Claude Code). Anything unclear or wrong – say so and I'll take a look. I read the desktop changes in full and the Android client selectively (networking, manifest, backup, build config); I didn't run anything. |
Resolve the package.json conflict and adapt the phone merge-readiness handler to main: buildMergeReadiness no longer returns an overall state, so the handler derives it, and it now passes evidence like merge-state.
request() kept only the error message, so a 409 from the close route lost the warnings array that lists the work closing would discard.
The dialog promised unmerged work stays on the branch, but closing deletes the branch when deleteBranchOnClose is set (the default). The readiness reply now carries that rule, and refusal warnings render as the list the force option refers to.
The commit, stage-all and unstage-all actions call /api/mobile/tasks/:id/commit, which only a later desktop change adds; on this desktop every use failed with a 404.
A phone that left Wi-Fi without a close frame kept its socket, and with it the PTY at phone width, for as long as the agent stayed idle. Sockets that miss a ping are terminated, which hands the size back.
LookPresetsGeneratedTest compares the phone looks with src/styles.css, look.ts and theme.ts, so changes there must run it too. Build only main, keep the job token out of .git/config, and sign without restored caches.
The meter and its percent label were gated on a truthy percent, so a limit with nothing spent yet showed neither.
Every PTY mirror kept 10k lines and allocated a 2 MB replay buffer, for desktop-only use too. Mirrors are back to 200 lines and grow to 10k while Remote Access is on. Phones read live history from the mirror, so the replay buffer returns to 64 KB.
|
Thanks a lot for this, @BigWebstas! I'd like to take the Android app in. I've started working through the review items above on top of your commits (rebase onto |
|
Thank you very much for this! <3 |
Description
Adds a native Android app (
android/, Kotlin/Compose) for Remote Access, plus the desktop routes it relies on. This replaces #292 and adds everything built since then.Android app
/api/mobile/usage(added in feat(git): commit dialog, plus phone commit and usage routes #298); otherwise they stay hidden..github/workflows/android.yml) builds, tests and releases the app.Desktop support
GET /api/mobile/tasks/:id/diff,POST /api/mobile/tasks/:id/close,GET /api/mobile/tasks/:id/readinessandPOST /api/mobile/tasks/:id/merge.force, close and merge answer 409 with their warnings instead of acting.view-sizeWebSocket message: a paired phone may size a terminal while viewing it.@xterm/addon-serialize).Overlap with my other PRs
/api/mobile/tasks/:id/commit, which is added in feat(git): commit dialog, plus phone commit and usage routes #298. Without that route, the button reports an error and the rest of the app is unaffected.Build and install instructions are in
android/README.md.Screenshots
Issues Resolved
None directly. Related to #192 (installing on Android): this offers a native app instead of the PWA.
Check List
android/README.md.)🤖 Generated with Claude Code