Add specs for the account menu, downloads-as-offline-library, offline downloaded-only filter, and scoped search (+ boundary revision). Add the new UR/DR entries to requirements.md, update ux-flows, and regenerate the traceability matrix. TRACES: UR-049, UR-050, UR-052, UR-053, UR-054, UR-055, UR-056
165 lines
7.1 KiB
Markdown
165 lines
7.1 KiB
Markdown
# Spec: Account menu and global chrome availability
|
||
|
||
**Status:** Implemented
|
||
**Scope:** Frontend only. No Rust changes required.
|
||
**Requirements:** UR-054 → DR-075, DR-076, DR-077 (see
|
||
[requirements.md](../requirements.md)).
|
||
**UX spec:** [ux-flows.md §1.2–1.4](../ux-flows.md).
|
||
|
||
## Summary
|
||
|
||
Account actions — Settings, Downloads, Display preferences, Sign out — are
|
||
currently reachable **only from `/library/*`**. Move them into a single shared
|
||
account menu anchored to the user's name, and make that menu available on every
|
||
authenticated non-immersive screen.
|
||
|
||
## Motivation
|
||
|
||
A user sitting on the home screen cannot open Settings or sign out. The bottom
|
||
nav offers Home / Search / Library only, and the header that hosts those actions
|
||
belongs to the library layout. The user has to guess that account actions live
|
||
*inside* Library — an unrelated section — and navigate there first.
|
||
|
||
Desktop and mobile also disagree today: desktop shows an unlabeled logout icon
|
||
with no grouped menu, mobile shows a three-dot overflow with labelled items. The
|
||
same two actions are found two different ways.
|
||
|
||
## Background: verified current state
|
||
|
||
1. **The header is not global.** It is defined in
|
||
[library/+layout.svelte](../../src/routes/library/+layout.svelte). The root
|
||
layout [+layout.svelte](../../src/routes/+layout.svelte) renders no header at
|
||
all.
|
||
|
||
2. **`routeOwnsLayout`** in
|
||
[layoutShell.ts](../../src/lib/utils/layoutShell.ts) returns true for
|
||
`/library`, `/player/`, `/login` — those routes own their own full-height
|
||
flex column. Everything else renders into the root scroller with the root's
|
||
`BottomUi` below it.
|
||
|
||
3. **Bottom nav is Home / Search / Library only**
|
||
([BottomNav.svelte](../../src/lib/components/BottomNav.svelte)) — no Settings
|
||
or account entry.
|
||
|
||
4. **Net effect:** on `/`, `/search`, and `/downloads` there is no route to
|
||
Settings or Sign out.
|
||
|
||
5. **Desktop username is inert text** — a `<span>` next to the icons, not a
|
||
trigger.
|
||
|
||
6. **The mobile overflow menu already has the right contents** (Downloads,
|
||
Settings, divider, Sign out) and the right dismissal behaviour (backdrop
|
||
click, keyboard handler). **Extract and reuse it rather than rewriting it.**
|
||
|
||
7. **`viewMode` is already a persisted store** in
|
||
[library.ts](../../src/lib/stores/library.ts) (`jellytau-view-mode`,
|
||
`localStorage`). The Display setting is a second view onto it — **no new
|
||
state, no migration.**
|
||
|
||
## Design
|
||
|
||
### `AccountMenu` component (DR-075)
|
||
|
||
One component used by both breakpoints. Contents in fixed order:
|
||
|
||
```
|
||
Signed in as <name> ← identity block, not interactive
|
||
<server host>
|
||
────────────────────────
|
||
Downloads
|
||
Settings
|
||
Display ← grid/list preference
|
||
────────────────────────
|
||
Sign out ← destructive, last, after a divider
|
||
```
|
||
|
||
- **Trigger is the username/avatar**, not a bare three-dot icon. On mobile where
|
||
horizontal space is tight, the avatar (or initial) alone is acceptable; the
|
||
name shows inside the open menu regardless.
|
||
- **Same items, same order, both platforms.**
|
||
- Preserve the existing dismissal behaviour: click-outside backdrop, `Escape`,
|
||
and focus return to the trigger on close.
|
||
- Menu items are real links/buttons — keyboard reachable, correct roles,
|
||
`aria-expanded` on the trigger.
|
||
|
||
"Display" may either navigate to the Settings Display section or expose the
|
||
grid/list choice inline. Prefer navigating — it keeps one source of truth for
|
||
preferences and avoids a nested control inside a dropdown.
|
||
|
||
### Global chrome (DR-076)
|
||
|
||
Make the header — and therefore the account menu — available on `/`, `/search`,
|
||
and `/downloads`.
|
||
|
||
The cleanest route is to lift the header out of the library layout into a shared
|
||
component rendered by the root layout, with the library layout consuming the
|
||
same component rather than defining its own. **Do not duplicate the markup into
|
||
each route.**
|
||
|
||
Constraints that must survive the change:
|
||
|
||
- `/player/*` and `/login` stay chrome-free.
|
||
- `/settings` already owns its layout; it needs no account menu (the user is
|
||
already there), but must not double up on chrome.
|
||
- The root layout's flex/scroller structure is deliberate — the comments in
|
||
[layoutShell.ts](../../src/lib/utils/layoutShell.ts) and
|
||
[+layout.svelte](../../src/routes/+layout.svelte) explain why routes own their
|
||
own column. Preserve the scroll containment; a regression here reintroduces
|
||
the "last row hidden behind the nav" bug called out in those comments.
|
||
- Mini-player and bottom-nav visibility rules (`showGlobalMiniPlayer`,
|
||
`showBottomNav`) must be unchanged.
|
||
|
||
### Display section in Settings (DR-077)
|
||
|
||
Add a Display section to [settings/+page.svelte](../../src/routes/settings/+page.svelte)
|
||
with the grid/list control bound to the existing `viewMode` store via
|
||
`library.setViewMode(...)`. The page-header toggle in `LibraryGrid` stays — both
|
||
controls drive the same store, so they stay in sync for free.
|
||
|
||
## Out of scope
|
||
|
||
- Redesigning the Settings page or reorganising its existing sections.
|
||
- Multi-server / account switching (UR-047) — the identity block displays the
|
||
active server but offers no switcher.
|
||
- Changing the bottom nav's three destinations.
|
||
|
||
## Acceptance criteria
|
||
|
||
- [ ] Settings and Sign out are reachable from `/`, `/search`, and `/downloads`
|
||
without first navigating into Library.
|
||
- [ ] Desktop and mobile show the same account menu items in the same order.
|
||
- [ ] The username/avatar opens the menu; it is a real button with
|
||
`aria-expanded`.
|
||
- [ ] Sign out is last, after a divider, and still logs out + resets library
|
||
state + redirects as it does today.
|
||
- [ ] `/player/*` and `/login` remain chrome-free.
|
||
- [ ] Settings has a Display section that changes grid/list, and the change is
|
||
immediately reflected by the library page-header toggle (same store).
|
||
- [ ] No regression in scroll containment, mini-player visibility, or bottom-nav
|
||
visibility on any route.
|
||
- [ ] `bun run check` and `bun run test` pass.
|
||
|
||
## Testing
|
||
|
||
- Extend the existing `layoutShell` tests: chrome-visibility for `/`, `/search`,
|
||
`/downloads` (now true) and `/player/*`, `/login` (still false).
|
||
- `AccountMenu`: renders the documented items in order; trigger toggles
|
||
`aria-expanded`; `Escape` and backdrop click close it; Sign out invokes the
|
||
logout handler.
|
||
- Display setting: writes through to the `viewMode` store and persists.
|
||
|
||
New requirement-implementing code needs `TRACES:` comments — see
|
||
[CLAUDE.md](../../CLAUDE.md). Suggested: `AccountMenu` → `UR-054 | DR-075`,
|
||
shell/header changes → `UR-054 | DR-076`, Settings Display section →
|
||
`UR-054, UR-029 | DR-077`.
|
||
|
||
## Notes for the implementer
|
||
|
||
- Read [ux-flows.md §1.2–1.4](../ux-flows.md) first — behavioural spec; this is
|
||
the implementation plan.
|
||
- The layout shell is subtle and the existing comments record real bugs that
|
||
were fixed there. Read them before restructuring.
|
||
- Another session may be active in this repo, including in
|
||
`src/routes/settings/+page.svelte`. Check `git diff` before "repairing"
|
||
unexpected changes, and expect to coordinate on that file.
|