feat(frontend): restyle ThothII to the Omics Portal (GSD) design system

Adopt the portal's design language so the app embeds seamlessly into its
header/sidebar chrome later:
- Token layer rewritten as raw OKLCH triplets (the previous oklch()-wrapped
  vars produced invalid colors; the UI was effectively unstyled). Values mirror
  GSD: Manrope + Fraunces fonts, #cb333b red brand, warm off-white surfaces,
  pill buttons, brand focus rings. Dark tokens follow .dark or the portal's
  [data-bs-theme=dark].
- Layout: no left sidebar, no header (the portal supplies them). Conversation
  column flush-left with a slim F1-F8 phase strip; session rail moved to the
  RIGHT, mirroring the portal's left sidebar (red section label, red-tint active
  item). Editorial empty state.
- Transcript component now actually renders the streamed assistant turns (the
  store accumulated them but nothing displayed them). Toaster mounted for errors
  (matchMedia polyfill added to the jsdom test setup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 18:05:23 +02:00
co-authored by Claude Opus 4.8
parent f59d6d1478
commit 4e6d4a4cea
12 changed files with 424 additions and 180 deletions
+51 -17
View File
@@ -23,24 +23,58 @@ export function NavSessions({ activeSessionId, onSessionSelect }: Props) {
onSessionSelect(id);
}
if (sessions.length === 0) {
return (
<p className="px-1 py-6 text-center text-xs text-muted-foreground">
Nessuna sessione. Inizia con <span className="font-medium text-foreground">Nuova domanda</span>.
</p>
);
}
return (
<ul className="flex flex-col gap-1">
{sessions.map((s) => (
<li key={s.id}>
<button
data-testid={`session-item-${s.id}`}
data-active={s.id === activeSessionId ? "true" : "false"}
onClick={() => handleClick(s.id)}
className={[
"w-full text-left rounded px-2 py-1 text-xs hover:bg-accent truncate",
s.id === activeSessionId ? "bg-accent font-semibold" : "",
].join(" ")}
>
<span className="block truncate">{s.question || s.id}</span>
<span className="block text-muted-foreground text-[10px]">{s.status}</span>
</button>
</li>
))}
<ul className="flex flex-col gap-0.5">
{sessions.map((s) => {
const active = s.id === activeSessionId;
return (
<li key={s.id}>
<button
data-testid={`session-item-${s.id}`}
data-active={active ? "true" : "false"}
onClick={() => handleClick(s.id)}
className={[
"group w-full rounded-lg px-2.5 py-2 text-left transition-colors",
active
? "bg-[oklch(var(--primary)/0.12)]"
: "hover:bg-accent",
].join(" ")}
>
<span
className={[
"block truncate text-[0.8rem] leading-snug",
active ? "font-bold text-primary" : "text-foreground/90",
].join(" ")}
>
{s.question || s.id}
</span>
<span className="mt-1 flex items-center gap-1.5">
<span
className={[
"size-1.5 rounded-full",
s.status === "open"
? "bg-[oklch(var(--success))]"
: s.status === "finalized"
? "bg-primary"
: "bg-muted-foreground/50",
].join(" ")}
/>
<span className="text-[0.65rem] uppercase tracking-wide text-muted-foreground">
{s.status}
</span>
</span>
</button>
</li>
);
})}
</ul>
);
}