d6f7412 docs(shell): the phone has no top bar, and why the modal is a dialog
—
CLAUDE.md prose, three dated facts in .planning/NOTES.md
The four decisions, and the arguments
1. The search button goes in the view's own header, as an element
rather than a PageAction.page-header already takes actions as
data and overflows them into a menu, which was the obvious seam — and
it is the wrong one here for a reason that only shows up on the second
look: two of the seven searchable views have no page-header. playlist-details and smart-playlist-details are detail views that
filter on the term and say so in their own headers. Declaring search as
an action would mean seven hosts each writing it out, which is the
second list of searchable views that search-store's existing map
exists to prevent, and it would put a phone mode for actions inside page-header, which that component documents its refusal to grow. So <search-trigger> is one component that asks searchStore.isSearchableView(), placed in three headers. Adding a
fourth is one line.
It is also never collapsible into the overflow menu: on a phone that
menu is already the only home for the page's own actions, and search
behind an ellipsis is the top bar's problem moved rather than fixed.
2. library-filter goes into Settings → Libraries, at every width.
It could not simply be dropped — it is the only caller of setSelectedLibrary, and #143 explicitly refused to hide it as a fit
step on the grounds that doing so trades #24's promise for itself.
Dropping it here would have been the same trade, and #148 is that hole
already filed. It is a second placement of the same component, not
a second control, and deliberately not phone-only: "where do I change
which library I am browsing" having two answers by viewport is the
fault, not the fix.
3. The row goes away entirely, and the h1 stays. The grid template
in index.css's max-width: 599px block loses "top-bar" 3.25em. The
header itself is visually hidden rather than display: none,
because that h1 is the document's top-level heading and several pages
have no other one — page-header renders no h1 when heading is '', and Settings has no page-header at all. Its four controls are display: noneinside it, which is what keeps them out of the tab
order: a visually-hidden container is still focusable. The skip link is
untouched (#main-content is where it always pointed, and it is
absolutely positioned for the auto-placement reason).
measureTopBarFit returns early when the bar's computed position is absolute — asking the stylesheet's own answer rather than
reintroducing a breakpoint in JS.
4. This modal does not wait for #60, and does not become its
pattern. #73's order stands. A search modal and an action sheet are
different shapes under the rule CLAUDE.md already states: a dialog
that only asks a question is a confirmAction() call, and a dialog
carrying input is a <wa-dialog> in the host's own template. This
one carries input. What it does share with #60 is the reason it is a wa-dialog at all, and that is the stronger half of #60's own
argument:
wa-popup renders <div popover="manual"> and feature-detects the
Popover API, falling back to strategy: "fixed" where there is none —
Chrome 113, the reference device. position: fixed escapes ancestor
overflow but notcontain: paint, which .main-panel carries.
So a popup-shaped search panel opened from a view's header would have
been structurally clipped on the device, and no tier here could have
seen it: CI's Chromium and WebKit both have the Popover API, so the
popup is top-layered and correct and a spec asserting "not clipped"
passes on the broken build. Nothing here needs undoing when #60 lands; <action-sheet> replaces the six menus, not this.
Verification
Formalities, run and green, but they could not have failed on this
change: make lint (0 issues, three tag sets), make test (all three
passes), make bindings-check, make skill-check. No Go changed.
Real:
make e2e --project=chromium — 201 passed. This is the tier that
matters and it is the reason for one of the commits above: the first
green run of the new spec was followed by a red suite, and what
failed was header-action-overflow.spec.ts at 320×600, which has
nothing to do with search. #62's lesson holding for a second change
in a row.
make ui-test — 964 passed (955 before; 9 new).
tsc --noEmit in bothfrontend/ and e2e/, make css-check
(which caught five backticks in css/html literal comments during
the work — not a formality).
WebKit could not be run locally: --project=webkit is not
configured outside CI, whose Linux WebKit links Ubuntu 24.04
libraries Arch does not provide. Checking the per-step API after this
lands, since that step carries if: ${{ !cancelled() }} and has
silently skipped before.
The specs bite
Each was proved by reverting the source and watching it fail, not by
inspection:
reverted
fails
the phone grid template + sr-only bar
phone-search "gives the row back to the content", "leaves nothing in the bar to tab into"; top-bar-fit "the bar is out of the layout at 390px"
<search-trigger> out of page-header
phone-search "is a button in the view that can be searched", "opens a modal, filters the page, and says so"
the Settings placement
both phone-search "library filter has a home" cases
isSearchableView guard on the trigger
search-dialog.test.ts "is absent where there is nothing to search"
the Escape capture listener
search-dialog.test.ts "keeps the search when it is dismissed"
Measured per element, not per shell.phone-search.spec.ts reads
the bar's computed position and box, and the content's top against
the row above it; layout-overflow.spec.ts would have been green
throughout every one of the failures above.
And one spec was made to bite that did not.phone-search's
playlist-detail case first used a .playlist-card selector that
matches nothing and skipped itself. A spec that quietly skips when its
selector stops matching reports success for a renamed class; it asserts
the row is visible now.
top-bar-fit.spec.ts at 390
Not dropped, and not weakened. "No control sits outside the bar" is trivially true of a bar with no row, so it would have passed on a
build that merely broke the phone header. That width now asserts the
stronger property — the bar is out of flow and the content starts where
the row above it ends — plus that the work is still visible in <job-band>, which is what made the row removable at all.
Note the shape of the first draft's failure, which is in NOTES.md:
asserting contentTop === 0 passed alone and failed in a suite run,
because the e2e app is long-lived and top-bar-fit had staged a job.
An absolute coordinate was quietly also asserting "and no background
job is running".
What this cost, and what it bought
At 424×439 (the reference device's own viewport) the content now starts
at y=0 instead of 52. Screenshots at 424×439 and 390×780 — Tracks, the
modal open over a page already filtering live, the filtered result, and
the library filter in Settings reached through "More" — are in the
comment below.
The one thing given up is the page header's count at 320px, argued
in 47bd9ef and in CLAUDE.md: after the title has ellipsed to
nothing and every action is in the menu, it is the only item on that
row that is neither an identity nor an action.
Not done, deliberately
No device check. None is attached and the app is uninstalled, so
Chrome 113's behaviour is read from the Web Awesome source rather than
observed — which is exactly what #60's own comment says to do, and is
why the regression guard is the mechanism rather than the symptom.
Closes #57 and #148. Phase 3, item 2 of #73, immediately after #62.
## Commits
| commit | issue | what |
|---|---|---|
| `b801fa5` feat(shell): make search a button and a modal where searching applies | #57 | `<search-trigger>` + `<search-dialog>`, placed in `page-header` and the two searchable detail views |
| `47bd9ef` fix(header): let the count yield before an action is clipped | — | fallout, measured: the search button is 43px more than a 320px header has |
| `ac8f86e` fix(settings): give the library selection a home that is not the top bar | #148 | `library-filter`, a second placement in Settings → Libraries |
| `1ab767a` feat(shell): take the top bar out of the phone's layout | #57 | the grid row is deleted; the fit pass stands down |
| `d6f7412` docs(shell): the phone has no top bar, and why the modal is a dialog | — | `CLAUDE.md` prose, three dated facts in `.planning/NOTES.md` |
## The four decisions, and the arguments
**1. The search button goes in the view's own header, as an element
rather than a `PageAction`.** `page-header` already takes actions as
data and overflows them into a menu, which was the obvious seam — and
it is the wrong one here for a reason that only shows up on the second
look: **two of the seven searchable views have no `page-header`.**
`playlist-details` and `smart-playlist-details` are detail views that
filter on the term and say so in their own headers. Declaring search as
an action would mean seven hosts each writing it out, which is the
second list of searchable views that `search-store`'s existing map
exists to prevent, and it would put a *phone mode for actions* inside
`page-header`, which that component documents its refusal to grow. So
`<search-trigger>` is one component that asks
`searchStore.isSearchableView()`, placed in three headers. Adding a
fourth is one line.
It is also never collapsible into the overflow menu: on a phone that
menu is already the only home for the page's own actions, and search
behind an ellipsis is the top bar's problem moved rather than fixed.
**2. `library-filter` goes into Settings → Libraries, at every width.**
It could not simply be dropped — it is the only caller of
`setSelectedLibrary`, and #143 explicitly *refused* to hide it as a fit
step on the grounds that doing so trades #24's promise for itself.
Dropping it here would have been the same trade, and #148 is that hole
already filed. It is a second **placement** of the same component, not
a second control, and deliberately not phone-only: "where do I change
which library I am browsing" having two answers by viewport is the
fault, not the fix.
**3. The row goes away entirely, and the `h1` stays.** The grid template
in `index.css`'s `max-width: 599px` block loses `"top-bar" 3.25em`. The
header itself is **visually hidden rather than `display: none`**,
because that `h1` is the document's top-level heading and several pages
have no other one — `page-header` renders no `h1` when `heading` is
`''`, and Settings has no `page-header` at all. Its four controls are
`display: none` *inside* it, which is what keeps them out of the tab
order: a visually-hidden container is still focusable. The skip link is
untouched (`#main-content` is where it always pointed, and it is
absolutely positioned for the auto-placement reason).
`measureTopBarFit` returns early when the bar's computed `position` is
`absolute` — asking the stylesheet's own answer rather than
reintroducing a breakpoint in JS.
**4. This modal does not wait for #60, and does not become its
pattern. #73's order stands.** A search modal and an action sheet are
different shapes under the rule `CLAUDE.md` already states: *a dialog
that only asks a question is a `confirmAction()` call, and a dialog
carrying **input** is a `<wa-dialog>` in the host's own template.* This
one carries input. What it *does* share with #60 is the reason it is a
`wa-dialog` at all, and that is the stronger half of #60's own
argument:
> `wa-popup` renders `<div popover="manual">` and feature-detects the
> Popover API, falling back to `strategy: "fixed"` where there is none —
> Chrome 113, the reference device. `position: fixed` escapes ancestor
> overflow but **not** `contain: paint`, which `.main-panel` carries.
So a popup-shaped search panel opened from a view's header would have
been structurally clipped on the device, and **no tier here could have
seen it**: CI's Chromium and WebKit both have the Popover API, so the
popup is top-layered and correct and a spec asserting "not clipped"
passes on the broken build. Nothing here needs undoing when #60 lands;
`<action-sheet>` replaces the six *menus*, not this.
## Verification
Formalities, run and green, but they could not have failed on this
change: `make lint` (0 issues, three tag sets), `make test` (all three
passes), `make bindings-check`, `make skill-check`. No Go changed.
Real:
- **`make e2e --project=chromium` — 201 passed.** This is the tier that
matters and it is the reason for one of the commits above: the first
green run of the *new* spec was followed by a red suite, and what
failed was `header-action-overflow.spec.ts` at 320×600, which has
nothing to do with search. #62's lesson holding for a second change
in a row.
- **`make ui-test` — 964 passed** (955 before; 9 new).
- `tsc --noEmit` in **both** `frontend/` and `e2e/`, `make css-check`
(which caught five backticks in `css`/`html` literal comments during
the work — not a formality).
- **WebKit could not be run locally**: `--project=webkit` is not
configured outside CI, whose Linux WebKit links Ubuntu 24.04
libraries Arch does not provide. Checking the per-step API after this
lands, since that step carries `if: ${{ !cancelled() }}` and has
silently skipped before.
### The specs bite
Each was proved by reverting the source and watching it fail, not by
inspection:
| reverted | fails |
|---|---|
| the phone grid template + sr-only bar | `phone-search` "gives the row back to the content", "leaves nothing in the bar to tab into"; `top-bar-fit` "the bar is out of the layout at 390px" |
| `<search-trigger>` out of `page-header` | `phone-search` "is a button in the view that can be searched", "opens a modal, filters the page, and says so" |
| the Settings placement | both `phone-search` "library filter has a home" cases |
| `isSearchableView` guard on the trigger | `search-dialog.test.ts` "is absent where there is nothing to search" |
| the Escape capture listener | `search-dialog.test.ts` "keeps the search when it is dismissed" |
**Measured per element, not per shell.** `phone-search.spec.ts` reads
the bar's computed `position` and box, and the content's top against
the row above it; `layout-overflow.spec.ts` would have been green
throughout every one of the failures above.
**And one spec was made to bite that did not.** `phone-search`'s
playlist-detail case first used a `.playlist-card` selector that
matches nothing and skipped itself. A spec that quietly skips when its
selector stops matching reports success for a renamed class; it asserts
the row is visible now.
### `top-bar-fit.spec.ts` at 390
Not dropped, and not weakened. "No control sits outside the bar" is
*trivially* true of a bar with no row, so it would have passed on a
build that merely broke the phone header. That width now asserts the
stronger property — the bar is out of flow and the content starts where
the row above it ends — plus that the work is still visible in
`<job-band>`, which is what made the row removable at all.
Note the shape of the first draft's failure, which is in `NOTES.md`:
asserting `contentTop === 0` passed alone and failed in a suite run,
because the e2e app is long-lived and `top-bar-fit` had staged a job.
An absolute coordinate was quietly also asserting "and no background
job is running".
## What this cost, and what it bought
At 424×439 (the reference device's own viewport) the content now starts
at y=0 instead of 52. Screenshots at 424×439 and 390×780 — Tracks, the
modal open over a page already filtering live, the filtered result, and
the library filter in Settings reached through "More" — are in the
comment below.
The one thing given up is the page header's **count** at 320px, argued
in `47bd9ef` and in `CLAUDE.md`: after the title has ellipsed to
nothing and every action is in the menu, it is the only item on that
row that is neither an identity nor an action.
## Not done, deliberately
No device check. None is attached and the app is uninstalled, so
Chrome 113's behaviour is read from the Web Awesome source rather than
observed — which is exactly what #60's own comment says to do, and is
why the regression guard is the mechanism rather than the symptom.
The phone's top bar is about to go, and the search box is the one thing
in it that is an action rather than chrome. It becomes a button in the
row that already says which page you are on, opening a wa-dialog with
the real search box in it.
Three decisions worth the words.
**A wa-dialog, and that is a mechanism rather than a taste.** wa-popup
renders `<div popover="manual">` and feature-detects the Popover API,
falling back to `strategy: "fixed"` where there is none -- which is
Chrome 113, the reference device, since `popover` is Chrome 114. And
`position: fixed` escapes ancestor overflow but not `contain: paint`,
which `.main-panel` carries, so a popup-shaped search panel opened from
a view's header is structurally clipped on that device. `<dialog>` /
`showModal()` is Chrome 37 and uses the real top layer. No tier here can
see the difference -- CI's Chromium and WebKit both have the Popover
API -- so the component test asserts the *mechanism*, a native
`<dialog>` in the tree, rather than the symptom.
**An element, not a PageAction.** Two of the seven searchable views are
detail views with no page-header; they filter on the term and say so in
their own headers. Declaring search as an action would mean seven hosts
each writing it out, which is a second list of searchable views, and it
would put a phone mode for actions inside page-header, which that
component documents its refusal to grow. search-store's own map is the
condition, asked by one component placed three times.
**The modal carries the real search-bar**, so there is still one
debounce, one clear button and one view-scoped placeholder. Escape
closes it and *keeps* the term -- the input treats Escape as "clear the
search", which is right in a header where the box stays on screen and
wrong in a surface whose dismissal would then discard the search.
Adding the phone's search button to this header is 43px more than the
row has at 320px, which is a width the app promises and which
header-action-overflow.spec.ts asks about. Measured on Playlists there,
after the fit pass had already collapsed all three actions into "More
actions" and truncated the title to nothing: title 0, count 50, sort
143, search 40, More 38, five 12px gaps and 32px of gutters -- 363 in
320, with the More button ending 27px past the edge. That is an action
clipped, which is the exact defect this pass exists to prevent.
The count is what yields, last, because it is the only item on that row
that is neither an identity nor an action. The title yields first and
may ellipsis away entirely, since the navigation also says which page
you are on; the sort control and the buttons are each the only place
they are said. An empty page says it is empty in its empty state and a
full one is being looked at. With the count gone the header is 304 in
304, and the title comes back to 19px.
It is rendered and hidden with an attribute rather than returned as
`nothing`, for the reason the action buttons are: every pass starts
from all-visible and needs a node to un-hide, or the first 320px window
costs the count for the rest of the session.
library-filter is the only control in the app that calls
setSelectedLibrary, and the phone already hid it with a comment saying
it was "reachable from the drawer's Settings". It was not: Settings adds,
removes, renames and scans libraries, and does not set the view filter,
which is a different thing -- it decides what Albums, Artists and Genres
show. A phone therefore inherited whatever a desktop session last chose
and could neither change nor see it, which is #24's sentence broken in
the band it was written for.
It is a second *placement* of the same component, not a second control,
and it is at every width rather than below 600px. A phone-only copy is
the cheaper answer and is the fault rather than the fix: "where do I
change which library I am browsing" having two answers by viewport is
exactly what one control in two places avoids.
Closes#148
The row is deleted from the grid template below 600px, not the header
hidden. That is 3.25em of a 439 CSS px viewport -- the single biggest
vertical win the reference device has to give, and the reason the issue
asks for the row rather than for a smaller bar.
Each of the five things the bar held has somewhere else to be there:
nav-history is the platform's own back gesture and was already gone from
899 down, the job indicator is <job-band> (#62, which is what this was
blocked on), the search box is a modal opened from the view's own
header, the library filter is Settings -> Libraries, and the wordmark
stays where it is.
Three things are load-bearing.
**The header is visually hidden rather than display: none**, because
that h1 is the document's top-level heading and several pages have no
other one -- page-header renders no h1 when its heading is empty, and
Settings has no page-header at all. Its four controls are display: none
*inside* it, which is what keeps them out of the tab order: a
visually-hidden container is still focusable, and tabbing into a search
box nobody can see is worse than not having one.
**The fit pass stands down**, from the bar's computed position rather
than from a width. With the bar out of flow there is no content box to
measure children against, and a pass that ran would collapse the
wordmark on every resize and report success about a 1px box.
**top-bar-fit.spec.ts keeps 390 and asserts the stronger property.**
"Nothing hangs out of the bar" is trivially true of a bar with no row
and would pass on a build that merely broke it, so what that width asks
now is that the content starts where the row above it ends. Measuring
against the window instead would have been asserting "and no background
job is running", which that spec is not about and cannot arrange.
Closes#57
CLAUDE.md's shell prose said the phone's header "controls shrink or
stand down"; there is no header there now. The search box's section
gains the modal and the four rules behind it, page-header gains the
count as the last thing to yield, and the top-bar-fit section gains
what happens below its own band.
NOTES.md gets the three measured facts, dated: `contain: paint` is why
a Web Awesome popup is clipped on Chrome 113 and why no tier here can
reproduce it, the arithmetic that cost the page header its count at
320px, and the shared long-lived e2e app that makes an absolute
coordinate a hidden assertion about background jobs.
424×439 — the reference device's own viewport. Content starts at
y=0; it started at 52 on main. The search button is top-right in the
view's own header; the tab bar is the navigation.
Tracks
Search modal
Filtered
Settings via "More"
Two things in the modal shot are worth pointing at, because both were
decided rather than fallen into. The page behind it is already
filtered — the term is view-scoped and live, so the modal is the entry
surface and the page is the results surface; Enter closes and hands the
screen back rather than the dialog growing a results list it could not
give row actions to. And the header behind reads Tra… with no count:
that is the fit pass in 47bd9ef, at a width where the search button
is 43px more than the row has.
390×780 — a common small phone.
Tracks
Search modal
Filtered
Settings via "More"
The Settings shot is #148's half: "Showing", with the sentence saying
it is a view filter rather than a setting about the libraries
themselves, above the list where they are added, renamed and scanned.
## Screenshots
**424×439 — the reference device's own viewport.** Content starts at
y=0; it started at 52 on `main`. The search button is top-right in the
view's own header; the tab bar is the navigation.
| Tracks | Search modal | Filtered | Settings via "More" |
|---|---|---|---|
|  |  |  |  |
Two things in the modal shot are worth pointing at, because both were
decided rather than fallen into. The page **behind** it is already
filtered — the term is view-scoped and live, so the modal is the entry
surface and the page is the results surface; Enter closes and hands the
screen back rather than the dialog growing a results list it could not
give row actions to. And the header behind reads `Tra…` with no count:
that is the fit pass in `47bd9ef`, at a width where the search button
is 43px more than the row has.
**390×780 — a common small phone.**
| Tracks | Search modal | Filtered | Settings via "More" |
|---|---|---|---|
|  |  |  |  |
The Settings shot is #148's half: "Showing", with the sentence saying
it is a *view filter* rather than a setting about the libraries
themselves, above the list where they are added, renamed and scanned.
CI green at d6f7412 — run 17326, check and e2e both successful.
The WebKit step ran, checked per-step rather than by the tick: it
carries if: ${{ !cancelled() }} and has silently skipped before, which
is how the one source of WebKit signal produced none for two sessions.
That is the one thing this branch could not check locally: --project= webkit is not configured outside CI, whose Linux WebKit links Ubuntu
24.04 libraries Arch does not provide.
**CI green at `d6f7412`** — run 17326, `check` and `e2e` both successful.
**The WebKit step ran**, checked per-step rather than by the tick: it
carries `if: ${{ !cancelled() }}` and has silently skipped before, which
is how the one source of WebKit signal produced none for two sessions.
```
28559 e2e completed success
E2E — chromium completed success
E2E — webkit completed success
```
That is the one thing this branch could not check locally: `--project=
webkit` is not configured outside CI, whose Linux WebKit links Ubuntu
24.04 libraries Arch does not provide.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #57 and #148. Phase 3, item 2 of #73, immediately after #62.
Commits
b801fa5feat(shell): make search a button and a modal where searching applies<search-trigger>+<search-dialog>, placed inpage-headerand the two searchable detail views47bd9effix(header): let the count yield before an action is clippedac8f86efix(settings): give the library selection a home that is not the top barlibrary-filter, a second placement in Settings → Libraries1ab767afeat(shell): take the top bar out of the phone's layoutd6f7412docs(shell): the phone has no top bar, and why the modal is a dialogCLAUDE.mdprose, three dated facts in.planning/NOTES.mdThe four decisions, and the arguments
1. The search button goes in the view's own header, as an element
rather than a
PageAction.page-headeralready takes actions asdata and overflows them into a menu, which was the obvious seam — and
it is the wrong one here for a reason that only shows up on the second
look: two of the seven searchable views have no
page-header.playlist-detailsandsmart-playlist-detailsare detail views thatfilter on the term and say so in their own headers. Declaring search as
an action would mean seven hosts each writing it out, which is the
second list of searchable views that
search-store's existing mapexists to prevent, and it would put a phone mode for actions inside
page-header, which that component documents its refusal to grow. So<search-trigger>is one component that askssearchStore.isSearchableView(), placed in three headers. Adding afourth is one line.
It is also never collapsible into the overflow menu: on a phone that
menu is already the only home for the page's own actions, and search
behind an ellipsis is the top bar's problem moved rather than fixed.
2.
library-filtergoes into Settings → Libraries, at every width.It could not simply be dropped — it is the only caller of
setSelectedLibrary, and #143 explicitly refused to hide it as a fitstep on the grounds that doing so trades #24's promise for itself.
Dropping it here would have been the same trade, and #148 is that hole
already filed. It is a second placement of the same component, not
a second control, and deliberately not phone-only: "where do I change
which library I am browsing" having two answers by viewport is the
fault, not the fix.
3. The row goes away entirely, and the
h1stays. The grid templatein
index.css'smax-width: 599pxblock loses"top-bar" 3.25em. Theheader itself is visually hidden rather than
display: none,because that
h1is the document's top-level heading and several pageshave no other one —
page-headerrenders noh1whenheadingis'', and Settings has nopage-headerat all. Its four controls aredisplay: noneinside it, which is what keeps them out of the taborder: a visually-hidden container is still focusable. The skip link is
untouched (
#main-contentis where it always pointed, and it isabsolutely positioned for the auto-placement reason).
measureTopBarFitreturns early when the bar's computedpositionisabsolute— asking the stylesheet's own answer rather thanreintroducing a breakpoint in JS.
4. This modal does not wait for #60, and does not become its
pattern. #73's order stands. A search modal and an action sheet are
different shapes under the rule
CLAUDE.mdalready states: a dialogthat only asks a question is a
confirmAction()call, and a dialogcarrying input is a
<wa-dialog>in the host's own template. Thisone carries input. What it does share with #60 is the reason it is a
wa-dialogat all, and that is the stronger half of #60's ownargument:
So a popup-shaped search panel opened from a view's header would have
been structurally clipped on the device, and no tier here could have
seen it: CI's Chromium and WebKit both have the Popover API, so the
popup is top-layered and correct and a spec asserting "not clipped"
passes on the broken build. Nothing here needs undoing when #60 lands;
<action-sheet>replaces the six menus, not this.Verification
Formalities, run and green, but they could not have failed on this
change:
make lint(0 issues, three tag sets),make test(all threepasses),
make bindings-check,make skill-check. No Go changed.Real:
make e2e --project=chromium— 201 passed. This is the tier thatmatters and it is the reason for one of the commits above: the first
green run of the new spec was followed by a red suite, and what
failed was
header-action-overflow.spec.tsat 320×600, which hasnothing to do with search. #62's lesson holding for a second change
in a row.
make ui-test— 964 passed (955 before; 9 new).tsc --noEmitin bothfrontend/ande2e/,make css-check(which caught five backticks in
css/htmlliteral comments duringthe work — not a formality).
--project=webkitis notconfigured outside CI, whose Linux WebKit links Ubuntu 24.04
libraries Arch does not provide. Checking the per-step API after this
lands, since that step carries
if: ${{ !cancelled() }}and hassilently skipped before.
The specs bite
Each was proved by reverting the source and watching it fail, not by
inspection:
phone-search"gives the row back to the content", "leaves nothing in the bar to tab into";top-bar-fit"the bar is out of the layout at 390px"<search-trigger>out ofpage-headerphone-search"is a button in the view that can be searched", "opens a modal, filters the page, and says so"phone-search"library filter has a home" casesisSearchableViewguard on the triggersearch-dialog.test.ts"is absent where there is nothing to search"search-dialog.test.ts"keeps the search when it is dismissed"Measured per element, not per shell.
phone-search.spec.tsreadsthe bar's computed
positionand box, and the content's top againstthe row above it;
layout-overflow.spec.tswould have been greenthroughout every one of the failures above.
And one spec was made to bite that did not.
phone-search'splaylist-detail case first used a
.playlist-cardselector thatmatches nothing and skipped itself. A spec that quietly skips when its
selector stops matching reports success for a renamed class; it asserts
the row is visible now.
top-bar-fit.spec.tsat 390Not dropped, and not weakened. "No control sits outside the bar" is
trivially true of a bar with no row, so it would have passed on a
build that merely broke the phone header. That width now asserts the
stronger property — the bar is out of flow and the content starts where
the row above it ends — plus that the work is still visible in
<job-band>, which is what made the row removable at all.Note the shape of the first draft's failure, which is in
NOTES.md:asserting
contentTop === 0passed alone and failed in a suite run,because the e2e app is long-lived and
top-bar-fithad staged a job.An absolute coordinate was quietly also asserting "and no background
job is running".
What this cost, and what it bought
At 424×439 (the reference device's own viewport) the content now starts
at y=0 instead of 52. Screenshots at 424×439 and 390×780 — Tracks, the
modal open over a page already filtering live, the filtered result, and
the library filter in Settings reached through "More" — are in the
comment below.
The one thing given up is the page header's count at 320px, argued
in
47bd9efand inCLAUDE.md: after the title has ellipsed tonothing and every action is in the menu, it is the only item on that
row that is neither an identity nor an action.
Not done, deliberately
No device check. None is attached and the app is uninstalled, so
Chrome 113's behaviour is read from the Web Awesome source rather than
observed — which is exactly what #60's own comment says to do, and is
why the regression guard is the mechanism rather than the symptom.
Screenshots
424×439 — the reference device's own viewport. Content starts at
y=0; it started at 52 on
main. The search button is top-right in theview's own header; the tab bar is the navigation.
Two things in the modal shot are worth pointing at, because both were
decided rather than fallen into. The page behind it is already
filtered — the term is view-scoped and live, so the modal is the entry
surface and the page is the results surface; Enter closes and hands the
screen back rather than the dialog growing a results list it could not
give row actions to. And the header behind reads
Tra…with no count:that is the fit pass in
47bd9ef, at a width where the search buttonis 43px more than the row has.
390×780 — a common small phone.
The Settings shot is #148's half: "Showing", with the sentence saying
it is a view filter rather than a setting about the libraries
themselves, above the list where they are added, renamed and scanned.
CI green at
d6f7412— run 17326,checkande2eboth successful.The WebKit step ran, checked per-step rather than by the tick: it
carries
if: ${{ !cancelled() }}and has silently skipped before, whichis how the one source of WebKit signal produced none for two sessions.
That is the one thing this branch could not check locally:
--project= webkitis not configured outside CI, whose Linux WebKit links Ubuntu24.04 libraries Arch does not provide.