prod-check reads swipes and a reader that will not scroll (#384)

Step 7 counts swipes by outcome, lists the ones that bounced at the end,
and every scroll_stuck with its state, and says how to read each: an end
with more to load is #383 back, and a scroll_stuck is filed with its
state and the swipes before it, being the one record of that bug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 20:56:15 +00:00
parent 5c1970e8ee
commit 1b2343cf28

View File

@@ -72,7 +72,7 @@ Count those from the metrics, not the log.
| `ipx::scan` | `ev` and the event's own: `feed`, `new`, `downloaded`, `failed`, `bytes`, `msg`, `url`, `feeds`, `reason`, `from`, `to`; on a failure `error.type` and, from an HTTP error, `http.response.status_code` | the daemon's events, one line each, in words; warnings are feed and download failures |
| `ipx::io` | `ev`, `feeds`, `pending`, `downloaded` on the `status` reply | commands arriving on the socket (`-> {...}`) and the answer to `ipx status` |
| `ipx::daemon`, `ipx::cli`, `ipx::catalogue`, `ipx::scan`, ... | message, sometimes fields | start-up, shutdown, account and config messages, under the module that logs them; plain `ipx` before 2026-10-24, when main.rs was split (#356) |
| `ipx::client` | `ev`, `user`, `feed`, `enclosure`, `deadair`; `msg`, `src`, `line` on an error; `ms` on a timing | what the page reports from the browser (#190): `page_error`, `rejection` and `player_error` as warnings; `play_start` (ms from play to sound), `stall` (ms waiting mid-play) and `list_shown` (ms from a click to its list drawn, with `feed` the view and `filter`, #246) as info. Lines carry the trace of the action in the page: its API requests send `traceparent`, one trace id per tap or key |
| `ipx::client` | `ev`, `user`, `feed`, `enclosure`, `deadair`; `msg`, `src`, `line` on an error; `ms` on a timing | what the page reports from the browser (#190): `page_error`, `rejection`, `player_error` and `scroll_stuck` (a drag that should have scrolled the reader and did not, its snapshot in `state`, #384) as warnings; `swipe` (a swipe in the reader: `msg` next, prev, list, end or short, `state` its position, `5/50 of 312, more to load`, #384), `play_start` (ms from play to sound), `stall` (ms waiting mid-play) and `list_shown` (ms from a click to its list drawn, with `feed` the view and `filter`, #246) as info. Lines carry the trace of the action in the page: its API requests send `traceparent`, one trace id per tap or key |
Events (`ev`): `feed_start` (debug), `feed_done` (new, downloaded, failed, torrents), `feed_skip`
(not due, debug), `feed_error` (msg), `feed_moved` (from, to: a permanent redirect followed, the address
@@ -272,6 +272,9 @@ a count says something happened, the lines and traces say why.
$Q metric 'sum by (ev, msg) (count_over_time({container="iPX"} |= "\"target\":\"ipx::client\"" | json | __error__="" | level="WARN" [$range]))' 7d
$Q logs '{container="iPX"} |= "\"target\":\"ipx::client\"" | json | ev=~"play_start|stall" | line_format "{{.timestamp}} {{.user}} {{.ev}} {{.ms}}ms deadair={{.deadair}} {{.feed}}"' 24h
$Q metric 'quantile_over_time(0.95, {container="iPX"} |= "\"ev\":\"list_shown\"" | json | __error__="" | unwrap ms [$range]) by (feed, filter)' 24h
$Q metric 'sum by (msg) (count_over_time({container="iPX"} |= "\"ev\":\"swipe\"" | json | __error__="" [$range]))' 7d
$Q logs '{container="iPX"} |= "\"ev\":\"swipe\"" |= "\"msg\":\"end\"" | json | line_format "{{.timestamp}} {{.user}} {{.feed}} {{.filter}} {{.state}}"' 7d
$Q logs '{container="iPX"} |= "\"ev\":\"scroll_stuck\"" | json | line_format "{{.timestamp}} {{.user}} {{.feed}} {{.state}}"' 7d
```
A `player_error` is playback that failed in front of someone: `hls.js: ...` is DeadAir's
playlist (its trace has the playlist and audio requests), `media error 4` a file the browser
@@ -279,6 +282,19 @@ a count says something happened, the lines and traces say why.
the minified `app.js`, so match the message to `web/src`. A `list_shown` over 1000 ms is a list someone waited for: its trace says whether the API or the network held it. `play_start` over 2000 ms, or a
`stall`, is worth a trace: was it the server, Cloudflare, or the network.
Swipes and a reader that will not scroll (#383, #384). An `end` swipe is the reader bouncing
off the last item loaded: one whose `state` says `more to load` is the page failing to fetch
the next page in time, and several in a row from one person at the same position is #383 back.
An `end` without `more to load` is someone at the real end of the list, and fine. Many `short`
against few `next` is swipes not being taken, worth a look at `swipeMin` in `gestures.ts`.
Any `scroll_stuck` is a reader someone could not scroll, the bug #383 reported and never
found: read its `state` before anything else. `under the finger` other than `#detail` or
something inside it names a layer lying over the reader; `inert` other than `#dpeek`, `dialog
open`, a `transform` other than `none`, or `peek left behind` name what the page left in a wrong
state; `shown Ns ago` small says it followed a return from the background. File it with the
`state` line and that person's swipes just before it (`$Q logs` on `ev=~"swipe|scroll_stuck"` and
the `user`), even when the cause is not clear yet: it is the one record of the bug.
`docker logs --since 10m iPX 2>&1 | tail -5` shows the newest lines when Loki is behind.
## ipx-web, the page