feat: add URL parameter syncing for session and player

Implement URL search parameter synchronization for `session` and
`player`
to allow for bookmarkable links. Added `sessionName` to the journal
stream state to support displaying human-readable session names in the
UI. Improved layout transitions and updated the JournalPanel to show
more detailed connection information.
This commit is contained in:
2026-07-06 16:16:01 +08:00
parent 6b5fcdc051
commit 13f454de9c
4 changed files with 110 additions and 13 deletions
+3 -2
View File
@@ -10,6 +10,7 @@ import {
createSession,
deleteSession,
disconnectStream,
setSessionId,
} from "../stores/journalStream";
import { journalSetState } from "../stores/journalStream";
@@ -38,7 +39,7 @@ export const ConnectionBar: Component = () => {
const handleSessionSelect = async (id: string) => {
try {
await hydrateFromServer(id);
journalSetState("sessionId", id);
setSessionId(id);
} catch (e) {
setError("Failed to load session");
}
@@ -56,7 +57,7 @@ export const ConnectionBar: Component = () => {
<span class="text-xs text-gray-500">connected</span>
<Show when={stream.sessionId}>
<span class="text-xs font-mono bg-gray-100 px-1.5 py-0.5 rounded">
{stream.sessionId}
{stream.sessionName || stream.sessionId}
</span>
</Show>
<div class="flex-1" />
+24 -6
View File
@@ -11,6 +11,7 @@ import {
hydrateFromServer,
useJournalStream,
setMyName,
setSessionId,
} from "../stores/journalStream";
import { ConnectionBar } from "./ConnectionBar";
import { StreamView } from "./StreamView";
@@ -26,9 +27,9 @@ export const JournalPanel: Component<JournalPanelProps> = (props) => {
return (
<>
{/* Backdrop — hidden on desktop since panel doesn't overlay content */}
{/* Backdrop — only on mobile, starts below header */}
<div
class="fixed inset-0 bg-black/30 z-40 md:hidden"
class="fixed top-16 inset-x-0 bottom-0 bg-black/30 z-40 md:hidden"
classList={{ hidden: !props.open }}
onClick={props.onClose}
/>
@@ -43,10 +44,9 @@ export const JournalPanel: Component<JournalPanelProps> = (props) => {
>
{/* Header */}
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 shrink-0">
<div class="flex items-center gap-2">
<h2 class="text-sm font-semibold text-gray-700">Journal</h2>
<div class="flex items-center gap-2 min-w-0">
<span
class="w-2 h-2 rounded-full"
class="w-2 h-2 rounded-full shrink-0"
classList={{
"bg-gray-400": stream.connectionStatus === "disconnected",
"bg-yellow-400 animate-pulse":
@@ -56,10 +56,27 @@ export const JournalPanel: Component<JournalPanelProps> = (props) => {
}}
title={stream.connectionStatus}
/>
<Show
when={stream.connected && stream.sessionId}
fallback={
<h2 class="text-sm font-semibold text-gray-700 truncate">
Disconnected
</h2>
}
>
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-700 truncate leading-tight">
{stream.myName}
</p>
<p class="text-[10px] text-gray-400 truncate leading-tight">
{stream.sessionName || stream.sessionId}
</p>
</div>
</Show>
</div>
<button
onClick={props.onClose}
class="text-gray-400 hover:text-gray-600 text-sm px-1"
class="text-gray-400 hover:text-gray-600 text-sm px-1 shrink-0"
title="Hide panel"
>
@@ -114,6 +131,7 @@ const ConnectDialog: Component = () => {
try {
setMyName(name);
const sessionId = stream.sessionId || "default";
setSessionId(sessionId);
await hydrateFromServer(sessionId);
await connectStream(sessionId, brokerUrl);
} catch (e) {