fix(pilot): wipe full task-lane state on chat switch + extract palette event
All checks were successful
Mirror to GitHub / mirror (push) Successful in 10s

Two fixes from the Phase 5 shakedown:

1. Stale lane data leaking across chats. handleNewChat, sendPrefill, and
   handleResumeNew were each missed when Phase 3/5 added activeFix,
   previewKind, previewData, and scriptPanelOpen — only selectChat reset
   the full set. Result: starting a new chat while a Suggested Fix card
   was active showed the previous session's fix card (and any open
   preview/script panel) until the next backend refresh swept it.
   Consolidated all four entry points behind a single
   resetSessionDerivedState() helper so adding new lane state in future
   phases only requires touching one place.

2. CommandPalette TDZ on cold load. SCRIPTS_INLINE_QUICK_ACTION (line 66)
   referenced PILOT_INLINE_SCRIPT_PATH declared at line 94 — module-level
   evaluation hit the use before the declaration. Browser blanked with
   "Cannot access 'PILOT_INLINE_SCRIPT_PATH' before initialization".
   Moved the path const above its first use; also extracted
   PILOT_INLINE_SCRIPT_EVENT into a tiny @/lib/pilotEvents module so
   AssistantChatPage doesn't import the palette component just to read a
   string — that mixed-export pattern broke Fast Refresh ("consistent
   components exports") and added an unnecessary import edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 01:30:18 -04:00
parent fa61376303
commit ce7c8ac3d5
3 changed files with 45 additions and 30 deletions

View File

@@ -18,7 +18,7 @@ import { SuggestedFix } from '@/components/pilot/sections/SuggestedFix'
import { ResolutionNotePreview as ResolutionNotePreviewPopover } from '@/components/pilot/ResolutionNotePreview'
import { TemplateMatchPanel } from '@/components/pilot/script/TemplateMatchPanel'
import { NoTemplateDialog } from '@/components/pilot/script/NoTemplateDialog'
import { PILOT_INLINE_SCRIPT_EVENT } from '@/components/layout/CommandPalette'
import { PILOT_INLINE_SCRIPT_EVENT } from '@/lib/pilotEvents'
import { sessionFactsApi, type SessionFact } from '@/api/sessionFacts'
import {
sessionSuggestedFixesApi,
@@ -163,9 +163,7 @@ export default function AssistantChatPage() {
const sendPrefill = async () => {
// Clear stale task lane from previous session
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
resetSessionDerivedState()
setActiveSessionStatus('active')
setActivePsaTicketId(null)
@@ -274,6 +272,24 @@ export default function AssistantChatPage() {
}
}
// Single source of truth for "wipe every per-session task-lane state field"
// before switching to a different chat. Called from selectChat, handleNewChat,
// sendPrefill, and handleResumeNew so adding new lane-scoped state in future
// phases only requires touching this one helper. Forgetting to clear a field
// leaks the previous session's data into the new one (Phase 5 regression).
const resetSessionDerivedState = useCallback(() => {
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
setFacts([])
setActiveFix(null)
setPreviewKind(null)
setPreviewData(null)
setPreviewError(null)
setPreviewPosting(false)
setScriptPanelOpen(false)
}, [])
// Phase 2 facts — fetch + handlers. `refreshFacts` is called from selectChat
// and after each chat send, because the AI may have emitted [PROMOTE] markers
// that synthesized new facts server-side (see unified_chat_service.
@@ -503,17 +519,9 @@ export default function AssistantChatPage() {
currentChatRef.current = chatId
setActiveChatId(chatId)
// Clear TaskLane when switching chats — will restore from backend if available
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
resetSessionDerivedState()
setActiveSessionStatus(null)
setActivePsaTicketId(null)
setFacts([])
setActiveFix(null)
setPreviewData(null)
setPreviewError(null)
setPreviewKind(null)
setScriptPanelOpen(false)
// Fire facts + active-fix fetches in parallel with session detail.
refreshSessionDerived(chatId)
try {
@@ -558,12 +566,8 @@ export default function AssistantChatPage() {
// for the previous session sees a mismatch and bails — prevents stale task lane appearing
// in the new empty session (same pattern as selectChat, which sets ref before its await).
currentChatRef.current = null
// Clear stale state immediately — don't wait for API to return
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
setFacts([])
setScriptPanelOpen(false)
// Clear stale state immediately — don't wait for API to return.
resetSessionDerivedState()
setMessages([])
setActiveSessionStatus('active')
setActivePsaTicketId(null)
@@ -763,10 +767,8 @@ export default function AssistantChatPage() {
const handleResumeNew = async (summary: string) => {
// Invalidate currentChatRef BEFORE the await — same guard as handleNewChat
currentChatRef.current = null
// Clear stale state immediately — don't wait for API to return
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
// Clear stale state immediately — don't wait for API to return.
resetSessionDerivedState()
setActiveSessionStatus('active')
setActivePsaTicketId(null)
try {