Why this is four panels and not one Settings section
The report says "the background-jobs tab should become a panel inside
Settings"; the Direction says split by kind. Reading the app first
settled it, because four of the five kinds already had a home showing
their work:
kind
already shown by
index-build, catalog-enrich
Settings → Search Index's tier list
download
downloads-view's lifecycle states
autotag-apply
autotag-view's per-album progress ring
library-scan
nothing but the tab
So the tab's unique content was the per-library scan controls — which
its own comment says had been taken out of Settings to build it — and
the generic affordances none of those four have: pause, cancel,
Details, the log, and a finished job you can dismiss. <job-panel kinds="…"> carries those to each of them. One "Background
jobs" section in Settings would have been the tab again under another
name, and a catch-all is what a per-kind split is trying not to need.
The header job-indicator is untouched, per the Direction: it is still
the one view of everything at once, from every page.
Three rules in the panel
The controls are applyJobControl, not a reimplementation. That
is what keeps the index build's "you will discard hours of
downloading" confirmation alive — it is keyed on KindIndexBuild
inside the shared handler, and a host drawing its own buttons would
have dropped it silently. Asserted both ways: cancelling a scan
does not ask, and that is the same code path.
An idle panel is hidden, host margin included — an empty panel
in four places is four pieces of furniture describing an absence.
No "Clear finished", because ClearFinishedJobs is global: a
Clear under Libraries would discard the index build's history too. A
finished row dismisses itself.
RetiredViews is a migration, and it is worth saying why there is one
so soon after arguing there would not be. A visibility entry is a
map key, and an unknown key is dropped on load — that is what made this
issue's ordering safe. A launch page is a value, and an unknown
one fails validation, which on the load path means the app refuses to
start for anyone who had Jobs selected. So ApplyDefaults treats a
retired name as a zero value, while an unknown-but-not-retired one
still errors: that is a typo, and saying so is the useful answer.
Also fixed while there: config-page's Launch Page <select> still
listed its ten options by hand, which #25 left behind. It is derived
from VIEW_META now — removing a destination is precisely the change
that leaves two hand-written copies disagreeing.
Two shared selectors had to give
config-page config-section .header resolves to two elements once a
section holds a job, because job-details-drawer carries that class
too — so settings-reach.spec.ts (which has used it since plan 007)
failed Playwright strict mode. It locates a disclosure by role and name
now. Filed in .planning/NOTES.md, because it cannot be reproduced by
opening Settings and looking: the drawer only exists once a job does.
Verification
make lint (3 configurations) — 0 issues
make test (3 configurations) — pass, +3 cases in backend/config/views_test.go
make ui-test — 945 pass, including 5 new in job-panel.test.ts
make e2e — 155 pass (chromium), including 4 new in jobs-in-settings.spec.ts
make bindings-check, make css-check, make skill-check
npx tsc --noEmit in frontend/ and e2e/
By hand against make dev-headless SEED=default: sidebar without
Jobs; Scan All from Settings produces a scan row with stats, a
working Details drawer and a dismiss; Full Rescan asks first; the
Launch Page select no longer offers Jobs; the Autotag view's layout
is unchanged while idle. Screenshots read, not just assertions.
One unrelated flake met on the way is filed as #146 rather than
attributed to this branch — TestYtDlpSearchSkipsUnparseableLines
failed once in a full make test and has not reproduced in ten runs
since, on a diff that touches neither that package nor anything it
imports.
The Jobs destination is gone, and everything it carried is now beside
the thing that starts the work.
| Commit | Issue |
|---|---|
| `feat(jobs): show background work where the work is started` | #27 |
| `feat(settings): scan from Settings, and watch every job in place` | #27 |
| `refactor(shell): retire the Jobs destination` | #27 |
| `test: cover jobs in Settings, and unpick two shared selectors` | #27 |
| `docs: record where background jobs went, and two traps` | #27 |
## Why this is four panels and not one Settings section
The report says "the background-jobs tab should become a panel inside
Settings"; the Direction says split by kind. Reading the app first
settled it, because **four of the five kinds already had a home showing
their work**:
| kind | already shown by |
|---|---|
| `index-build`, `catalog-enrich` | Settings → Search Index's tier list |
| `download` | `downloads-view`'s lifecycle states |
| `autotag-apply` | `autotag-view`'s per-album progress ring |
| `library-scan` | **nothing but the tab** |
So the tab's unique content was the per-library scan controls — which
its own comment says had been taken *out* of Settings to build it — and
the **generic affordances** none of those four have: pause, cancel,
Details, the log, and a finished job you can dismiss.
`<job-panel kinds="…">` carries those to each of them. One "Background
jobs" section in Settings would have been the tab again under another
name, and a catch-all is what a per-kind split is trying not to need.
The header `job-indicator` is untouched, per the Direction: it is still
the one view of everything at once, from every page.
## Three rules in the panel
- **The controls are `applyJobControl`**, not a reimplementation. That
is what keeps the index build's "you will discard hours of
downloading" confirmation alive — it is keyed on `KindIndexBuild`
inside the shared handler, and a host drawing its own buttons would
have dropped it silently. Asserted both ways: cancelling a *scan*
does not ask, and that is the same code path.
- **An idle panel is `hidden`**, host margin included — an empty panel
in four places is four pieces of furniture describing an absence.
- **No "Clear finished"**, because `ClearFinishedJobs` is global: a
Clear under Libraries would discard the index build's history too. A
finished row dismisses itself.
## The one thing #25 did not make free
`RetiredViews` is a migration, and it is worth saying why there is one
so soon after arguing there would not be. A **visibility entry** is a
map key, and an unknown key is dropped on load — that is what made this
issue's ordering safe. A **launch page** is a *value*, and an unknown
one fails validation, which on the load path means the app refuses to
start for anyone who had Jobs selected. So `ApplyDefaults` treats a
retired name as a zero value, while an unknown-but-not-retired one
still errors: that is a typo, and saying so is the useful answer.
Also fixed while there: `config-page`'s Launch Page `<select>` still
listed its ten options by hand, which #25 left behind. It is derived
from `VIEW_META` now — removing a destination is precisely the change
that leaves two hand-written copies disagreeing.
## Two shared selectors had to give
`config-page config-section .header` resolves to **two** elements once a
section holds a job, because `job-details-drawer` carries that class
too — so `settings-reach.spec.ts` (which has used it since plan 007)
failed Playwright strict mode. It locates a disclosure by role and name
now. Filed in `.planning/NOTES.md`, because it cannot be reproduced by
opening Settings and looking: the drawer only exists once a job does.
## Verification
- `make lint` (3 configurations) — 0 issues
- `make test` (3 configurations) — pass, +3 cases in
`backend/config/views_test.go`
- `make ui-test` — **945 pass**, including 5 new in `job-panel.test.ts`
- `make e2e` — **155 pass** (chromium), including 4 new in
`jobs-in-settings.spec.ts`
- `make bindings-check`, `make css-check`, `make skill-check`
- `npx tsc --noEmit` in `frontend/` and `e2e/`
- By hand against `make dev-headless SEED=default`: sidebar without
Jobs; Scan All from Settings produces a scan row with stats, a
working Details drawer and a dismiss; Full Rescan asks first; the
Launch Page select no longer offers Jobs; the Autotag view's layout
is unchanged while idle. Screenshots read, not just assertions.
One unrelated flake met on the way is filed as #146 rather than
attributed to this branch — `TestYtDlpSearchSkipsUnparseableLines`
failed once in a full `make test` and has not reproduced in ten runs
since, on a diff that touches neither that package nor anything it
imports.
Closes #27
Reading the app before moving anything turned up that four of the five
job kinds already have a home showing their work: Settings → Search
Index draws per-tier index progress, `downloads-view` draws every
download's lifecycle state, `autotag-view` draws its own apply ring,
and only `library-scan` had nowhere but the Jobs tab. What none of the
four had is the *generic* affordances — pause, cancel, Details, the
log, and a finished job you can dismiss.
So this is a panel embedded beside each of them rather than one
"Background jobs" section in Settings, which would have been the tab
again under another name.
Three rules in it. The controls are `applyJobControl`, not a
reimplementation, which is what keeps the index build's "you will
discard hours of downloading" confirmation alive across the move. A
panel with nothing to say is `hidden` rather than empty, host margin
included, because an idle panel in four places is four pieces of
furniture describing an absence. And there is no "Clear finished":
`ClearFinishedJobs` is global, so a Clear under Libraries would discard
the index build's history too.
`JobKind` also gains `download`, which the backend has had all along.
Scanning goes back where libraries are managed -- the Jobs tab's own
comment said the per-library controls had been taken out of Settings to
build it. "Scan All" and "Full Rescan" join "Add Library", "Scan now"
joins the per-library overflow menu beside Rename and Remove, and a
library being scanned says so where its track count goes.
Each surface then gets the job rows for its own kind: scans under
Libraries, index and enrichment under Search Index, downloads under the
download clients, and the autotag apply in the Autotag view -- where
stopping a run matters most, since applying rewrites tags on disk and
that view had no cancel at all.
The autotag panel shares the header's grid row through a wrapper rather
than taking a third row: it is display:none while nothing is applying,
which is nearly always, and a grid row would still spend the
container's gap on it.
The Launch Page select is derived from `VIEW_META` instead of listing
its ten options, since removing a destination is exactly the change
that leaves two hand-written copies disagreeing.
Nothing it carried is gone -- the two commits before this put all of it
somewhere the work is already being done.
A retired destination is the one shape #25's storage decision does not
make free. A visibility entry is a map key and an unknown key is
dropped on load; a launch page is a *value*, and an unknown one fails
validation -- which on the load path means the app refuses to start for
whoever had Jobs selected. `RetiredViews` is that list, read by
`ApplyDefaults`, which treats a retired name as a zero value. An
unknown-but-not-retired name still errors, because that is a typo and
saying so is the useful answer.
The spec worth having is not that the tab is gone -- that is one line
of a table -- but that nothing became unreachable when it went. #24
promises that no action is ever unreachable at any supported size, and
deleting a destination is exactly the change that quietly breaks it.
Two existing selectors had to give. `config-section .header` is
ambiguous the moment a section holds a job, because `job-details-drawer`
carries that class too -- so `settings-reach.spec.ts` locates a
disclosure by role and name instead. And `page-header`'s and
`offline-icons`'s view lists lose an entry each.
Closes#27
The retired-destination note sits beside #25's storage paragraph
because it is the exception to it: an absent key is free, a value is
not.
`config-section .header` resolving to two elements is in NOTES because
it cannot be reproduced by opening Settings and looking -- the drawer
only exists once a job does.
`config-section .header` is ambiguous once a section holds a job, and
`failure-voice.spec.ts` was the one I did not grep for. It passed on
chromium and failed on webkit in the same CI run, which is the tell:
the two engines share one app, so the second one runs with a finished
scan the first one left behind.
The NOTES entry already says a class name is not a selector's contract;
this is the same fix, by role and name.
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.
The Jobs destination is gone, and everything it carried is now beside
the thing that starts the work.
feat(jobs): show background work where the work is startedfeat(settings): scan from Settings, and watch every job in placerefactor(shell): retire the Jobs destinationtest: cover jobs in Settings, and unpick two shared selectorsdocs: record where background jobs went, and two trapsWhy this is four panels and not one Settings section
The report says "the background-jobs tab should become a panel inside
Settings"; the Direction says split by kind. Reading the app first
settled it, because four of the five kinds already had a home showing
their work:
index-build,catalog-enrichdownloaddownloads-view's lifecycle statesautotag-applyautotag-view's per-album progress ringlibrary-scanSo the tab's unique content was the per-library scan controls — which
its own comment says had been taken out of Settings to build it — and
the generic affordances none of those four have: pause, cancel,
Details, the log, and a finished job you can dismiss.
<job-panel kinds="…">carries those to each of them. One "Backgroundjobs" section in Settings would have been the tab again under another
name, and a catch-all is what a per-kind split is trying not to need.
The header
job-indicatoris untouched, per the Direction: it is stillthe one view of everything at once, from every page.
Three rules in the panel
applyJobControl, not a reimplementation. Thatis what keeps the index build's "you will discard hours of
downloading" confirmation alive — it is keyed on
KindIndexBuildinside the shared handler, and a host drawing its own buttons would
have dropped it silently. Asserted both ways: cancelling a scan
does not ask, and that is the same code path.
hidden, host margin included — an empty panelin four places is four pieces of furniture describing an absence.
ClearFinishedJobsis global: aClear under Libraries would discard the index build's history too. A
finished row dismisses itself.
The one thing #25 did not make free
RetiredViewsis a migration, and it is worth saying why there is oneso soon after arguing there would not be. A visibility entry is a
map key, and an unknown key is dropped on load — that is what made this
issue's ordering safe. A launch page is a value, and an unknown
one fails validation, which on the load path means the app refuses to
start for anyone who had Jobs selected. So
ApplyDefaultstreats aretired name as a zero value, while an unknown-but-not-retired one
still errors: that is a typo, and saying so is the useful answer.
Also fixed while there:
config-page's Launch Page<select>stilllisted its ten options by hand, which #25 left behind. It is derived
from
VIEW_METAnow — removing a destination is precisely the changethat leaves two hand-written copies disagreeing.
Two shared selectors had to give
config-page config-section .headerresolves to two elements once asection holds a job, because
job-details-drawercarries that classtoo — so
settings-reach.spec.ts(which has used it since plan 007)failed Playwright strict mode. It locates a disclosure by role and name
now. Filed in
.planning/NOTES.md, because it cannot be reproduced byopening Settings and looking: the drawer only exists once a job does.
Verification
make lint(3 configurations) — 0 issuesmake test(3 configurations) — pass, +3 cases inbackend/config/views_test.gomake ui-test— 945 pass, including 5 new injob-panel.test.tsmake e2e— 155 pass (chromium), including 4 new injobs-in-settings.spec.tsmake bindings-check,make css-check,make skill-checknpx tsc --noEmitinfrontend/ande2e/make dev-headless SEED=default: sidebar withoutJobs; Scan All from Settings produces a scan row with stats, a
working Details drawer and a dismiss; Full Rescan asks first; the
Launch Page select no longer offers Jobs; the Autotag view's layout
is unchanged while idle. Screenshots read, not just assertions.
One unrelated flake met on the way is filed as #146 rather than
attributed to this branch —
TestYtDlpSearchSkipsUnparseableLinesfailed once in a full
make testand has not reproduced in ten runssince, on a diff that touches neither that package nor anything it
imports.
Closes #27