fix(development): make driveless /dashboard/development a GLOBAL view - #2027
Conversation
The Development surface's driveless route redirected to a resolved
"current/last" drive instead of showing everything, per the routing
correction in the Development surface spec. It's now a real global
command center: all machines across all drives the user can access,
grouped by drive.
- packages/lib/services/machines/machine-list: add
listMachinesAcrossDrives, grouping listMachinesInDrive's per-drive
result by drive and dropping drives with no visible machines.
- apps/web api/machines route: no-driveId request now returns
{ drives: [...] } instead of 400, under the same app-admin gate +
per-page canUserViewPage filtering as the per-drive path.
- machine-list-runtime: listAllMachines(), sourcing accessible drives
from listAccessibleDrives (the same drive universe GET /api/drives
hands DriveSwitcher).
- useDriveMachines: add useAllMachines(enabled), same SWR convention.
- DevelopmentSidebar: branches on driveId presence — drive-scoped list
unchanged, new grouped-by-drive list in global mode. MachineTreeSection
now takes a basePath so global-mode machines route to
/dashboard/development/{machineId} rather than crossing into a
specific drive's route tree.
- New /dashboard/development/layout.tsx + [machineId]/page.tsx: the
global twin of the drive-scoped detail region, sourced from
useAllMachines(). Kept as its own route tree (not merged into the
drive-scoped layout) so opening a machine from the global list never
crosses into /dashboard/{driveId}/development and tears down the
keep-alive host.
- Extracted useStickyMachineIds, useDrainPendingSession, and
DetailState/DetailNotice out of the drive-scoped layout into
lib/development/ so both layouts share the exact same logic instead
of duplicating it.
- Removed the now-dead resolveActiveDriveId redirect helper.
Colocated tests cover the new grouping/filtering logic, the API route's
global branch, the global layout, and the sidebar's global mode.
|
Warning Review limit reached
Next review available in: 20 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (23)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
💡 Codex ReviewThis template literal contains an actual NUL byte rather than the textual ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The key template literal accidentally embedded an actual NUL byte (introduced during extraction from the per-drive layout) instead of a textual escape. Same runtime string, but a raw NUL byte in a .ts file confuses editors/diff/search tooling. Flagged by Codex review on PR #2027.
|
Addressed in 210effc: replaced the raw NUL byte in |
Resolves conflict in apps/web/src/app/dashboard/[driveId]/development/layout.tsx against master's #2017 (workspace-owns-pane-grid): kept this branch's extraction of useStickyMachineIds/useDrainPendingSession/DetailState into shared apps/web/src/lib/development/ files, and ported master's updated workspace lookup (selectActiveWorkspace instead of a flat workspaces map) into the shared use-drain-pending-session.ts hook so both the drive-scoped and new global layout pick it up. Also updated the new global layout test to reset the store via the new machines shape.
Fixes from an 8-angle automated code review plus a Codex review comment: - listMachinesAcrossDrives: one drive's scan failing (or its per-page visibility check throwing) no longer 500s the ENTIRE global view -- Promise.allSettled + drop the failed drive, matching this file's own existing "swallow and hide" philosophy for per-page permission-check failures. New tests cover both failure points. - sidebar-routes.ts: fixed a stale DEVELOPMENT_PATH doc comment that still described the driveless route as "which redirects" with "no driveless twin" -- both were made false by this PR. - Extracted the duplicated isKnownMachine/displayedMachineId derivation (present in both layouts, previously copy-pasted) into a shared resolveDisplayedMachine, completing the extraction this PR already did for useStickyMachineIds/useDrainPendingSession/DetailState. - Centralized the machine detail-URL builder (buildMachineHref) alongside the existing parseSelectedMachineId in development-route.ts, so the parse and build sides of the URL shape can't drift apart. MachineTreeSection now takes a driveId again instead of an ad hoc basePath string. - Deduped the near-identical guard-chain early-returns in DriveMachineList/ GlobalMachineList into a shared resolveListNotice helper. - Deduped the byte-for-byte identical fetcher/globalFetcher bodies in useDriveMachines.ts into one generic machinesFetcher<T>. - Dropped the GLOBAL_SCOPE magic-string sentinel in favor of passing undefined to useStickyMachineIds, matching the hook's own "no scope" idiom. - Documented (rather than attempted to fix, as out of scope for this PR) a known tradeoff the global layout's route-tree separation doesn't solve: navigating from the global Development view into a specific drive's Development view crosses route trees and disconnects every warm terminal across every drive, not just the one being left. This mirrors an already-accepted tradeoff elsewhere in the surface (switching drives already tears down the other drive's terminals) and is recoverable (reconnects on remount), not data loss. Also merged origin/master (bb72a70..b7859dc) to resolve a real conflict: master's #2017 changed [driveId]/development/layout.tsx's pending-session workspace lookup from a flat workspaces map to selectActiveWorkspace (the new workspace-owns-pane-grid model). Ported that into the shared use-drain-pending-session.ts hook so both layouts pick it up. All targeted tests green (232 tests across 16 files), full monorepo typecheck+build green, lint green (no new warnings).
|
Follow-up convergence pass on this PR — ran an 8-angle automated review (correctness ×3, reuse, simplification, efficiency, altitude, CLAUDE.md conventions) plus resolved a real merge conflict against master (#2017 changed the same layout.tsx this PR refactors). Summary of what changed, pushed in f475d16: Correctness fix (real bug): Merge conflict resolved: master's #2017 (workspace-owns-pane-grid) changed Cleanup (converged on by 2-3 finders independently):
Documented, not fixed (explicitly out of scope for this PR): navigating from the global Development view into a specific drive's Development view crosses route trees and disconnects every warm terminal across every drive, not just the one being left. This mirrors an already-accepted tradeoff elsewhere in the surface (switching drives already tears down the other drive's terminals — "a PTY stream must not outlive its drive context") and is recoverable (reconnects on remount), not data loss. A real fix would mean hoisting one Not changed (considered and deliberately left alone): the per-drive→per-machine All targeted tests green (232+ tests), full monorepo typecheck+build green, lint green. |
Master landed PR #2027 (fix(development): make driveless /dashboard/development a GLOBAL view) after this branch was cut, which: - Extracted useStickyMachineIds/useDrainPendingSession/DetailState/ resolveDisplayedMachine out of [driveId]/development/layout.tsx into shared apps/web/src/lib/development/ hooks, reused by a new apps/web/src/app/dashboard/development/layout.tsx (the driveless GLOBAL twin). - Reworked DevelopmentSidebar into two list bodies (DriveMachineList / GlobalMachineList) sharing one MachineTreeSection, routed via a new buildMachineHref helper. Both touched the exact same seam this branch reworks (the sidebar's click-intent flow and MachineKeepAliveHost wiring), causing real conflicts. Resolved by: - Porting the workspace-id rename (usePendingSessionStore/ resolvePendingSession -> usePendingWorkspaceStore/ resolvePendingWorkspace, openTerminal(scope) -> setActiveWorkspace) into the now-shared apps/web/src/lib/development/ use-drain-pending-session.ts hook, so BOTH the drive-scoped and new global layout pick up the workspace-selector model, not just one. - Keeping master's DriveMachineList/GlobalMachineList split; git's 3-way merge already combined it correctly with WorkspaceLeaves/ WorkspaceNodeExtras in MachineTreeSection (verified by reading the merged output, not just trusting a clean merge). - Adding the `embedded` prop to the new global dashboard/development/layout.tsx's MachineKeepAliveHost call too (master's version predates that prop), so a machine opened from the global Development view also skips its redundant inner Terminal-tab tree. - Updating dashboard/development/__tests__/layout.test.tsx (global layout) to the renamed store/hook and to assert embedded:true, mirroring the drive-scoped layout's test. typecheck clean (apps/web + packages/lib), lint clean, 462 tests green across 34 files in the affected area. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sTw4qnoPwfzC2E4xnZQ78
Summary
Fixes a scope bug in the Development surface (merged in #2015): the driveless
/dashboard/developmentroute redirected to a resolved "current/last" drive, opening a seemingly-random drive instead of showing everything. Per the "Routing correction (2026-07-12)" section of the Development surface spec, it's now a real GLOBAL command center — all machines across all drives the user can access, grouped by drive./dashboard/{driveId}/development(per-drive) is unchanged.packages/lib/services/machines/machine-list: newlistMachinesAcrossDrives, grouping the existing per-drivelistMachinesInDriveresult by drive and dropping drives with no visible machines (avoids empty group headers). Per-drive failures are isolated (Promise.allSettled) so one bad drive can't blank the whole global view.GET /api/machines: a request with nodriveIdnow returns{ drives: [{ driveId, driveName, machines }] }instead of 400 — same app-admin gate, same per-pagecanUserViewPagefiltering as the per-drive path (a machine withheld by a page-level grant never appears in either mode).machine-list-runtime:listAllMachines(), sourcing accessible drives fromlistAccessibleDrives— the same drive universeGET /api/drivesalready handsDriveSwitcher.useDriveMachines: newuseAllMachines(enabled)hook, same SWR/polling convention asuseDriveMachines.DevelopmentSidebar: branches ondriveIdpresence — drive-scoped list unchanged, new grouped-by-drive list in global mode.MachineTreeSectionroutes via a centralizedbuildMachineHref(next toparseSelectedMachineIdindevelopment-route.ts), so global-mode machines route to/dashboard/development/{machineId}rather than crossing into a specific drive's route tree./dashboard/development/layout.tsx+[machineId]/page.tsx: the global twin of the drive-scoped detail region, sourced fromuseAllMachines(). Deliberately its own route tree (not folded into the drive-scoped layout) — Next.js remounts a segment when the route tree changes, so crossing into/dashboard/{driveId}/developmentwould tear down the global layout's keep-alive host and every terminal it's keeping warm. (Known, documented tradeoff: navigating from global into a specific drive's Development view still crosses that boundary and disconnects every warm terminal, not just the one being left — mirrors an already-accepted tradeoff elsewhere in the surface and is recoverable, not data loss.)useStickyMachineIds,useDrainPendingSession,DetailState/DetailNotice, andresolveDisplayedMachineout of the drive-scoped layout intolib/development/so both layouts share identical logic instead of duplicating it.resolveActiveDriveIdredirect helper (and its test).MachineTree/MachineView/Machine-page tabs are untouched, per the sub-task's scope.Merged origin/master to resolve a real conflict: #2017 (workspace-owns-pane-grid) changed the same
[driveId]/development/layout.tsxthis PR refactors (pending-session workspace lookup moved from a flat map toselectActiveWorkspace). Ported that into the shareduse-drain-pending-session.tshook.Convergence pass (8-angle automated review + Codex): fixed the
Promise.all→Promise.allSettledcorrectness bug above, a stale doc comment insidebar-routes.ts, dedupedfetcher/globalFetcherand the sidebar's list-notice guard chains, replaced aGLOBAL_SCOPEmagic string withundefined, and replaced a raw NUL byte Codex flagged inuse-sticky-machine-ids.tswith a proper escape. See PR comments for full detail on what was fixed vs. deliberately left alone (and why).Test plan
packages/lib/services/machines/machine-list.test.ts— grouping, per-page filtering, and per-drive failure isolation forlistMachinesAcrossDrivesapps/web/api/machines/route.test.ts— global-mode branch: admin gate, filtering, empty list, auth propagationapps/web/app/dashboard/development/__tests__/layout.test.tsx— new global layout (flattening, sticky ids, admin gate, detail states)apps/web/app/dashboard/[driveId]/development/__tests__/layout.test.tsx— unchanged behavior, still green after extracting shared hooks + the master mergeDevelopmentSidebar.test.tsx— drive-scoped tests still green + global-mode tests (grouped rendering, routing to/dashboard/development/{machineId}, admin gate, empty/error states)development-route.test.ts— driveless URL prefix + newbuildMachineHref, including a parse/build round-trip testdisplayed-machine.test.ts— new, for the extractedresolveDisplayedMachinebun run typecheck(full monorepo, includingnext build) — greenbun run lint(web + lib) — green, no new warningsapps/webvitest suite — 12,800+ passed; a handful of pre-existing failures are unrelated (DB-role provisioning tests needingbun run test's DB setup, one pre-existing timezone flake)🤖 Generated with Claude Code
https://claude.ai/code/session_01PZNYjLCR2FxEcvxZbsfDqp