From 88b8883122bdeb31528d9c8937308e8fd956b9c6 Mon Sep 17 00:00:00 2001 From: James Long Date: Thu, 16 Jul 2026 16:40:34 -0400 Subject: [PATCH] feat(tui): resolve action state priority (#37373) --- .../src/routes/session/composer/shell-tab.tsx | 6 +++--- .../routes/session/composer/subagents-tab.tsx | 17 +++++++++++------ packages/tui/src/theme/v2/component.ts | 9 +++++++-- packages/tui/src/theme/v2/schema.ts | 2 +- packages/tui/test/theme/v2/component.test.ts | 13 +++++++++++++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/tui/src/routes/session/composer/shell-tab.tsx b/packages/tui/src/routes/session/composer/shell-tab.tsx index 00dd83d380..9d44480740 100644 --- a/packages/tui/src/routes/session/composer/shell-tab.tsx +++ b/packages/tui/src/routes/session/composer/shell-tab.tsx @@ -1,6 +1,6 @@ import { createMemo, For, Show, createEffect, onMount, onCleanup } from "solid-js" import { createStore } from "solid-js/store" -import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core" +import { TextAttributes, ScrollBoxRenderable } from "@opentui/core" import { useData } from "../../../context/data" import { useLocation } from "../../../context/location" import { useClient } from "../../../context/client" @@ -105,11 +105,11 @@ export function ShellTab(props: { sessionID: string }) { flexDirection="row" paddingLeft={1} paddingRight={1} - backgroundColor={active() ? themeV2.background.action.primary("selected") : RGBA.fromInts(0, 0, 0, 0)} + backgroundColor={themeV2.background.action.primary({ focused: active() })} onMouseOver={() => setStore("selected", index())} > diff --git a/packages/tui/src/routes/session/composer/subagents-tab.tsx b/packages/tui/src/routes/session/composer/subagents-tab.tsx index f96cb2898c..27e117754d 100644 --- a/packages/tui/src/routes/session/composer/subagents-tab.tsx +++ b/packages/tui/src/routes/session/composer/subagents-tab.tsx @@ -1,6 +1,6 @@ import { createMemo, For, Show, createEffect, onMount, onCleanup } from "solid-js" import { createStore } from "solid-js/store" -import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core" +import { TextAttributes, ScrollBoxRenderable } from "@opentui/core" import { useRoute, useRouteData } from "../../../context/route" import { useData } from "../../../context/data" import { useClient } from "../../../context/client" @@ -216,7 +216,7 @@ export function SubagentsTab(props: { sessionID: string }) { flexDirection="row" paddingLeft={1} paddingRight={1} - backgroundColor={themeV2.background.action.primary(active() ? "focused" : "default")} + backgroundColor={themeV2.background.action.primary({ focused: active(), selected: entry.current })} onMouseOver={() => setStore("selected", index())} onMouseUp={() => { setStore("selected", index()) @@ -225,9 +225,7 @@ export function SubagentsTab(props: { sessionID: string }) { > @@ -235,7 +233,14 @@ export function SubagentsTab(props: { sessionID: string }) { - + {status()} diff --git a/packages/tui/src/theme/v2/component.ts b/packages/tui/src/theme/v2/component.ts index 3896c61aed..6b913f285b 100644 --- a/packages/tui/src/theme/v2/component.ts +++ b/packages/tui/src/theme/v2/component.ts @@ -1,7 +1,6 @@ import type { RGBA } from "@opentui/core" import type { Accessor } from "solid-js" import type { - ActionState, ActionVariant, FormfieldState, ResolvedActionState, @@ -9,6 +8,9 @@ import type { ResolvedThemeView, HueStep, } from "./index" +import { ActionState } from "./schema" + +export type ActionStates = Partial> export function createComponentTheme(current: Accessor) { const textAction = actions((variant, state) => current().text.action[variant][state]) @@ -121,7 +123,10 @@ export function createComponentTheme(current: Accessor) { } function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RGBA) { - const action = (variant: ActionVariant) => (state: ActionState | "default" = "default") => get(variant, state) + const action = (variant: ActionVariant) => (states: ActionState | "default" | ActionStates = "default") => { + if (typeof states === "string") return get(variant, states) + return get(variant, ActionState.literals.find((state) => states[state]) ?? "default") + } const primary = action("primary") return Object.assign(primary, { primary, diff --git a/packages/tui/src/theme/v2/schema.ts b/packages/tui/src/theme/v2/schema.ts index f863077c4e..6a5c8c379c 100644 --- a/packages/tui/src/theme/v2/schema.ts +++ b/packages/tui/src/theme/v2/schema.ts @@ -12,7 +12,7 @@ export type HueAlias = Schema.Schema.Type export const ActionVariant = Schema.Literals(["primary", "secondary", "destructive"]) export type ActionVariant = Schema.Schema.Type -export const ActionState = Schema.Literals(["focused", "pressed", "selected", "disabled"]) +export const ActionState = Schema.Literals(["disabled", "pressed", "focused", "selected"]) export type ActionState = Schema.Schema.Type export type ActionStateKey = `$${ActionState}` diff --git a/packages/tui/test/theme/v2/component.test.ts b/packages/tui/test/theme/v2/component.test.ts index a65a2e5577..da04e0a19c 100644 --- a/packages/tui/test/theme/v2/component.test.ts +++ b/packages/tui/test/theme/v2/component.test.ts @@ -23,6 +23,19 @@ test("provides reactive property, variant, state, and context accessors", () => expect(theme.text.action.primary("pressed")).toBe(resolved().text.action.primary.pressed) expect(theme.text.action.primary("selected")).toBe(resolved().text.action.primary.selected) expect(theme.background.action.primary("selected")).toBe(resolved().background.action.primary.selected) + expect(theme.background.action.primary({ selected: true })).toBe(resolved().background.action.primary.selected) + expect(theme.background.action.primary({ focused: true, selected: true })).toBe( + resolved().background.action.primary.focused, + ) + expect(theme.background.action.primary({ pressed: true, focused: true, selected: true })).toBe( + resolved().background.action.primary.pressed, + ) + expect(theme.background.action.primary({ disabled: true, pressed: true, focused: true, selected: true })).toBe( + resolved().background.action.primary.disabled, + ) + expect(theme.background.action.primary({ disabled: false, selected: false })).toBe( + resolved().background.action.primary.default, + ) expect(theme.background.action.secondary("disabled")).toBe( resolved().background.action.secondary.disabled, )