close out the navigation audit's remaining decide-or-skip items

Jobs step deep-link: skipped, and measured first — selectedKey is plain
useState, not a channel, so it breaks none of this document's rules. The only
thing anchor semantics would buy is a deep link nobody asked for.

Preview slug: void, there is no Preview app.

FileBrowser widget: stays local, and M4 turned that shrug into a rule — only a
workspace guaranteed to host one browser may own the address bar.

Phases 1-4 are now closed except H4 and the New Chat button, both of which
live in the chat nucleus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 12:52:31 +00:00
co-authored by Claude Opus 5
parent bbcb041ef3
commit 98636224eb
+26 -10
View File
@@ -1,11 +1,19 @@
# Frontend Navigation Audit — the "opaque click" anti-pattern # Frontend Navigation Audit — the "opaque click" anti-pattern
> **Status, 2026-07-31.** H1 (jobs), H2 (dashboards) and H4's groundwork are **done** — rows are real > **Status, 2026-08-07. Phases 14 are closed except the two chat items.** Every finding H1H5 and M1M10
> `<Link>`s and selection lives in `?selected=` / `useParams`. **H3 is void: the Projects feature was > is done, void, or decided-and-recorded, and so is every Phase 4 line. What remains, and only this:
> deleted**, so every reference to `Projects/`, `SELECTED_PROJECT` and `/projects/:id` below is historical > **H4** (chat detail reading `sessionId` from `useParams` instead of the `chat:selected-session` channel)
> and should be read as an example of the pattern, not as work to do. **H4 (chat detail reading > and the **"New Chat"** button that depends on it. Both are inside the chat nucleus, which has its own
> `sessionId` from `useParams` rather than the `chat:selected-session` channel) is the one item still > owner — they are not stalled here, they are somebody else's to land.
> open.** The rules in this file are current and authoritative; the findings table is a snapshot. >
> Three findings turned out to be wrong on inspection and are marked as such rather than quietly dropped:
> **H3** is void (Projects was deleted, so every `Projects/`, `SELECTED_PROJECT` and `/projects/:id`
> reference below is historical), **M7**'s "every caller inherits the opaque click" was false (it had no
> callers, and the component is deleted), and the **Preview slug** item names an app that does not exist.
> Two components — `Combobox` and `BackButton` — were deleted rather than fixed, having never been used.
>
> The rules in this file are current and authoritative; the findings table is a snapshot.
> **Nothing below has had a runtime click-through.** Every "Needs runtime test" note is still true.
**Date:** 2026-07-30 · **Origin:** written as exploration before any of the routing work was done. **Date:** 2026-07-30 · **Origin:** written as exploration before any of the routing work was done.
@@ -125,9 +133,17 @@ is **one design decision** that cascades across many files:
costs no extra request. En route: the row's Focus and Close buttons were nested *inside* the row costs no extra request. En route: the row's Focus and Close buttons were nested *inside* the row
`<button>`, which is invalid HTML and only worked because of two `stopPropagation` calls; they are `<button>`, which is invalid HTML and only worked because of two `stopPropagation` calls; they are
siblings of the anchor now. And its "Set up in Integrations" was a raw `<a href>` that reloaded the SPA. siblings of the anchor now. And its "Set up in Integrations" was a raw `<a href>` that reloaded the SPA.
- **Jobs step/iteration** (`Jobs/JobDetail.tsx:234,255`) — intra-detail sub-selection; only if we want `/jobs/:id/:stepKey` deep links. - **Jobs step/iteration** (`Jobs/JobDetail.tsx`) — **decided: skipped, and it is not an anti-pattern.**
- **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. Measured rather than assumed: `selectedKey` is plain `useState` behind a context, not a channel and not a
- **FileBrowser widget** (`FileBrowserWidget/.../BrowseTab.tsx:36`) — compact sidebar widget; keeping its browse cursor local is defensible. global, so it breaks none of the rules this document sets. What it lacks is anchor semantics, and the only
thing that buys is a `/jobs/:id/:stepKey` deep link — a feature nobody has asked for, on keys that are
array indexes and iteration labels. Building it is a product call, not a fix. **Owner's if he wants it.**
- ~~**Preview slug**~~ (`Preview/PreviewApp.tsx:23`) — **void.** There is no Preview app. Nothing in the tree
matches `PreviewApp`, and it is in no registry. It went with the Projects removal on 2026-07-30, or earlier.
- **FileBrowser widget** (`FileBrowserWidget/.../BrowseTab.tsx`) — **decided: stays local**, and M4 turned
the shrug into a rule. A widget sits on dashboards next to other browsers, and one shared `?path=` would
drive all of them; only a workspace guaranteed to host exactly one browser may own the address bar, which
is why the widget passes no `searchForPath` and its breadcrumb stays buttons. Same test, same answer.
- **Music favorites view** — the channel is `music:favorites`, not `music:fav`. **Decided: it stays a channel.** - **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 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, case where its being off-URL shows is handled explicitly: going Home or opening a favorite closes it by hand,
@@ -246,7 +262,7 @@ publishers means changing the chat panel, which is another agent's, so it is wri
shape that fits none of the icon-only back controls in the app. Adopting it would have been a visual shape that fits none of the icon-only back controls in the app. Adopting it would have been a visual
redesign of the Jobs header dressed up as a navigation fix, so it is deleted instead. The two sibling redesign of the Jobs header dressed up as a navigation fix, so it is deleted instead. The two sibling
panes (`ScriptJobDetail`, `DownloadJobDetail`) were already `<Link to="/jobs">`; this was the odd one out. panes (`ScriptJobDetail`, `DownloadJobDetail`) were already `<Link to="/jobs">`; this was the odd one out.
- [ ] 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.) - [x] Decided/skipped: Jobs step deep-link (a feature, not a fix — owner's call), Preview slug (**void**: no such app), FileBrowser widget (stays local, on M4's rule). Reasoning for each in the LOW section. (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 ### Cross-cutting for the refactor itself
- [ ] Standardise a URL-as-source-of-truth pattern for panel selection (replace the `usePanelChannel`/`useGlobal` - [ ] Standardise a URL-as-source-of-truth pattern for panel selection (replace the `usePanelChannel`/`useGlobal`