Fix phone strip ergonomics; add device launch helper

The two chip rows sat flush: the reference spaces them with
'#strip .chips + .chips', but the .lbl between them is display:none on
phones and still in the DOM, so the sibling selector never matched.
Measured gap was 0px, and both selected chips are light-filled and
left-aligned, so they merged into one shape.

- collapse the 7 context chips into one 44px control opening the existing
  sheet, whose rows now show CTXHINT (never surfaced on phones before)
- chip targets 34.9px -> 44px; they scroll, so a mis-hit scrolled instead
  of tapping
- right-edge fade only on rows that actually overflow (703px and 952px of
  content in a 360px viewport, scrollbars hidden)

Applied in native/ so reference/ stays frozen; CLAUDE.md records what the
React port must carry.

scripts/device.sh makes the screen-off renderer denial unmissable: it
verifies the phone is awake, waits for the page to parse rather than for
the activity, and reads the pid after start (adb install -r kills the old
process, which produced one false 'page never loaded').

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 22:34:50 +03:00
parent 6445114203
commit f8565c35cd
5 changed files with 262 additions and 1 deletions

View File

@@ -39,6 +39,36 @@ JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
ANDROID_HOME=$HOME/Android/Sdk
```
## Driving the device
**The screen must be awake and unlocked before launching, every time.** While
the display dozes, TCL denies the WebView renderer and the app comes up blank
with no crash, no JS error and an empty `document.title`. The failure is sticky
for the life of the process, so waking the phone afterwards does not repair it —
the app has to be force-stopped and started again.
`scripts/device.sh` handles this so it cannot be forgotten:
| Command | What it does |
|---|---|
| `npm run device:keep-awake` | Stay awake while charging. Do this at the start of a session. |
| `npm run device:launch` | Check awake/unlocked, force-stop, start, wait for the page to actually parse |
| `npm run device:awake` | Just the check — prints a loud warning if locked |
| `npm run device:sleep-normal` | Revert the stay-awake setting when finished |
`device:launch` waits for the page, not merely for the activity: the activity
starts fine even when the renderer was denied. On failure it prints the
`AppBootManager` denial lines rather than leaving a blank window to puzzle over.
It also reads the pid *after* starting the app. `adb install -r` kills the
running process, so a pid captured earlier points at a dead process and the
devtools socket lookup fails while the app itself is healthy — that produced one
false "page never loaded" during Phase 1.
To attach Chrome DevTools by hand: `chrome://inspect`, or
`adb forward tcp:9333 localabstract:webview_devtools_remote_$(adb shell pidof dev.ncode.sp404layers)`
and open `http://127.0.0.1:9333/json`.
## Toolchain (verified, do not bump blind)
Pinned against Capacitor 8.5.2's `capacitor/build.gradle`:
@@ -101,6 +131,31 @@ arrive before the page has run any JS.
installs over Phase 1's without an uninstall and drill progress survives.
`appId` is `dev.ncode.sp404layers` and must not change for the same reason.
## Phase 2 must carry these UI changes
Phase 1 fixes a strip ergonomics problem in `native/native.{css,js}` rather than
in `reference/`, which stays frozen. The React port must reproduce these, since
they will not come along with the reference:
1. **The two chip rows sit flush on phones.** The reference spaces them with
`#strip .chips + .chips { margin-top: 6px }`, but the `.lbl` heading between
them is `display:none` on phones and still in the DOM, so the adjacent-sibling
selector never matches. Measured gap: 0px. Both selected chips are also
light-filled and left-aligned, so they visually merge into one shape.
2. **The 7 context chips collapse into one 44px control** that opens the existing
sheet, freeing a row for the panel. The sheet rows show `CTXHINT`, which the
phone layout otherwise never displays.
3. **Chip targets go from 34.9px to 44px.** They scroll horizontally, so an
undersized target turns a mis-hit into a scroll instead of a tap.
4. **Overflowing rows get a right-edge fade.** Scrollbars are hidden, and both
rows overflow badly (703px and 952px of content in a 360px viewport) with
nothing indicating more content. The fade is applied only to rows that
actually overflow.
In React this should be real component state, not a post-render DOM patch:
Phase 1 has to re-apply the collapse after every `renderExplore()` because that
function rewrites `#strip` wholesale.
## Hand-edited native files
`cap sync` does not regenerate these — keep them when recreating `android/`: