Mark the session on screen as Current in the session lists

- The conversation page tells the rail and the sessions modal which
  session it shows (announceShownSession, useShownSession); they reload,
  so a session reopened by ?session_id= shows at once.
- Only that session is Current and greyed out (the active one without
  ?session_id=); the active row then leads back to it.
- The rail lists the session on screen even when it's older than its
  latest sessions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-26 05:56:13 +01:00
co-authored by Claude Opus 5.5
parent cc109bc361
commit 657a76d868
10 changed files with 153 additions and 31 deletions
@@ -9,40 +9,48 @@ type Props = {
loading?: boolean;
disabled?: boolean;
onOpen: () => void;
/**
* The session the page shows (`?session_id=`, else the active one):
* marked Current and greyed out.
*/
current: boolean;
};
/**
* One session in the rail's sub-list under the open bot: aligned with the
* bot's name, its title and how recent. The active one says so; any other
* reopens on click.
* bot's name, its title and how recent. The one on screen is
* Current (greyed out); any other opens on click (reopening a closed one,
* going back to the active one).
*/
export default function RailBotSessionRow({
session,
loading,
disabled,
onOpen,
current,
}: Props) {
const active = session.status == "active";
const at = session.last_activity_at || session.created_at;
return (
<Button
title={
active
? "This is the current session"
: `Reopen "${session.title || "Untitled"}"`
current
? "This is the session on screen"
: session.status == "active"
? "Go back to the active session"
: `Reopen "${session.title || "Untitled"}"`
}
type="button"
variant="ghost"
color="text"
loading={loading}
disabled={active || disabled}
disabled={current || disabled}
onClick={onOpen}
buttonContentProps={{ className: "w-full min-w-0 flex-nowrap" }}
className={twMerge(
"w-full h-8 shrink-0 justify-start! items-center! gap-2! rounded-default px-2! @rail:pl-14! text-left text-ink dark:text-ink hover:bg-ink/5 hover:dark:bg-ink/5 focus-visible:ring-2 focus-visible:ring-ink",
active &&
"bg-transparent hover:bg-transparent hover:dark:bg-transparent opacity-100!",
current &&
"bg-transparent hover:bg-transparent hover:dark:bg-transparent opacity-100!",
)}
loadingProps={{
size: "smaller",
@@ -51,18 +59,18 @@ export default function RailBotSessionRow({
<Span
className={twMerge(
"grow min-w-0 truncate text-xs leading-4 font-bold",
active ? "text-ink-secondary" : "text-ink",
current ? "text-ink-secondary" : "text-ink",
)}
>
{session.title || "Untitled session"}
</Span>
{session.unread && !active ? (
{session.unread && !current ? (
<Span className="shrink-0 text-[11px] font-bold leading-4 text-ink">
New
</Span>
) : null}
<Span className="shrink-0 text-[11px] leading-4 text-ink-secondary">
{active
{current
? "Current"
: at
? relativeTime({ at: Number(at) })
@@ -4,6 +4,8 @@ import Stack from "@moduletrace/twui/layout/Stack";
import BotSessionsModal from "@/src/components/general/bot-sessions-modal";
import { AppData } from "@/src/data/app-data";
import useBotSessions from "@/src/hooks/use-bot-sessions";
import useShownSession from "@/src/hooks/use-shown-session";
import conversationHref from "@/src/utils/conversation-href";
import type { BotWithLastMessage } from "@/src/types";
import RailBotSessionRow from "./rail-bot-session-row";
@@ -13,18 +15,30 @@ type Props = {
};
/**
* Under the open bot in the rail: its latest sessions, latest first, and,
* Under the open bot in the rail: its latest sessions, latest first (plus
* the one on screen when it's older). The one on screen (`?session_id=`,
* else the active one) is Current. Then,
* once there are more than the ones listed, "All sessions" which opens the
* conversation's sessions modal. Hidden when the rail collapses to icons.
*/
export default function RailBotSessions({ bot, workspace_id }: Props) {
const on_screen = useShownSession({ bot_id: Number(bot.id), workspace_id });
const sessions = useBotSessions({
bot_id: Number(bot.id),
workspace_id,
open: true,
limit: AppData.RailRecentSessions + 1,
refresh_key: on_screen ? `${on_screen.id}:${on_screen.status}` : null,
});
const shown = sessions.sessions.slice(0, AppData.RailRecentSessions);
const recent = sessions.sessions.slice(0, AppData.RailRecentSessions);
/**
* The session on screen is always listed, even when it's older than
* the latest ones.
*/
const shown =
on_screen?.id && !recent.some((session) => session.id == on_screen.id)
? [...recent, { ...on_screen, message_count: 0 }]
: recent;
if (!shown.length) return null;
@@ -41,7 +55,12 @@ export default function RailBotSessions({ bot, workspace_id }: Props) {
session={session}
loading={sessions.busy_id == session.id}
disabled={Boolean(sessions.busy_id)}
onOpen={() => sessions.reopenSession({ session_id: Number(session.id) })}
current={on_screen?.id ? on_screen.id == session.id : session.status == "active"}
onOpen={() =>
session.status == "active"
? window.location.assign(conversationHref({ bot_id: Number(bot.id), workspace_id }))
: sessions.reopenSession({ session_id: Number(session.id) })
}
/>
))}
</Stack>