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
+45 -15
View File
@@ -14,25 +14,55 @@ const PHASES = [
export function WorkflowBar() {
const currentPhase = useSessionStore((s) => s.currentPhase);
const activeIdx = PHASES.findIndex((p) => p.id === currentPhase);
return (
<nav className="flex gap-1 overflow-x-auto">
{PHASES.map((p) => {
<nav
aria-label="Avanzamento workflow"
className="flex items-center gap-0 overflow-x-auto py-0.5"
>
{PHASES.map((p, i) => {
const isActive = currentPhase === p.id;
const isDone = activeIdx > -1 && i < activeIdx;
return (
<span
key={p.id}
data-testid={`phase-${p.id}`}
data-active={isActive ? "true" : "false"}
className={[
"px-2 py-0.5 rounded text-xs whitespace-nowrap",
isActive
? "bg-primary text-primary-foreground font-bold"
: "text-muted-foreground border",
].join(" ")}
>
{p.id}: {p.name}
</span>
<div key={p.id} className="flex items-center">
{i > 0 && (
<span
aria-hidden
className={[
"mx-1.5 h-px w-4 shrink-0",
isDone || isActive ? "bg-primary/40" : "bg-border",
].join(" ")}
/>
)}
<span
data-testid={`phase-${p.id}`}
data-active={isActive ? "true" : "false"}
title={`${p.id}: ${p.name}`}
className={[
"inline-flex items-center gap-1.5 whitespace-nowrap rounded-full px-2.5 py-1 text-xs transition-colors",
isActive
? "bg-primary text-primary-foreground font-semibold shadow-sm shadow-primary/20"
: isDone
? "text-foreground/70 font-medium"
: "text-muted-foreground",
].join(" ")}
>
<span
className={[
"font-heading text-[0.7rem] font-semibold",
isActive
? "text-primary-foreground"
: isDone
? "text-primary/80"
: "text-muted-foreground/70",
].join(" ")}
>
{p.id}
</span>
<span className={isActive ? "inline" : "hidden md:inline"}>{p.name}</span>
</span>
</div>
);
})}
</nav>