Skip to content

Commit 8627b55

Browse files
feat: use clientLoader + localStorage for resizable panel persistence
Replace server-side cookie reading (getResizableSnapshot) with client-side localStorage for persisting resizable panel sizes. Cookies were hitting the ~4KB size limit; localStorage supports 5-10MB. - Run detail route: uses Remix clientLoader with hydrate=true to read panel snapshots from localStorage before first render - Prompts route: returns undefined for snapshots, letting the react-window-splitter library read from localStorage natively via autosaveStrategy="localStorage" Co-Authored-By: Eric Allam <eallam@icloud.com>
1 parent 7d82041 commit 8627b55

File tree

2 files changed

+34
-19
lines changed
  • apps/webapp/app/routes
    • _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug
    • _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam

2 files changed

+34
-19
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
7171
import { type GenerationRow, PromptPresenter } from "~/presenters/v3/PromptPresenter.server";
7272
import { SpanView } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route";
7373
import { clickhouseClient } from "~/services/clickhouseInstance.server";
74-
import { getResizableSnapshot } from "~/services/resizablePanel.server";
7574
import { requireUserId } from "~/services/session.server";
7675
import { PromptService } from "~/v3/services/promptService.server";
7776

@@ -271,7 +270,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
271270
console.error("Prompt generations query exception:", e);
272271
}
273272

274-
// Load distinct filter values and resizable snapshots in parallel
273+
// Load distinct filter values in parallel
275274
const distinctQuery = (col: string, name: string) =>
276275
clickhouseClient.reader.query({
277276
name,
@@ -281,16 +280,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
281280
})({ environmentId: environment.id, promptSlug: prompt.slug });
282281

283282
const [
284-
resizableOuter,
285-
resizableVertical,
286-
resizableGenerations,
287283
[modelsErr, modelsRows],
288284
[opsErr, opsRows],
289285
[provsErr, provsRows],
290286
] = await Promise.all([
291-
getResizableSnapshot(request, "prompt-detail"),
292-
getResizableSnapshot(request, "prompt-vertical"),
293-
getResizableSnapshot(request, "prompt-generations"),
294287
distinctQuery("response_model", "promptDistinctModels"),
295288
distinctQuery("operation_id", "promptDistinctOperations"),
296289
distinctQuery("gen_ai_system", "promptDistinctProviders"),
@@ -302,9 +295,9 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
302295

303296
return typedjson({
304297
resizable: {
305-
outer: resizableOuter,
306-
vertical: resizableVertical,
307-
generations: resizableGenerations,
298+
outer: undefined as ResizableSnapshot | undefined,
299+
vertical: undefined as ResizableSnapshot | undefined,
300+
generations: undefined as ResizableSnapshot | undefined,
308301
},
309302
prompt: {
310303
id: prompt.id,

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
StopCircleIcon,
1313
} from "@heroicons/react/20/solid";
1414

15-
import { useLoaderData, useRevalidator } from "@remix-run/react";
15+
import { type ClientLoaderFunctionArgs, useLoaderData, useRevalidator } from "@remix-run/react";
1616
import { type LoaderFunctionArgs, type SerializeFrom, json } from "@remix-run/server-runtime";
1717
import { type Virtualizer } from "@tanstack/react-virtual";
1818
import {
@@ -95,7 +95,6 @@ import { RunEnvironmentMismatchError, RunPresenter } from "~/presenters/v3/RunPr
9595
import { clickhouseClient } from "~/services/clickhouseInstance.server";
9696
import { getImpersonationId } from "~/services/impersonation.server";
9797
import { logger } from "~/services/logger.server";
98-
import { getResizableSnapshot } from "~/services/resizablePanel.server";
9998
import { requireUserId } from "~/services/session.server";
10099
import { cn } from "~/utils/cn";
101100
import { lerp } from "~/utils/lerp";
@@ -279,10 +278,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
279278
throw error;
280279
}
281280

282-
//resizable settings
283-
const parent = await getResizableSnapshot(request, resizableSettings.parent.autosaveId);
284-
const tree = await getResizableSnapshot(request, resizableSettings.tree.autosaveId);
285-
286281
const runsList = await getRunsListFromTableState({
287282
tableStateParam: url.searchParams.get("tableState"),
288283
organizationSlug,
@@ -297,13 +292,40 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
297292
trace: result.trace,
298293
maximumLiveReloadingSetting: result.maximumLiveReloadingSetting,
299294
resizable: {
300-
parent,
301-
tree,
295+
parent: undefined as ResizableSnapshot | undefined,
296+
tree: undefined as ResizableSnapshot | undefined,
302297
},
303298
runsList,
304299
});
305300
};
306301

302+
function getLocalStorageSnapshot(key: string): ResizableSnapshot | undefined {
303+
try {
304+
const raw = localStorage.getItem(key);
305+
if (raw) {
306+
const parsed: unknown = JSON.parse(raw);
307+
if (parsed != null && typeof parsed === "object" && "status" in parsed) {
308+
return parsed as ResizableSnapshot;
309+
}
310+
}
311+
} catch {
312+
// Silently ignore localStorage errors
313+
}
314+
return undefined;
315+
}
316+
317+
export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {
318+
const serverData = await serverLoader<typeof loader>();
319+
return {
320+
...serverData,
321+
resizable: {
322+
parent: getLocalStorageSnapshot(resizableSettings.parent.autosaveId),
323+
tree: getLocalStorageSnapshot(resizableSettings.tree.autosaveId),
324+
},
325+
};
326+
}
327+
clientLoader.hydrate = true as const;
328+
307329
type LoaderData = SerializeFrom<typeof loader>;
308330

309331
export default function Page() {

0 commit comments

Comments
 (0)