Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,11 @@
},
"devDependencies": {
"@intx/inference": "workspace:*",
"@tailwindcss/vite": "^4.3.3",
"@types/bun": "catalog:",
"@types/react": "^19.2.2",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "^5.0.0",
"sonner": "^2.0.7",
"tailwindcss": "^4.3.3",
"typescript": "catalog:",
"vite": "^7.1.0"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/bench/approvals-insights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function ApprovalsCard({
{capped ? ` Based on the ${runs.length} most recent runs.` : ""}
</p>
{tally.kind === "loading" ? (
<Skeleton className="h-24 w-full" />
<Skeleton className="skeleton-card-lg" />
) : tally.kind === "error" ? (
<p className="bi-sub">Couldn't load approvals.</p>
) : (
Expand Down Expand Up @@ -83,7 +83,7 @@ export function ApprovalsLine({
readonly runs: readonly InsightsRun[];
}) {
const tally = useApprovalTally(tenantId, runs);
if (tally.kind === "loading") return <Skeleton className="h-5 w-full" />;
if (tally.kind === "loading") return <Skeleton className="skeleton-line" />;
if (tally.kind === "error") return <p className="insights-note">Couldn't load approvals.</p>;
return (
<div className="appr-line">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/bench/artifacts-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ArtifactsTab({ workbenchTenantId }: { readonly workbenchTenantId
<h3>Files saved here</h3>
<Link to={benchLink(ARTIFACTS_PATH_PREFIX, workbenchTenantId)}>Open all</Link>
</div>
{page.kind === "loading" ? <Skeleton className="h-16 w-full" /> : null}
{page.kind === "loading" ? <Skeleton className="skeleton-card" /> : null}
{page.kind === "error" ? <p className="workbench-info-empty-note">{page.message}</p> : null}
{page.kind === "ready" && rows.length === 0 ? (
<p className="workbench-info-empty-note">Nothing saved here yet.</p>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/bench/bench-pill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function BenchPill({
>
{worker === undefined ? (
rosterReady ? null : (
<Skeleton className="size-6 rounded-full" />
<Skeleton className="skeleton-avatar" />
)
) : (
<IdentityAvatar kind="agent" name={worker.name} principalId={worker.id} />
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/bench/information-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export function InformationTab({
) : null}

<Section title="Needs you">
{approvalsQuery.kind === "loading" ? <Skeleton className="h-16 w-full" /> : null}
{approvalsQuery.kind === "loading" ? <Skeleton className="skeleton-card" /> : null}
{approvalsQuery.kind === "error" ? (
<p className="workbench-info-empty-note">{approvalsQuery.message}</p>
) : null}
Expand Down Expand Up @@ -283,7 +283,7 @@ export function InformationTab({
title="Active workflows"
action={{ to: benchLink(WORKFLOWS_PATH_PREFIX, workbenchTenantId), label: "View all" }}
>
{flows.isLoading ? <Skeleton className="h-10 w-full" /> : null}
{flows.isLoading ? <Skeleton className="skeleton-row" /> : null}
{flows.isSuccess && liveFlows.length === 0 ? (
<p className="workbench-info-empty-note">No workflows are running here.</p>
) : null}
Expand All @@ -308,7 +308,7 @@ export function InformationTab({
title="Latest artifacts"
action={{ to: benchLink(ARTIFACTS_PATH_PREFIX, workbenchTenantId), label: "View all" }}
>
{artifacts.kind === "loading" ? <Skeleton className="h-10 w-full" /> : null}
{artifacts.kind === "loading" ? <Skeleton className="skeleton-row" /> : null}
{artifacts.kind === "ready" && latest.length === 0 ? (
<p className="workbench-info-empty-note">Nothing saved here yet.</p>
) : null}
Expand All @@ -334,7 +334,7 @@ export function InformationTab({
title="Skills"
action={{ to: benchLink(SKILLS_PATH_PREFIX, workbenchTenantId), label: "View all" }}
>
{skills.isLoading ? <Skeleton className="h-10 w-full" /> : null}
{skills.isLoading ? <Skeleton className="skeleton-row" /> : null}
{skills.isSuccess && shownSkills.length === 0 ? (
<p className="workbench-info-empty-note">No skills here yet.</p>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/bench/insights-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function InsightsTab({ workbenchTenantId }: { readonly workbenchTenantId:
),
});

if (runs.kind === "loading") return <Skeleton className="h-32 w-full" />;
if (runs.kind === "loading") return <Skeleton className="skeleton-panel-sm" />;
if (runs.kind !== "ready") {
return (
<RichEmptyState
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/bench/members-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function MembersTab({
<h3>People</h3>
<Link to="/settings/people">Invite</Link>
</div>
{people.isPending ? <Skeleton className="h-12 w-full" /> : null}
{people.isPending ? <Skeleton className="skeleton-row-lg" /> : null}
{people.isError ? (
<RichEmptyState
title="Couldn't load people"
Expand Down Expand Up @@ -119,7 +119,7 @@ export function MembersTab({
<h3>Workers</h3>
<Link to="/workers">Add</Link>
</div>
{loading ? <Skeleton className="h-12 w-full" /> : null}
{loading ? <Skeleton className="skeleton-row-lg" /> : null}
{!loading && workers.length === 0 ? (
<p className="workbench-info-empty-note">No workers yet.</p>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/bench/workflows-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function WorkflowsTab({ workbenchTenantId }: { readonly workbenchTenantId
<h3>Scheduled here</h3>
<Link to={benchLink(WORKFLOWS_PATH_PREFIX, workbenchTenantId)}>Open all</Link>
</div>
{flows.isLoading ? <Skeleton className="h-16 w-full" /> : null}
{flows.isLoading ? <Skeleton className="skeleton-card" /> : null}
{flows.isError ? (
<p className="workbench-info-empty-note">
{describeApiError(flows.error, "loading workflows")}
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/chat/avatar.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/chat/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const TONE_BY_COLOR: Record<AvatarColor, AvatarTone> = {
};

export const avatarColorClass: Record<AvatarColor, string> = {
"--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;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/confirm-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ConfirmButton({ onConfirm, children, confirmLabel, ...props }: C
return (
<span
ref={rootRef}
className="inline-flex items-center gap-1"
className="inline-actions"
onKeyDown={(e) => {
if (e.key === "Escape") setArmed(false);
}}
Expand Down
170 changes: 170 additions & 0 deletions apps/web/src/components/page-parts.css
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion apps/web/src/lib/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <LucideProvider strokeWidth={2}>{children}</LucideProvider>;
Expand Down
Loading
Loading