<job-band>, the shell grid row, the indicator standing down, 5 specs
f76ee96 docs(jobs): the phone's band, and why it is in flow
—
CLAUDE.md + the measurement that decided the shape
What it does
Below 600px the header job-indicator stands down and <job-band>
takes over, as a grid row between the top bar and the main panel.
It is the existing job-panel — so pause, cancel, Details and the log
all come along, and so does applyJobControl, which is what carries
the "you will discard hours of downloading" confirmation for an index
build. A surface drawing its own buttons drops that silently; #27
already named that trap.
The report did not reproduce, and the spec says so
The issue's motivation is that the popover "is obscured by other UI, so
it cannot be read while jobs run". I could not reproduce that in any
tier available to me, and I looked before designing anything:
viewport
jobs
panel clipped?
elementFromPoint at its centre
424×439 (the device's own)
1
no, 325px of room to spare
job-indicator
424×439
4
no, 259px spare
job-indicator
390×780
6
no
job-indicator
1100×800
1
no
job-indicator
So this is not a stacking fix, and jobs-on-a-phone.spec.ts says
so in its header rather than asserting a property that was never
broken here. What justifies the change without that symptom:
a popover is a disclosure, and background work is the one thing a
phone should not make you open something to see;
#57 is blocked on this issue and needs the bar emptied, which a
popover anchored to that bar cannot deliver.
The device is no longer available to check the original symptom against
Chrome 113, and I have not claimed it is fixed.
The design changed because the suite said so
The first version was wrong, and this is the useful part of the PR. #62 says a job notice is "the Transient/Persistent shape", and the app
has exactly one notification surface, so the panel went into notification-host's fixed band. It renders correctly, sits on top and
stays inside the viewport — and it is unusable. At 424×439 a compact
panel showing two jobs is ~216px of a 439px screen, drawn over the
content with pointer-events: auto, so it swallows every tap
underneath.
make ui-test (955 tests), tsc, lint and the whole Go suite passed
on that version. What caught it was four e2e specs, none of them
about jobs:
✘ header-action-overflow › offers every action from the menu on a phone
✘ phone-shell › opens the full-screen now playing, and comes back
✘ phone-shell › opens the full-screen now playing for a track with no art
✘ top-bar-fit › no control sits outside the bar at 390px, with a job running
Three were click timeouts on the band. So the band is in the layout
now — it pushes rather than covers, which is #24's one sentence ("no
action is ever unreachable at any supported size") deciding a layout
question. A band that hides the app in order to say the app is busy has
traded the popover's fault for a worse one.
Two things fell out of that and are now properties of the code:
A finished row in flow is furniture, holding the content down
after the work is done — hence active-only. Settings keeps finished
rows, because that is where "did the last scan work" is asked (#27).
job-row already had the right density.compact is described
in its own source as "the popover density", which is exactly what
this replaces: 216px against 259px for the same two jobs.
Verification
The specs bite
Five new specs in jobs-on-a-phone.spec.ts. Reverting the three source
files:
✘ are shown in the band, without opening anything
✘ leave the top bar, which is what #57 is waiting for
✘ push the content down rather than covering it
✘ are not inside the live region they sit beside
✓ do not leave a second panel behind on a desktop
The fifth cannot fail against the old code — there was never a panel —
and guards the bottom-nav trap for the future. Component tier, same
treatment: answers for every kind when asked with a star and passes its density to the rows both fail without the change.
push the content down rather than covering it is the one that would
have caught my own first attempt: it asserts the main panel's top moves and clears the band's bottom, and that a hit test at the band's centre
returns the band rather than something of the app's underneath it.
One accessibility point worth flagging
A running job repaints several times a second, and the notification
stack it would have shared is role="status" aria-live="polite" — a
progress bar in a live region is a screen reader reading a number out
over and over. The band is deliberately not one, and there is a spec
asserting it is not nested in one either.
The suite
make e2e192 passed (187 before, +5), make ui-test955 (952
before, +3), make lint (3 configs, 0 issues), make test (3 configs), tsc --noEmit in both packages, make css-check, make bindings-check, make skill-check.
Formalities:make test, make lint, make bindings-check — this
branch touches no Go and no bindings. make css-check is not a
formality here and earned its place: it caught a backtick inside a
comment in a css literal, which ends the literal.
Scope deliberately left
The Direction's second half — "consider an actual Android system
notification for long work" — is not here. The issue itself says it
needs its own argument, and the foreground service currently exists to
keep audio playing; extending it to scans is a battery and permissions
question, not a UI one. Happy to file it if wanted.
#73 Phase 3's first item, and the one **#57 is blocked on**: the job
indicator needed somewhere to live before the top bar can go.
## Commits
| commit | issue | what |
|---|---|---|
| `502b814` feat(jobs): let a panel answer for every kind, at either density | #62 | `job-panel` gains `kinds="*"`, `density`, `active-only` |
| `23f5a0c` feat(shell): show background jobs in the phone's layout, not a popover | **#62** | `<job-band>`, the shell grid row, the indicator standing down, 5 specs |
| `f76ee96` docs(jobs): the phone's band, and why it is in flow | — | CLAUDE.md + the measurement that decided the shape |
## What it does
Below 600px the header `job-indicator` stands down and `<job-band>`
takes over, as a **grid row between the top bar and the main panel**.
It is the existing `job-panel` — so pause, cancel, Details and the log
all come along, and so does `applyJobControl`, which is what carries
the "you will discard hours of downloading" confirmation for an index
build. A surface drawing its own buttons drops that silently; #27
already named that trap.
## The report did not reproduce, and the spec says so
The issue's motivation is that the popover "is obscured by other UI, so
it cannot be read while jobs run". **I could not reproduce that in any
tier available to me**, and I looked before designing anything:
| viewport | jobs | panel clipped? | `elementFromPoint` at its centre |
|---|---|---|---|
| 424×439 (the device's own) | 1 | no, 325px of room to spare | `job-indicator` |
| 424×439 | 4 | no, 259px spare | `job-indicator` |
| 390×780 | 6 | no | `job-indicator` |
| 1100×800 | 1 | no | `job-indicator` |
So this is **not** a stacking fix, and `jobs-on-a-phone.spec.ts` says
so in its header rather than asserting a property that was never
broken here. What justifies the change without that symptom:
- a popover is a **disclosure**, and background work is the one thing a
phone should not make you open something to see;
- **#57 is blocked on this issue** and needs the bar emptied, which a
popover anchored to that bar cannot deliver.
The device is no longer available to check the original symptom against
Chrome 113, and I have not claimed it is fixed.
## The design changed because the suite said so
**The first version was wrong, and this is the useful part of the PR.**
`#62` says a job notice is "the Transient/Persistent shape", and the app
has exactly one notification surface, so the panel went into
`notification-host`'s fixed band. It renders correctly, sits on top and
stays inside the viewport — and it is unusable. At 424×439 a *compact*
panel showing two jobs is **~216px of a 439px screen**, drawn over the
content with `pointer-events: auto`, so it swallows every tap
underneath.
`make ui-test` (955 tests), `tsc`, `lint` and the whole Go suite passed
on that version. What caught it was **four e2e specs, none of them
about jobs**:
```
✘ header-action-overflow › offers every action from the menu on a phone
✘ phone-shell › opens the full-screen now playing, and comes back
✘ phone-shell › opens the full-screen now playing for a track with no art
✘ top-bar-fit › no control sits outside the bar at 390px, with a job running
```
Three were click timeouts on the band. So the band is **in the layout**
now — it pushes rather than covers, which is #24's one sentence ("no
action is ever unreachable at any supported size") deciding a layout
question. A band that hides the app in order to say the app is busy has
traded the popover's fault for a worse one.
Two things fell out of that and are now properties of the code:
- **A finished row in flow is furniture**, holding the content down
after the work is done — hence `active-only`. Settings keeps finished
rows, because that is where "did the last scan work" is asked (#27).
- **`job-row` already had the right density.** `compact` is described
in its own source as "the popover density", which is exactly what
this replaces: 216px against 259px for the same two jobs.
## Verification
### The specs bite
Five new specs in `jobs-on-a-phone.spec.ts`. Reverting the three source
files:
```
✘ are shown in the band, without opening anything
✘ leave the top bar, which is what #57 is waiting for
✘ push the content down rather than covering it
✘ are not inside the live region they sit beside
✓ do not leave a second panel behind on a desktop
```
The fifth cannot fail against the old code — there was never a panel —
and guards the `bottom-nav` trap for the future. Component tier, same
treatment: `answers for every kind when asked with a star` and `passes
its density to the rows` both fail without the change.
`push the content down rather than covering it` is the one that would
have caught my own first attempt: it asserts the main panel's top moves
*and* clears the band's bottom, and that a hit test at the band's centre
returns the band rather than something of the app's underneath it.
### One accessibility point worth flagging
A running job repaints several times a second, and the notification
stack it would have shared is `role="status" aria-live="polite"` — a
progress bar in a live region is a screen reader reading a number out
over and over. The band is deliberately not one, and there is a spec
asserting it is not nested in one either.
### The suite
`make e2e` **192 passed** (187 before, +5), `make ui-test` **955** (952
before, +3), `make lint` (3 configs, 0 issues), `make test` (3 configs),
`tsc --noEmit` in both packages, `make css-check`, `make bindings-check`,
`make skill-check`.
**Formalities:** `make test`, `make lint`, `make bindings-check` — this
branch touches no Go and no bindings. `make css-check` is *not* a
formality here and earned its place: it caught a backtick inside a
comment in a `css` literal, which ends the literal.
## Scope deliberately left
The Direction's second half — "consider an actual Android system
notification for long work" — is not here. The issue itself says it
needs its own argument, and the foreground service currently exists to
keep audio playing; extending it to scans is a battery and permissions
question, not a UI one. Happy to file it if wanted.
Closes #62
Three properties the phone's band needs, added here so it is the same
panel rather than a second job UI -- which is what keeps
`applyJobControl` and its "you will discard hours of downloading"
confirmation in the picture.
`kinds="*"` is every kind, which is what the header indicator was for.
Spelled as a star rather than taken as the meaning of an empty
attribute, because empty is what a typo and a dropped binding both
produce and "show everything" is the wrong thing to do by accident;
empty still shows nothing.
`density` is passed to `job-row`, whose `compact` variant its own
source calls "the popover density" -- which is exactly what the band
replaces. `full` stays the default, so the four settings call sites are
untouched.
`active-only` drops terminal rows. The band is in the layout, so a
finished row there holds the content down after the work is done;
Settings keeps them, because that is where "did the last scan work" is
asked and a finished row there dismisses itself.
The header indicator is a disclosure anchored to a bar 3.25em tall on a
screen 439 CSS px tall, and it was reported as unreadable behind other
UI. Background work is the one thing a phone should not make you open
something to see, and #57 deletes the bar it hangs from and is blocked
on it having somewhere else to live. Below 600px the indicator stands
down and <job-band> takes over.
It is the existing job-panel at `kinds="*"`, so pause, cancel, Details
and the log come along, and so does applyJobControl.
**It is in the layout, not over it**, and that was measured rather than
assumed. The first version put the panel in notification-host's fixed
band: it renders correctly, sits on top and stays inside the viewport,
and is unusable -- at 424x439 a compact panel showing two jobs is
~216px of a 439px screen, drawn over the content and swallowing every
tap under it. Four e2e specs caught it, and none of them was about
jobs: two phone-shell journeys and the header's action menu, all
failing on clicks the band was intercepting. As a grid row above the
main panel it pushes instead, which is #24's one sentence deciding a
layout question -- a band that hides the app to say the app is busy has
traded the popover's fault for a worse one.
It renders nothing above 600px, from matchMedia rather than a media
query, because that decides whether the element exists: Settings
already holds four job-panels and a fifth answering for every kind is
bottom-nav's "resolved to 2 elements" trap again. index.css keeps it
display:none off the phone for a second reason -- an in-flow grid child
with no named area is auto-placed into one of the shell's rows, which
is what the skip link is absolutely positioned to avoid.
top-bar-fit's 390px case asserted the indicator was up, so that it
could not pass by measuring the idle case under another name. At phone
width it is now deliberately away, so the assertion takes the other
branch of the same rule -- the indicator is hidden, the band has the
row, and the bar still has nothing hanging out of it -- rather than
the width being quietly dropped from the list.
The report's own symptom is deliberately not asserted anywhere: it did
not reproduce in this tier. Measured at 424x439 the popover was neither
clipped nor covered, so a spec claiming a stacking fix would be
asserting something that was never true here. The spec says so.
Closes#62
CLAUDE.md's jobs section said the header indicator is "the one view of
everything at once, from every page"; that is now true on a desktop
only, and the band is the phone's half.
NOTES.md takes the measurement that decided the shape -- an overlay
band at 424x439 is a lid, not a notification -- and the corollary about
which tier can see it: ui-test, tsc, lint and the Go suite all passed
on the broken version, and what failed was three e2e specs that have
nothing to do with jobs. Run the suite, not the spec you wrote.
logan
merged commit 8879192097 into main2026-08-20 22:32:47 +00:00
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.
#73 Phase 3's first item, and the one #57 is blocked on: the job
indicator needed somewhere to live before the top bar can go.
Commits
502b814feat(jobs): let a panel answer for every kind, at either densityjob-panelgainskinds="*",density,active-only23f5a0cfeat(shell): show background jobs in the phone's layout, not a popover<job-band>, the shell grid row, the indicator standing down, 5 specsf76ee96docs(jobs): the phone's band, and why it is in flowWhat it does
Below 600px the header
job-indicatorstands down and<job-band>takes over, as a grid row between the top bar and the main panel.
It is the existing
job-panel— so pause, cancel, Details and the logall come along, and so does
applyJobControl, which is what carriesthe "you will discard hours of downloading" confirmation for an index
build. A surface drawing its own buttons drops that silently; #27
already named that trap.
The report did not reproduce, and the spec says so
The issue's motivation is that the popover "is obscured by other UI, so
it cannot be read while jobs run". I could not reproduce that in any
tier available to me, and I looked before designing anything:
elementFromPointat its centrejob-indicatorjob-indicatorjob-indicatorjob-indicatorSo this is not a stacking fix, and
jobs-on-a-phone.spec.tssaysso in its header rather than asserting a property that was never
broken here. What justifies the change without that symptom:
phone should not make you open something to see;
popover anchored to that bar cannot deliver.
The device is no longer available to check the original symptom against
Chrome 113, and I have not claimed it is fixed.
The design changed because the suite said so
The first version was wrong, and this is the useful part of the PR.
#62says a job notice is "the Transient/Persistent shape", and the apphas exactly one notification surface, so the panel went into
notification-host's fixed band. It renders correctly, sits on top andstays inside the viewport — and it is unusable. At 424×439 a compact
panel showing two jobs is ~216px of a 439px screen, drawn over the
content with
pointer-events: auto, so it swallows every tapunderneath.
make ui-test(955 tests),tsc,lintand the whole Go suite passedon that version. What caught it was four e2e specs, none of them
about jobs:
Three were click timeouts on the band. So the band is in the layout
now — it pushes rather than covers, which is #24's one sentence ("no
action is ever unreachable at any supported size") deciding a layout
question. A band that hides the app in order to say the app is busy has
traded the popover's fault for a worse one.
Two things fell out of that and are now properties of the code:
after the work is done — hence
active-only. Settings keeps finishedrows, because that is where "did the last scan work" is asked (#27).
job-rowalready had the right density.compactis describedin its own source as "the popover density", which is exactly what
this replaces: 216px against 259px for the same two jobs.
Verification
The specs bite
Five new specs in
jobs-on-a-phone.spec.ts. Reverting the three sourcefiles:
The fifth cannot fail against the old code — there was never a panel —
and guards the
bottom-navtrap for the future. Component tier, sametreatment:
answers for every kind when asked with a starandpasses its density to the rowsboth fail without the change.push the content down rather than covering itis the one that wouldhave caught my own first attempt: it asserts the main panel's top moves
and clears the band's bottom, and that a hit test at the band's centre
returns the band rather than something of the app's underneath it.
One accessibility point worth flagging
A running job repaints several times a second, and the notification
stack it would have shared is
role="status" aria-live="polite"— aprogress bar in a live region is a screen reader reading a number out
over and over. The band is deliberately not one, and there is a spec
asserting it is not nested in one either.
The suite
make e2e192 passed (187 before, +5),make ui-test955 (952before, +3),
make lint(3 configs, 0 issues),make test(3 configs),tsc --noEmitin both packages,make css-check,make bindings-check,make skill-check.Formalities:
make test,make lint,make bindings-check— thisbranch touches no Go and no bindings.
make css-checkis not aformality here and earned its place: it caught a backtick inside a
comment in a
cssliteral, which ends the literal.Scope deliberately left
The Direction's second half — "consider an actual Android system
notification for long work" — is not here. The issue itself says it
needs its own argument, and the foreground service currently exists to
keep audio playing; extending it to scans is a battery and permissions
question, not a UI one. Happy to file it if wanted.
Closes #62