-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Summary
Currently, AnalysisContext.tsx uses a single isAnalyzingRef boolean + a single AbortController that globally blocks any second analysis from starting while one is in-flight. This forces users to wait for one session's analysis to complete before triggering another.
The server (Hono) already handles concurrent requests fine. This is a client-side guard that was appropriate when the context was designed for a single panel, but is now a friction point as users browse multiple sessions.
Goal: Replace the single-analysis boolean guard with per-session concurrent analysis support, so users can trigger analysis on multiple sessions simultaneously.
Current Behavior
AnalysisContext.tsx state (CURRENT):
state: AnalysisState ← single slot (one session at a time)
isAnalyzingRef: boolean ← global lock
abortControllerRef: AbortController | null ← single cancel handle
Consequence: starting analysis on session B while session A is in-flight
shows "Analysis already in progress. Please wait or cancel it first."
All consumer components (AnalyzeButton, AnalyzePromptQualityButton, AnalyzeDropdown) have an isAnalyzingOther branch that renders a disabled "Analysis in progress... Waiting for X" state.
Target Behavior
AnalysisContext.tsx state (TARGET):
analyses: Map<string, { state: PerSessionAnalysisState, controller: AbortController }>
key = `${sessionId}:${type}` ← "session-abc:session" or "session-abc:prompt_quality"
startAnalysis(session, type) → adds entry to Map, streams independently
cancelAnalysis(sessionId, type) → aborts + removes that entry
getState(sessionId, type) → returns per-session state (or null = idle)
Files to Change
| File | Change |
|---|---|
dashboard/src/components/analysis/AnalysisContext.tsx |
Core refactor — Map-based state, updated context API |
dashboard/src/components/analysis/AnalyzeButton.tsx |
Remove isAnalyzingOther branch; pass cancelAnalysis(session.id, 'session') |
dashboard/src/components/analysis/AnalyzePromptQualityButton.tsx |
Remove isAnalyzingOtherSession + isAnalyzingThisSessionOtherType; per-session cancel |
dashboard/src/components/analysis/AnalyzeDropdown.tsx |
Remove isAnalyzingOther branch + "Waiting for X" copy |
dashboard/src/components/sessions/SessionDetailPanel.tsx |
Update isAnalyzingThisSession query to use per-session getter |
No server changes needed. Dashboard-only.
Acceptance Criteria
- Triggering analysis on session A while session B is already analyzing starts both concurrently (no block, no toast warning)
- Each in-flight analysis renders its own loading state + Cancel button independently
- Cancelling session A's analysis does not affect session B's analysis
- Per-session progress (phase, chunk/total) is shown independently per session
-
isAnalyzingOther/ "Waiting for X" disabled states are removed from all three button components - Toast strategy: each analysis uses its own toast ID (e.g.
analysis-${sessionId}-${type}) so toasts do not clobber each other - Batch backfill (
reflect backfill) is NOT impacted — it hits the server API directly and stays sequential -
pnpm buildfrom repo root passes with zero errors - TypeScript strict — no
any, no implicit casts
Context API Shape (suggested, UX Engineer review may refine)
interface AnalysisContextValue {
// Get state for a specific (sessionId, type) pair — null means idle
getAnalysisState: (sessionId: string, type: 'session' | 'prompt_quality') => PerSessionAnalysisState | null;
// Check if any analysis is in-flight (useful for global UI indicators if needed)
hasActiveAnalysis: boolean;
startAnalysis: (session: Session, type: 'session' | 'prompt_quality') => Promise<void>;
cancelAnalysis: (sessionId: string, type: 'session' | 'prompt_quality') => void;
clearResult: (sessionId: string, type: 'session' | 'prompt_quality') => void;
}The PerSessionAnalysisState type mirrors the existing AnalysisState shape but without the top-level sessionId/sessionTitle (those become the Map key context).
Verification Tags
- VISUAL: YES — per-session progress indicators, cancel buttons, removed blocked states
- OUTPUT_ARTIFACT: NO
- NEW_DEPS: NO — pure React state refactor
- API_CHANGE: NO — dashboard-only
T-Shirt Size
M — 5 files, all within dashboard/src/. No new packages. No schema changes. No server changes. State management refactor with consumer updates.
Implementation Notes
- Use
useCallbackwith stable Map ref (viauseRef) rather than putting the Map in useState — Map mutations shouldn't trigger re-renders; only the derived state snapshots should - A
useState<Map<string, PerSessionAnalysisState>>(cloned on update) is the simpler React-idiomatic approach if the above creates complexity — pick whichever the UX review validates - The key
${sessionId}:${type}uniquely identifies an in-flight analysis (session A can run session analysis AND prompt_quality analysis simultaneously) - Existing ANALYSIS_TOAST_ID constant should be replaced with per-analysis toast IDs
- The
clearResultfunction should accept(sessionId, type)to clear a specific entry