rebuild the chat session list on the shared data primitives

the list drew its own border per row on top of nothing, so every boundary
between two rows was a double hairline, and it emphasised two things per row
where the design language allows one. DataRow/DataList settle both.

loading and empty were the same grey sentence, which made a slow transcript
read look like an account with no history; they are now LoadingBlock and
EmptyBlock, and a failed read gets an ErrorBlock with the actual message
instead of rendering as "no sessions".

rename and delete swallowed their failures whole — useClient only raises a
dialog for 401, 403 and 5xx, and the likely error here is a 404 from a
transcript that vanished under you. both toast now, as does a deep link to a
session that cannot be read, which used to open an empty pane and say nothing.

the New Chat button was duck-teal filled with duck-yellow text: duck-teal is
a bright cyan in dark mode and duck-yellow has no dark override, so the pair
sat near 2:1 contrast in both themes.

active-row highlight now comes from the route rather than the selection
channel, so it is right on a deep link before any panel has published, and
deleting the open session navigates out of it instead of leaving a dead route.

deletes SessionBar and SessionContextMenu: the first was exported through two
barrels and imported nowhere, the second was never imported at all and typed
its session id as a number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 23:24:24 +00:00
co-authored by Claude Opus 5
parent 8c2790184a
commit 306def14f3
7 changed files with 173 additions and 209 deletions
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef } from 'react';
import { useParams, useNavigate, useSearchParams } from 'react-router';
import type { LayoutNode, SelectedSession } from 'officerdev';
import { WorkspaceView } from 'officerdev';
import { toast } from '@/components/ui/sonner';
import { useIsMobile } from 'hooks/useIsMobile';
import { useClient } from 'hooks/useClient';
import { useDashboardState } from 'state/useDashboardState';
@@ -96,8 +97,13 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => {
total: detail.total,
initialOffset: detail.offset,
});
} catch {
if (!cancelled) setSelected({ id: sessionId });
} catch (err) {
if (cancelled) return;
// Falling back to a bare id still opens a usable pane, but silently: you get an empty chat and
// no hint that the transcript could not be read, which is indistinguishable from a new session.
// Most often the id is stale — the transcript was deleted or pruned out from under the link.
toast.error(`Could not load this conversation: ${err instanceof Error ? err.message : 'not found'}`);
setSelected({ id: sessionId });
}
})();
return () => {