fix(ui): match Omics header and compact workspace layout

This commit is contained in:
Codex
2026-09-13 15:36:08 +02:00
parent 648434a32e
commit 7d826e46c0
12 changed files with 134 additions and 12 deletions
+25 -3
View File
@@ -230,6 +230,9 @@ body.thot-full-document { margin: 0; }
}
.thot-host > .thot-context-layout { flex: 1; min-height: 0; height: auto; }
.thot-full-header {
/* Match Omics static/css/gsd-theme.css --gsd-red-primary in both themes. */
--thot-header-background: #cb333b;
--thot-header-foreground: oklch(99.85% 0.0006 17.2);
display: flex;
flex: none;
flex-wrap: wrap;
@@ -238,13 +241,32 @@ body.thot-full-document { margin: 0; }
gap: 0.5rem 1rem;
min-height: 3.75rem;
padding: 0.625rem var(--thot-shell-gutter);
border-bottom: 1px solid oklch(var(--border));
background: oklch(var(--card));
border-bottom: 1px solid var(--thot-header-background);
background: var(--thot-header-background);
color: var(--thot-header-foreground);
}
.thot-full-header__brand { font: 600 1.25rem/1.2 var(--font-heading); }
.thot-full-header__controls { display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem; min-width: 0; }
.thot-full-header__user { max-width: 15rem; min-width: 0; font-size: var(--text-control); }
.thot-full-header__error { flex-basis: 100%; font-size: var(--text-control); color: oklch(var(--destructive)); }
.thot-full-header__error { flex-basis: 100%; font-size: var(--text-control); color: inherit; }
.thot-full-header__controls button { color: var(--thot-header-foreground); }
.thot-full-header__controls button:is(:hover, [aria-expanded="true"]) {
color: var(--thot-header-foreground);
background: color-mix(in srgb, var(--thot-header-foreground) 14%, transparent);
}
.thot-full-header :is(button, select):focus-visible {
outline: 2px solid var(--thot-header-foreground);
outline-offset: 2px;
}
.thot-full-header .thot-shell-select {
color: var(--thot-header-foreground);
background: transparent;
border-color: color-mix(in srgb, var(--thot-header-foreground) 65%, transparent);
}
.thot-full-header .thot-shell-select option {
color: oklch(var(--foreground));
background: oklch(var(--card));
}
.thot-shell-select {
min-height: 2rem;
max-width: 9rem;
@@ -128,6 +128,8 @@ test("option A has one collapsible global context and preserves the Core draft a
await userEvent.click(screen.getByRole("button", { name: /Working context/ }));
expect(screen.getAllByRole("combobox", { name: "Workspace" })).toHaveLength(1);
expect(screen.getAllByRole("combobox", { name: "Model" })).toHaveLength(1);
expect(screen.getByRole("button", { name: "Done" }).closest(".thot-context-fields"))
.toBe(screen.getByRole("combobox", { name: "Model" }).closest(".thot-context-fields"));
await userEvent.selectOptions(screen.getByRole("combobox", { name: "Workspace" }), "beta");
expect(workspacePreferences.load()).toMatchObject({ workspaceId: "beta", provider: "test", model: "test" });
await userEvent.click(screen.getByRole("button", { name: "Done" }));
@@ -190,6 +190,8 @@ test("administrators can explicitly switch to all sessions and see owners", asyn
expect(mySessions).toHaveAttribute("tabindex", "0");
expect(mySessions).toHaveAttribute("data-tab-state", "active");
expect(mySessions).toHaveClass("bg-[oklch(var(--nav-active))]");
expect(mySessions).toHaveClass("border-b-0");
expect(allSessions).toHaveClass("border-b-0");
expect(allSessions).toHaveAttribute("aria-selected", "false");
expect(allSessions).toHaveAttribute("aria-controls", "session-scope-panel");
expect(allSessions).toHaveAttribute("tabindex", "-1");
+1 -1
View File
@@ -61,7 +61,7 @@ interface AppShellProps {
const SESSION_SCOPES: readonly SessionScope[] = ["mine", "all"];
function sessionScopeTabClass(selected: boolean): string {
const base = "relative -mb-px flex h-8 w-full cursor-pointer items-center justify-center rounded-t-md border border-b-2 px-2 text-xs font-semibold tracking-[0.005em] outline-none focus-visible:z-10 focus-visible:ring-3 focus-visible:ring-[oklch(var(--nav-active-border)/0.28)]";
const base = "relative -mb-px flex h-8 w-full cursor-pointer items-center justify-center rounded-t-md border border-b-0 px-2 text-xs font-semibold tracking-[0.005em] outline-none focus-visible:z-10 focus-visible:ring-3 focus-visible:ring-[oklch(var(--nav-active-border)/0.28)]";
return selected
? `${base} border-[oklch(var(--nav-active-border))] bg-[oklch(var(--nav-active))] text-[oklch(var(--nav-active-foreground))] hover:bg-[oklch(var(--nav-active-hover))]`
: `${base} border-border bg-card text-muted-foreground hover:border-muted-foreground/45 hover:bg-muted/55 hover:text-foreground`;
+3 -3
View File
@@ -10,13 +10,13 @@
.thot-context-trigger[aria-expanded=true] .thot-context-action > svg:last-child { transform: rotate(180deg); }
.thot-context-body { margin: 0 var(--thoth-content-gutter) var(--thoth-content-gutter); padding: var(--thoth-content-padding); background: oklch(var(--card)); }
.thot-context-body[hidden] { display: none; }
.thot-context-fields { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 24px; }
.thot-context-fields { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)) auto; align-items: end; gap: 16px; }
.thot-context-fields label { display: grid; min-width: 0; gap: 8px; font-size: var(--text-control); }
.thot-context-fields label > span { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.thot-context-fields select { min-width: 0; width: 100%; padding: 9px 12px; border: 1px solid oklch(var(--border)); border-radius: 6px; background: oklch(var(--card)); }
.thot-context-fields select { min-width: 0; width: 100%; height: 32px; padding: 4px 12px; border: 1px solid oklch(var(--border)); border-radius: 6px; background: oklch(var(--card)); }
.thot-context-body > p[role] { color: oklch(var(--muted-foreground)); font-size: .75rem; line-height: 1.6; }
.thot-context-body > p[role] { margin-top: 12px; }
.thot-context-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
.thot-context-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.thot-context-shelf :is(button,select):focus-visible { outline: 2px solid oklch(var(--ring)); outline-offset: 3px; }
.thot-context-shelf select:disabled { opacity: .65; }
@container working-context (max-width: 38rem) {
+1 -1
View File
@@ -31,11 +31,11 @@ export function WorkingContextShelf({ context, locked, onWorkspace, onModel }: {
<option value="" disabled>{translate("Choose a model")}</option>{context.models.map(model => <option key={`${model.provider}/${model.id}`} value={`${model.provider}/${model.id}`}>{model.name}</option>)}
</select>
</label>
<div className="thot-context-actions">{context.error && <Button variant="outline" onClick={context.retry}>{translate("Retry context")}</Button>}{context.ready && <Button variant="outline" onClick={() => setExpanded(false)}>{translate("Done")}</Button>}</div>
</div>
{locked && <p role="status">{translate("Workspace and model are locked until the current operation finishes.")}</p>}
{context.ready && context.error && <p role="status">{translate("Context refresh failed. Your current choices and unsaved work have been retained.")}</p>}
{!context.ready && <p role="status">{context.loading ? translate("Loading working context…") : context.error ? translate("Working context is unavailable. Retry or ask the installation operator to check configuration.") : translate("Select an available workspace and model to enable Core and Administration. Unavailable saved choices are not replaced automatically.")}</p>}
<div className="thot-context-footer">{context.error && <Button variant="outline" onClick={context.retry}>{translate("Retry context")}</Button>}{context.ready && <Button variant="outline" onClick={() => setExpanded(false)}>{translate("Done")}</Button>}</div>
</div>
</section>;
}
@@ -105,6 +105,7 @@
grid-area: status;
min-width: 0;
padding-inline: var(--fleet-summary-inset);
padding-top: 1rem;
}
.thot-fleet-ledger .thot-administration-header,
+1 -1
View File
@@ -38,7 +38,7 @@ export function FullHeader({ onLogout }: { onLogout?: () => Promise<void> }) {
return (
<header className="thot-full-header" aria-label={t("Application header")}>
<span className="thot-full-header__brand">Thoth<span className="text-primary">II</span></span>
<span className="thot-full-header__brand">Thoth<span>II</span></span>
<div className="thot-full-header__controls">
<label className="sr-only" htmlFor="thot-ui-language">{t("Interface language")}</label>
<select id="thot-ui-language" className="thot-shell-select" value={shell.locale} onChange={event => shell.changeLocale(event.target.value)}>