feat(escalations): Escalation Mode wedge — live arrival + magic-moment pickup #155

Merged
chihlasm merged 34 commits from feat/escalation-metric-endpoint into main 2026-04-30 21:32:16 +00:00
Showing only changes of commit 8914391336 - Show all commits

View File

@@ -97,7 +97,21 @@ export default function AssistantChatPage() {
const [logContent, setLogContent] = useState('')
const [pendingUploads, setPendingUploads] = useState<PendingUpload[]>([])
const [isDragOver, setIsDragOver] = useState(false)
// Task-lane mount restoration is gated on (a) the persisted chatId
// matching whatever activeChatId resolved to, AND (b) the page not being
// entered with a prefill in location.state. The prefill case means we're
// about to create a brand-new session and discard the previous one's
// task lane anyway — restoring it just causes the previous chat's
// questions/actions to flash on the first paint before sendPrefill's
// resetSessionDerivedState clears them. Same logic for the bell-icon
// pickup flow (?pickup=true): the senior is entering an unrelated
// session and any leftover task-lane meta from their own prior chat is
// noise. Both gates collapse to "are we about to leave the previous
// chat behind?" — if yes, start clean.
const incomingPrefill = !!(location.state as { prefill?: string } | null)?.prefill
const skipTaskLaneRestore = incomingPrefill || isPickup
const [activeQuestions, setActiveQuestions] = useState<QuestionItem[]>(() => {
if (skipTaskLaneRestore) return []
try {
const saved = sessionStorage.getItem('rf-tasklane-meta')
if (saved) { const d = JSON.parse(saved); if (d.chatId === activeChatId) return d.questions || [] }
@@ -105,6 +119,7 @@ export default function AssistantChatPage() {
return []
})
const [activeActions, setActiveActions] = useState<ActionItem[]>(() => {
if (skipTaskLaneRestore) return []
try {
const saved = sessionStorage.getItem('rf-tasklane-meta')
if (saved) { const d = JSON.parse(saved); if (d.chatId === activeChatId) return d.actions || [] }
@@ -112,6 +127,7 @@ export default function AssistantChatPage() {
return []
})
const [showTaskLane, setShowTaskLane] = useState(() => {
if (skipTaskLaneRestore) return false
try {
const saved = sessionStorage.getItem('rf-tasklane-meta')
if (saved) { const d = JSON.parse(saved); return d.show === true && d.chatId === activeChatId }
@@ -479,6 +495,16 @@ export default function AssistantChatPage() {
// Phase 9: tab strip reset
setChatTab('chat')
setScriptBuilderHasProgress(false)
// Belt-and-braces: also wipe the persisted task-lane meta. Without this,
// a remount or page reload before the next AI response can re-hydrate
// the previous session's questions/actions from sessionStorage even
// though the in-memory state has been cleared. The persistence effect
// re-saves on the next state change anyway, so the only window where
// sessionStorage is empty is between this reset and the next response —
// which is exactly the window where stale-tag leakage was happening.
try {
sessionStorage.removeItem('rf-tasklane-meta')
} catch { /* ignore */ }
}, [])
// Phase 2 facts — fetch + handlers. `refreshFacts` is called from selectChat