put the open file in the url on /code-editor
audit m5. the active file is `?file=`, tree file rows and tabs are links, and a `?file=` naming something that is not open now opens it — which is the part that makes a pasted link actually work rather than just describe. the open-tab *set* stays local state and i want that on the record as a choice, not an omission. it is a working session, not an address: it grows without bound, every entry costs a read on load, and nobody has ever linked someone else to a tab bar. opt-in via a prop from the screen rather than the workspace identity the file browser uses, because /code-editor renders CodeEditorView directly inside a Widget instead of through the panel wrapper — there is no workspace to ask. a dashboard editor is unchanged. tree *folder* rows stay buttons, and unlike the file browser's folders this needs nobody's call: expanding a directory is disclosure, not navigation. two things fixed while in here. the tab close control was a role="button" span nested inside the tab's own button — invalid before, and a nested interactive inside an anchor after — so it is a sibling button with an aria-label now. and closeFile picked the next-active file inside a setFiles updater, which is the impurity react double-invokes in development to catch. a path that fails to read is remembered, so a broken link errors once instead of once per render, and the address is left alone rather than rewritten.
This commit is contained in:
@@ -42,9 +42,9 @@ was in this list; the Projects feature was removed end to end on 2026-07-30, so
|
||||
and navigate-site 22 below still name it — they are the record of work that happened, not of code that exists.)
|
||||
**Section routes** (a param that names a view rather than an entity): `/settings/:page/:section`,
|
||||
`/soulseek/:section`, `/headscale/:section`, `/wallet/:section`, `/system-monitor/:scope`.
|
||||
**Query-param screens:** `/music?path=`, `/files?path=`.
|
||||
**Genuinely flat, still:** `/` `/plans` `/terminal` `/desktop` `/code-editor` `/qr-transfer`.
|
||||
`/code-editor` is M5 — the last one that ought not to be.
|
||||
**Query-param screens:** `/music?path=`, `/files?path=`, `/code-editor?file=`.
|
||||
**Genuinely flat, still:** `/` `/plans` `/terminal` `/desktop` `/qr-transfer`. Of these only `/plans` selects
|
||||
something (M9); the other four have nothing addressable to put in an address.
|
||||
The five settings pages are route **pairs** now, not flat screens — `/settings/{profile,ai,system,integrations,user-management}`
|
||||
plus a `:section` each (M6). There has never been a `/settings/apps`; that entry was wrong when this list
|
||||
was written.
|
||||
@@ -81,7 +81,7 @@ are good building blocks. The **Workspace/Panel framework** contains **zero** ro
|
||||
| ~~M2~~ | ~~`Screens/Dashboard/TaskLogs/index.tsx:104`~~ | a task-log run | `/task-logs/:id` | **Done.** As predicted — the detail fetch already keyed off the id, so only its source changed. `showDetail` is gone; the mobile swap and both back arrows derive from the param. |
|
||||
| ~~M3~~ | ~~`Screens/Dashboard/Activity/ActivityScreen.tsx:63,73`~~ | background task / detached job | `/activity/:id` | **Done.** One param for both row kinds; the screen looks the id up in the polled registry and derives `task=`/`path=` from the row. The SSE effect now depends on that derived *string*, so the 3s poll no longer risks re-opening the stream. An id that has left the registry says so instead of hanging on "waiting for output". |
|
||||
| ~~M4~~ | ~~`FileBrowser/.../useFileBrowserApp.ts:269`~~, `FileItem.tsx:516`, ~~`Breadcrumb.tsx:16`~~ | a folder | `/files?path=<dir>` | **Partly done — the rest is an owner decision, not a defect.** `currentPath` is `?path=` on `/files`, so back/forward and linking a folder work, and the crumbs are `<Link>`s. Two things the audit line did not know: `?view=` is *ephemeral* (wiped on mount by `useFileViewerPanels`), so `path` is the screen's first durable param, and four `setSearchParams({…})` calls replaced the whole query string — opening any file would have silently reset the folder. They go through a `setViewerParams` helper now that keeps `path`. Opt-in via the parsed `WorkspaceIdentity` (`screens/files`), because a dashboard can hold two browsers and one shared param would move both. **Folder *items* stay buttons:** ⌘/Ctrl/Shift-click is already bound to multi-select in `FileItem.tsx` and open is double-click, so anchor semantics collide with an existing gesture. |
|
||||
| M5 | `CodeEditor/FileTree.tsx:59`, `EditorTabs.tsx:33` | open source file / active tab | `/code-editor?file=<path>` (+ `open=` for tabs) | tree file rows → `<Link>`; active file in a param. |
|
||||
| ~~M5~~ | ~~`CodeEditor/FileTree.tsx:59`, `EditorTabs.tsx:33`~~ | open source file / active tab | `/code-editor?file=<path>` | **Done, minus `open=`.** The active file is `?file=`; tree *file* rows and tabs are `<Link>`s. **The tab set stays local** — it is a working session, not an address: it grows without bound, each entry costs a read on load, and nobody links someone else to a tab bar. A `?file=` naming a file that is not open now *opens* it, which is what makes a pasted link work; a path that fails to read is remembered so a bad link errors once instead of once per render, and the address is left alone rather than rewritten. **Tree folder rows stay buttons** — unlike the M4 case this needs no owner call, because expanding a directory is disclosure, not navigation. Two things fixed in passing: the tab close control was a `role="button"` span *nested inside* the tab (invalid then, a nested interactive inside an anchor now) and is a sibling `<button>` with an `aria-label`; and `closeFile` computed the next-active file *inside* a `setFiles` updater, which is exactly the impurity React double-invokes to catch. |
|
||||
| ~~M6~~ | `Settings/SettingsPanel.tsx` | a settings sub-section | `/settings/:page/:section` | **Done.** `<NavLink>` + `useParams`, five `*_SELECTED` globals gone, one `SettingsRoute` guard per page. The "one change covers all settings pages" claim was *almost* right: Integrations builds its own sidebar and did not go through `createSettingsPanelComponents`, and it also held the Enterprise/Personal tab in a second global — derived from the section key now, which is what fixes deep-linking a Personal section. |
|
||||
| M7 | `workspaces/components/Combobox.tsx:53` | caller-supplied route | (existing) | shared widget: `href`-bearing options do `navigate(option.href)`; render them as `<Link>`. Every caller inherits the opaque click. |
|
||||
| ~~M8~~ | `Layout/Header/UserMenu.tsx` | — | — | **Done.** Removed rather than routed: nothing had ever been built behind `/settings/resources`, so the item was a bounce to `/` dressed as navigation. Its `header.userMenu.resources` locale keys went with it. |
|
||||
@@ -218,7 +218,7 @@ publishers means changing the chat panel, which is another agent's, so it is wri
|
||||
- [x] **M2** TaskLogs → `/task-logs/:id`; rows are `<Link>`s, `showDetail` deleted. **Needs runtime test.**
|
||||
- [x] **M3** Activity → `/activity/:id`; the `{label, query}` selection object is gone — the id is the URL and the stream query is derived from the registry row. `/activity` also had no `usePageTitle` rule (it read "Officer"); added. **Needs runtime test.**
|
||||
- [x] **M4** FileBrowser folders → `/files?path=`; breadcrumbs are `<Link>`s. Folder *rows* deliberately still buttons — ⌘-click is multi-select, open is double-click; converting them needs an owner call on the gesture.
|
||||
- [ ] **M5** CodeEditor active file → `/code-editor?file=` (`FileTree.tsx:59`, `EditorTabs.tsx:33`).
|
||||
- [x] **M5** CodeEditor active file → `/code-editor?file=`; tree file rows and tabs are `<Link>`s. The open-tab *set* stays local state, on purpose — see the findings row.
|
||||
- [ ] **M7** Combobox: render `href` options as `<Link>` (`Combobox.tsx:53`).
|
||||
|
||||
### Phase 3 — Whole-workspace routing decisions (needs a design call first)
|
||||
|
||||
Reference in New Issue
Block a user