feat(ui): link sessions to chat page

- Session names in the Sessions table are now clickable links
- Clicking navigates to /chat?session=<key> 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
This commit is contained in:
Daniel Griesser
2026-01-08 11:05:40 +01:00
committed by Peter Steinberger
parent b3b84ffefa
commit 2e1dee197a
4 changed files with 38 additions and 4 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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());

View File

@@ -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) {
</div>
${rows.length === 0
? html`<div class="muted">No sessions found.</div>`
: rows.map((row) => renderRow(row, props.onPatch))}
: rows.map((row) => renderRow(row, props.basePath, props.onPatch))}
</div>
</section>
`;
}
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`
<div class="table-row">
<div class="mono">${row.displayName ?? row.key}</div>
<div class="mono">${canLink
? html`<a href=${chatUrl} class="session-link">${displayName}</a>`
: displayName}</div>
<div>${row.kind}</div>
<div>${updated}</div>
<div>${formatSessionTokens(row)}</div>