Repository navigation
Conversation
The cover gave up after 20 s of wall clock, so a slow link that was answering (10-25 KB/s, or 50 KB/s at 2 s latency) got "Can't reach your PC" mid-load. It now gives up only when nothing came back in 20 s, says the connection is slow with the bytes so far, and offers Keep waiting after 20 s with no new bytes (OpeningWatch, clock and byte counts injected for its tests). A timeout to a public server no longer blames a home network or the PC's firewall.
|
No actionable comments were generated in the recent review. Summary
Merge Risk:
|
…are homeHost Review of #3850: bytes alone (a TLS handshake) no longer count as the PC's answer. Before the page commits, the open gives up once the limit has passed with 5 s of quiet, and at 60 s in any case, with "accepted the connection but sent no page". The stalled cover offers Try again, the tick stops in the background, publicHttp reuses homeHost, and a test keeps error.html's copy of homeHost in step with shell.js.
|
Gate review at c9c23d1 (BLOCK), each finding and how 4b521fb resolves it:
Checks at 4b521fb: |
|
Gate review at 4b521fb: CLEAR. Its LOW notes are left as follow-ups rather than pushed here, which would restart the review and CI:
|
…s up Once the PC answers, the cover now stays over index.html's loading spinner until the client replaces it, with a bar: the bytes received against the sizes of the page and the files it starts with (a HEAD of each from the page), indeterminate when a size is unknown, never going back and held at 95 percent until the client is up. The text says how much of the total has come, in English and Korean.
…t it waits on Merge-pass review of #3850: the cover is asked of the page every 200 ms once it shows (it lifted up to a second late, fast and cached opens too); a page with no root while it still parses is undecided, not "no client"; once the page has fully loaded a stall says the app did not start and offers Try again, not Keep waiting; the page-size check leaves out the query (a pairing code); a test keeps index.html's #root > .boot, which the cover waits on; the timeout copy test no longer pins English prose.
|
Merge-pass review at 39c26c6, resolved in 1e4f266:
Checks at 1e4f266: |
|
Merge-pass leftovers at 1e4f266, not pushed (each push restarts CI and the reviews):
|
At the head (1e4f266): before the PC answers, the error page comes after 20 s (later only while bytes keep arriving, until 5 s of quiet, never past 60 s); once it answers the open is never cut off. The cover stays until the client is up, asked every 200 ms once the page shows, with the progress bar. A stall offers Keep waiting while the page is still arriving, and only Try again once it has fully loaded and the app did not start.
Why
A user on a slow link (about 10 KB/s, VPN, mobile data) got "Can't reach your PC ... did not answer within 20 seconds" for a public server that was answering. The cover's timer was a wall clock: whatever was still arriving at 20 s was stopped and the PC was blamed (off, home network, firewall).
Reproduced on an Android 15 emulator (API 35) with this branch's base, against a test Ovis behind a throttling proxy (rate and round trip set per run). The PC's first byte arrived in 1-4 s in every run.
Usable = the client replaced index.html's loading spinner. The time is the page's weight, not the app: about 90 KB before the first paint and about 1.1 MB (the main script, 978 KB brotli) before the client runs. That is a separate follow-up, not in this PR.
What changes
OpeningWatch(new, pure Java): the cover's stage from what arrived and when. Once the PC's page has answered (onPageStartedfor a page of the PC: its response committed) the open is never cut off: after 5 s the cover says "Slow connection. Still opening X... N KB so far"; after 20 s with no new bytes it says nothing has come for 20 s and offers Keep waiting and Try again (Change PC stays).TrafficStatsfor the app's uid) counts as arriving, so a slow TLS handshake puts the limit off, while a server that completes its handshake and never responds gets "accepted the connection but sent no page". The uid total also counts the app's other traffic, which is why it can only put the limit off, never past 60 s.onStop) and looks again at once ononStart.MainActivity: the one-shot timer becomes a 1 s tick that asksOpeningWatch;gaveUp, the error page and its reason are as before for a real no-answer.publicHttp(the connect screen's plain-http warning) now reuseshomeHost, and a test checks that error.html's own copy agrees with it on 25 addresses.homeHostinshell.js, the same test inerror.html. English and Korean.PROBE_TIMEOUT_MS) is kept: it is per phase (connect, then each read, which covers the TLS handshake). Measured through the same proxy: a first TLS handshake took 0.7 s at 300 ms, 1.2 s at 800 ms and 2.4 s at 2000 ms round trip, and the whole/api/health4.5 s at 10 KB/s and 2000 ms, inside both the 5 s per phase and the 10 s budget.Fast failures stay fast (emulator, this PR)
Screens (emulator)
Before, 10 KB/s: the error page at 20 s while the page was arriving.
After, 10 KB/s at 8 s / stalled 20 s (Keep waiting, Try again) / after Keep waiting:
A connection that never sends a page, the error page at 22 s:
No answer at all, still the error page at 20 s:
Progress bar (second commit)
Once the PC answers, the cover now stays until the client is up (its
#rootno longer holds index.html's loading spinner; a page with no root, such as a sign-in or the error page, lifts it as before). On a 10 KB/s link that spinner was otherwise the screen for about two minutes. The bar fills from the bytes received (TrafficStats) against the sizes of the page and the files it starts with: the page asks a HEAD of each (so with its own encoding and cookies), and when any of them gives noContent-Length(a proxy that streams compressed files) the bar is indeterminate. It never goes back and holds at 95 % until the client is up; the text says "409 KB of 1,227 KB", or "N KB so far" without a total. Time to a usable client is unchanged: 125.6 s at 10 KB/s, 27.0 s at 50 KB/s.A second open (no change needed)
Nothing in the app clears the WebView's cache, and
/assets/*isimmutablefor a year whileindex.htmlrevalidates (no-cache). On main a second open at 10 KB/s is usable in 2.0 s once a first open has finished; at 10 KB/s main never let one finish (it stopped every load at 20 s), so nothing was ever cached. With this PR: first open 126 s, second open 1.6 s at 10 KB/s. Also measured: a new release (a new script hash in index.html) is fetched on the next open (1,152,733 bytes over the wire), the old release's file comes back from cache (transferSize0), and a second PC on another origin downloads its own copy while the first PC still opens in 2.1 s. API answers areno-storeor carry no validators, so no PC data is cached.Note on the measurement: the first table above came from a throttling proxy that queued the whole link FIFO, which delays the first paint behind the script but does not change the time to a usable client (total bytes over the rate); the bar runs used a proxy that shares the link round-robin between connections.
Not in this PR
A store update is needed for users to get this (Android 1.0.1 is the build on Play); the owner plans it with the 0.5.4 release.
Validation
./gradlew :app:testDebugUnitTest :app:assembleReleaseat 1e4f266: passed (all unit-test files, 0 failures; release build successful).bun run check fast: passed at the head (typecheck, build, unit tests 17261 pass / 0 fail) at the head../gradlew :app:testDebugUnitTest :app:assembleDebug: passed;OpeningWatchTest11/11, with the clock and byte counts injected (no sleeps).bun test scripts/app-shell.test.ts: 12/12.