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
+18 -1
View File
@@ -1,6 +1,23 @@
import { describe, expect, it } from "vitest";
import { backendBaseUrl, joinBackendPath, resolveBackendUrl } from "./runtime-config";
import { backendBaseUrl, joinBackendPath, resolveBackendUrl, resolveShellConfig, type RuntimeConfig } from "./runtime-config";
describe("resolveShellConfig", () => {
it("preserves legacy embedded Omics configuration", () => {
expect(resolveShellConfig({})).toEqual({ mode: "embedded", defaultLocale: "en", adapter: "omics-portal" });
expect(resolveShellConfig({ shell: { mode: "embedded", defaultLocale: "it" } }).adapter).toBe("omics-portal");
});
it("full does not use an adapter", () => {
expect(resolveShellConfig({ shell: { mode: "full", defaultLocale: "en", adapter: "omics-portal" } })).toEqual({ mode: "full", defaultLocale: "en" });
});
it.each([
{ mode: "other", defaultLocale: "en" },
{ mode: "full", defaultLocale: "" },
{ mode: "embedded", defaultLocale: "en", adapter: "unknown" },
])("rejects invalid shell configuration %j", shell => {
expect(() => resolveShellConfig({ shell } as RuntimeConfig)).toThrow();
});
});
describe("resolveBackendUrl", () => {
it("uses same-origin /api by default", () => {
+24
View File
@@ -1,5 +1,29 @@
export interface RuntimeConfig {
backendBaseUrl?: string;
shell?: ShellConfig;
}
export interface ShellConfig {
mode: "full" | "embedded";
defaultLocale: string;
adapter?: "omics-portal";
}
export function resolveShellConfig(config: RuntimeConfig | undefined = window.__THOTHII_CONFIG__): ShellConfig {
const shell = config?.shell;
if (shell === undefined) return { mode: "embedded", defaultLocale: "en", adapter: "omics-portal" };
if (!shell || (shell.mode !== "full" && shell.mode !== "embedded")) {
throw new Error("Invalid shell mode. Use full or embedded.");
}
if (typeof shell.defaultLocale !== "string" || !shell.defaultLocale.trim()) {
throw new Error("A default interface language is required.");
}
if (shell.adapter !== undefined && shell.adapter !== "omics-portal") {
throw new Error("Unknown portal adapter. Check the installation configuration.");
}
return shell.mode === "full"
? { mode: "full", defaultLocale: shell.defaultLocale }
: { ...shell, adapter: "omics-portal" };
}
declare global {
+27
View File
@@ -7,6 +7,30 @@ import {
deleteSession, getSessionDocuments,
} from "./sessions";
import { workspacePreferences } from "../workspaces/preferences";
import { setLocale } from "../i18n";
afterEach(() => setLocale("en"));
test("new sessions capture the selected interaction language before asynchronous preparation", async () => {
workspacePreferences.save({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
});
let body: Record<string, unknown> | undefined;
server.use(
http.get("/api/workspaces", () => HttpResponse.json([
workspaceSummaryFixture("psd-clinical", { revision: workspaceRevisionFixture("psd-clinical") }),
])),
http.post("/api/sessions", async ({ request }) => {
body = await request.json() as Record<string, unknown>;
return HttpResponse.json({ id: "italian-session" });
}),
);
setLocale("it-IT");
const creation = createSession({ question: "Pazienti con fibrillazione" });
setLocale("en");
await creation;
expect(body?.interactionLanguage).toBe("it");
});
test("createSession seeds ephemeral selections and posts them", async () => {
localStorage.clear();
@@ -33,6 +57,7 @@ test("createSession seeds ephemeral selections and posts them", async () => {
expect(await createSession({ question: "q" })).toEqual({ id: "s1" });
expect(body).toEqual({
question: "q", workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
interactionLanguage: "en",
});
expect(workspacePreferences.load()).toEqual({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
@@ -138,6 +163,7 @@ test("createSession replaces a stale ephemeral workspace with the current instal
expect(body).toEqual({
question: "q", workspaceId: "psd-clinical",
provider: "zai", model: "glm-5.2", thinking: "low",
interactionLanguage: "en",
});
expect(workspacePreferences.load()).toEqual({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
@@ -161,6 +187,7 @@ test("createSession preserves a local legacy selection when the registry is empt
expect(body).toEqual({
question: "q", workspaceId: "legacy-workspace",
provider: "zai", model: "glm-5.2", thinking: "low",
interactionLanguage: "en",
});
});
+3 -1
View File
@@ -1,4 +1,5 @@
import { apiFetch } from "./client";
import { getLocale } from "../i18n";
import {
captureAuthOperation,
isAuthOperationCurrent,
@@ -93,6 +94,7 @@ async function ensureWorkspaceSelectionPolicy(precondition?: AuthOperationPrecon
}
export async function createSession(i: NewSessionInput, precondition?: AuthOperationPrecondition) {
const interactionLanguage = getLocale();
const initiatingOperation = captureAuthOperation();
const effectivePrecondition = precondition ?? (initiatingOperation ? {
operation: initiatingOperation,
@@ -108,7 +110,7 @@ export async function createSession(i: NewSessionInput, precondition?: AuthOpera
requireAuthOperationPrecondition(effectivePrecondition);
return apiFetch<{ id: string }>("/sessions", {
method: "POST",
body: JSON.stringify({ ...i, ...selection }),
body: JSON.stringify({ ...i, ...selection, interactionLanguage }),
});
}
+2
View File
@@ -1,6 +1,8 @@
export interface WidgetOption {
id: string;
label: string;
/** Present only for harness-authored deterministic chrome; model prose has no key. */
label_i18n?: string;
detail?: string;
rationale?: string;
meta?: Record<string, unknown>;