Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .server-changes/fix-resizable-panel-stuck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: fix
---

Fix the run-view inspector panel glitching out and locking up in Firefox. Disabled the underlying resizable library's collapse animation on Firefox (where its `requestAnimationFrame`-driven actor caused visual glitches and intermittent state-machine errors) while keeping it intact for Chromium and Safari, and bumped the inspector minimum from 50px to 250px so dragging can't shrink the panel into a near-useless width.
14 changes: 9 additions & 5 deletions apps/webapp/app/components/primitives/Resizable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import React, { useRef } from "react";
import { PanelGroup, Panel, PanelResizer } from "react-window-splitter";
import { PanelGroup, Panel, PanelResizer } from "@window-splitter/react";
import { cn } from "~/utils/cn";

const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps<typeof PanelGroup>) => (
Expand Down Expand Up @@ -69,10 +69,14 @@ const ResizableHandle = ({
</PanelResizer>
);

const RESIZABLE_PANEL_ANIMATION = {
easing: "ease-in-out" as const,
duration: 200,
};
// react-window-splitter drives the collapse animation through @react-spring/rafz,
// which has timing/interaction issues with Firefox that produce visual glitches
// (alternating frames, panels stuck at min, panelHasSpace invariant violations).
// Disable the animation on Firefox; it works correctly in Chromium and Safari.
const RESIZABLE_PANEL_ANIMATION =
typeof navigator !== "undefined" && /firefox/i.test(navigator.userAgent)
? undefined
: ({ easing: "ease-in-out", duration: 300 } as const);

const COLLAPSIBLE_HANDLE_CLASSNAME = "transition-opacity duration-200";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { DiscordIcon } from "@trigger.dev/companyicons";
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
import type { TaskRunStatus } from "@trigger.dev/database";
import { Fragment, Suspense, useCallback, useEffect, useRef, useState } from "react";
import type { PanelHandle } from "react-window-splitter";
import type { PanelHandle } from "@window-splitter/react";
import { Bar, BarChart, ResponsiveContainer, Tooltip, type TooltipProps } from "recharts";
import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ import { SpanView } from "../resources.orgs.$organizationSlug.projects.$projectP

const resizableSettings = {
parent: {
autosaveId: "panel-run-parent-v2",
autosaveId: "panel-run-parent-v3",
handleId: "parent-handle",
main: {
id: "run",
Expand All @@ -124,7 +124,7 @@ const resizableSettings = {
inspector: {
id: "inspector",
default: "500px" as const,
min: "50px" as const,
min: "250px" as const,
},
},
tree: {
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"@upstash/ratelimit": "^1.1.3",
"@vercel/sdk": "^1.19.1",
"@whatwg-node/fetch": "^0.9.14",
"@window-splitter/react": "1.1.3",
"ai": "^4.3.19",
"assert-never": "^1.2.1",
"aws4fetch": "^1.0.18",
Expand Down Expand Up @@ -199,7 +200,6 @@
"react-resizable-panels": "^2.0.9",
"react-stately": "^3.29.1",
"react-use": "17.5.1",
"react-window-splitter": "^0.4.1",
"recharts": "^2.15.2",
"regression": "^2.0.1",
"remix-auth": "^3.6.0",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@sentry/remix@9.46.0": "patches/@sentry__remix@9.46.0.patch",
"@upstash/ratelimit@1.1.3": "patches/@upstash__ratelimit.patch",
"antlr4ts@0.5.0-alpha.4": "patches/antlr4ts@0.5.0-alpha.4.patch",
"@window-splitter/state@0.4.1": "patches/@window-splitter__state@0.4.1.patch"
"@window-splitter/state@1.1.3": "patches/@window-splitter__state@1.1.3.patch"
},
"overrides": {
"typescript": "5.5.4",
Expand Down
28 changes: 0 additions & 28 deletions patches/@window-splitter__state@0.4.1.patch

This file was deleted.

114 changes: 114 additions & 0 deletions patches/@window-splitter__state@1.1.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
diff --git a/dist/commonjs/index.js b/dist/commonjs/index.js
index e3bdcf702702392e9a06c981545f659ee7c5970e..d88ae6b2dc5b4cf1970cb693f58a926bd12a8f45 100644
--- a/dist/commonjs/index.js
+++ b/dist/commonjs/index.js
@@ -757,30 +757,14 @@ function updateLayout(context, dragEvent) {
panelAfter.onCollapseChange.current(false);
}
}
- const panelBeforeIsAboutToCollapse = panelBefore.currentValue.value.eq(getUnitPixelValue(context, panelBefore.min));
- // If the panel was expanded and now is at it's min size, collapse it
- if (!dragEvent.disregardCollapseBuffer &&
- panelBefore.collapsible &&
- panelBeforeIsAboutToCollapse) {
- if (panelBefore.onCollapseChange?.current &&
- panelBefore.collapseIsControlled &&
- !dragEvent.controlled &&
- !dragEvent.isVirtual) {
- panelBefore.onCollapseChange.current(true);
- return { dragOvershoot: newDragOvershoot };
- }
- // Make it collapsed
- panelBefore.collapsed = true;
- panelBeforeNewValue = getUnitPixelValue(context, panelBefore.collapsedSize);
- // Add the extra space created to the before panel
- panelAfterNewValue = panelAfter.currentValue.value.add(panelBeforePreviousValue.minus(panelBeforeNewValue));
- if (panelBefore.onCollapseChange?.current &&
- !panelBefore.collapseIsControlled &&
- !dragEvent.controlled &&
- !dragEvent.isVirtual) {
- panelBefore.onCollapseChange.current(true);
- }
- }
+ // Drag-to-collapse is disabled in this fork: every consumer of the
+ // library uses controlled `collapsed` props and triggers collapse
+ // explicitly (close button, ESC, URL change, etc.). The original auto-
+ // collapse-on-drag logic that lived here would notify the parent when a
+ // collapsible panel reached its min during a drag — keeping it for our
+ // (controlled-only) case caused state-machine deadlocks when handlers
+ // were no-ops, so the block is removed entirely. Panels just clamp at
+ // `min` during drag now.
panelBefore.currentValue = { type: "pixel", value: panelBeforeNewValue };
panelAfter.currentValue = { type: "pixel", value: panelAfterNewValue };
const leftoverSpace = new big_js_1.default(getGroupSize(context)).minus(newItems.reduce((acc, b) => acc.add(isPanelData(b) ? b.currentValue.value : b.size.value), new big_js_1.default(0)));
@@ -940,7 +924,12 @@ function setCookie(name, jsonData) {
function getDeltaForEvent(context, event) {
const panel = getPanelWithId(context, event.panelId);
if (event.type === "expandPanel") {
- return new big_js_1.default(panel.sizeBeforeCollapse ?? getUnitPixelValue(context, panel.min)).minus(panel.currentValue.value);
+ // Fall back to `default` before `min` so the first-ever expand of a
+ // panel that started life collapsed lands at its configured default
+ // size rather than getting stuck at `min`.
+ const defaultPx = panel.default ? getUnitPixelValue(context, panel.default) : undefined;
+ const target = panel.sizeBeforeCollapse ?? defaultPx ?? getUnitPixelValue(context, panel.min);
+ return new big_js_1.default(target).minus(panel.currentValue.value);
}
const collapsedSize = getUnitPixelValue(context, panel.collapsedSize);
return panel.currentValue.value.minus(collapsedSize);
diff --git a/dist/esm/index.js b/dist/esm/index.js
index f8fddd70c0f1aaed29f2fb0ca0d8093d8ce66335..d1dae8beb1447afca47b91e796b8279135f50c36 100644
--- a/dist/esm/index.js
+++ b/dist/esm/index.js
@@ -728,30 +728,14 @@ function updateLayout(context, dragEvent) {
panelAfter.onCollapseChange.current(false);
}
}
- const panelBeforeIsAboutToCollapse = panelBefore.currentValue.value.eq(getUnitPixelValue(context, panelBefore.min));
- // If the panel was expanded and now is at it's min size, collapse it
- if (!dragEvent.disregardCollapseBuffer &&
- panelBefore.collapsible &&
- panelBeforeIsAboutToCollapse) {
- if (panelBefore.onCollapseChange?.current &&
- panelBefore.collapseIsControlled &&
- !dragEvent.controlled &&
- !dragEvent.isVirtual) {
- panelBefore.onCollapseChange.current(true);
- return { dragOvershoot: newDragOvershoot };
- }
- // Make it collapsed
- panelBefore.collapsed = true;
- panelBeforeNewValue = getUnitPixelValue(context, panelBefore.collapsedSize);
- // Add the extra space created to the before panel
- panelAfterNewValue = panelAfter.currentValue.value.add(panelBeforePreviousValue.minus(panelBeforeNewValue));
- if (panelBefore.onCollapseChange?.current &&
- !panelBefore.collapseIsControlled &&
- !dragEvent.controlled &&
- !dragEvent.isVirtual) {
- panelBefore.onCollapseChange.current(true);
- }
- }
+ // Drag-to-collapse is disabled in this fork: every consumer of the
+ // library uses controlled `collapsed` props and triggers collapse
+ // explicitly (close button, ESC, URL change, etc.). The original auto-
+ // collapse-on-drag logic that lived here would notify the parent when a
+ // collapsible panel reached its min during a drag — keeping it for our
+ // (controlled-only) case caused state-machine deadlocks when handlers
+ // were no-ops, so the block is removed entirely. Panels just clamp at
+ // `min` during drag now.
panelBefore.currentValue = { type: "pixel", value: panelBeforeNewValue };
panelAfter.currentValue = { type: "pixel", value: panelAfterNewValue };
const leftoverSpace = new Big(getGroupSize(context)).minus(newItems.reduce((acc, b) => acc.add(isPanelData(b) ? b.currentValue.value : b.size.value), new Big(0)));
@@ -911,7 +895,12 @@ function setCookie(name, jsonData) {
function getDeltaForEvent(context, event) {
const panel = getPanelWithId(context, event.panelId);
if (event.type === "expandPanel") {
- return new Big(panel.sizeBeforeCollapse ?? getUnitPixelValue(context, panel.min)).minus(panel.currentValue.value);
+ // Fall back to `default` before `min` so the first-ever expand of a
+ // panel that started life collapsed lands at its configured default
+ // size rather than getting stuck at `min`.
+ const defaultPx = panel.default ? getUnitPixelValue(context, panel.default) : undefined;
+ const target = panel.sizeBeforeCollapse ?? defaultPx ?? getUnitPixelValue(context, panel.min);
+ return new Big(target).minus(panel.currentValue.value);
}
const collapsedSize = getUnitPixelValue(context, panel.collapsedSize);
return panel.currentValue.value.minus(collapsedSize);
Loading
Loading