Eleven sidebar entries is more than most libraries need, so each is
toggleable from Settings → Navigation; Autotag is off until asked
for, and Downloads is absent until there is a client to download with.
Commit
Issue
feat(config): make the shell's destinations configurable
Storage is a map keyed by view id, where an absent key means that
view's own default. A HiddenViews []string cannot express "Autotag
off by default" — its zero value is hide nothing — and a boolean per
view turns a view that later stops existing into stored garbage. With a
map, an unknown key is dropped on load, a view added later gets its own
default, and no install needs migrating in either direction. Same
polarity AllowMeteredCatalogDownload established.
That is also why the #25 → #27 order on #73 is safe rather than
backwards, which was worth checking before starting: when #27 folds Jobs
into Settings, jobs = true in somebody's config is a key nothing asks
about, not a cleanup task. Argued in full in a comment on #73.
Settings and the launch page refuse in the config, not in the
checkbox.config.toml is hand-editable, so the disabled checkbox is
the affordance and SetViewVisible is the rule — an app that can be
locked out of its own Settings by a typo in TOML is a support problem
nobody can debug remotely. On load the launch page is instead
un-hidden rather than refused: there is nobody to tell, and the honest
reading of "my launch page is Autotag" is that this user wants Autotag,
not that their launch page should be silently reset to something they
did not choose.
Downloads is gated at the nav, not in the config, on downloadStore.available — so switching it on in Settings still means
what it says once a client exists, and the tab appears without a
restart (#37's rule). available is false until the providers have
loaded, which makes the item appear on a fresh launch rather than
appearing and then vanishing.
The tab bar honours the toggles, and the reason is local rather than
a general rule about phones. PHONE_COLUMN_IDS is the precedent for
"what a phone shows is a different question" and would have applied —
except that bottom-nav's "More" opens the same<app-sidebar>,
which filters, so an unfiltered bar would contradict its own drawer one
tap away. Verified at 390px: hiding Albums removed it from the bar and
from the drawer together. Which four tabs is still plan 016's committed
subset; this only removes from it, and "More" is never filtered.
Two things this deliberately did not need
No special case for the highlight.#72 moved the active view onto active-view-store, so app-sidebar asks isActive(id) per rendered
item — a hidden view lights nothing, exactly as a detail view does.
No re-seed. The skill warns that a seed freezes persisted defaults,
but this default is an absent key: a seed built before the feature
existed came up without Autotag on the first launch of the new binary.
Filed as a general note in .planning/NOTES.md.
Its fourth bullet says the sidebar's item count is one of the two
reasons MinHeight is 600. Both reasons were retired by #24 — backend/config/window.go records that the subtitle is display: none
from 899px down and the sidebar host scrolls. 800×600 is a comfort
floor now, not a correctness one, so hiding tabs relieves nothing
there. Not claimed anywhere in this change.
Verification
make lint (3 configurations) — 0 issues
make test (3 configurations) — pass, including 8 new cases in backend/config/views_test.go
make ui-test — 941 pass, including 6 new in view-visibility.test.ts
make e2e — 151 pass (chromium), including 5 new in view-visibility.spec.ts
make bindings-check, make css-check, make skill-check
npx tsc --noEmit in both frontend/ and e2e/
By hand against make dev-headless SEED=default: the sidebar comes up
without Autotag or Downloads on a pre-existing seed; toggling Autotag
in Settings adds it live; the resulting config.toml carries exactly
one line; SetViewVisible('settings', false) and hiding the launch
page are both refused by the backend; the phone bar and its drawer
agree at 390px. Screenshots read, not just assertions.
The e2e assertions are about the navigation, not about the setting
being saved — "the config was written" is the plumbing, and #69 and #72
both shipped green under specs that measured exactly that.
Eleven sidebar entries is more than most libraries need, so each is
toggleable from **Settings → Navigation**; Autotag is off until asked
for, and Downloads is absent until there is a client to download with.
| Commit | Issue |
|---|---|
| `feat(config): make the shell's destinations configurable` | #25 |
| `feat(shell): draw only the destinations the user kept` | #25 |
| `test(e2e): cover configurable destinations; stop assuming a nav item` | #25 |
| `docs: record that destinations are configuration` | #25 |
## The four decisions
**Storage is a map keyed by view id, where an absent key means that
view's own default.** A `HiddenViews []string` cannot express "Autotag
off by default" — its zero value is *hide nothing* — and a boolean per
view turns a view that later stops existing into stored garbage. With a
map, an unknown key is dropped on load, a view added later gets its own
default, and no install needs migrating in either direction. Same
polarity `AllowMeteredCatalogDownload` established.
That is also why the `#25 → #27` order on #73 is safe rather than
backwards, which was worth checking before starting: when #27 folds Jobs
into Settings, `jobs = true` in somebody's config is a key nothing asks
about, not a cleanup task. Argued in full in a comment on #73.
**Settings and the launch page refuse in the config, not in the
checkbox.** `config.toml` is hand-editable, so the disabled checkbox is
the affordance and `SetViewVisible` is the rule — an app that can be
locked out of its own Settings by a typo in TOML is a support problem
nobody can debug remotely. On *load* the launch page is instead
un-hidden rather than refused: there is nobody to tell, and the honest
reading of "my launch page is Autotag" is that this user wants Autotag,
not that their launch page should be silently reset to something they
did not choose.
**Downloads is gated at the nav, not in the config**, on
`downloadStore.available` — so switching it on in Settings still means
what it says once a client exists, and the tab appears without a
restart (#37's rule). `available` is false until the providers have
loaded, which makes the item *appear* on a fresh launch rather than
appearing and then vanishing.
**The tab bar honours the toggles**, and the reason is local rather than
a general rule about phones. `PHONE_COLUMN_IDS` is the precedent for
"what a phone shows is a different question" and would have applied —
except that `bottom-nav`'s "More" opens the *same* `<app-sidebar>`,
which filters, so an unfiltered bar would contradict its own drawer one
tap away. Verified at 390px: hiding Albums removed it from the bar and
from the drawer together. Which four tabs is still plan 016's committed
subset; this only removes from it, and "More" is never filtered.
## Two things this deliberately did not need
**No special case for the highlight.** #72 moved the active view onto
`active-view-store`, so `app-sidebar` asks `isActive(id)` per *rendered*
item — a hidden view lights nothing, exactly as a detail view does.
**No re-seed.** The skill warns that a seed freezes persisted defaults,
but this default is an absent key: a seed built before the feature
existed came up without Autotag on the first launch of the new binary.
Filed as a general note in `.planning/NOTES.md`.
## One finding in #25 was stale
Its fourth bullet says the sidebar's item count is one of the two
reasons `MinHeight` is 600. Both reasons were retired by #24 —
`backend/config/window.go` records that the subtitle is `display: none`
from 899px down and the sidebar host scrolls. 800×600 is a comfort
floor now, not a correctness one, so hiding tabs relieves nothing
there. Not claimed anywhere in this change.
## Verification
- `make lint` (3 configurations) — 0 issues
- `make test` (3 configurations) — pass, including 8 new cases in
`backend/config/views_test.go`
- `make ui-test` — **941 pass**, including 6 new in
`view-visibility.test.ts`
- `make e2e` — **151 pass** (chromium), including 5 new in
`view-visibility.spec.ts`
- `make bindings-check`, `make css-check`, `make skill-check`
- `npx tsc --noEmit` in both `frontend/` and `e2e/`
- By hand against `make dev-headless SEED=default`: the sidebar comes up
without Autotag or Downloads on a pre-existing seed; toggling Autotag
in Settings adds it live; the resulting `config.toml` carries exactly
one line; `SetViewVisible('settings', false)` and hiding the launch
page are both refused by the backend; the phone bar and its drawer
agree at 390px. Screenshots read, not just assertions.
The e2e assertions are about the **navigation**, not about the setting
being saved — "the config was written" is the plumbing, and #69 and #72
both shipped green under specs that measured exactly that.
Closes #25
The sidebar's eleven entries are more than most libraries need, and
Autotag rewrites tags on disk, which is not what a fresh install should
be one click from.
Stored as a map keyed by view id, where an absent key means that view's
own default. A `HiddenViews []string` cannot express "Autotag off by
default" -- its zero value is *hide nothing* -- and a boolean per view
turns a view that later stops existing into stored garbage. With a map,
an unknown key is dropped on load, a view added later gets its own
default, and no install needs migrating in either direction. Same
polarity as AllowMeteredCatalogDownload: the zero value is the intended
answer.
`Views` is also what DefaultPage now validates against, so which views
exist and which may be the launch page are one list rather than two.
Two states the user could not get out of are refused rather than
allowed: Settings is never hideable, and the launch page is not
hideable while it is the launch page. Both refuse in the *config*, not
in the UI, because `config.toml` is hand-editable. On load the launch
page is instead un-hidden -- there is nobody to tell, and the honest
reading of "my launch page is Autotag" is that this user wants Autotag,
not that their launch page should be silently reset.
The navigation reads the resolved map from the backend rather than
holding a copy of the defaults, which would be the copy that shipped in
the binary rather than the one being edited.
Hiding takes away the nav item and nothing else: `navigate` still
resolves a hidden view, which detail views and the launch page depend
on. No special case was needed for the highlight, because #72 moved
that onto `active-view-store` -- the sidebar asks `isActive(id)` per
*rendered* item, so a hidden view lights nothing exactly as a detail
view does.
Downloads is gated at the nav on `downloadStore.available` rather than
in the config, so switching it on in Settings still means what it says
once a client exists, and the tab appears without a restart. `available`
is false until the providers have loaded, which makes the item appear on
a fresh launch rather than appearing and then vanishing.
The tab bar honours the toggles too, and the reason is local rather than
a general rule about phones: "More" opens the *same* `<app-sidebar>`,
which filters, so an unfiltered bar would contradict its own drawer one
tap away. Which four tabs is still plan 016's subset; this only removes
from it, and "More" is never filtered.
`services/view-meta.ts` is the destination list, on `shortcut-meta.ts`'s
pattern, because Settings is now a second reader of the same labels in
the same order.
Two existing sidebar tests had to say which world they describe: eleven
destinations now assumes a configured download client.
The assertions are about the navigation, not about the setting: "the
config was saved" is the plumbing, and #69 and #72 both shipped green
under specs that measured exactly that.
Four existing specs reached a view by clicking its nav item, which since
this change is not guaranteed to exist -- Autotag is hidden by default
and Downloads is absent without a download client -- so they timed out
waiting for a locator that will never resolve. `navigateTo` dispatches
the app's own `navigate` event, which is what every nav item, card and
detail view dispatches, so it is the mechanism rather than a test-only
door. Click the item when the nav is the subject.
Closes#25
Beside the three navigation paragraphs, since it is the fourth thing
the shell states about where the user can go.
Two notes are measured facts rather than design: a default expressed as
an *absent key* survives an existing seed, where one expressed as a zero
value does not; and a spec can no longer assume a destination has a nav
item.
logan
merged commit 5af545e38d into main2026-08-19 23:46:23 +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.
Eleven sidebar entries is more than most libraries need, so each is
toggleable from Settings → Navigation; Autotag is off until asked
for, and Downloads is absent until there is a client to download with.
feat(config): make the shell's destinations configurablefeat(shell): draw only the destinations the user kepttest(e2e): cover configurable destinations; stop assuming a nav itemdocs: record that destinations are configurationThe four decisions
Storage is a map keyed by view id, where an absent key means that
view's own default. A
HiddenViews []stringcannot express "Autotagoff by default" — its zero value is hide nothing — and a boolean per
view turns a view that later stops existing into stored garbage. With a
map, an unknown key is dropped on load, a view added later gets its own
default, and no install needs migrating in either direction. Same
polarity
AllowMeteredCatalogDownloadestablished.That is also why the
#25 → #27order on #73 is safe rather thanbackwards, which was worth checking before starting: when #27 folds Jobs
into Settings,
jobs = truein somebody's config is a key nothing asksabout, not a cleanup task. Argued in full in a comment on #73.
Settings and the launch page refuse in the config, not in the
checkbox.
config.tomlis hand-editable, so the disabled checkbox isthe affordance and
SetViewVisibleis the rule — an app that can belocked out of its own Settings by a typo in TOML is a support problem
nobody can debug remotely. On load the launch page is instead
un-hidden rather than refused: there is nobody to tell, and the honest
reading of "my launch page is Autotag" is that this user wants Autotag,
not that their launch page should be silently reset to something they
did not choose.
Downloads is gated at the nav, not in the config, on
downloadStore.available— so switching it on in Settings still meanswhat it says once a client exists, and the tab appears without a
restart (#37's rule).
availableis false until the providers haveloaded, which makes the item appear on a fresh launch rather than
appearing and then vanishing.
The tab bar honours the toggles, and the reason is local rather than
a general rule about phones.
PHONE_COLUMN_IDSis the precedent for"what a phone shows is a different question" and would have applied —
except that
bottom-nav's "More" opens the same<app-sidebar>,which filters, so an unfiltered bar would contradict its own drawer one
tap away. Verified at 390px: hiding Albums removed it from the bar and
from the drawer together. Which four tabs is still plan 016's committed
subset; this only removes from it, and "More" is never filtered.
Two things this deliberately did not need
No special case for the highlight. #72 moved the active view onto
active-view-store, soapp-sidebarasksisActive(id)per rendereditem — a hidden view lights nothing, exactly as a detail view does.
No re-seed. The skill warns that a seed freezes persisted defaults,
but this default is an absent key: a seed built before the feature
existed came up without Autotag on the first launch of the new binary.
Filed as a general note in
.planning/NOTES.md.One finding in #25 was stale
Its fourth bullet says the sidebar's item count is one of the two
reasons
MinHeightis 600. Both reasons were retired by #24 —backend/config/window.gorecords that the subtitle isdisplay: nonefrom 899px down and the sidebar host scrolls. 800×600 is a comfort
floor now, not a correctness one, so hiding tabs relieves nothing
there. Not claimed anywhere in this change.
Verification
make lint(3 configurations) — 0 issuesmake test(3 configurations) — pass, including 8 new cases inbackend/config/views_test.gomake ui-test— 941 pass, including 6 new inview-visibility.test.tsmake e2e— 151 pass (chromium), including 5 new inview-visibility.spec.tsmake bindings-check,make css-check,make skill-checknpx tsc --noEmitin bothfrontend/ande2e/make dev-headless SEED=default: the sidebar comes upwithout Autotag or Downloads on a pre-existing seed; toggling Autotag
in Settings adds it live; the resulting
config.tomlcarries exactlyone line;
SetViewVisible('settings', false)and hiding the launchpage are both refused by the backend; the phone bar and its drawer
agree at 390px. Screenshots read, not just assertions.
The e2e assertions are about the navigation, not about the setting
being saved — "the config was written" is the plumbing, and #69 and #72
both shipped green under specs that measured exactly that.
Closes #25