diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts index ae57bc259..a259a9f0a 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.test.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts @@ -14,7 +14,6 @@ import * as DesktopClientSettings from "./DesktopClientSettings.ts"; const clientSettings: ClientSettings = { appearanceContrast: 100, - dotMatrixMotion: "smooth", browserDefaultViewport: { _tag: "preset", width: 1024, height: 600, presetId: "nest-hub" }, browserDefaultZoomFactor: 1.25, browserDefaultAppearance: "dark", diff --git a/apps/mobile/generated-uniwind-default-theme-variables.json b/apps/mobile/generated-uniwind-default-theme-variables.json index 26eef52e8..427d370ac 100644 --- a/apps/mobile/generated-uniwind-default-theme-variables.json +++ b/apps/mobile/generated-uniwind-default-theme-variables.json @@ -31,11 +31,6 @@ "--color-danger": "#fef2f2", "--color-danger-border": "rgba(239, 68, 68, 0.12)", "--color-danger-foreground": "#dc2626", - "--color-warning": "#fe9a00", - "--color-warning-foreground": "#bb4d00", - "--color-warning-surface": "#fcf4e8", - "--color-status-active": "#1b4ed8", - "--color-status-info": "#7622bf", "--color-input": "#ffffff", "--color-input-border": "rgba(0, 0, 0, 0.1)", "--color-sidebar-search": "rgba(118, 118, 128, 0.12)", @@ -103,11 +98,6 @@ "--color-danger": "rgba(239, 68, 68, 0.14)", "--color-danger-border": "rgba(248, 113, 113, 0.18)", "--color-danger-foreground": "#fca5a5", - "--color-warning": "#fe9a00", - "--color-warning-foreground": "#ffb900", - "--color-warning-surface": "#312108", - "--color-status-active": "#477cf4", - "--color-status-info": "#e9d5ff", "--color-input": "#141414", "--color-input-border": "rgba(255, 255, 255, 0.08)", "--color-sidebar-search": "rgba(118, 118, 128, 0.24)", diff --git a/apps/mobile/generated-uniwind-themes.css b/apps/mobile/generated-uniwind-themes.css index ce57c6483..6c4102a4d 100644 --- a/apps/mobile/generated-uniwind-themes.css +++ b/apps/mobile/generated-uniwind-themes.css @@ -171,11 +171,6 @@ --color-danger: #fde4f1; --color-danger-border: rgba(247, 8, 108, 0.32); --color-danger-foreground: #9d174d; - --color-warning: #f59e0b; - --color-warning-foreground: #a64c08; - --color-warning-surface: #fcf0ea; - --color-status-active: #0f766e; - --color-status-info: #7622bf; --color-input: #fdfafd; --color-input-border: #e7c1dc; --color-sidebar-search: #f8f8f7; @@ -310,11 +305,6 @@ --color-danger: #331a2b; --color-danger-border: rgba(157, 23, 77, 0.32); --color-danger-foreground: #fbd0e8; - --color-warning: #f59e0b; - --color-warning-foreground: #fbbf24; - --color-warning-surface: #412f20; - --color-status-active: #2dd4bf; - --color-status-info: #e9d5ff; --color-input: #2c2631; --color-input-border: #302029; --color-sidebar-search: #261922; @@ -449,11 +439,6 @@ --color-danger: #f4e7e5; --color-danger-border: rgba(251, 44, 54, 0.32); --color-danger-foreground: #c10007; - --color-warning: #fe9a00; - --color-warning-foreground: #b14800; - --color-warning-surface: #f4f0e1; - --color-status-active: #1d4ed8; - --color-status-info: #7622bf; --color-input: #ecefed; --color-input-border: #becbc5; --color-sidebar-search: #d3dcd8; @@ -588,11 +573,6 @@ --color-danger: #3f2c28; --color-danger-border: rgba(251, 65, 74, 0.32); --color-danger-foreground: #ff6668; - --color-warning: #fe9a00; - --color-warning-foreground: #ffb900; - --color-warning-surface: #3f3a1c; - --color-status-active: #60a5fa; - --color-status-info: #e9d5ff; --color-input: #36413c; --color-input-border: #4f725f; --color-sidebar-search: #45554d; @@ -727,11 +707,6 @@ --color-danger: #f5e6e9; --color-danger-border: rgba(251, 44, 54, 0.32); --color-danger-foreground: #c10007; - --color-warning: #fe9a00; - --color-warning-foreground: #b14800; - --color-warning-surface: #f6efe4; - --color-status-active: #0e7490; - --color-status-info: #7622bf; --color-input: #edeff1; --color-input-border: #c0c9d4; --color-sidebar-search: #d5dbe2; @@ -866,11 +841,6 @@ --color-danger: #3c2630; --color-danger-border: rgba(251, 65, 74, 0.32); --color-danger-foreground: #ff6467; - --color-warning: #fe9a00; - --color-warning-foreground: #ffb900; - --color-warning-surface: #3c3424; - --color-status-active: #22d3ee; - --color-status-info: #e9d5ff; --color-input: #333b45; --color-input-border: #4f677b; --color-sidebar-search: #424e5a; @@ -1005,11 +975,6 @@ --color-danger: #f9e7e6; --color-danger-border: rgba(251, 44, 54, 0.32); --color-danger-foreground: #c10007; - --color-warning: #fe9a00; - --color-warning-foreground: #b24900; - --color-warning-surface: #f9efe2; - --color-status-active: #075985; - --color-status-info: #7622bf; --color-input: #f1efee; --color-input-border: #d4c6c1; --color-sidebar-search: #e2d9d6; @@ -1144,11 +1109,6 @@ --color-danger: #4a2321; --color-danger-border: rgba(251, 65, 74, 0.32); --color-danger-foreground: #ff6467; - --color-warning: #fe9a00; - --color-warning-foreground: #ffb900; - --color-warning-surface: #4b3215; - --color-status-active: #7dd3fc; - --color-status-info: #e9d5ff; --color-input: #433835; --color-input-border: #7a5d4d; --color-sidebar-search: #584943; @@ -1283,11 +1243,6 @@ --color-danger: #f8e6ea; --color-danger-border: rgba(251, 44, 54, 0.32); --color-danger-foreground: #c10007; - --color-warning: #fe9a00; - --color-warning-foreground: #b24800; - --color-warning-surface: #f8efe5; - --color-status-active: #0369a1; - --color-status-info: #7622bf; --color-input: #f0eff2; --color-input-border: #ccc5d6; --color-sidebar-search: #ddd9e3; @@ -1422,11 +1377,6 @@ --color-danger: #40202e; --color-danger-border: rgba(251, 65, 74, 0.32); --color-danger-foreground: #ff6467; - --color-warning: #fe9a00; - --color-warning-foreground: #ffb900; - --color-warning-surface: #412e23; - --color-status-active: #38bdf8; - --color-status-info: #e9d5ff; --color-input: #383443; --color-input-border: #5d527b; --color-sidebar-search: #494459; diff --git a/apps/mobile/global.css b/apps/mobile/global.css index d8b455c18..e6961eac4 100644 --- a/apps/mobile/global.css +++ b/apps/mobile/global.css @@ -57,13 +57,6 @@ --color-danger-border: rgba(239, 68, 68, 0.12); --color-danger-foreground: #dc2626; - /* Thread status */ - --color-warning: #fe9a00; - --color-warning-foreground: #bb4d00; - --color-warning-surface: #fcf4e8; - --color-status-active: #1b4ed8; - --color-status-info: #7622bf; - /* Inputs */ --color-input: #ffffff; --color-input-border: rgba(0, 0, 0, 0.1); @@ -163,13 +156,6 @@ --color-danger-border: rgba(248, 113, 113, 0.18); --color-danger-foreground: #fca5a5; - /* Thread status */ - --color-warning: #fe9a00; - --color-warning-foreground: #ffb900; - --color-warning-surface: #312108; - --color-status-active: #477cf4; - --color-status-info: #e9d5ff; - /* Inputs */ --color-input: #141414; --color-input-border: rgba(255, 255, 255, 0.08); diff --git a/apps/mobile/src/features/threads/thread-list-items.tsx b/apps/mobile/src/features/threads/thread-list-items.tsx index f48abdd8d..76b7a4a75 100644 --- a/apps/mobile/src/features/threads/thread-list-items.tsx +++ b/apps/mobile/src/features/threads/thread-list-items.tsx @@ -453,7 +453,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: { const pressedBackgroundColor = useUniwindTheme()["--color-subtle"]; const selectedBackgroundColor = useUniwindTheme()["--color-user-bubble"]; const selectedForegroundColor = useUniwindTheme()["--color-user-bubble-foreground"]; - const warningForegroundColor = useUniwindTheme()["--color-warning-foreground"]; const { thread, onSelectThread, onArchiveThread, onDeleteThread, onRegenerateThreadTitle } = props; @@ -471,10 +470,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: { const effectivePressedBackground = selected ? themeColorWithAlpha(String(selectedForegroundColor), 0.16) : pressedBackgroundColor; - const usesWarningTreatment = - status?.kind === "pending-approval" || status?.kind === "awaiting-input"; const effectiveStatus = - selected && status && !usesWarningTreatment + selected && status ? { ...status, pillClassName: "bg-user-bubble-foreground/20", @@ -518,17 +515,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { ); const statusPill = effectiveStatus ? ( - - {effectiveStatus.kind === "awaiting-input" ? ( - - ) : null} + {effectiveStatus.label} diff --git a/apps/mobile/src/features/threads/thread-list-v2-items.tsx b/apps/mobile/src/features/threads/thread-list-v2-items.tsx index 21f9108cf..84b0eee7b 100644 --- a/apps/mobile/src/features/threads/thread-list-v2-items.tsx +++ b/apps/mobile/src/features/threads/thread-list-v2-items.tsx @@ -46,15 +46,15 @@ const MONO_FONT = Platform.select({ default: "monospace", }); -// Active work and Plan Ready follow shared theme roles. User-blocking Input -// uses the orange warning treatment instead of operational Waiting's neutral treatment. +// Status hues follow upstream's cross-surface convention. Plan Ready is a +// Pylon-only semantic state and uses upstream's existing violet plan hue. const STATUS_LABEL_BY_STATUS: Partial< Record > = { - approval: { label: "Approval", className: "text-warning-foreground" }, - input: { label: "Input", className: "text-warning-foreground" }, - "plan-ready": { label: "Plan Ready", className: "text-status-info" }, - working: { label: "Working", className: "text-status-active" }, + approval: { label: "Approval", className: "text-adaptive-amber-700-300" }, + input: { label: "Input", className: "text-adaptive-indigo-600-300" }, + "plan-ready": { label: "Plan Ready", className: "text-adaptive-violet-700-300" }, + working: { label: "Working", className: "text-adaptive-sky-600-400" }, failed: { label: "Failed", className: "text-adaptive-red-700-300" }, }; @@ -423,8 +423,6 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { const pressedBackgroundColor = useUniwindTheme()["--color-subtle"]; const selectedBackgroundColor = useUniwindTheme()["--color-user-bubble"]; const pinTintColor = useUniwindTheme()["--color-foreground-muted"]; - const warningForegroundColor = useUniwindTheme()["--color-warning-foreground"]; - const selectedForegroundColor = useUniwindTheme()["--color-user-bubble-foreground"]; const sidebarPane = props.pane === "sidebar"; const selected = props.selected === true; @@ -703,35 +701,16 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { {pinnedRow ? ( ) : null} - {statusLabel ? ( - - {status === "input" ? ( - - ) : null} - - {statusLabel.label} - - - ) : ( - - {timeLabel} - - )} + + {statusLabel?.label ?? timeLabel} + { - it("uses the orange warning treatment for user input", () => { + it("restores distinct upstream approval and input hues", () => { + expect(resolveThreadStatus({ ...baseThread, hasPendingApprovals: true })).toMatchObject({ + kind: "pending-approval", + pillClassName: "bg-adaptive-amber-500-a12-a16", + textClassName: "text-adaptive-amber-700-300", + iconColor: "#ff9f0a", + }); expect(resolveThreadStatus({ ...baseThread, hasPendingUserInput: true })).toMatchObject({ kind: "awaiting-input", - pillClassName: "bg-warning-surface", - textClassName: "text-warning-foreground", + pillClassName: "bg-adaptive-indigo-500-a12-a16", + textClassName: "text-adaptive-indigo-700-300", + iconColor: "#5e5ce6", }); }); - it("uses the purple info role for plan-ready information", () => { + it("uses the upstream violet plan-ready treatment", () => { expect( resolveThreadStatus({ ...baseThread, @@ -35,13 +42,14 @@ describe("resolveThreadStatus", () => { } as EnvironmentThreadShell), ).toMatchObject({ kind: "plan-ready", - pillClassName: "bg-screen", - textClassName: "text-status-info", + pillClassName: "bg-adaptive-violet-500-a12-a16", + textClassName: "text-adaptive-violet-700-300", + iconColor: "#bf5af2", }); }); it.each(["running", "starting"] as const)( - "uses the active theme role while the session is %s", + "uses upstream sky while the session is %s", (status) => { expect( resolveThreadStatus({ @@ -49,9 +57,18 @@ describe("resolveThreadStatus", () => { session: { status }, } as EnvironmentThreadShell), ).toMatchObject({ - pillClassName: "bg-screen", - textClassName: "text-status-active", + pillClassName: "bg-adaptive-sky-500-a12-a16", + textClassName: "text-adaptive-sky-700-300", + iconColor: "#0a84ff", + pulse: true, }); }, ); + + it("retains upstream neutral icon metadata", () => { + expect(THREAD_STATUS_NEUTRAL_ICON).toEqual({ + iconColor: "#8e8e93", + iconBackground: "rgba(142,142,147,0.22)", + }); + }); }); diff --git a/apps/mobile/src/features/threads/threadPresentation.ts b/apps/mobile/src/features/threads/threadPresentation.ts index 1ddda3362..b91c7152a 100644 --- a/apps/mobile/src/features/threads/threadPresentation.ts +++ b/apps/mobile/src/features/threads/threadPresentation.ts @@ -17,10 +17,20 @@ export type ThreadStatusKind = export interface ThreadStatusPresentation extends StatusTone { readonly kind: ThreadStatusKind; + /** Foreground color for the leading status icon. */ + readonly iconColor: string; + /** Background color for the leading status icon circle. */ + readonly iconBackground: string; /** Whether the indicator represents in-flight activity. */ readonly pulse: boolean; } +/** Neutral icon colors for threads with no actionable status. */ +export const THREAD_STATUS_NEUTRAL_ICON = { + iconColor: "#8e8e93", + iconBackground: "rgba(142,142,147,0.22)", +} as const; + function isLatestTurnSettled( latestTurn: OrchestrationLatestTurn | null, session: OrchestrationSession | null, @@ -43,8 +53,10 @@ export function resolveThreadStatus( return { kind: "pending-approval", label: "Needs Approval", - pillClassName: "bg-warning-surface", - textClassName: "text-warning-foreground", + pillClassName: "bg-adaptive-amber-500-a12-a16", + textClassName: "text-adaptive-amber-700-300", + iconColor: "#ff9f0a", + iconBackground: "rgba(255,159,10,0.22)", pulse: false, }; } @@ -53,8 +65,10 @@ export function resolveThreadStatus( return { kind: "awaiting-input", label: "Awaiting Input", - pillClassName: "bg-warning-surface", - textClassName: "text-warning-foreground", + pillClassName: "bg-adaptive-indigo-500-a12-a16", + textClassName: "text-adaptive-indigo-700-300", + iconColor: "#5e5ce6", + iconBackground: "rgba(94,92,230,0.22)", pulse: false, }; } @@ -63,8 +77,10 @@ export function resolveThreadStatus( return { kind: "working", label: "Working", - pillClassName: "bg-screen", - textClassName: "text-status-active", + pillClassName: "bg-adaptive-sky-500-a12-a16", + textClassName: "text-adaptive-sky-700-300", + iconColor: "#0a84ff", + iconBackground: "rgba(10,132,255,0.22)", pulse: true, }; } @@ -73,8 +89,10 @@ export function resolveThreadStatus( return { kind: "connecting", label: "Connecting", - pillClassName: "bg-screen", - textClassName: "text-status-active", + pillClassName: "bg-adaptive-sky-500-a12-a16", + textClassName: "text-adaptive-sky-700-300", + iconColor: "#0a84ff", + iconBackground: "rgba(10,132,255,0.22)", pulse: true, }; } @@ -85,6 +103,8 @@ export function resolveThreadStatus( label: "Error", pillClassName: "bg-adaptive-rose-500-a12-a16", textClassName: "text-adaptive-rose-700-300", + iconColor: "#ff453a", + iconBackground: "rgba(255,69,58,0.22)", pulse: false, }; } @@ -97,8 +117,10 @@ export function resolveThreadStatus( return { kind: "plan-ready", label: "Plan Ready", - pillClassName: "bg-screen", - textClassName: "text-status-info", + pillClassName: "bg-adaptive-violet-500-a12-a16", + textClassName: "text-adaptive-violet-700-300", + iconColor: "#bf5af2", + iconBackground: "rgba(191,90,242,0.22)", pulse: false, }; } diff --git a/apps/mobile/src/lib/mobileTheme.test.ts b/apps/mobile/src/lib/mobileTheme.test.ts index 2ab9cb22d..9c35be022 100644 --- a/apps/mobile/src/lib/mobileTheme.test.ts +++ b/apps/mobile/src/lib/mobileTheme.test.ts @@ -153,7 +153,7 @@ describe("mobile themes", () => { it("maps semantic palette roles onto every mobile color variable", () => { const variables = createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light"); - expect(Object.keys(variables)).toHaveLength(70); + expect(Object.keys(variables)).toHaveLength(65); expect(variables["--color-sheet-solid"]).toBe( themeColorToNativeColor(BUILT_IN_THEMES[0].colors.chrome), ); diff --git a/apps/mobile/src/lib/mobileTheme.ts b/apps/mobile/src/lib/mobileTheme.ts index 86691f224..cd3a52a24 100644 --- a/apps/mobile/src/lib/mobileTheme.ts +++ b/apps/mobile/src/lib/mobileTheme.ts @@ -242,11 +242,6 @@ export function createMobileThemeVariables( "--color-danger": c.errorSurface, "--color-danger-border": withAlpha(c.error, 0.32), "--color-danger-foreground": c.errorForeground, - "--color-warning": c.warning, - "--color-warning-foreground": readableForeground(c.warningForeground, c.sidebar, 4.6), - "--color-warning-surface": c.warningSurface, - "--color-status-active": c.statusActive, - "--color-status-info": c.statusInfo, "--color-input": c.surfaceRaised, "--color-input-border": c.input, "--color-sidebar-search": c.sidebarControlSurface, diff --git a/apps/mobile/src/widgets/AgentActivity.test.ts b/apps/mobile/src/widgets/AgentActivity.test.ts index 806e64eb7..680a6ddb0 100644 --- a/apps/mobile/src/widgets/AgentActivity.test.ts +++ b/apps/mobile/src/widgets/AgentActivity.test.ts @@ -83,10 +83,11 @@ const lightEnvironment = { } as const; describe("AgentActivity widget layout", () => { - it("keeps warning status text readable on light and dark system materials", () => { - expect(contrastRatio("#bb4d00", "#ffffff")).toBeGreaterThanOrEqual(4.5); - expect(contrastRatio("#ffb900", "#000000")).toBeGreaterThanOrEqual(4.5); + it("keeps approval status text readable on light and dark system materials", () => { + expect(contrastRatio("#b45309", "#ffffff")).toBeGreaterThanOrEqual(4.5); + expect(contrastRatio("#fcd34d", "#000000")).toBeGreaterThanOrEqual(4.5); }); + it("tints each row by its own phase using the web sidebar's dark palette", () => { const layout = AgentActivity( { @@ -101,7 +102,7 @@ describe("AgentActivity widget layout", () => { ); const banner = JSON.stringify(layout.banner); expect(banner).toContain("#7dd3fc"); // sky-300: running - expect(banner).toContain("#ffb900"); // warning foreground: waiting_for_approval + expect(banner).toContain("#fcd34d"); // amber-300: waiting_for_approval }); it("switches to the web sidebar's light palette when the scheme is light", () => { @@ -120,9 +121,9 @@ describe("AgentActivity widget layout", () => { ); const banner = JSON.stringify(layout.banner); expect(banner).toContain("#0284c7"); // sky-600: running - expect(banner).toContain("#bb4d00"); // warning foreground: waiting_for_approval + expect(banner).toContain("#b45309"); // amber-700: waiting_for_approval expect(banner).not.toContain("#7dd3fc"); - expect(banner).not.toContain("#ffb900"); + expect(banner).not.toContain("#fcd34d"); }); it("orders rows attention-first in the banner", () => { @@ -176,10 +177,10 @@ describe("AgentActivity widget layout", () => { }, environment as never, ); - expect(JSON.stringify(layout.compactLeading)).toContain("#ffb900"); // warning foreground + expect(JSON.stringify(layout.compactLeading)).toContain("#a5b4fc"); // indigo-300 expect(JSON.stringify(layout.compactTrailing)).toContain("Input"); - expect(JSON.stringify(layout.minimal)).toContain("#ffb900"); - expect(JSON.stringify(layout.minimal)).toContain("exclamationmark.circle.fill"); + expect(JSON.stringify(layout.minimal)).toContain("#a5b4fc"); + expect(JSON.stringify(layout.minimal)).toContain("questionmark.circle.fill"); }); it("deep links the banner to the row that needs attention", () => { diff --git a/apps/mobile/src/widgets/AgentActivity.tsx b/apps/mobile/src/widgets/AgentActivity.tsx index 989eecade..c244e05b4 100644 --- a/apps/mobile/src/widgets/AgentActivity.tsx +++ b/apps/mobile/src/widgets/AgentActivity.tsx @@ -65,11 +65,12 @@ export function AgentActivity( const primaryForeground = "primary"; const secondaryForeground = "secondary"; - // Status tints mirror the semantic web and mobile roles: warning foreground - // for user attention, sky for working, and emerald for completed. On iPhone - // the LA sits on a dark material, but macOS (iPhone Mirroring / Mac - // notification center) renders it on a light one, so each tint must remain - // readable on the corresponding system material. + // Status tints mirror the web sidebar's pills + // (apps/web/src/components/Sidebar.logic.ts resolveThreadStatusPill): amber + // for approval, indigo for input, sky for working, emerald for completed. + // On iPhone the LA sits on a dark material, but macOS (iPhone Mirroring / + // Mac notification center) renders it on a light one — so pick the web + // palette's light (-600) or dark (-300) variant off the color scheme. const isLightScheme = environment.colorScheme === "light"; const phaseTint = (phase: AgentActivityPhase | undefined): string => { if (environment.isLuminanceReduced) { @@ -77,9 +78,9 @@ export function AgentActivity( } switch (phase) { case "waiting_for_approval": - return isLightScheme ? "#bb4d00" : "#ffb900"; + return isLightScheme ? "#b45309" : "#fcd34d"; // amber-700 / amber-300 case "waiting_for_input": - return isLightScheme ? "#bb4d00" : "#ffb900"; + return isLightScheme ? "#4f46e5" : "#a5b4fc"; // indigo-600 / indigo-300 case "failed": return isLightScheme ? "#dc2626" : "#fca5a5"; // red-600 / red-300 case "completed": @@ -161,7 +162,7 @@ export function AgentActivity( case "waiting_for_approval": return "exclamationmark.circle.fill"; case "waiting_for_input": - return "exclamationmark.circle.fill"; + return "questionmark.circle.fill"; case "failed": return "xmark.octagon.fill"; case "completed": diff --git a/apps/web/THIRD_PARTY_NOTICES.md b/apps/web/THIRD_PARTY_NOTICES.md index 95d3c9238..c9a675ef4 100644 --- a/apps/web/THIRD_PARTY_NOTICES.md +++ b/apps/web/THIRD_PARTY_NOTICES.md @@ -9,15 +9,3 @@ Copyright (c) 2016 Roberto Huertas Licensed under the MIT License. The full license text is available in the upstream repository: . - -## assistant-ui Dot Matrix - -The Dot Matrix state patterns in `src/components/ui/dot-matrix.tsx` are adapted -from assistant-ui's [standalone Dot Matrix](https://www.assistant-ui.com/standalone/dot-matrix) -component. Pylon adds queued, terminal, and orchestration states and offers -smooth or stepped animation timing for status surfaces. - -Copyright (c) AgentbaseAI Inc. - -Licensed under the MIT License. The full license text is available in the -upstream repository: . diff --git a/apps/web/src/components/AgentsPanel.test.tsx b/apps/web/src/components/AgentsPanel.test.tsx index ffa6284e9..29764cfd1 100644 --- a/apps/web/src/components/AgentsPanel.test.tsx +++ b/apps/web/src/components/AgentsPanel.test.tsx @@ -86,7 +86,7 @@ describe("AgentsPanel agent cancellation", () => { expect(markup).toContain("Working"); }); - it("keeps queued, running, waiting, and settled agent states distinct", () => { + it("keeps truthful labels while rendering upstream static status dots", () => { const statuses: RuntimeSubagent["status"][] = [ "pending", "running", @@ -102,17 +102,32 @@ describe("AgentsPanel agent cancellation", () => { }; const markup = renderToStaticMarkup(); - expect(markup).toContain('data-state="orchestrating"'); - expect(markup).toContain('data-state="queued"'); - expect(markup).toContain('data-state="waiting"'); - expect(markup).toContain('data-state="paused"'); - expect(markup).toContain('data-state="success"'); - expect(markup).toContain('data-state="error"'); - expect(markup).toContain('data-state="stopped"'); + expect(markup.match(/bg-info/g)).toHaveLength(3); + expect(markup).toContain("bg-muted-foreground/50"); + expect(markup).toContain("bg-muted-foreground/60"); + expect(markup).toContain("bg-success"); + expect(markup).toContain("bg-destructive"); + expect(markup).not.toContain('data-slot="dot-matrix"'); expect(markup).toContain("Queued"); expect(markup).toContain("Waiting"); }); + it("keeps active status labels visible when an activity detail is present", () => { + const statusModel = { + ...model, + directAgents: [ + { ...agent("agent-pending", "Pending reviewer", "pending"), progress: "Waiting for slot" }, + { ...agent("agent-running", "Running reviewer", "running"), lastToolName: "ipython" }, + { ...agent("agent-waiting", "Waiting reviewer", "waiting"), progress: "Awaiting review" }, + ], + }; + const markup = renderToStaticMarkup(); + + expect(markup).toContain("Queued · Waiting for slot"); + expect(markup).toContain("Working · ▸ ipython"); + expect(markup).toContain("Waiting · Awaiting review"); + }); + it("keeps every agent row read-only without an advertised capability", () => { const markup = renderToStaticMarkup(); expect(markup).not.toContain('aria-label="Stop '); diff --git a/apps/web/src/components/AgentsPanel.tsx b/apps/web/src/components/AgentsPanel.tsx index 92679b749..c098b0f2a 100644 --- a/apps/web/src/components/AgentsPanel.tsx +++ b/apps/web/src/components/AgentsPanel.tsx @@ -9,8 +9,7 @@ * changing data must never change their height. * - Workflow expansion is presentation state. A live run stays expanded when * it settles; older collapsed runs can still be opened at run granularity. - * - Status reads through the shared DotMatrix language, with DOM-write - * elapsed timers and plain token counters. + * - Static status dots, DOM-write elapsed timers, plain token counters. */ import { useAtomValue } from "@effect/atom-react"; import type { @@ -67,28 +66,27 @@ import { DialogTitle, } from "~/components/ui/dialog"; import { Textarea } from "~/components/ui/textarea"; -import { DotMatrix, type DotMatrixState } from "./ui/dot-matrix"; import { AgentLiveActivity } from "./AgentLiveActivity"; -/** - * Agent status is a truth-bearing visual contract: the orbit is reserved for - * running subagents, while queued and waiting agents use their own patterns. - * Settled outcomes stay static except for attention states such as failure. - */ -const STATUS_VISUALS: Record = - { - pending: { matrix: "queued", label: "Queued" }, - running: { matrix: "orchestrating", label: "Working" }, - waiting: { matrix: "waiting", label: "Waiting" }, - idle: { matrix: "paused", label: "Idle · resumable" }, - completed: { matrix: "success", label: "Completed" }, - failed: { matrix: "error", label: "Failed" }, - cancelled: { matrix: "stopped", label: "Stopped" }, - interrupted: { matrix: "stopped", label: "Stopped" }, - }; +/** Status truth stays in labels; the marker follows upstream's static dot vocabulary. */ +const STATUS_VISUALS: Record = { + pending: { dotClass: "bg-info", label: "Queued" }, + running: { dotClass: "bg-info", label: "Working" }, + waiting: { dotClass: "bg-info", label: "Waiting" }, + idle: { dotClass: "bg-muted-foreground/50", label: "Idle · resumable" }, + completed: { dotClass: "bg-success", label: "Completed" }, + failed: { dotClass: "bg-destructive", label: "Failed" }, + cancelled: { dotClass: "bg-muted-foreground/60", label: "Stopped" }, + interrupted: { dotClass: "bg-muted-foreground/60", label: "Stopped" }, +}; function StatusDot({ status }: { status: RuntimeSubagent["status"] }) { - return ; + return ( + + ); } function formatElapsedSeconds(totalSeconds: number): string { @@ -220,9 +218,7 @@ function AgentRow({ const liveActivityAvailable = liveActivityEligible && active; return ( - // The marker track keeps a 14px alignment frame around the standard 12px - // Dot Matrix so the glyph cannot bleed into the title column. -
+
@@ -294,12 +290,11 @@ function AgentRow({ agent.status === "failed" ? "text-destructive-foreground" : "text-muted-foreground", )} > - {activity ?? visuals.label} + {activity ? `${visuals.label} · ${activity}` : visuals.label} {metadata.join(" · ")} - {visuals.label}
); } diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 350d5466b..873676739 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -428,7 +428,6 @@ import { AlertDialogTitle, } from "./ui/alert-dialog"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; -import { DotMatrix } from "./ui/dot-matrix"; import { ServerUpdateAction, ServerUpdateProgress } from "./ServerUpdateAction"; import { buildVersionMismatchDismissalKey, @@ -2299,7 +2298,12 @@ function ChatViewContent(props: ChatViewProps) { variant: "default", // Live connection status: calm styling, but it must front the stack. urgent: true, - icon: , + icon: ( +