From 2e1dee197af8911e37b499c49751a6d7e46daa3d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 8 Jan 2026 11:05:40 +0100 Subject: [PATCH] feat(ui): link sessions to chat page - Session names in the Sessions table are now clickable links - Clicking navigates to /chat?session= with that session loaded - Global sessions excluded (not real conversations) - Added .session-link CSS styling (accent color, underline on hover) - Chat page reads 'session' query param and cleans URL after applying --- ui/src/styles/components.css | 9 +++++++++ ui/src/ui/app-render.ts | 1 + ui/src/ui/app.ts | 16 +++++++++++++++- ui/src/ui/views/sessions.ts | 16 +++++++++++++--- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index f4ef315ec..58965472e 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -454,6 +454,15 @@ background: rgba(0, 0, 0, 0.2); } +.session-link { + text-decoration: none; + color: var(--accent); +} + +.session-link:hover { + text-decoration: underline; +} + .log-stream { border: 1px solid var(--border); border-radius: 14px; diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index c19820d4e..873f99a93 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -358,6 +358,7 @@ export function renderApp(state: AppViewState) { limit: state.sessionsFilterLimit, includeGlobal: state.sessionsIncludeGlobal, includeUnknown: state.sessionsIncludeUnknown, + basePath: state.basePath, onFiltersChange: (next) => { state.sessionsFilterActive = next.activeMinutes; state.sessionsFilterLimit = next.limit; diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 6e959494e..d1b0a954f 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -824,7 +824,9 @@ export class ClawdbotApp extends LitElement { const params = new URLSearchParams(window.location.search); const tokenRaw = params.get("token"); const passwordRaw = params.get("password"); + const sessionRaw = params.get("session"); let changed = false; + let shouldCleanUrl = false; if (tokenRaw != null) { const token = tokenRaw.trim(); @@ -833,6 +835,7 @@ export class ClawdbotApp extends LitElement { changed = true; } params.delete("token"); + shouldCleanUrl = true; } if (passwordRaw != null) { @@ -842,9 +845,20 @@ export class ClawdbotApp extends LitElement { changed = true; } params.delete("password"); + shouldCleanUrl = true; } - if (!changed && tokenRaw == null && passwordRaw == null) return; + if (sessionRaw != null) { + const session = sessionRaw.trim(); + if (session) { + this.sessionKey = session; + changed = true; + } + params.delete("session"); + shouldCleanUrl = true; + } + + if (!shouldCleanUrl) return; const url = new URL(window.location.href); url.search = params.toString(); window.history.replaceState({}, "", url.toString()); diff --git a/ui/src/ui/views/sessions.ts b/ui/src/ui/views/sessions.ts index c82526538..47e910750 100644 --- a/ui/src/ui/views/sessions.ts +++ b/ui/src/ui/views/sessions.ts @@ -2,6 +2,7 @@ import { html, nothing } from "lit"; import { formatAgo } from "../format"; import { formatSessionTokens } from "../presenter"; +import { pathForTab } from "../navigation"; import type { GatewaySessionRow, SessionsListResult } from "../types"; export type SessionsProps = { @@ -12,6 +13,7 @@ export type SessionsProps = { limit: string; includeGlobal: boolean; includeUnknown: boolean; + basePath: string; onFiltersChange: (next: { activeMinutes: string; limit: string; @@ -118,19 +120,27 @@ export function renderSessions(props: SessionsProps) { ${rows.length === 0 ? html`
No sessions found.
` - : rows.map((row) => renderRow(row, props.onPatch))} + : rows.map((row) => renderRow(row, props.basePath, props.onPatch))} `; } -function renderRow(row: GatewaySessionRow, onPatch: SessionsProps["onPatch"]) { +function renderRow(row: GatewaySessionRow, basePath: string, onPatch: SessionsProps["onPatch"]) { const updated = row.updatedAt ? formatAgo(row.updatedAt) : "n/a"; const thinking = row.thinkingLevel ?? ""; const verbose = row.verboseLevel ?? ""; + const displayName = row.displayName ?? row.key; + const canLink = row.kind !== "global"; + const chatUrl = canLink + ? `${pathForTab("chat", basePath)}?session=${encodeURIComponent(row.key)}` + : null; + return html`
-
${row.displayName ?? row.key}
+
${canLink + ? html`${displayName}` + : displayName}
${row.kind}
${updated}
${formatSessionTokens(row)}