From e40b7dc199aad3cefe5777d0a995fcabb9d820aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Mon, 23 Mar 2026 00:49:54 +0100 Subject: [PATCH] Fix SSR and automerge timing SSR fix: - ClientOnly wrapper prevents Leaflet/Yjs from running during SSR - SessionView lazy-loaded to avoid browser API usage on server Automerge fix: - Set MERGE_READY_STATE to 'clean' only (was including 'unstable') - Prevents merging before all CI checks complete Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/automerge.yml | 1 + apps/planner/app/components/ClientOnly.tsx | 17 ++++++ apps/planner/app/components/SessionView.tsx | 62 +++++++++++++++++++++ apps/planner/app/routes/session.$id.tsx | 61 ++++++-------------- 4 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 apps/planner/app/components/ClientOnly.tsx create mode 100644 apps/planner/app/components/SessionView.tsx diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 14fe922..94cf8b7 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -30,4 +30,5 @@ jobs: MERGE_LABELS: "automerge" MERGE_METHOD: "merge" MERGE_DELETE_BRANCH: "true" + MERGE_READY_STATE: "clean" UPDATE_METHOD: "rebase" diff --git a/apps/planner/app/components/ClientOnly.tsx b/apps/planner/app/components/ClientOnly.tsx new file mode 100644 index 0000000..4423f62 --- /dev/null +++ b/apps/planner/app/components/ClientOnly.tsx @@ -0,0 +1,17 @@ +import { useState, useEffect, type ReactNode } from "react"; + +export function ClientOnly({ + children, + fallback = null, +}: { + children: () => ReactNode; + fallback?: ReactNode; +}) { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + return mounted ? <>{children()} : <>{fallback}; +} diff --git a/apps/planner/app/components/SessionView.tsx b/apps/planner/app/components/SessionView.tsx new file mode 100644 index 0000000..4876bdb --- /dev/null +++ b/apps/planner/app/components/SessionView.tsx @@ -0,0 +1,62 @@ +import { Suspense, lazy } from "react"; +import { useYjs } from "~/lib/use-yjs"; +import { useRouting } from "~/lib/use-routing"; + +const PlannerMap = lazy(() => + import("~/components/PlannerMap").then((m) => ({ default: m.PlannerMap })), +); +const WaypointSidebar = lazy(() => + import("~/components/WaypointSidebar").then((m) => ({ default: m.WaypointSidebar })), +); + +export function SessionView({ sessionId }: { sessionId: string }) { + const yjs = useYjs(sessionId); + const { isHost, computing, routeStats, requestRoute } = useRouting(yjs); + + if (!yjs) { + return ( +
+

Connecting...

+
+ ); + } + + return ( + <> +
+

trails.cool Planner

+
+ {computing && ( + Computing route... + )} + {isHost && ( + + Host + + )} + + {yjs.connected ? "Connected" : "Connecting..."} · {sessionId.slice(0, 8)} + +
+
+
+
+ + Loading map... +
+ } + > + + + + + + + ); +} diff --git a/apps/planner/app/routes/session.$id.tsx b/apps/planner/app/routes/session.$id.tsx index bb7879f..e99de06 100644 --- a/apps/planner/app/routes/session.$id.tsx +++ b/apps/planner/app/routes/session.$id.tsx @@ -2,15 +2,11 @@ import { useParams } from "react-router"; import type { Route } from "./+types/session.$id"; import { getSession } from "~/lib/sessions"; import { data } from "react-router"; -import { useYjs } from "~/lib/use-yjs"; -import { useRouting } from "~/lib/use-routing"; +import { ClientOnly } from "~/components/ClientOnly"; import { lazy, Suspense } from "react"; -const PlannerMap = lazy(() => - import("~/components/PlannerMap").then((m) => ({ default: m.PlannerMap })), -); -const WaypointSidebar = lazy(() => - import("~/components/WaypointSidebar").then((m) => ({ default: m.WaypointSidebar })), +const SessionView = lazy(() => + import("~/components/SessionView").then((m) => ({ default: m.SessionView })), ); export function meta(_args: Route.MetaArgs) { @@ -30,53 +26,28 @@ export async function loader({ params }: Route.LoaderArgs) { export default function SessionPage() { const { id } = useParams(); - const yjs = useYjs(id!); - const { isHost, computing, routeStats, requestRoute } = useRouting(yjs); - - if (!yjs) { - return ( -
-

Connecting...

-
- ); - } return (
-
-

trails.cool Planner

-
- {computing && ( - Computing route... - )} - {isHost && ( - - Host - - )} - - {yjs.connected ? "Connected" : "Connecting..."} · {id?.slice(0, 8)} - -
-
-
-
+ +

Loading planner...

+
+ } + > + {() => ( - Loading map... +
+

Loading planner...

} > - +
- - -
+ )} + ); }