Files
kandev/docs/session-page-data-flow.md
T
2026-01-17 19:02:22 +00:00

7.6 KiB

Task Session Page Data Flow (SSR + WS + Store)

This document maps the current data flow for /task/:id/:sessionId and highlights which components/hooks depend on which data. It also calls out places where hooks could be made more granular or shared.

SSR Entry Point

Route: apps/web/app/task/[id]/[sessionId]/page.tsx

Server fetches:

  • fetchTask(taskId)
  • fetchBoardSnapshot(task.board_id)
  • listAgents()
  • listRepositories(task.workspace_id)
  • listTaskSessions(task.id) for each task in board
  • listTaskSessionMessages(sessionId) (best-effort)

Hydration:

  • snapshotToState(snapshot) → kanban columns + tasks
  • taskToState(task, sessionId, messages?) → tasks.activeTaskId, tasks.activeSessionId, messages.bySession[sessionId]
  • Repositories + agent profiles are merged into store
  • Render <StateHydrator initialState={...} /> and <TaskPageClient ... />

Client Composition

TaskSessionPage (SSR)
  └─ TaskPageClient
      ├─ TaskTopBar
      └─ TaskLayout
          ├─ TaskSessionSidebar (left)
          ├─ TaskCenterPanel (chat/changes/notes)
          └─ TaskRightPanel (files + terminal)

Active Selection (Store)

Store slice: tasks.activeTaskId, tasks.activeSessionId

  • Set on initial mount by TaskPageClient from URL + SSR task.
  • Updated when selecting a task/session in sidebar.

WebSocket Data Flow

WebSocket client:

  • apps/web/lib/ws/client.ts with deduplicated subscriptions.
  • Components call client.subscribe(taskId); dedup ensures single WS subscription.
  • On reconnect, re-subscribes to active task channels.

Handlers (global router):

  • message.added / message.updated → messages.bySession[sessionId]
  • task_session.state_changed → taskSessionStatesByTaskId + taskSessions.items
  • git.status → gitStatus (keyed by task)
  • shell.output → shell.outputs[taskId]

Component Data Dependencies

TaskPageClient (apps/web/app/task/[id]/page-client.tsx)

  • Props: task, sessionId, initialSessionsByTask, initialRepositories, initialAgentProfiles
  • Store:
    • tasks.activeTaskId, tasks.activeSessionId via setActiveSession
    • kanban.tasks, kanban.columns, workspaces.items
    • agentProfiles.items
  • Hooks:
    • useSessionResumption(task?.id, sessionId)
    • useTaskAgent(task) (agent state, task.execution polling)
    • useRepositories(workspaceId) (fallback to SSR data)
  • WS:
    • Subscribes to all tasks in board (deduped)
    • Listens for task_session.state_changed to refresh sessionsByTask
  • Responsibilities:
    • URL sync via history.replaceState
    • Task selection triggers fetchTask + listTaskSessions

TaskSessionSidebar (apps/web/components/task/task-session-sidebar.tsx)

  • Store:
    • tasks.activeTaskId
    • tasks.activeSessionId
  • Props:
    • tasks + columns + sessionsByTask from TaskPageClient
  • Uses:
    • TaskSessionSwitcher (sessions list)
    • TaskSwitcher (tasks list)
  • Calls back to parent for selection and session list reload.

TaskCenterPanel (apps/web/components/task/task-center-panel.tsx)

  • Props: taskId, onSendMessage, selectedDiffPath, openFileRequest
  • Includes:
    • TaskChatPanel
    • TaskChangesPanel
    • FileViewerContent

TaskChatPanel (apps/web/components/task/task-chat-panel.tsx)

  • Store:
    • tasks.activeSessionId (fallback unless sessionId prop passed)
  • Hooks:
    • useTaskSession(sessionId) → taskId + session details
    • useTaskMessages(taskId, sessionId) → messages + meta
    • useLazyLoadMessages(sessionId) → pagination
  • Data:
    • Renders from messages.bySession[sessionId]

TaskRightPanel (apps/web/components/task/task-right-panel.tsx)

  • Store:
    • tasks.activeSessionId
  • Uses:
    • ShellTerminal taskId + activeSessionId

ShellTerminal (apps/web/components/task/shell-terminal.tsx)

  • Store:
    • shell.outputs[taskId]
    • taskSessions.items[sessionId]?.state or taskSessionStatesByTaskId[taskId]
  • WS:
    • Subscribes to shell only when session is RUNNING or WAITING_FOR_INPUT.

TaskFilesPanel / TaskChangesPanel

  • Store:
    • gitStatus keyed by active task
  • No direct WS subscriptions.

ASCII Diagram (Current)

SSR
┌──────────────────────────────────────────────────────────────┐
│ /task/:id/:sessionId                                          │
│  fetchTask + snapshot + agents + repos + sessions + messages  │
│  StateHydrator -> store (activeTaskId + activeSessionId)      │
└──────────────────────────────────────────────────────────────┘

Client
┌─────────────────────────────── TaskPageClient ──────────────────────────────┐
│ setActiveSession(taskId, sessionId)                                          │
│ subscribe(board tasks)  + listen task_session.state_changed                  │
│                                                                              │
│  ┌─ TaskSessionSidebar (sessions + tasks)                                    │
│  ├─ TaskCenterPanel                                                         │
│  │   └─ TaskChatPanel                                                       │
│  │       ├─ useTaskSession(sessionId)                                       │
│  │       ├─ useTaskMessages(taskId, sessionId)                              │
│  │       └─ useLazyLoadMessages(sessionId)                                  │
│  └─ TaskRightPanel                                                          │
│      └─ ShellTerminal (shell.output + session state)                         │
└──────────────────────────────────────────────────────────────────────────────┘

WS
  task.subscribe -> deduped in client
  message.added/updated -> messages.bySession
  task_session.state_changed -> taskSessions + taskSessionStatesByTaskId
  git.status -> gitStatus (task)
  shell.output -> shell.outputs[task]

Hooks Review (Granularity + Reuse)

Current hooks:

  • useTaskMessages(taskId, sessionId)
    • Fetches + sets loading meta + subscribes to task updates.
    • Could be split into:
      • useSessionMessages(sessionId) (only fetch + store)
      • useTaskSubscription(taskId) (subscription only)
  • useLazyLoadMessages(sessionId) uses store meta; OK as a focused pagination hook.
  • useTaskSession(sessionId) derives taskId from messages if session not in store.
    • Can be simplified once session list is guaranteed in store.
  • useTaskAgent(task) + useSessionResumption(taskId, sessionId)
    • Both access execution state. Consider a combined useTaskExecution(taskId, sessionId) hook.
  • useRepositories(workspaceId) currently handles fetch + loading; OK but could be generalized (e.g. useListResource).

Suggested split for sharing:

  • useTaskSubscription(taskId) → handles client.subscribe(taskId) only.
  • useSessionMessages(sessionId) → fetch + store; no taskId.
  • useActiveSession() selector → centralizes active session usage.
  • useTaskGitStatus(taskId) → pure selector + optional subscribe.
  • useShellOutput(taskId) → pure selector + optional subscribe.

This would allow panels to mix and match only what they need without pulling in extra behavior.