Add full shell, replaceable Omics adapter and bilingual interaction

Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
Codex
2026-09-13 14:26:39 +02:00
parent 2d1b714ebe
commit d8a29bfbdd
207 changed files with 8570 additions and 2164 deletions
+11 -9
View File
@@ -1,3 +1,4 @@
import { useI18n } from "../i18n";
import { useLayoutEffect, useRef } from "react";
import { Settings2, X } from "lucide-react";
import ReactMarkdown from "react-markdown";
@@ -46,11 +47,12 @@ function MarkdownBody({ entry }: { entry: ActivityEntry }) {
}
function ActivityRow({ entry }: { entry: ActivityEntry }) {
const { t: translate } = useI18n();
return (
<article className="w-full min-w-0 border-b border-border/50 py-2 last:border-b-0">
<div className="mb-1 flex flex-wrap items-center gap-1 text-[0.65rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
<span className="text-foreground/80">{ACTIVITY_LABELS[entry.kind]}</span>
{entry.phase && <span aria-label={`Phase ${entry.phase}`}>{entry.phase}</span>}
<span className="text-foreground/80">{translate(ACTIVITY_LABELS[entry.kind] ?? entry.kind)}</span>
{entry.phase && <span aria-label={translate("Phase {phase}", { phase: entry.phase })}>{entry.phase}</span>}
</div>
<MarkdownBody entry={entry} />
</article>
@@ -69,6 +71,7 @@ export function ModelActivityPanel({
onOpenWorkspaceManager?: () => void;
hidden?: boolean;
}) {
const { t: translate } = useI18n();
const activityLog = useSessionStore((s) => s.activityLog);
const visibleActivity = activityLog.filter(isVisibleModelActivity);
const visibleCount = visibleActivity.length;
@@ -87,12 +90,11 @@ export function ModelActivityPanel({
: "fixed inset-y-0 left-0 z-30 flex min-w-0 w-[min(90vw,24rem)] shrink-0 flex-col border-r border-border bg-sidebar"
}>
<div className="flex items-center justify-between gap-2 border-b border-border/60 px-3 py-2">
<h2 className="font-heading text-sm font-semibold text-foreground">Model activity</h2>
<h2 className="font-heading text-sm font-semibold text-foreground">{translate("Model activity")}</h2>
<div className="flex items-center gap-1">
<Button size="sm" variant="ghost" aria-label="Workspace management" onClick={onOpenWorkspaceManager}>
<Settings2 className="size-3.5" />Workspace management
</Button>
<Button size="icon-sm" variant="ghost" aria-label="Close model activity" onClick={onClose}>
<Button size="sm" variant="ghost" aria-label={translate("Workspace management")} onClick={onOpenWorkspaceManager}>
<Settings2 className="size-3.5" />{translate("Workspace management")}</Button>
<Button size="icon-sm" variant="ghost" aria-label={translate("Close model activity")} onClick={onClose}>
<X className="size-4" />
</Button>
</div>
@@ -101,14 +103,14 @@ export function ModelActivityPanel({
ref={scrollRef}
data-testid="activity-scroll"
tabIndex={0}
aria-label="Model activity timeline"
aria-label={translate("Model activity timeline")}
className="w-full min-w-0 flex-1 overflow-y-auto px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40"
onScroll={(event) => {
followRef.current = isNearBottom(event.currentTarget);
}}
>
{visibleActivity.length === 0 ? (
<p className="py-3 text-sm text-muted-foreground">No activity yet.</p>
<p className="py-3 text-sm text-muted-foreground">{translate("No activity yet.")}</p>
) : (
visibleActivity.map((entry, index) => (
<ActivityRow key={`${index}-${entry.toolCallId ?? entry.kind}`} entry={entry} />