FlowPilot: /pilot/:id direct URL load shows blank main pane until sidebar click #143

Closed
opened 2026-04-21 05:39:55 +00:00 by chihlasm · 1 comment
Owner

Symptom

Opening a FlowPilot session via direct URL in a fresh tab — either /pilot/<session-id> or the Phase 1 legacy redirect /assistant/<session-id> — the main chat pane renders empty (no messages, no input state) until the user clicks the session in the left sidebar, at which point the conversation loads.

Surfaced during Gate 1 verification of the FlowPilot migration (Phase 1) on 2026-04-21.

Reproduction

  1. Log in to FlowPilot at /pilot.
  2. Open an existing session so the URL becomes /pilot/<id>.
  3. Copy that URL, then open it in a fresh browser tab (do not come from the dashboard dispatcher).
  4. Observe: sidebar populates with the chat list, but the main pane is blank.
  5. Click the session entry in the sidebar — the messages and input now appear.

Also reproducible via /assistant/<id> (same redirect path through AssistantSessionRedirect).

What works correctly (ruled out)

  • The redirect itself fires. AssistantSessionRedirect at frontend/src/router.tsx#L109-L112 correctly <Navigate>s to /pilot/<id> with replace.
  • The backend detail endpoint returns 200: captured GET /api/v1/ai-sessions/<id> HTTP/1.1 200 ~3 seconds after the redirect during a real session.
  • The URL preserves the session ID through the redirect.
  • Auth survives the new-tab transition.

Hypothesis

Render-before-load flash. AssistantChatPage seeds activeChatId from urlSessionId on mount (AssistantChatPage.tsx#L36-L39) and fires selectChat(urlSessionId) via useEffect (L106-L110), but the main pane renders once with messages=[] and activeChatId=<id> in the gap between setActiveChatId(chatId) (synchronous, L227) and the awaited aiSessionsApi.getSession(chatId) resolving (L235). The pane probably has no loading state that covers this window — it just looks empty.

Clicking the sidebar entry re-triggers selectChat, but by then the original promise has already resolved — what actually fixed the UI is a re-render, not the second fetch.

Proposed fixes

  1. Add a loading skeleton / spinner in AssistantChatPage's main pane while the initial selectChat promise is in flight. Gate on something like isLoadingSession set to true at the top of selectChat and cleared in the finally block.
  2. Verify the sidebar entry for activeChatId highlights as "active" immediately when activeChatId is URL-seeded. If it doesn't, the user perceives the session as unselected, which is the other half of the confusing UX.

Low priority — no data loss, no broken state, just polish — but it's the kind of thing that makes the direct-URL path feel broken to anyone who hits it cold.

Context

Branch: feat/flowpilot-migration (Phase 1 merged the /pilot route unification).
Related handoff doc (now deleted): docs/FlowAssist_Migration/MIGRATION-HANDOFF.md §Gate 1 item 5.

## Symptom Opening a FlowPilot session via direct URL in a fresh tab — either `/pilot/<session-id>` or the Phase 1 legacy redirect `/assistant/<session-id>` — the main chat pane renders empty (no messages, no input state) until the user clicks the session in the left sidebar, at which point the conversation loads. Surfaced during Gate 1 verification of the FlowPilot migration (Phase 1) on 2026-04-21. ## Reproduction 1. Log in to FlowPilot at `/pilot`. 2. Open an existing session so the URL becomes `/pilot/<id>`. 3. Copy that URL, then open it in a **fresh browser tab** (do not come from the dashboard dispatcher). 4. Observe: sidebar populates with the chat list, but the main pane is blank. 5. Click the session entry in the sidebar — the messages and input now appear. Also reproducible via `/assistant/<id>` (same redirect path through `AssistantSessionRedirect`). ## What works correctly (ruled out) - The redirect itself fires. `AssistantSessionRedirect` at [frontend/src/router.tsx#L109-L112](frontend/src/router.tsx#L109-L112) correctly `<Navigate>`s to `/pilot/<id>` with `replace`. - The backend detail endpoint returns 200: captured `GET /api/v1/ai-sessions/<id> HTTP/1.1 200` ~3 seconds after the redirect during a real session. - The URL preserves the session ID through the redirect. - Auth survives the new-tab transition. ## Hypothesis Render-before-load flash. `AssistantChatPage` seeds `activeChatId` from `urlSessionId` on mount ([AssistantChatPage.tsx#L36-L39](frontend/src/pages/AssistantChatPage.tsx#L36-L39)) and fires `selectChat(urlSessionId)` via `useEffect` ([L106-L110](frontend/src/pages/AssistantChatPage.tsx#L106-L110)), but the main pane renders once with `messages=[]` and `activeChatId=<id>` in the gap between `setActiveChatId(chatId)` (synchronous, L227) and the awaited `aiSessionsApi.getSession(chatId)` resolving (L235). The pane probably has no loading state that covers this window — it just looks empty. Clicking the sidebar entry re-triggers `selectChat`, but by then the original promise has already resolved — what actually fixed the UI is a re-render, not the second fetch. ## Proposed fixes 1. Add a loading skeleton / spinner in `AssistantChatPage`'s main pane while the initial `selectChat` promise is in flight. Gate on something like `isLoadingSession` set to `true` at the top of `selectChat` and cleared in the finally block. 2. Verify the sidebar entry for `activeChatId` highlights as "active" immediately when `activeChatId` is URL-seeded. If it doesn't, the user perceives the session as unselected, which is the other half of the confusing UX. Low priority — no data loss, no broken state, just polish — but it's the kind of thing that makes the direct-URL path feel broken to anyone who hits it cold. ## Context Branch: `feat/flowpilot-migration` (Phase 1 merged the `/pilot` route unification). Related handoff doc (now deleted): `docs/FlowAssist_Migration/MIGRATION-HANDOFF.md` §Gate 1 item 5.
Author
Owner

Closing as duplicate of #142 (filed 14 seconds earlier with identical body). Tracking the bug in #142.

Closing as duplicate of #142 (filed 14 seconds earlier with identical body). Tracking the bug in #142.
Sign in to join this conversation.