fix: stale chunk auto-reload + image paste upload UX

- Add lazyWithRetry wrapper for all lazy-loaded routes to auto-reload
  on stale chunk errors after deploys (prevents ErrorBoundary flash)
- Show toast notification when image paste/upload fails due to storage
  not configured (503), instead of silent tiny error thumbnails
- Remove failed uploads from thumbnail strip on 503 (was showing
  confusing retry icon)
- Pass completed upload IDs in navigation state from dashboard input
- Suppress Sentry dialog for chunk load errors (deploy artifacts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-24 04:21:41 +00:00
parent 65eb630254
commit 48f2b3faaf
7 changed files with 137 additions and 76 deletions

View File

@@ -70,7 +70,14 @@ export function ErrorBoundary({ children, fallback }: Props) {
if (fallback) return fallback as React.ReactElement
return <DefaultFallback error={error as Error} resetError={resetError} />
}}
showDialog
beforeCapture={(scope, error) => {
// Don't report chunk load errors to Sentry — they're deploy artifacts, not bugs
if (error && isChunkLoadError(error as Error)) {
scope.setLevel('info')
scope.setTag('chunk_load_error', 'true')
}
}}
showDialog={false}
>
{children}
</Sentry.ErrorBoundary>

View File

@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { Send, Paperclip, Terminal, Loader2, X, RotateCcw, ImagePlus } from 'lucide-react'
import { cn } from '@/lib/utils'
import { uploadsApi } from '@/api/uploads'
import { toast } from '@/lib/toast'
import type { PendingUpload } from '@/types/upload'
const SUGGESTIONS = [
@@ -44,6 +45,12 @@ export function StartSessionInput() {
if (logContent.trim()) {
state.logs = logContent.trim()
}
const completedUploadIds = pendingUploads
.filter((u) => u.status === 'done' && u.result?.id)
.map((u) => u.result!.id)
if (completedUploadIds.length > 0) {
state.uploadIds = completedUploadIds
}
navigate('/assistant', { state })
}
@@ -81,12 +88,16 @@ export function StartSessionInput() {
)
})
.catch((err) => {
const errorMsg = err?.response?.status === 503
const is503 = err?.response?.status === 503
const errorMsg = is503
? 'File uploads not available'
: err?.message || 'Upload failed'
setPendingUploads((prev) =>
prev.map((u) => u.id === upload.id ? { ...u, status: 'error' as const, error: errorMsg } : u)
)
if (is503) {
toast.warning('Image attachments are not available yet — describe the issue in text instead')
} else {
toast.error(`Upload failed: ${errorMsg}`)
}
setPendingUploads((prev) => prev.filter((u) => u.id !== upload.id))
})
})
}, [])

View File

@@ -2,6 +2,7 @@ import { useState, useRef, useCallback, useEffect } from 'react'
import { Send, Paperclip, Terminal, Loader2, X, RotateCcw, ImagePlus } from 'lucide-react'
import { cn } from '@/lib/utils'
import { uploadsApi } from '@/api/uploads'
import { toast } from '@/lib/toast'
import type { StepResponseRequest } from '@/types/ai-session'
import type { PendingUpload } from '@/types/upload'
@@ -81,12 +82,13 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
)
})
.catch((err) => {
const errorMsg = err?.response?.status === 503
? 'File uploads not available'
: err?.message || 'Upload failed'
setPendingUploads((prev) =>
prev.map((u) => u.id === upload.id ? { ...u, status: 'error' as const, error: errorMsg } : u)
)
const is503 = err?.response?.status === 503
if (is503) {
toast.warning('Image attachments are not available yet — describe the issue in text instead')
} else {
toast.error(`Upload failed: ${err?.message || 'Unknown error'}`)
}
setPendingUploads((prev) => prev.filter((u) => u.id !== upload.id))
})
})
}, [])

View File

@@ -1,4 +1,5 @@
import { lazy, Suspense } from 'react'
import { Suspense } from 'react'
import { lazyWithRetry } from '@/lib/lazyWithRetry'
import { TreePreviewPanel } from '@/components/tree-preview/TreePreviewPanel'
import { FlowCanvas } from './FlowCanvas'
import { NodeEditorPanel } from './NodeEditorPanel'
@@ -8,7 +9,7 @@ import { cn } from '@/lib/utils'
import { Spinner } from '@/components/common/Spinner'
// Lazy load CodeModeEditor (Monaco is ~2MB)
const CodeModeEditor = lazy(() =>
const CodeModeEditor = lazyWithRetry(() =>
import('./code-mode/CodeModeEditor').then(m => ({ default: m.CodeModeEditor }))
)