lablup / lablup/backend.ai-webui

Router-level preloaded queries — session list pilot (wayfinder map)

Open
#8,982 0 comments 0 reactions 0 assignees View on GitHub
wayfinder:map
Dominant language
TypeScript
Stars
133
Forks
81
Avg merge
1d 12h
Merged PRs (30d)
355

Description

## Destination

`ComputeSessionListPage` is **merged to production** fetching its first data through a router-level preloaded query instead of `useLazyLoadQuery`, with a measured before/after in the PR — and the pattern it settles is written down as the convention the remaining ~166 `useLazyLoadQuery` files will follow.

> **Body sync note.** The Jira description is the source of truth: a webhook mirrors it onto GitHub #8982 and overwrites edits made there. Edit this description, not the GitHub body.

## Notes

- **This map carries execution** (overriding wayfinder's plan-only default): the destination is a merged PR, so the prototype and ship tickets do change code. Every other ticket still resolves a decision, not a slice of the build.
- Navigation UX is **fixed**: clicking a nav item must show the new page shell immediately with a skeleton, exactly as today.
- Skills each session should consult: `/grilling`, `/domain-modeling`, `/research`, `/prototype`; `fw:stacked-pr-workflow` + `fw:pr-ready-gate` for the ship ticket; `fw:jira-workflow` for ticket hygiene.
- Sources of truth: `react/src/routes.tsx`, `react/src/App.tsx`, `react/src/RelayEnvironment.ts`, `react/src/pages/ComputeSessionListPage.tsx`, `react/src/hooks/useCurrentProject.tsx`, `react/src/helper/loginSessionAuth.ts`, `pnpm-workspace.yaml`.
- Ticket workflow: claim by assigning yourself **before** any work; resolve with a resolution comment; close; append a one-line gist to Decisions so far here.

## Status (2026-08-23)

Charted 2026-08-23. Both research tickets resolved the same day via a 23-agent investigate → adversarially-refute → synthesize → critique run (4 of 10 load-bearing claims refuted, all high confidence). **Frontier: FR-3637 (#8983) and FR-3641 (#8987).**

## Decisions so far

(from the charting grilling session)

- **Destination is a production merge** of the session list page, not a spec handoff. Rolling out to the rest is a follow-on effort.
- **Navigation UX stays "new page shell immediately + skeleton"**, so React Router's default blocking loader is out.
- **react-router v7 conditionally in scope**, necessity verified first.
- **No performance target asserted up front** — measure the baseline, then set it.

(from resolved tickets)

- [**FR-3638**](https://lablup.atlassian.net/browse/FR-3638) **— v7 is irrelevant to this goal.** Every mechanism that issues a request before a navigation commits (``, `PrefetchPageLinks`, `clientLoader`) is Framework-mode-only and unreachable from `createBrowserRouter` — in Data mode `` typechecks, renders, and silently does nothing. Everything reachable in Data mode already ships in 6.30.4. **The pilot builds on 6.30.4.**
- [**FR-3639**](https://lablup.atlassian.net/browse/FR-3639) **— the shape survives, the timing premise does not.** `loadQuery()` is synchronous, but a route with a loader commits at **+17 microtasks** vs **+0** with no loader, and an `async` loader commits at the same 17 — the router awaits the result regardless, so the synchronicity buys nothing at the router level. Warm-path saving is **one React render pass, zero frames**. `useQueryLoader(query, loaderRef)` adopts and re-adopts cleanly (the bridge works); a bare `usePreloadedQuery` leaks a permanent store root per variables tuple; EntryPoints are no-go (the installed compiler panics on `@preloadable` without `persistConfig`). Ships with C1–C7 conditions and a 15-trap reviewer checklist.
- **The loader can build its variables synchronously** — `globalThis.backendaiclient.groupIds` is a login-time name→UUID map (`loginSessionAuth.ts:131-137`), so no jotai read and no stale-project window.
- `v7_partialHydration` is a 6.30.4 flag and becomes the governing one. With it off, a loader that awaits anything blanks the whole app on deep-link/refresh (`react-router-dom/dist/index.js:590`). With it on plus a route `HydrateFallback`, that becomes a page-level skeleton. Moved into FR-3640's decision set.
- `v7_startTransition` is recommended against, not an upside: FR-3638 measured it holding stale content on project switch where v6 shows the skeleton.

## Measured at charting (facts the tickets rest on)

- `react-router-dom` **6.30.4** via the pnpm catalog; 67 files import it, 23–24 distinct symbols, all present in v7. nuqs **2.9.5**, React **19.2.8**, react-relay/relay-runtime **20.1.1**.
- **167 files / 360 occurrences** of `useLazyLoadQuery` across `react/src` + `packages/backend.ai-ui/src`.
- **Preloading prior art exists, but component-level, not route-level**: `AdminUserManagement`, `AdminUserCredentialList`, `AdminDeployment`, `ScopedAuditLog`, `VFolderDeployModal`.
- `RelayEnvironment.ts` gates on the client: awaits `globalThis.backendaiclient` via `backend-ai-connected`, so nothing leaves before client connect on a cold boot. In-app navigation is unaffected.
- `ComputeSessionListPage` is eagerly imported (`routes.tsx:36-37`), so the lazy-chunk waterfall does not apply to the pilot. `grep -cE '\bloader:' routes.tsx` = 0 — this map introduces the app's first route loader.
- **No baseline measurement exists** — `reportWebVitals.ts` is present but unused.

## Open question the map now carries

FR-3639's own verdict is that as a **latency** optimization the shape does not pay for itself on the warm path, and that the honest case for it is architectural (route-owned data contracts; hover-intent warming later via `useFetcher().load()`, which also needs no v7). Three suspending call sites sit **above** the pilot's Suspense boundary — `ProjectScopeLayout` → `useAccessibleProjects`, `RouteAccessBreadcrumbGate` → `useCurrentUserProjectRoles`, and `useSuspendedBackendaiClient` — so the **cold** path may pay an ancestor round-trip where the warm path pays one render pass. That is unmeasured. FR-3637 (#8983) now measures warm and cold separately and is the map's go/no-go. **"Do not do this" is a legitimate outcome**, and FR-3640 is instructed not to dodge it.

## Not yet specified

- How the page's inner Suspense boundaries (session table / resource grid, the five per-type count fields) participate in the preload — FR-3639 established the top-level contract only.
- Whether `groupIds` contains MODEL_STORE projects (`group.list(true, false, …)`), and whether a miss should fail loud or fall back silently — narrowed to a yes/no on FR-3641.
- Whether the E2E suite depends on the skeleton appearing: FR-3639 trap 10 says a won race renders with data and no suspense. `grep -rn skeleton e2e` returns 7 hits, none checked against the session-list path.
- Relay 20 × react-router v7 interaction with the page's `useDeferredValue` refetch pattern — unanswered by FR-3638, inherited by the standalone v7 effort rather than this map.
- Whether the shape should become a shared helper (`defineRelayLoader()` or a lint rule) before it is copy-pasted — traps 1–3 are silent-failure modes. Only relevant if the pilot goes ahead.

## Out of scope

- **The react-router v7 migration** — FR-3638 verdict: irrelevant to this goal, separable in both directions. Priced, not dropped: PR A (six `v7_*` flags on 6.30.x, 1 hunk) + PR B (catalog bump, ~1 line + lockfile) ≈ 1 day; PR C (the `react-router-dom` → `react-router` rename that buys the v8 path, 73 lines / 68 files + 5 traps) 1–2 days. **Caveat**: PR A is not neutral toward this map — it would turn on `v7_partialHydration` underneath the pilot. Sequence it after FR-3640, or exclude that flag from it.
- **React Router framework mode** — at any price. It rewrites the `projectRootStaticPlugin` / root-template pipeline, collides with StyleX / VitePWA / node-polyfills / CSP-nonce injection, and because SSR is impossible here would still yield module-only prefetch that never touches Relay data.
- Rolling the pattern out to the remaining ~166 `useLazyLoadQuery` files — a follow-on effort once this pilot fixes the direction.
- Shortening cold boot itself (login → first page). The Relay network layer gating on `backend-ai-connected` is a bigger structural question than this map.
- Route chunk-splitting (`React.lazy`) waterfalls — the pilot page is eagerly imported, so nothing about it can be measured here.

JIRA Issue: FR-3636

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the measured decisions in FR-3637 (#8983) and FR-3641 (#8987), then read react/src/routes.tsx, react/src/App.tsx, react/src/RelayEnvironment.ts, and react/src/pages/ComputeSessionListPage.tsx. Run the warm and cold measurements and resolve the listed preload, skeleton, and groupIds questions; done means a documented go/no-go decision or a merged ComputeSessionListPage pilot with its convention recorded.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.