Repository navigation
feat(terminal): list the dev servers started in the terminal and open them in the in-app browser - #408
Merged
Conversation
…in the browser
A dev server started in the embedded terminal had no status and no way to
reach it: the user had to remember the port and type the address into the
in-app browser by hand.
Read the ports servers announce on screen ("Local: http://localhost:5173/",
"Serving HTTP on 0.0.0.0 port 8000", "listening on :3000"), probe them with a
TCP connect to the loopback addresses, and show the ones that answer as chips
above the output. A chip opens http://localhost:PORT/ in the in-app browser;
a server that stops answering is shown as stopped, and forgotten after about
half a minute. A port is only shown once it has answered, because the echoed
command line ("curl http://localhost:8080") names ports nothing listens on.
Only the port number is taken from the output, never the host, so a program's
output cannot send the in-app browser anywhere but this device. Probing starts
when the output first names a server and ends with the shell, so a plain shell
costs nothing. Only screen lines that are new since the last look are
searched, so an old address still in the scrollback cannot revive a server
that was forgotten.
Refs #402
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…real servers Apply the pre-PR review's suggestions. The terminal stores a line wider than the screen as several rows, so an address crossing the wrap point was whole in none of them: python's "Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/)" was missed on a narrow phone, and a cut-off "80" could be read as a port. Rows that fill the width are now joined to the next one before searching, and the trailing digits of a line that may still be growing are held back until it ends. When the slots are full, a new port now pushes out the oldest candidate that never answered (echoed commands, cut-off fragments) instead of being dropped; a port that has answered is never pushed out. Also: a failure while searching for servers no longer takes the output stream down; the probe loop is created inside the lock that decides to start it, so a close or an exit that comes in between cancels it before it runs; the chip's touch target is 48dp and its port number is always left-to-right; and onOpenServer is a required parameter. Refs #402 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eir length The pre-PR re-review noted that a row filling the width is joined to the next one whether it wrapped or merely ended in the last column. Say so in the KDoc and in LOCAL_RUNTIME.md, with what it costs. Comments and docs only. Refs #402 Co-Authored-By: Claude Sonnet 5.5 <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 free
to 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.
Summary
Part of #402 ("make starting and reaching a dev server easier"). Refs #402; it does not close it — the in-app browser report there has no reproduction steps and is not addressed here.
A server started in the embedded terminal had no status and no way to reach it: the user had to remember the port and type the address into the in-app browser by hand. Now:
Local: http://localhost:5173/,Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/),listening on :3000) and probes them with a TCP connect to127.0.0.1/::1.:5173) above the output. Tapping a chip openshttp://localhost:PORT/in the in-app browser.Design decisions
curl http://localhost:8080) also contains a URL, so mentioned-but-silent ports are probed but never listed. Probing uses a plain connect because/proc/net/tcpis unreadable to apps since Android 10.DevServer.urlis alwayshttp://localhost:<int>/, so program output cannot point the in-app browser anywhere but this device. A tap is still required.StyledLines, end of screen only), so an address still in the scrollback cannot revive a forgotten server. Rows the screen width wrapped are joined first (WrappedLines), so a banner that crosses the wrap point on a narrow phone is still found; trailing digits of a line that may still be growing are held back so half of a port is not read as a port.DevServerDetector,DevServerTracker(state machine),WrappedLines,DevServerProbe/LoopbackDevServerProbe.TerminalViewModelgains aserverProbeconstructor parameter (defaulted, beforeioDispatcher);TerminalScreengets a requiredonOpenServer.Known limitations (documented in
docs/LOCAL_RUNTIME.md)Test plan
./gradlew spotlessCheck detekt :app:testGithubDebugUnitTest :app:lintGithubDebug— green: 1616 unit tests, 0 failures (8 skipped = pre-existing live tests), lint 0 errors. Note the rootdetekttask reportsNO-SOURCEin this repo, so it checks nothing.DevServerDetectorTest(9),DevServerTrackerTest(16: shown only after answering, running/stopped/dropped transitions, 8-slot limit, eviction rules),WrappedLinesTest(10),LoopbackDevServerProbeTest(3, real loopback sockets), and 16 new cases inTerminalViewModelTest(shows a server once it answers; echoed command not shown; colour codes inside the URL; address split across two chunks; address wrapped across rows; front of a port not read as a port; stop/recover/forget; probing ends with the shell and on close; restart starts empty; a throwing probe; nothing probed for plain output).TerminalScreenInstrumentedTest— 16/16 on an API 36 emulator, including 4 new chip tests (running chip opens its address, stopped chip is displayed but has no click action, several chips, no row without servers)..github/workflows/i18n-check.yml): 1155 required keys, all 7 translations complete.python3 -m http.server,npm run dev, etc. in the terminal, confirm the chip appears, opens the page, turns to "stopped" on^Cand disappears with the shell.contentDescription; whether it is read twice is unverified.Pre-PR review
The named
repo-reviewersubagent (.opencode/agents/repo-reviewer.md) is not registered in Claude Code, so the review was run by a read-only general-purpose subagent given that definition. Two rounds:4eeabc2f(+ the merge oforigin/main) → APPROVE with suggestions; the substantive ones were applied in36830800: rows wrapped by a narrow screen hid addresses (python's banner at ~47 columns) and could be read as a wrong port; slots held by never-answered fragments; detection outside the output stream's safety net; a start/cancel race window; touch target and RTL port display;onOpenServerdefault removed; tests for a failing probe.36830800→ APPROVE (report below, verbatim).Commit
36e7b0adwas added after the approval and changes only a KDoc comment anddocs/LOCAL_RUNTIME.mdto say that joining wrapped rows is an inference from their length (the reviewer's suggestion 1). No code changed; it was not re-reviewed.🤖 Generated with Claude Code