Workers
Add
- {loading ? : null}
+ {loading ? : null}
{!loading && workers.length === 0 ? (
No workers yet.
) : null}
diff --git a/apps/web/src/bench/workflows-tab.tsx b/apps/web/src/bench/workflows-tab.tsx
index b8ff6dacfc..a25a81ba15 100644
--- a/apps/web/src/bench/workflows-tab.tsx
+++ b/apps/web/src/bench/workflows-tab.tsx
@@ -26,7 +26,7 @@ export function WorkflowsTab({ workbenchTenantId }: { readonly workbenchTenantId
Scheduled here
Open all
- {flows.isLoading ? : null}
+ {flows.isLoading ? : null}
{flows.isError ? (
{describeApiError(flows.error, "loading workflows")}
diff --git a/apps/web/src/chat/avatar.css b/apps/web/src/chat/avatar.css
index fc992bd591..de4e09f419 100644
--- a/apps/web/src/chat/avatar.css
+++ b/apps/web/src/chat/avatar.css
@@ -17,6 +17,23 @@
color: var(--canvas);
}
+.avatar-fill-1 {
+ background: var(--avatar-1);
+ color: black;
+}
+.avatar-fill-2 {
+ background: var(--avatar-2);
+ color: black;
+}
+.avatar-fill-3 {
+ background: var(--avatar-3);
+ color: black;
+}
+.avatar-fill-4 {
+ background: var(--avatar-4);
+ color: black;
+}
+
/* The status dot is the only round non-initials child. */
.wb-av > span.rounded-full {
width: 8px;
diff --git a/apps/web/src/chat/avatar.tsx b/apps/web/src/chat/avatar.tsx
index 637ef79784..3df3972b49 100644
--- a/apps/web/src/chat/avatar.tsx
+++ b/apps/web/src/chat/avatar.tsx
@@ -27,10 +27,10 @@ const TONE_BY_COLOR: Record = {
};
export const avatarColorClass: Record = {
- "--avatar-1": "bg-(--avatar-1) text-black",
- "--avatar-2": "bg-(--avatar-2) text-black",
- "--avatar-3": "bg-(--avatar-3) text-black",
- "--avatar-4": "bg-(--avatar-4) text-black",
+ "--avatar-1": "avatar-fill-1",
+ "--avatar-2": "avatar-fill-2",
+ "--avatar-3": "avatar-fill-3",
+ "--avatar-4": "avatar-fill-4",
};
const WORKER_HUES = ["orange", "blue", "green"] as const;
diff --git a/apps/web/src/components/confirm-button.tsx b/apps/web/src/components/confirm-button.tsx
index 6dae6086ba..7a19d4fb9a 100644
--- a/apps/web/src/components/confirm-button.tsx
+++ b/apps/web/src/components/confirm-button.tsx
@@ -21,7 +21,7 @@ export function ConfirmButton({ onConfirm, children, confirmLabel, ...props }: C
return (
{
if (e.key === "Escape") setArmed(false);
}}
diff --git a/apps/web/src/components/page-parts.css b/apps/web/src/components/page-parts.css
new file mode 100644
index 0000000000..d5376a8956
--- /dev/null
+++ b/apps/web/src/components/page-parts.css
@@ -0,0 +1,170 @@
+/* The frame every routed page shares: a column that fills the stage, with
+ one scrolling body. */
+.page-frame {
+ display: flex;
+ height: 100%;
+ min-height: 0;
+ flex-direction: column;
+}
+
+.page-scroll {
+ min-height: 0;
+ flex: 1;
+ overflow-y: auto;
+}
+
+.page-scroll-auto {
+ min-height: 0;
+ flex: 1;
+ overflow: auto;
+}
+
+.page-scroll-column {
+ display: flex;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
+ overflow-y: auto;
+}
+
+.page-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.page-note {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+}
+
+.page-error {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--destructive);
+}
+
+.page-error-inline {
+ margin-bottom: 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--destructive);
+}
+
+.page-meta {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--muted-foreground);
+}
+
+.inline-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.inline-row {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+/* Icons default to the surrounding text size; `:where` keeps zero
+ specificity so a sized icon class still wins. */
+:where(svg.lucide) {
+ width: 1em;
+ height: 1em;
+}
+
+.icon-sm {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+/* Loading placeholders, sized by the content they stand in for. */
+.skeleton-line {
+ width: 100%;
+ height: 1.25rem;
+}
+.skeleton-row {
+ width: 100%;
+ height: 2.5rem;
+}
+.skeleton-row-lg {
+ width: 100%;
+ height: 3rem;
+}
+.skeleton-card {
+ width: 100%;
+ height: 4rem;
+}
+.skeleton-card-lg {
+ width: 100%;
+ height: 6rem;
+}
+.skeleton-panel-sm {
+ width: 100%;
+ height: 8rem;
+}
+.skeleton-panel {
+ width: 100%;
+ height: 10rem;
+}
+.skeleton-panel-lg {
+ width: 100%;
+ height: 12rem;
+}
+.skeleton-avatar {
+ width: 1.5rem;
+ height: 1.5rem;
+ border-radius: 9999px;
+}
+.skeleton-avatar-lg {
+ width: 2rem;
+ height: 2rem;
+ border-radius: 9999px;
+}
+.skeleton-name {
+ width: 5rem;
+ height: 1rem;
+}
+.skeleton-stat {
+ width: 4rem;
+ height: 26px;
+}
+
+.page-meta-plain {
+ font-size: 0.75rem;
+ line-height: 1rem;
+}
+
+.page-error-list {
+ list-style: inside disc;
+}
+
+.code-textarea {
+ font-family: var(--font-mono);
+ font-size: 0.8125rem;
+ line-height: 1.5;
+}
+
+.code-textarea-canvas {
+ background: var(--background);
+}
+
+.code-textarea-tall {
+ min-height: 320px;
+}
diff --git a/apps/web/src/lib/icons/index.tsx b/apps/web/src/lib/icons/index.tsx
index 18fc374c0f..d73cf8e36e 100644
--- a/apps/web/src/lib/icons/index.tsx
+++ b/apps/web/src/lib/icons/index.tsx
@@ -88,7 +88,7 @@ export {
} from "lucide-react";
/** Wraps a subtree so every icon under it defaults to a 2px stroke without
- * repeating it at each call site (the `1em` default size is `tailwind.css`).
+ * repeating it at each call site (the `1em` default size is `page-parts.css`).
* Mounted once at each app's root (see `apps/web/src/app.tsx`). */
export function BoldIconProvider({ children }: { children: ReactNode }) {
return {children};
diff --git a/apps/web/src/library/artifact-renderer.css b/apps/web/src/library/artifact-renderer.css
new file mode 100644
index 0000000000..d3861baea9
--- /dev/null
+++ b/apps/web/src/library/artifact-renderer.css
@@ -0,0 +1,61 @@
+.artifact-doc {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.625;
+ color: var(--foreground);
+}
+
+.artifact-doc-heading {
+ font-weight: 600;
+ line-height: 1.375;
+}
+
+.artifact-doc-bullets {
+ list-style: disc;
+ padding-left: 1.25rem;
+}
+
+.artifact-pdf {
+ border-radius: var(--ui-radius-md);
+ border: 1px solid var(--border);
+ background: var(--card);
+ padding: 1.5rem;
+ box-shadow:
+ 0 1px 3px 0 rgb(0 0 0 / 0.1),
+ 0 1px 2px -1px rgb(0 0 0 / 0.1);
+}
+
+.artifact-pdf-title {
+ margin-bottom: 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 600;
+}
+
+.artifact-pdf-text {
+ white-space: pre-wrap;
+ font-size: 0.875rem;
+ line-height: 1.625;
+ color: var(--foreground);
+}
+
+.artifact-html-frame {
+ width: 100%;
+ height: 100%;
+ min-height: 24rem;
+ border: 0;
+}
+
+.artifact-unsupported {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 2rem 0;
+ text-align: center;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+}
diff --git a/apps/web/src/library/artifact-renderer.tsx b/apps/web/src/library/artifact-renderer.tsx
index f0a0e36bbf..513332dbd1 100644
--- a/apps/web/src/library/artifact-renderer.tsx
+++ b/apps/web/src/library/artifact-renderer.tsx
@@ -3,6 +3,7 @@
import { CsvTable } from "@corbits/react-ui";
import { FileDashed } from "@/lib/icons";
+import "./artifact-renderer.css";
import type { ArtifactRendererKind } from "./renderer-kind";
export type ArtifactRenderProps = {
@@ -76,19 +77,19 @@ function DocRenderer({
const lines = parseDocLines(content);
const HeadingTag = ["h1", "h2", "h3"] as const;
return (
-
+
{lines.map((line, index) => {
if (line.kind === "heading") {
const Tag = HeadingTag[line.level - 1] ?? "h3";
return (
-
+
{line.text}
);
}
if (line.kind === "bullet") {
return (
-
+
);
@@ -143,9 +144,9 @@ function PdfRenderer({
);
}
return (
-
-
{title}
-
{content}
+
);
}
@@ -170,19 +171,19 @@ function HtmlPreviewRenderer({
title={`${title} preview`}
src={previewSrc}
sandbox="allow-scripts"
- className="h-full min-h-[24rem] w-full border-0"
+ className="artifact-html-frame"
/>
);
}
function EmptyContent({ message }: { readonly message: string }) {
- return
{message}
;
+ return
{message}
;
}
function UnsupportedRenderer({ unavailableReason }: { readonly unavailableReason?: string }) {
return (
-
-
+
+
{unavailableReason ?? "No inline preview for this artifact."}
);
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 5c777d337a..05aa8325fe 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -4,7 +4,7 @@
import "@corbits/react-ui/styles.css";
import "./app.css";
-import "./tailwind.css";
+import "./components/page-parts.css";
import "./theme-tokens.css";
import { StrictMode } from "react";
diff --git a/apps/web/src/pages/bench-insights.tsx b/apps/web/src/pages/bench-insights.tsx
index 95c711ff7d..9a60971885 100644
--- a/apps/web/src/pages/bench-insights.tsx
+++ b/apps/web/src/pages/bench-insights.tsx
@@ -217,7 +217,7 @@ export function BenchInsights({
let body: ReactNode;
if (runs.kind === "loading") {
- body =
;
+ body =
;
} else if (runs.kind !== "ready") {
body = (
+
-
+
{body}
diff --git a/apps/web/src/pages/create-agent-panel.css b/apps/web/src/pages/create-agent-panel.css
new file mode 100644
index 0000000000..cf51653d9f
--- /dev/null
+++ b/apps/web/src/pages/create-agent-panel.css
@@ -0,0 +1,18 @@
+.create-agent-servers {
+ margin-top: 0.75rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.create-agent-server-list {
+ margin-top: 0.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.create-agent-server-name {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
diff --git a/apps/web/src/pages/create-agent-panel.tsx b/apps/web/src/pages/create-agent-panel.tsx
index 1013967a42..f506da1d8e 100644
--- a/apps/web/src/pages/create-agent-panel.tsx
+++ b/apps/web/src/pages/create-agent-panel.tsx
@@ -19,6 +19,7 @@ import { ApiQueryError } from "@/lib/api-query";
import type { DeployedAgent } from "../agent-deploy";
import { useDeployAgentMutation } from "../agents-api";
import { useMcpServers } from "../tools/mcp-servers-query";
+import "./create-agent-panel.css";
function submitErrorFromCause(
cause: unknown,
@@ -124,12 +125,12 @@ export function CreateAgentPanel({
{submitError !== null && (
-
+
{submitError.message}
{submitError.refId !== undefined ? (
<>
- Reference: {submitError.refId}
+ Reference: {submitError.refId}
>
) : null}
@@ -149,7 +150,7 @@ export function CreateAgentPanel({
System prompt