Repository navigation
[Bug]: React #185 crash opening thread, no floating preview, reload ineffective #15120
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Thanks for the detailed report and for checking #14959 first, @MonkeyMed! Even though you didn't float a preview yourself, this looks like the same crash as #14959. Nightly
0.0.46-nightly.20261003.2623doesn't have a second trigger for it.Same crash site
Your minified frames match the pair already mapped on
.2610.ThreadDetailsCard's layout effect callsreportDetailsCard, andChatCanvasstores that rect withsetDetailsCard. Your offsets (_chat2:135935/51:62309) differ by only a few dozen bytes from that mapping. Those call sites are unchanged on currentmain(263097a8da).Why a floating preview is still involved
The update loop can only run away while a floating preview is in the canvas state. The details card position is only read inside the preview branch, and a portrait player next to the inline card flips between two positions until React stops with error #185:
t3code/apps/web/src/components/chat/chatCanvasLayout.ts
Lines 46 to 81 in 263097a
if (preview && container.width > 0 && container.height > 0) { frame = resolvePreviewMiniPlayerFrame({ ...preview, container }); // Dragging stops at a readable chat lane on the left. Resizing can still // consume that space when the container requires message overlap. const minimumPreviewX = preview.lastInteraction === "resize" ? GAP : padding + minChatWidth + GAP; // New players start beside the composer, with the workspace card above them. if (preview.position === null) frame = { ...frame, y: container.height - frame.height - GAP }; if (preview.lastInteraction === "resize" && composerHeight > 0) { // Lift the preview while chat uses its remaining shrink room, reaching // the composer's top before the preview enters the readable chat lane. const laneWidth = Math.min(normalWidth, minChatWidth); const transitionWidth = Math.max(GAP, normalWidth - laneWidth); const lift = Math.min( 1, Math.max(0, (padding + normalWidth + GAP - frame.x) / transitionWidth), ); if (lift > 0) { frame = resolvePreviewMiniPlayerFrame({ width: frame.width, position: frame, source: preview.source, container: { ...container, height: Math.max(GAP * 2 + 1, container.height - composerHeight * lift), }, }); } } const preferredFrame = { ...frame, ...clampPreviewMiniPlayerPosition(frame, container, frame, undefined, minimumPreviewX), }; // A resize keeps its anchored edge and consumes card height first. A drag // clears the full card whenever it can, so moving alone never folds it. const cardObstacle = preview.lastInteraction === "resize" ? null : detailsCard;
t3code/apps/web/src/components/chat/ChatCanvas.tsx
Lines 27 to 35 in 263097a
const reportDetailsCard = useCallback((next: PreviewMiniPlayerObstacles["detailsCard"]) => { setDetailsCard((current) => current?.left === next?.left && current?.right === next?.right && current?.bottom === next?.bottom ? current : next, ); }, []);
t3code/apps/web/src/components/chat/ThreadDetailsCard.tsx
Lines 65 to 71 in 263097a
useLayoutEffect(() => { reportDetailsCard?.( inlineOpen && cardLeft !== undefined && cardRight !== undefined && cardBottom !== undefined ? { left: cardLeft, right: cardRight, bottom: cardBottom } : null, ); }, [reportDetailsCard, inlineOpen, cardLeft, cardRight, cardBottom]); The player doesn't have to be opened by hand, and it often never shows on screen. The loop is a nested
useLayoutEffect, so the crash screen replaces everything before that frame is painted.Auto-show floating preview is on by default. It opens a player when a device session shows up after the first snapshot, including when the device row arrives late, or when an agent opens a browser preview. An Android device is the portrait shape that fails to settle, and this thread is about Android testing.
t3code/apps/web/src/components/ChatView.tsx
Lines 5061 to 5098 in 263097a
// A device the agent opens floats over chat like an agent-driven browser, // or becomes a panel tab when floating previews are off. Sessions opened by // another client arrive the same way; sheet layouts get neither. The first // snapshot is a baseline: persisted tabs restore themselves, and existing // sessions must not resurrect closed tabs. A session whose device summary // has not arrived yet stays out of the baseline so a later snapshot opens it. const autoShowFloatingPreview = useClientSettings(selectAutoShowFloatingPreview); const previousDeviceSessions = useRef(new Map<string, Set<string>>()); useEffect(() => { if (!activeThreadRef || !deviceStateLoaded) return; const threadKey = `${activeThreadRef.environmentId}:${activeThreadRef.threadId}`; const sessions = deviceState.sessions.filter( (session) => session.threadId === activeThreadRef.threadId, ); const key = (session: (typeof sessions)[number]) => `${session.hostId}:${session.deviceId}`; const deviceFor = (session: (typeof sessions)[number]) => deviceState.devices.find( (entry) => entry.hostId === session.hostId && entry.id === session.deviceId, ); const previous = previousDeviceSessions.current.get(threadKey); previousDeviceSessions.current.set( threadKey, new Set(sessions.filter((session) => deviceFor(session) !== undefined).map(key)), ); if (!previous || shouldUsePlanSidebarSheet) return; for (const session of sessions) { if (previous.has(key(session))) continue; const device = deviceFor(session); if (!device) continue; const target = { hostId: session.hostId, deviceId: session.deviceId, platform: device.platform, name: device.name, }; if (autoShowFloatingPreview) { usePreviewMiniPlayerStore.getState().open(activeThreadRef, { kind: "device", ...target }); continue; The missing row in
statev2.sqlitedoesn't rule this out either way. The mini player only lives in memory, and this thread's environment id isn't the local one, so its contents were never in that database.Status and workarounds
Open PR #14968 fixes this crash, but it isn't in this nightly yet. Until it lands:
- Close the inline thread details card, or dock the device or browser preview instead of letting it float.
- Turn off Auto-show floating preview in the Integrations settings so the app doesn't open one on its own.
- Reloading from the crash screen returns to the same route, so it crashes again as soon as a player opens. A full restart works because it clears the in-memory player.
If you can still reproduce this with the floating player confirmed closed on a cold open, that would be a new trigger, so please let us know. Otherwise, your canvas width and whether this thread had a device session or browser tab would be useful on #14959.
- addedduplicateThis issue or pull request already existsThis issue or pull request already existsvia-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 3, 2026 juliusmarminge commented
on Oct 3, 2026 MemberMore actions
Before submitting
Area
apps/web
Steps to reproduce
Expected behavior
Thread opens and stays usable.
Actual behavior
Renderer crashes with React error #185 (Maximum update depth exceeded). Reload from the crash screen does NOT help – only a hard restart of the app recovers. Reproducible on every open of this thread.
Version or commit
T3 Code (Nightly) 0.0.46-nightly.20261003.2623
Environment
Time: 2026-10-03T08:32:34.119Z
Path: /8327eebb-bf4c-409c-ae15-27306b5aad20/d40c4954-a990-4503-951b-9e2e860c4053 (= /$environmentId/$threadId)
Logs or stack traces
Note: _chat offsets differ only slightly from #14959 on .2610 (2:135900 / 51:62314), same ThreadDetailsCard/ChatCanvas frames, but without any floating preview here.
Diagnostics
Thread-ID is absent from local statev2.sqlite/state.sqlite (orchestration_v2_projection_threads, projection_threads, orchestration_events) – crash environment-ID differs from local one, so thread content could not be inspected locally.
Workaround
Reload from crash screen: ineffective. Hard app restart required.