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
+21 -17
View File
@@ -1,3 +1,4 @@
import { useI18n } from "../i18n";
import type { ReactNode } from "react";
import { Card, CardContent, CardHeader } from "../components/ui/card";
import { Badge } from "../components/ui/badge";
@@ -12,27 +13,28 @@ function Chip({ children }: { children: ReactNode }) {
}
function FilterRow({ filter, index }: { filter: CtePlanFilter; index: number }) {
const { t } = useI18n();
return (
<div
role="group"
aria-label={`Filter ${index}`}
aria-label={t("Filter {index}", { index })}
className="min-w-0 space-y-2 px-3 py-1 sm:px-4"
>
<div className="grid min-w-0 grid-cols-1 gap-2 md:grid-cols-[minmax(0,1fr)_minmax(5rem,auto)_minmax(0,1fr)]">
<div className="min-w-0 space-y-1">
<p className={FIELD_LABEL}>Column</p>
<p className={FIELD_LABEL}>{t("Column")}</p>
<code className="block min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere] font-mono text-xs leading-4 text-foreground">
{filter.column}
</code>
</div>
<div className="min-w-0 space-y-1 md:text-center">
<p className={FIELD_LABEL}>Operator</p>
<p className={FIELD_LABEL}>{t("Operator")}</p>
<code className="block min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere] font-mono text-xs font-semibold leading-4 text-primary">
{filter.op}
</code>
</div>
<div className="min-w-0 space-y-1">
<p className={FIELD_LABEL}>Value</p>
<p className={FIELD_LABEL}>{t("Value")}</p>
<code className="block min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere] font-mono text-xs leading-4 text-foreground">
{filter.value}
</code>
@@ -42,7 +44,7 @@ function FilterRow({ filter, index }: { filter: CtePlanFilter; index: number })
<div className="grid min-w-0 gap-x-4 gap-y-1 border-t border-border/50 pt-1 md:grid-cols-[5.5rem_minmax(0,1fr)]">
{filter.description && (
<>
<span className={FIELD_LABEL}>Description</span>
<span className={FIELD_LABEL}>{t("Description")}</span>
<p className="min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere] text-xs leading-4 text-muted-foreground">
{filter.description}
</p>
@@ -50,7 +52,7 @@ function FilterRow({ filter, index }: { filter: CtePlanFilter; index: number })
)}
{filter.rationale && (
<>
<span className={FIELD_LABEL}>Rationale</span>
<span className={FIELD_LABEL}>{t("Rationale")}</span>
<p className="min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere] text-xs leading-4 text-muted-foreground">
{filter.rationale}
</p>
@@ -63,6 +65,7 @@ function FilterRow({ filter, index }: { filter: CtePlanFilter; index: number })
}
function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
const { t } = useI18n();
const dependsOn = cte.depends_on ?? [];
return (
@@ -85,7 +88,7 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
<CardContent className="flex min-w-0 flex-col gap-3 px-3 py-2 sm:px-4">
<div className="grid min-w-0 gap-3 md:grid-cols-2">
<section className="min-w-0">
<h4 className="thot-label mb-1">Depends on</h4>
<h4 className="thot-label mb-1">{t("Depends on")}</h4>
{dependsOn.length ? (
<div className="flex min-w-0 flex-wrap gap-1.5">
{dependsOn.map((dependency) => (
@@ -93,13 +96,13 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
))}
</div>
) : (
<p className="text-xs text-muted-foreground">No dependencies</p>
<p className="text-xs text-muted-foreground">{t("No dependencies")}</p>
)}
</section>
{cte.keys && cte.keys.length > 0 && (
<section className="min-w-0">
<h4 className="thot-label mb-1">Keys</h4>
<h4 className="thot-label mb-1">{t("Keys")}</h4>
<div className="flex min-w-0 flex-wrap gap-1.5">
{cte.keys.map((key) => (
<Chip key={key}>{key}</Chip>
@@ -111,7 +114,7 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
{cte.tables && cte.tables.length > 0 && (
<section className="min-w-0">
<h4 className="thot-label mb-1">Tables</h4>
<h4 className="thot-label mb-1">{t("Tables")}</h4>
<div className="min-w-0 divide-y divide-border/50 rounded-lg border border-border/60">
{cte.tables.map((table) => (
<div
@@ -134,7 +137,7 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
{cte.filters && cte.filters.length > 0 && (
<section className="min-w-0">
<h4 className="thot-label mb-1">Filters</h4>
<h4 className="thot-label mb-1">{t("Filters")}</h4>
<div className="min-w-0 divide-y divide-border/50 rounded-lg border border-border/60">
{cte.filters.map((f, i) => (
<FilterRow key={`${f.column}-${i}`} filter={f} index={i + 1} />
@@ -145,7 +148,7 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
{cte.output_columns && cte.output_columns.length > 0 && (
<section className="min-w-0">
<h4 className="thot-label mb-1">Output columns</h4>
<h4 className="thot-label mb-1">{t("Output columns")}</h4>
<div className="flex min-w-0 flex-wrap gap-1.5">
{cte.output_columns.map((column) => (
<Chip key={column}>{column}</Chip>
@@ -156,7 +159,7 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
{cte.rationale && (
<section className="min-w-0 border-t border-border/60 pt-1">
<h4 className="thot-label mb-1">Rationale</h4>
<h4 className="thot-label mb-1">{t("Rationale")}</h4>
<p className="whitespace-pre-wrap [overflow-wrap:anywhere] text-sm leading-5 text-foreground/90">
{cte.rationale}
</p>
@@ -168,12 +171,13 @@ function CteCard({ cte, total }: { cte: CtePlanCte; total: number }) {
}
export function CtePlanViewer({ plan }: { plan: CtePlanV2 }) {
const { t } = useI18n();
return (
<div className="flex min-w-0 flex-col gap-6">
<div className="flex min-w-0 flex-col gap-3">
{plan.question && (
<div className="min-w-0 space-y-1.5">
<p className="thot-label">Question</p>
<p className="thot-label">{t("Question")}</p>
<p className="whitespace-pre-wrap [overflow-wrap:anywhere] text-sm leading-relaxed text-muted-foreground">
{plan.question}
</p>
@@ -181,7 +185,7 @@ export function CtePlanViewer({ plan }: { plan: CtePlanV2 }) {
)}
{plan.strategy && (
<div className="min-w-0 space-y-1.5">
<p className="thot-label">Strategy</p>
<p className="thot-label">{t("Strategy")}</p>
<p className="thot-prose whitespace-pre-wrap [overflow-wrap:anywhere] leading-relaxed">
{plan.strategy}
</p>
@@ -189,7 +193,7 @@ export function CtePlanViewer({ plan }: { plan: CtePlanV2 }) {
)}
<div className="min-w-0 space-y-1.5">
<p className="thot-label">Execution order</p>
<p className="thot-label">{t("Execution order")}</p>
<div className="flex min-w-0 flex-wrap items-center gap-1.5 font-mono text-xs text-muted-foreground">
{plan.ctes.map((c, i) => (
<span key={c.name} className="flex min-w-0 max-w-full items-center gap-1.5">
@@ -203,7 +207,7 @@ export function CtePlanViewer({ plan }: { plan: CtePlanV2 }) {
</div>
</div>
<ol aria-label="CTE plan steps" className="flex flex-col gap-3">
<ol aria-label={t("CTE plan steps")} className="flex flex-col gap-3">
{plan.ctes.map((cte) => (
<li key={cte.name} className="min-w-0">
<CteCard cte={cte} total={plan.ctes.length} />