feat(android): a name is not a link on a phone, the menu carries it #208

Merged
logan merged 1 commits from feat/67-entity-links-into-menus into main 2026-08-25 16:51:16 +00:00
Collaborator

What the issue was

#67: tapping a small text link inside a row to reach an artist or album
page is fiddly on a phone. Its Findings say why in the code rather than
in taste — utils/explore-link.ts' click handling is explicitly a
desktop compromise (the navigation is held for one double-click
interval so double-clicking the row can still play it), and since #63 a
claimed yj-tap has its click swallowed anyway, so on touch the link
was unreachable as well as small.

What changed

commit issue
feat(android): a name is not a link on a phone, the menu carries it #67
  • utils/explore-link.ts owns the rule, which is what the Findings
    ask for: below PHONE_QUERY a track/album/artist name renders as
    plain text. artistLink, albumLink, trackLink and creditLink
    all take it; a decomposed credit falls back to creditText, so the
    join phrases survive and only the affordance changes.
  • utils/go-to-menu.ts is the other half. "Go to Artist" / "Go to
    Album", drawn under exactly the condition the link is not, from
    explore-link's own exported routing (openArtistPage /
    openAlbumPage) — so an untagged entity reaches the library page by
    the same lookup rather than a second one that can disagree.
  • Seven menus carry it: track-list, queue-panel, both playlist
    detail views, cover-grid's album cards, Explore's cards and
    recording rows, and the artist page's top tracks (album only — the
    artist is the page you are on).

Three decisions worth reading

keepOnPhone is an exception with a reason, not a preference.
Three surfaces draw a name with no menu to carry the destination
now-playing-view, explore-album-details' header credit and
top-results-row. Suppressing those takes the action away rather than
moving it, which is what plan 018's "no action is unreachable at any
supported size" refuses. Each call site says so in a comment.

One row or none. The items follow the Play item's stated rule: one
row is a position, several are an explicit choice of those tracks,
and "go to the album" of five different albums means nothing.

There is no "Go to Genre", which the issue asks for. No row in the
app renders a genre link, so there is nothing for the phone to take
away — it would be new navigation rather than a replacement, and
belongs in its own issue if it is wanted. Said on the issue when
claiming it.

The desktop is untouched: the name beside the row is still a link, and
goToMenuItems returns nothing above the breakpoint.

Verification

  • make ui-test — 1100 passed, including 8 new in
    frontend/test/components/phone-entity-links.test.ts: the rule for
    all four link shapes, the keepOnPhone exception, the items'
    presence and absence, the navigation they fire, and the one-row rule.
  • make e2e — 241 passed against make dev-headless SEED=default
    (chromium; Playwright's WebKit is CI-only on this machine), including
    the new e2e/specs/phone-entity-links.spec.ts, which asserts the
    phone and the desktop in the same breath: plain text and the two
    items at 424x439, a link and neither item at 1280x800.
  • The e2e half was proved non-vacuous: with inlineLinksSuppressed()
    forced false and a rebuild, the phone test fails on the link count
    and the desktop test still passes.
  • A screenshot at 424x439 of the queue's bottom sheet, which is how
    the finding below was found.
  • npx tsc --noEmit, make css-check — clean. No Go, .sql or
    .templ changed, so make lint, make test, make generate and
    make bindings were not run.

Filed rather than fixed here

#207 — the track list's sheet is now 8 items and its last one
("Remove from Library") ends at y=470 in a 439px viewport. It is
reachable — the sheet's body is overflow-y: auto and scrolling it
39px brings the item fully into view — but nothing says the list
continues. The limit was already reached before this: seven 48px rows
is 364px in a 373px body, so the queue's menu fits with 8px to spare
and the track list's fitted exactly. It is a property of
components/menu-surface/, not of any one menu's items. Measurements
are in .planning/NOTES.md.

Deliberately not done

  • No "Go to Genre" (above).
  • The desktop menus are unchanged; a menu item that repeats a link two
    centimetres away is furniture.
  • explore-album-details' own tracklist rows are untouched — they have
    never rendered links, so they lost nothing.

Closes #67

## What the issue was #67: tapping a small text link inside a row to reach an artist or album page is fiddly on a phone. Its Findings say why in the code rather than in taste — `utils/explore-link.ts`' click handling is explicitly a *desktop* compromise (the navigation is held for one double-click interval so double-clicking the row can still play it), and since #63 a claimed `yj-tap` has its click swallowed anyway, so on touch the link was unreachable as well as small. ## What changed | commit | issue | |---|---| | `feat(android): a name is not a link on a phone, the menu carries it` | #67 | - **`utils/explore-link.ts` owns the rule**, which is what the Findings ask for: below `PHONE_QUERY` a track/album/artist name renders as plain text. `artistLink`, `albumLink`, `trackLink` and `creditLink` all take it; a decomposed credit falls back to `creditText`, so the join phrases survive and only the affordance changes. - **`utils/go-to-menu.ts` is the other half.** "Go to Artist" / "Go to Album", drawn under exactly the condition the link is *not*, from `explore-link`'s own exported routing (`openArtistPage` / `openAlbumPage`) — so an untagged entity reaches the library page by the same lookup rather than a second one that can disagree. - Seven menus carry it: `track-list`, `queue-panel`, both playlist detail views, `cover-grid`'s album cards, Explore's cards and recording rows, and the artist page's top tracks (album only — the artist is the page you are on). ### Three decisions worth reading **`keepOnPhone` is an exception with a reason, not a preference.** Three surfaces draw a name with **no menu to carry the destination** — `now-playing-view`, `explore-album-details`' header credit and `top-results-row`. Suppressing those takes the action away rather than moving it, which is what plan 018's "no action is unreachable at any supported size" refuses. Each call site says so in a comment. **One row or none.** The items follow the Play item's stated rule: one row is a position, several are an explicit choice of *those* tracks, and "go to the album" of five different albums means nothing. **There is no "Go to Genre"**, which the issue asks for. No row in the app renders a genre link, so there is nothing for the phone to take away — it would be new navigation rather than a replacement, and belongs in its own issue if it is wanted. Said on the issue when claiming it. The desktop is untouched: the name beside the row is still a link, and `goToMenuItems` returns `nothing` above the breakpoint. ## Verification - **`make ui-test`** — 1100 passed, including 8 new in `frontend/test/components/phone-entity-links.test.ts`: the rule for all four link shapes, the `keepOnPhone` exception, the items' presence and absence, the navigation they fire, and the one-row rule. - **`make e2e`** — 241 passed against `make dev-headless SEED=default` (chromium; Playwright's WebKit is CI-only on this machine), including the new `e2e/specs/phone-entity-links.spec.ts`, which asserts the phone and the desktop in the same breath: plain text **and** the two items at 424x439, a link **and** neither item at 1280x800. - **The e2e half was proved non-vacuous**: with `inlineLinksSuppressed()` forced `false` and a rebuild, the phone test fails on the link count and the desktop test still passes. - **A screenshot at 424x439** of the queue's bottom sheet, which is how the finding below was found. - `npx tsc --noEmit`, `make css-check` — clean. No Go, `.sql` or `.templ` changed, so `make lint`, `make test`, `make generate` and `make bindings` were not run. ## Filed rather than fixed here **#207** — the track list's sheet is now 8 items and its last one ("Remove from Library") ends at y=470 in a 439px viewport. It is **reachable** — the sheet's body is `overflow-y: auto` and scrolling it 39px brings the item fully into view — but nothing says the list continues. The limit was already reached before this: seven 48px rows is 364px in a 373px body, so the queue's menu fits with 8px to spare and the track list's fitted exactly. It is a property of `components/menu-surface/`, not of any one menu's items. Measurements are in `.planning/NOTES.md`. ## Deliberately not done - No "Go to Genre" (above). - The desktop menus are unchanged; a menu item that repeats a link two centimetres away is furniture. - `explore-album-details`' own tracklist rows are untouched — they have never rendered links, so they lost nothing. Closes #67
Author
Collaborator

CI is green — run 17663.

  • check: lint, test (three build configurations), typecheck, make ui-test, make bindings-check, make skill-check, CSS literals, commit messages — all success.
  • e2e: both engines ranE2E — chromium success and E2E — webkit success. Worth stating rather than reading off the job's conclusion, since a chromium failure has silently skipped the WebKit step before.

Nothing was re-run and nothing flaked.

**CI is green** — run 17663. - `check`: lint, test (three build configurations), typecheck, `make ui-test`, `make bindings-check`, `make skill-check`, CSS literals, commit messages — all success. - `e2e`: **both engines ran** — `E2E — chromium` success and `E2E — webkit` success. Worth stating rather than reading off the job's conclusion, since a chromium failure has silently skipped the WebKit step before. Nothing was re-run and nothing flaked.
logan added 1 commit 2026-08-25 16:50:55 +00:00
feat(android): a name is not a link on a phone, the menu carries it
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 3m5s
CI / e2e (pull_request) Successful in 10m3s
8de412cf36
Every track, album and artist name in the app navigates through
`utils/explore-link.ts`, and every sentence of how it does that is a
desktop compromise: the navigation is held for one double-click
interval so double-clicking the row can still play it, and the target
is a few characters of text inside a row. On touch that is a delay on
an ambiguous target, and since #63 the row's own tap claims the click
anyway -- so the link was unreachable as well as fiddly.

So below the phone breakpoint a name renders as plain text and the
row's context menu carries the destination instead: `go-to-menu.ts`
draws "Go to Artist" / "Go to Album" under exactly the condition the
link is not, using `explore-link`'s own exported routing so an untagged
entity reaches the library page by the same lookup.

Three things this leans on. Suppressing a link with no menu behind it
is not a smaller affordance but a destination the phone cannot reach,
so `keepOnPhone` is the exception for the three surfaces with no row
menu. The items are drawn for a single selection only, which is the
Play item's rule one step on. And there is no "Go to Genre", because
no row renders a genre link to lose -- that would be new navigation
rather than a replacement.

Closes #67
logan force-pushed feat/67-entity-links-into-menus from 9da657baf5 to 8de412cf36 2026-08-25 16:50:55 +00:00 Compare
logan merged commit 944995dc3c into main 2026-08-25 16:51:16 +00:00
Sign in to join this conversation.