put the music library location in the url
/music?path=<rel> replaces the music:cwd channel. Each panel reads the param
itself through useMusicCwd(), so MusicBrowser, MusicDetail and FavoritesView
no longer tell each other where they are, and every drill-in is a <Link>:
library rows, folder rows, album/artist cards, both "up" affordances, the
favorites rows, and the dock's now-playing tile. Track rows stay buttons —
they play, which is a mutation.
A query param rather than a nested route because the location is only one of
the things this screen holds (the lyrics split and the favorites view are the
others), and a splat has to be a route's last segment.
MusicPlayerHost is mounted outside <Routes> and used to write the channel and
then navigate('/music') to make the write visible — the audit's only
navigate-with-a-side-effect. That collapses to one <Link>.
music:resync (a refresh signal) and music:favorites (a view of one panel) stay
channels, deliberately.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,9 +87,15 @@ are good building blocks. The **Workspace/Panel framework** contains **zero** ro
|
||||
**Music** (M-music) and **Soulseek** (M-slsk) are whole-workspace channel apps — pulled out below because each
|
||||
is **one design decision** that cascades across many files:
|
||||
|
||||
- **Music** — `Music/{MusicBrowser:182,209 · MusicDetail:138,174,234 · FavoritesView:85,107}` hold the entire
|
||||
library location in `usePanelChannel('music:cwd')`; every drill-in is `setCwd(path)`. No URL, no deep-link to
|
||||
an album. → back `music:cwd` with `/music?path=<rel>`; rows become `<Link>`s.
|
||||
- ~~**Music**~~ — **done.** The library location is `/music?path=<rel>` (`rel` relative to the `Music` root);
|
||||
`music:cwd` is deleted. Each panel calls `useMusicCwd()` and reads the param itself, so `MusicBrowser`,
|
||||
`MusicDetail` and `FavoritesView` no longer tell each other anything. Every drill-in is a `<Link>` — library
|
||||
rows, folder rows, album/artist cards, both "up" affordances, the favorites rows, and the dock's now-playing
|
||||
tile. Track rows stay `<button>`s: they play, which is a mutation. Two things that were channel-shaped and
|
||||
stayed channels: `music:favorites` (a view toggle over this one panel) and `music:resync` (a refresh signal).
|
||||
A query param rather than `/music/*` because the location is only one of the things this screen holds — the
|
||||
lyrics split and the favorites view are the others — and because a splat has to be a route's last segment.
|
||||
**Needs runtime test.**
|
||||
- ~~**Soulseek**~~ — **mostly done.** The section is `/soulseek/:section` (nav entries are `NavLink`s, the
|
||||
view panel reads the same URL), the peer is `?user=<name>`, and the past-search was already `?search=<id>`.
|
||||
The `soulseek:section` and `soulseek:user` channels are deleted; `soulseek:refresh` stays, which is a
|
||||
@@ -114,7 +120,11 @@ is **one design decision** that cascades across many files:
|
||||
- **Jobs step/iteration** (`Jobs/JobDetail.tsx:234,255`) — intra-detail sub-selection; only if we want `/jobs/:id/:stepKey` deep links.
|
||||
- **Preview slug** (`Preview/PreviewApp.tsx:23`) — Preview usually derives its target from the surrounding dashboard/project `cwd`; the manual picker is a fallback. Probably leave URL-less.
|
||||
- **FileBrowser widget** (`FileBrowserWidget/.../BrowseTab.tsx:36`) — compact sidebar widget; keeping its browse cursor local is defensible.
|
||||
- **Music favorites view** (`music:fav`) & **SystemMonitor scope** — view toggles over tiny fixed sets; channel-state is arguably fine.
|
||||
- **Music favorites view** — the channel is `music:favorites`, not `music:fav`. **Decided: it stays a channel.**
|
||||
It is a view of the detail panel rather than a location, it survives no reload worth surviving, and the one
|
||||
case where its being off-URL shows is handled explicitly: going Home or opening a favorite closes it by hand,
|
||||
because navigating to where you already are fires no route change. (**SystemMonitor scope** was listed here
|
||||
as the same kind of thing and was not — it is M10, and it is in the URL.)
|
||||
- **"New Chat" button** (`ChatHistory/SessionList.tsx:68`) — a create-action that also `navigate('/chat/new')`; could be `<Link to="/chat/new">` if the state-set moves into the route. Its `chat:selected-session` write is part of the H4 cleanup.
|
||||
|
||||
---
|
||||
@@ -130,7 +140,6 @@ This is the primary surface to convert to URL-driven selection.
|
||||
| `chat:active-cwd` | chat working dir | query param on `/chat` | `ChatDetailPanel.tsx:102`, `SessionList.tsx:14` |
|
||||
| `SELECTED_DASHBOARD_KEY` (`useGlobal`) | selected dashboard | `/dashboards/:id` | `DashboardListApp.tsx:36`, `DashboardPreview.tsx:287` (H2) |
|
||||
| `SELECTED_PROJECT` (`useGlobal`) | selected project | `/projects/:id` | `ProjectListApp.tsx:45`, `ProjectPreview.tsx:386` (H3) |
|
||||
| `music:cwd` | library location (album/artist/folder) | `/music?path=` | `MusicBrowser/MusicDetail/FavoritesView` |
|
||||
| local `useState` selections | search / room / conversation / peer / open file / preview slug | respective nested routes | `SearchView`, `SoulseekRooms`, `SoulseekChat`, `SoulseekUsers`, `CodeEditor/useEditorState`, `PreviewProvider` |
|
||||
|
||||
**Excluded — event-bus / refresh signals, NOT selection:** `files:refresh-signal`,
|
||||
@@ -163,7 +172,7 @@ publishers means changing the chat panel, which is another agent's, so it is wri
|
||||
| 10 | `Jobs/JobDetail.tsx:709` | back button | `/jobs` | YES | use `BackButton`/`<Link>` |
|
||||
| 11 | `ChatHistory/SessionList.tsx:70` | "New Chat" | `/chat/new` | ~ | borderline (LOW) |
|
||||
| 12 | `components/Combobox.tsx:54` | nav combobox option | `option.href` | YES | M7 |
|
||||
| 13 | `MusicPlayer/MusicPlayerHost.tsx:251` | open current album (setCwd+nav) | `/music` | ~ | has side-effect |
|
||||
| ~~13~~ | `MusicPlayer/MusicPlayerHost.tsx:251` | open current album (setCwd+nav) | `/music` | ~ | **Done** — the side-effect was the `setCwd`; with the location in the URL the tile is a plain `<Link>` to `musicPath(albumRel)`. |
|
||||
| 14 | `FileBrowserWidget/useFileBrowserWidget.ts:53` | open folder in Files | `/files?view=…` | ~ | query nav |
|
||||
| 15 | `FileBrowserApp/useFileBrowserApp.ts:401` | create dashboard from folder | `/dashboards/new?…` | ~ | action-nav |
|
||||
| 16 | `FileBrowserApp/useFileBrowserApp.ts:427` | create dashboard from files | `/dashboards/new?…` | ~ | action-nav |
|
||||
@@ -209,7 +218,7 @@ publishers means changing the chat panel, which is another agent's, so it is wri
|
||||
- [ ] **M7** Combobox: render `href` options as `<Link>` (`Combobox.tsx:53`).
|
||||
|
||||
### Phase 3 — Whole-workspace routing decisions (needs a design call first)
|
||||
- [ ] **Music** — decide `/music?path=` (or nested); back `music:cwd` with the URL; rows → `<Link>` (M-music).
|
||||
- [x] **Music** — `/music?path=<rel>`; `music:cwd` deleted; every drill-in (including the dock's now-playing tile, navigate-site 13) is a `<Link>`. `music:favorites` and `music:resync` stay — a view toggle and a refresh signal. **Needs runtime test.**
|
||||
- [x] **Soulseek** — `/soulseek/:section` with the peer in `?user=` and the search already in `?search=`; the two selection channels are deleted. Rooms and conversations are still `useState`. **Needs runtime test.**
|
||||
- [x] **M10** SystemMonitor scope → `/system-monitor/:scope`; `monitor:scope` channel deleted. **Needs runtime test.**
|
||||
- [ ] **M9** Plans → `/plans/:name` (`Plans/index.tsx:36`).
|
||||
@@ -218,7 +227,7 @@ publishers means changing the chat panel, which is another agent's, so it is wri
|
||||
- [ ] Dock + Header + mobile sheet → react-router `<NavLink>` for active state; drop hand-rolled `isActive` (`Dock.tsx`, `Header.tsx`).
|
||||
- [ ] "New Chat" → `<Link to="/chat/new">` (`SessionList.tsx:68`) once H4's channel cleanup lands.
|
||||
- [ ] Jobs back button + any `navigate('/jobs')` → shared `BackButton` (`JobDetail.tsx:709`).
|
||||
- [ ] Decide/skip: Jobs step deep-link, Preview slug, FileBrowser widget, Music-favorites view toggle. (Browser tabs: **done** — see the LOW section. Monitor scope: **done** as M10, it was not a view toggle.)
|
||||
- [ ] Decide/skip: Jobs step deep-link, Preview slug, FileBrowser widget. (Browser tabs: **done** — see the LOW section. Monitor scope: **done** as M10, it was not a view toggle. Music favorites: **decided** — stays a channel, reasoning in the LOW section.)
|
||||
|
||||
### Cross-cutting for the refactor itself
|
||||
- [ ] Standardise a URL-as-source-of-truth pattern for panel selection (replace the `usePanelChannel`/`useGlobal`
|
||||
|
||||
@@ -621,7 +621,9 @@ All the same bug: an app guessing "am I being closed?" from an unmount, or payin
|
||||
|
||||
### 5.8 Navigation — finish the refactor
|
||||
|
||||
- [ ] **Move the seven channel-driven apps' selection into the URL.** Split today: 11 URL-driven
|
||||
- [x] **Move the seven channel-driven apps' selection into the URL.** _Six of the seven are done; the
|
||||
seventh (Chat detail) is chat-owned and deliberately untouched — see the foot of this item._
|
||||
Split when this was written: 11 URL-driven
|
||||
(everything post-refactor), 7 channel-driven, 3 on bare `useState`. The channel-driven set: Music
|
||||
(`music:cwd`), Soulseek sections, SystemMonitor (`monitor:scope`), Chat detail
|
||||
(`chat:selected-session`), Email, Browser, and all five Settings pages (one fix point,
|
||||
@@ -678,8 +680,31 @@ All the same bug: an app guessing "am I being closed?" from an unmount, or payin
|
||||
Focus and Close buttons were nested inside the row `<button>` (invalid HTML, held together by two
|
||||
`stopPropagation` calls — they are siblings of the anchor now), and "Set up in Integrations" was a
|
||||
raw `<a href>` that reloaded the whole SPA.
|
||||
Remaining here: Music. **Chat detail (`chat:selected-session`) is chat-owned —
|
||||
leave it; it is written up in `COMMS/`.**
|
||||
**Music is done** — the last of the seven that is mine, and the largest: the location touched four
|
||||
files across two packages. It is `/music?path=<rel>`, `rel` relative to the `Music` root, and
|
||||
`music:cwd` is deleted. A query param rather than the nested route the audit left open, for two
|
||||
reasons: the location is only one of the things this screen holds (the lyrics split and the
|
||||
favorites view are the others), and a splat has to be a route's last segment, which is the same
|
||||
constraint `/chat` hit when it spelled its group `/chat/g/*`. `useMusicCwd()` returns the location
|
||||
in the home-relative `Music/…` vocabulary the panels already spoke, so reading the URL cost each
|
||||
panel one line and no rewrite.
|
||||
What fell out of it is the point of the whole section. `MusicBrowser` published, `MusicDetail` and
|
||||
`FavoritesView` subscribed, and `MusicPlayerHost` — mounted *outside* `<Routes>` — wrote the channel
|
||||
and then called `navigate('/music')` to make the write visible, which is navigate-site 13 and the
|
||||
only "navigate with a side-effect" in the audit's table. With the location in the URL that pair
|
||||
collapses to one `<Link>`, and the host stops importing the library's channel at all. Every
|
||||
drill-in is a link now: library rows, folder rows, album and artist cards, both "up" affordances,
|
||||
the favorites rows, and the dock's now-playing tile. Track rows stay `<button>`s — they play, which
|
||||
is a mutation, not a location. `MusicDetail`'s once-only auto-nav to the playing album navigates
|
||||
with `replace`, on the same reasoning as Email's arrow keys: landing on `/music` and being moved to
|
||||
the playing album is one arrival, so Back should leave the screen rather than undo a jump the user
|
||||
never asked for. Two channels survived deliberately: `music:resync` (a refresh signal, never
|
||||
selection) and `music:favorites` (a view of one panel). The favorites one needs a note — going Home
|
||||
or opening a favorite has to close it *by hand*, because navigating to where you already are fires
|
||||
no route change and the effect that clears it keys on the location.
|
||||
Remaining here: **nothing that is mine.** Chat detail (`chat:selected-session`) is chat-owned —
|
||||
leave it; it is written up in `COMMS/`. Soulseek's rooms and conversations are still `useState`,
|
||||
noted in the audit and not scheduled.
|
||||
|
||||
### 5.9 The context has grown an app-config section — _(found 2026-08-07)_
|
||||
|
||||
|
||||
Reference in New Issue
Block a user