From 4db342f49645082a429c49fb3e783d2fcdb15ea6 Mon Sep 17 00:00:00 2001 From: waclaude Date: Sat, 1 Aug 2026 16:14:24 +0000 Subject: [PATCH] fix: prevent hydration drift and improve form semantics --- .../web/src/app/(app)/session/[id]/page.tsx | 47 +++++++++++++---- .../automations/automation-form.tsx | 8 ++- .../components/settings/sandbox-settings.tsx | 25 +++++----- .../web/src/hooks/use-session-attachments.ts | 50 +++++++++++-------- .../hooks/use-session-participant-profiles.ts | 16 +++--- packages/web/src/hooks/use-session-socket.ts | 27 ++++++---- 6 files changed, 112 insertions(+), 61 deletions(-) diff --git a/packages/web/src/app/(app)/session/[id]/page.tsx b/packages/web/src/app/(app)/session/[id]/page.tsx index 06a11d4d8d..b29e5d2f44 100644 --- a/packages/web/src/app/(app)/session/[id]/page.tsx +++ b/packages/web/src/app/(app)/session/[id]/page.tsx @@ -3,7 +3,15 @@ import { useParams, useRouter, useSearchParams } from "next/navigation"; import { mutate } from "swr"; import useSWRMutation from "swr/mutation"; -import { Suspense, useState, useRef, useEffect, useCallback, useMemo } from "react"; +import { + Suspense, + useState, + useRef, + useEffect, + useCallback, + useMemo, + useSyncExternalStore, +} from "react"; import { useSessionSocket } from "@/hooks/use-session-socket"; import { SessionTimeline } from "@/components/session-timeline"; import { MediaLightbox } from "@/components/media-lightbox"; @@ -52,6 +60,27 @@ import { useBrowserLayoutStorage } from "@/hooks/use-browser-layout-storage"; import { focusSessionDetailsTrigger } from "@/lib/session-details-focus"; import { useSessionParticipantProfiles } from "@/hooks/use-session-participant-profiles"; +const TERMINAL_VISIBILITY_KEY = "terminal-visible"; +const TERMINAL_VISIBILITY_EVENT = "terminal-visibility-change"; + +function subscribeToTerminalVisibility(onStoreChange: () => void) { + window.addEventListener("storage", onStoreChange); + window.addEventListener(TERMINAL_VISIBILITY_EVENT, onStoreChange); + return () => { + window.removeEventListener("storage", onStoreChange); + window.removeEventListener(TERMINAL_VISIBILITY_EVENT, onStoreChange); + }; +} + +function getTerminalVisibility() { + return localStorage.getItem(TERMINAL_VISIBILITY_KEY) === "true"; +} + +function setTerminalVisibility(visible: boolean) { + localStorage.setItem(TERMINAL_VISIBILITY_KEY, String(visible)); + window.dispatchEvent(new Event(TERMINAL_VISIBILITY_EVENT)); +} + type SessionState = ReturnType["sessionState"]; export default function SessionPage() { @@ -141,18 +170,16 @@ function SessionPageContent() { const actionsButtonRef = useRef(null); // Terminal panel state - const [terminalOpen, setTerminalOpen] = useState(() => { - if (typeof window === "undefined") return false; - return localStorage.getItem("terminal-visible") === "true"; - }); + const terminalOpen = useSyncExternalStore( + subscribeToTerminalVisibility, + getTerminalVisibility, + () => false + ); const toggleTerminal = useCallback(() => { - const next = !terminalOpen; - localStorage.setItem("terminal-visible", String(next)); - setTerminalOpen(next); + setTerminalVisibility(!terminalOpen); }, [terminalOpen]); const closeTerminal = useCallback(() => { - setTerminalOpen(false); - localStorage.setItem("terminal-visible", "false"); + setTerminalVisibility(false); }, []); const ttydUrl = sessionState?.ttydUrl; const ttydToken = sessionState?.ttydToken; diff --git a/packages/web/src/components/automations/automation-form.tsx b/packages/web/src/components/automations/automation-form.tsx index 3d706dd829..cdce4feaf9 100644 --- a/packages/web/src/components/automations/automation-form.tsx +++ b/packages/web/src/components/automations/automation-form.tsx @@ -371,7 +371,7 @@ export function AutomationForm({ mode, initialValues, onSubmit, submitting }: Au {/* Trigger Type */} {mode === "create" ? (
- +
Trigger Type
Scheduled automations run on a repeating timer. Other types run when the connected service sends an event (for example a GitHub webhook or Sentry alert). @@ -420,12 +420,16 @@ export function AutomationForm({ mode, initialValues, onSubmit, submitting }: Au {/* Repository Configuration */}
-
-
- +
+ Service Ports

Ports code-server and the web terminal bind to. Leave blank for the defaults ( {DEFAULT_CODE_SERVER_PORT} and {DEFAULT_TERMINAL_PORT}). Change a port to free the default @@ -634,11 +634,12 @@ export function SandboxSettingsEditor({ />

- + -
+
+ Tunnel Ports
- + Tunnel Ports
+
-
- +
+ Child Sessions

Limit agent-spawned child sessions to prevent runaway sandbox usage.

@@ -721,10 +722,10 @@ export function SandboxSettingsEditor({ />
- + -
- +
+ Resources

Reserve CPU and memory for each sandbox. Leave blank to use the provider's default reservation. @@ -764,7 +765,7 @@ export function SandboxSettingsEditor({ />

- +