| 1 | # Dock file navigation |
| 2 | |
| 3 | How the right dock opens a file, who owns the result, and why a click can no |
| 4 | longer start a render loop. Read this before changing |
| 5 | `WorkspaceDockRegion`, `WorkspacePanel`, `RemotePanel` or anything under |
| 6 | `lib/fileNavigation*`. |
| 7 | |
| 8 | ## Contract |
| 9 | |
| 10 | Opening a file is a command, not a render. |
| 11 | |
| 12 | ``` |
| 13 | file row / markdown link / verified answer reference / file tree / preview control |
| 14 | → bound open command (FileNavigationOwner) |
| 15 | → resolve the resource and re-check the command is still current |
| 16 | → pick the target dock and preview position |
| 17 | → commit a navigation record |
| 18 | → the dock subscribes and renders what was committed |
| 19 | ``` |
| 20 | |
| 21 | - **A render never navigates.** Panels read `owner.getSnapshot(key)` through |
| 22 | `useSyncExternalStore`; nothing is published while React renders. |
| 23 | - **Resource identity, access context and navigation parameters are separate.** |
| 24 | Preview identity is resource space + backend-canonical path. Session tab, |
| 25 | source and optional `toolCallId` belong to `FileAccessContext`; `preview`, |
| 26 | `source` and `reveal-tree` are parameters. Switching a parameter reuses the |
| 27 | preview tab, while switching sessions can rebind its access without changing |
| 28 | which file it represents. |
| 29 | - **Access context travels with the command.** Workspace, presented and |
| 30 | host-verified answer references keep distinct readers. Reopening a path from |
| 31 | another entry point uses that command's credentials, never an earlier |
| 32 | presentation's. An answer reference is resolved and authorized again by the |
| 33 | host for every read or direct action. |
| 34 | - **Records are keyed by dock tab.** A record holds the dock |
| 35 | instance identity, its lifecycle generation, the entries with their access |
| 36 | contexts, the last navigation intent, a display revision, a content revision |
| 37 | and the lifetime signal. |
| 38 | - **Only an explicit command advances a revision.** An equivalent command keeps |
| 39 | the entry objects and the entry list identical, so the preview does not |
| 40 | restart a read that is still valid. |
| 41 | |
| 42 | ## Harness design → Reasonix implementation |
| 43 | |
| 44 | | Harness design | Reasonix implementation | |
| 45 | | --- | --- | |
| 46 | | Command-driven navigation: opening is an event, rendering reads the result | `FileNavigationOwner` (`lib/fileNavigationOwner.ts`) commits records; `WorkspaceDockRegion` no longer builds a request during render; `useFileNavigationRecord` (`app-shell/useFileNavigation.ts`) only reads | |
| 47 | | Stable resource identity: a canonical file coordinate, not an object reference | `FileResourceRef` / `FileAccessContext` / `ResolvedFileResource` (`lib/fileResource.ts`); backend-resolved `identityPath` collapses relative/absolute aliases, while access context separately keeps workspace, presented and verified-reference readers distinct | |
| 48 | | Separate navigation parameters from identity | `FileNavigationParams` — `action` (`preview`/`source`/`reveal-tree`) and `view` (`files`/`changed`) never change what the resource is | |
| 49 | | Independent navigation instance per dock | One `FileNavigationOwner` per running app instance (`useFileNavigationRuntime`), one record per dock tab, `generation` per lifecycle | |
| 50 | | Command results are reported, not thrown | `FileNavigationOutcome` — `opened` / `cancelled` (superseded, closed, disposed) / `failed`; a cancelled command shows no error | |
| 51 | | Resource URL ownership on a cancelled browser preview | `openBrowserPreview` releases exactly one URL: the tab owns it once handed over, otherwise the command revokes it | |
| 52 | | No global cancellation | `fileNavigationLifetime.ts` is gone; a new command supersedes the record's pending one, and `retain`/`bindScope` end a record's lifetime | |
| 53 | | Tab restore without replay | The record survives a dock collapse; a remount reads the retained selection without re-applying the last intent or re-running the open command | |
| 54 | |
| 55 | Not ported: split view, floating preview windows, layout undo history, the |
| 56 | Cordis plugin framework, and the `dsh-resource://` scheme (resource ids stay |
| 57 | internal to navigation). |
| 58 | |
| 59 | ## Lifecycle and cancellation |
| 60 | |
| 61 | | Event | Effect | |
| 62 | | --- | --- | |
| 63 | | New command for the same dock | Aborts the record's pending operation; the older outcome is `cancelled` | |
| 64 | | Another command to a different dock | Nothing: panels never cancel each other | |
| 65 | | Dock tab closed or removed from the active workspace | `retain` drops the record and aborts its lifetime | |
| 66 | | Session changes inside the same project | `bindScope` keeps entries and selection, then replaces their access contexts with current-session workspace access | |
| 67 | | Project/remote-host resource space changes | `bindScope` rebuilds the record: empty entries, advanced generation, previous lifetime aborted | |
| 68 | | Dock collapsed (`workspacePanelOpen` false) | Nothing: the record is what re-expanding restores | |
| 69 | | Runtime unmounted | `dispose` aborts every record and every one-shot operation | |
| 70 | | Dock tab id reused after close | A new generation; remembered paths come back with workspace access only | |
| 71 | |
| 72 | ## Persistence |
| 73 | |
| 74 | Only paths are persisted (`workspaceViewMemory`, the dock tab list). Lifecycle |
| 75 | generations, cancellation signals and access contexts live in memory and are |
| 76 | never written to `localStorage`. Storage written by older versions is read as |
| 77 | paths and revalidated against the current workspace, which is why a restored |
| 78 | preview never regains a presented tool scope. |
| 79 | |
| 80 | No Go/Electron bridge payload, session log, tool schema, standing instruction or |
| 81 | provider request byte changes, so prompt caching is unaffected. |
| 82 | |
| 83 | ## Verification |
| 84 | |
| 85 | | Area | Test | |
| 86 | | --- | --- | |
| 87 | | Navigation instance | `file-navigation-owner.test.ts` | |
| 88 | | Command ordering and cancellation | `file-navigation-races.test.ts` | |
| 89 | | Verified answer-reference reader and source toggle | `workspace-reference-reader.test.tsx` | |
| 90 | | Dock chain (local and remote) | `dock-file-navigation.test.tsx` | |
| 91 | | Preview tabs, cap, source mode, reveal, generations | `file-navigation-dock.test.tsx` | |
| 92 | | Render-loop defect, StrictMode, re-renders, remount | `file-navigation-lifecycle.test.tsx` | |
| 93 | | Remote reads, save isolation, disconnect | `remote-file-navigation-races.test.tsx` | |
| 94 | | Dock request delivery | `dock-navigation.test.ts`, `dock-view-requests.test.tsx` | |
| 95 | | Real DOM and Electron | `bench/dock-file-navigation.mjs` (`test:app-browser`, `test:dock-electron`) | |
| 96 | | Answer-reference click into the running owner | `bench/chat-file-reference.mjs` (`test:chat-file-browser`) | |
| 97 |