Explorar o código

refactor(tui): remove secondary action styles (#37378)

James Long hai 1 mes
pai
achega
309860558d

+ 1 - 1
packages/tui/src/component/devtools-sidebar.tsx

@@ -20,7 +20,7 @@ export function DevToolsSidebar() {
         {(group) => (
           <box flexShrink={0} marginBottom={1}>
             <box marginBottom={1}>
-              <text fg={themeV2.background.action.primary()} attributes={TextAttributes.BOLD}>
+              <text fg={themeV2.background.action()} attributes={TextAttributes.BOLD}>
                 {group.title}
               </text>
             </box>

+ 20 - 35
packages/tui/src/component/prompt/index.tsx

@@ -187,7 +187,7 @@ export function Prompt(props: PromptProps) {
   const renderer = useRenderer()
   const exit = useExit()
   const dimensions = useTerminalDimensions()
-  const { themeV2, syntax } = useTheme()
+  const { themeV2, syntax, mode } = useTheme()
   const animationsEnabled = createMemo(() => config.animations ?? true)
   const list = createMemo(() => props.placeholders?.normal ?? [])
   const shell = createMemo(() => props.placeholders?.shell ?? [])
@@ -1319,7 +1319,7 @@ export function Prompt(props: PromptProps) {
 
   const highlight = createMemo(() => {
     if (leader()) return themeV2.border()
-    if (store.mode === "shell") return themeV2.background.action.primary()
+    if (store.mode === "shell") return themeV2.background.action()
     const agent = local.agent.current()
     if (!agent) return themeV2.border()
     return local.agent.color(agent.id)
@@ -1378,6 +1378,12 @@ export function Prompt(props: PromptProps) {
   })
   const maxHeight = createMemo(() => Math.max(6, Math.floor(dimensions().height / 3)))
 
+  const promptBg = createMemo(() =>
+    mode() === "light"
+      ? themeV2.increase(themeV2.background.surface.offset(), 1)
+      : themeV2.decrease(themeV2.background.surface.offset(), 1),
+  )
+
   return (
     <>
       <box ref={(r: BoxRenderable) => (anchor = r)} visible={props.visible !== false} width="100%">
@@ -1395,7 +1401,7 @@ export function Prompt(props: PromptProps) {
             paddingRight={2}
             paddingTop={1}
             flexShrink={0}
-            backgroundColor={themeV2.background.action.secondary("focused")}
+            backgroundColor={promptBg()}
             flexGrow={1}
             width="100%"
           >
@@ -1471,10 +1477,8 @@ export function Prompt(props: PromptProps) {
                 if (props.disabled) return
                 r.target?.focus()
               }}
-              focusedBackgroundColor={themeV2.background.action.secondary("focused")}
-              cursorColor={
-                props.disabled ? themeV2.background.surface.offset() : themeV2.text()
-              }
+              focusedBackgroundColor="transparent"
+              cursorColor={props.disabled ? themeV2.background.surface.offset() : themeV2.text()}
               syntaxStyle={syntax()}
             />
             <box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
@@ -1493,16 +1497,11 @@ export function Prompt(props: PromptProps) {
                           <text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}>·</text>
                           <text
                             flexShrink={0}
-                            fg={fadeColor(
-                              leader() ? themeV2.text.subdued() : themeV2.text(),
-                              modelMetaAlpha(),
-                            )}
+                            fg={fadeColor(leader() ? themeV2.text.subdued() : themeV2.text(), modelMetaAlpha())}
                           >
                             {local.model.parsed().model}
                           </text>
-                          <text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}>
-                            {currentProviderLabel()}
-                          </text>
+                          <text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}>{currentProviderLabel()}</text>
                           <Show when={showVariant()}>
                             <text fg={fadeColor(themeV2.text.subdued(), variantMetaAlpha())}>·</text>
                             <text>
@@ -1536,15 +1535,15 @@ export function Prompt(props: PromptProps) {
           borderColor={borderHighlight()}
           customBorderChars={{
             ...EmptyBorder,
-            vertical: themeV2.background.action.secondary("focused").a !== 0 ? "╹" : " ",
+            vertical: promptBg().a !== 0 ? "╹" : " ",
           }}
         >
           <box
             height={1}
             border={["bottom"]}
-            borderColor={themeV2.background.action.secondary("focused")}
+            borderColor={promptBg()}
             customBorderChars={
-              themeV2.background.action.secondary("focused").a !== 0
+              promptBg().a !== 0
                 ? {
                     ...EmptyBorder,
                     horizontal: "▀",
@@ -1561,23 +1560,15 @@ export function Prompt(props: PromptProps) {
             <Match when={status() === "running"}>
               <box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
                 <box marginLeft={1}>
-                  <Show
-                    when={config.animations ?? true}
-                    fallback={<text fg={themeV2.text.subdued()}>[⋯]</text>}
-                  >
+                  <Show when={config.animations ?? true} fallback={<text fg={themeV2.text.subdued()}>[⋯]</text>}>
                     <spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
                   </Show>
                 </box>
-                <text
-                  fg={store.interrupt > 0 ? themeV2.background.action.primary() : themeV2.text()}
-                >
+                <text fg={store.interrupt > 0 ? themeV2.background.action() : themeV2.text()}>
                   esc{" "}
                   <span
                     style={{
-                      fg:
-                        store.interrupt > 0
-                          ? themeV2.background.action.primary()
-                          : themeV2.text.subdued(),
+                      fg: store.interrupt > 0 ? themeV2.background.action() : themeV2.text.subdued(),
                     }}
                   >
                     {store.interrupt > 0 ? "again to interrupt" : "interrupt"}
@@ -1613,13 +1604,7 @@ export function Prompt(props: PromptProps) {
           <box gap={2} flexDirection="row">
             <Show when={editorContextLabelState() !== "none" ? editorFileLabelDisplay() : undefined}>
               {(file) => (
-                <text
-                  fg={
-                    editorContextLabelState() === "pending"
-                      ? themeV2.hue.accent(500)
-                      : themeV2.text.subdued()
-                  }
-                >
+                <text fg={editorContextLabelState() === "pending" ? themeV2.hue.accent(500) : themeV2.text.subdued()}>
                   {file()}
                 </text>
               )}

+ 2 - 2
packages/tui/src/routes/session/composer/shell-tab.tsx

@@ -105,11 +105,11 @@ export function ShellTab(props: { sessionID: string }) {
                   flexDirection="row"
                   paddingLeft={1}
                   paddingRight={1}
-                  backgroundColor={themeV2.background.action.primary({ focused: active() })}
+                  backgroundColor={themeV2.background.action({ focused: active() })}
                   onMouseOver={() => setStore("selected", index())}
                 >
                   <text
-                    fg={themeV2.text.action.primary({ focused: active() })}
+                    fg={themeV2.text.action({ focused: active() })}
                     attributes={active() ? TextAttributes.BOLD : undefined}
                     wrapMode="none"
                   >

+ 3 - 3
packages/tui/src/routes/session/composer/subagents-tab.tsx

@@ -216,7 +216,7 @@ export function SubagentsTab(props: { sessionID: string }) {
                   flexDirection="row"
                   paddingLeft={1}
                   paddingRight={1}
-                  backgroundColor={themeV2.background.action.primary({ focused: active(), selected: entry.current })}
+                  backgroundColor={themeV2.background.action({ focused: active(), selected: entry.current })}
                   onMouseOver={() => setStore("selected", index())}
                   onMouseUp={() => {
                     setStore("selected", index())
@@ -225,7 +225,7 @@ export function SubagentsTab(props: { sessionID: string }) {
                 >
                   <box flexGrow={1} minWidth={0} flexDirection="row">
                     <text
-                      fg={themeV2.text.action.primary({ focused: active(), selected: entry.current })}
+                      fg={themeV2.text.action({ focused: active(), selected: entry.current })}
                       attributes={active() ? TextAttributes.BOLD : undefined}
                       wrapMode="none"
                     >
@@ -236,7 +236,7 @@ export function SubagentsTab(props: { sessionID: string }) {
                     <text
                       fg={
                         active()
-                          ? themeV2.text.action.primary({ focused: active(), selected: entry.current })
+                          ? themeV2.text.action({ focused: active(), selected: entry.current })
                           : themeV2.text.subdued()
                       }
                       wrapMode="none"

+ 1 - 1
packages/tui/src/routes/session/form.tsx

@@ -848,7 +848,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
                 <text fg={themeV2.text.subdued()}>{external().description}</text>
               </Show>
               <text
-                fg={themeV2.background.action.primary()}
+                fg={themeV2.background.action()}
                 onMouseUp={() => {
                   if (renderer.getSelection()?.getSelectedText()) return
                   openExternal()

+ 11 - 11
packages/tui/src/routes/session/index.tsx

@@ -891,7 +891,7 @@ export function Session() {
                 paddingLeft: 1,
                 visible: showScrollbar(),
                 trackOptions: {
-                  backgroundColor: themeV2.background.action.secondary("focused"),
+                  backgroundColor: themeV2.background.action("focused"),
                   foregroundColor: themeV2.border(),
                 },
               }}
@@ -1222,7 +1222,7 @@ function SessionReasoningGroupView(props: {
                         <box
                           border={["left"]}
                           customBorderChars={SplitBorder.customBorderChars}
-                          borderColor={themeV2.background.action.secondary("focused")}
+                          borderColor={themeV2.background.action("focused")}
                           paddingLeft={1}
                         >
                           <code
@@ -1538,7 +1538,7 @@ function RevertMessage(props: {
         paddingTop={1}
         paddingBottom={1}
         paddingLeft={2}
-        backgroundColor={hover() ? themeV2.background.action.secondary("focused") : themeV2.background()}
+        backgroundColor={hover() ? themeV2.background.action("focused") : themeV2.background()}
       >
         <text fg={themeV2.text.subdued()}>
           {props.count} message{props.count === 1 ? "" : "s"} reverted
@@ -1644,7 +1644,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
           paddingTop={1}
           paddingBottom={1}
           paddingLeft={2}
-          backgroundColor={hover() ? themeV2.background.action.secondary("focused") : themeV2.background()}
+          backgroundColor={hover() ? themeV2.background.action("focused") : themeV2.background()}
           flexShrink={0}
         >
           <text fg={themeV2.text()}>{props.message.text}</text>
@@ -1664,7 +1664,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
                       >
                         {` ${label} `}
                       </span>
-                      <span style={{ bg: themeV2.background.action.secondary("focused"), fg: themeV2.text.subdued() }}>
+                      <span style={{ bg: themeV2.background.action("focused"), fg: themeV2.text.subdued() }}>
                         {" "}
                         {file.name ?? (file.source.type === "uri" ? file.source.uri : "attachment")}{" "}
                       </span>
@@ -1880,7 +1880,7 @@ function ReasoningPart(props: {
         <box
           border={!inMinimal() || expanded() ? ["left"] : undefined}
           customBorderChars={SplitBorder.customBorderChars}
-          borderColor={themeV2.background.action.secondary("focused")}
+          borderColor={themeV2.background.action("focused")}
           paddingLeft={!inMinimal() || expanded() ? 1 : 0}
         >
           <box onMouseUp={toggle}>
@@ -1898,7 +1898,7 @@ function ReasoningPart(props: {
             <box
               border={["left"]}
               customBorderChars={SplitBorder.customBorderChars}
-              borderColor={themeV2.background.action.secondary("focused")}
+              borderColor={themeV2.background.action("focused")}
               paddingLeft={inMinimal() ? 3 : 1}
             >
               <code
@@ -2093,7 +2093,7 @@ function GenericTool(props: ToolProps) {
           <Show when={Object.keys(props.input).length > 0}>
             <box gap={1}>
               <text>
-                <span style={{ bg: themeV2.background.action.secondary("focused"), fg: themeV2.text.subdued() }}> Input </span>
+                <span style={{ bg: themeV2.background.action("focused"), fg: themeV2.text.subdued() }}> Input </span>
               </text>
               <box paddingLeft={1}>
                 <code
@@ -2111,7 +2111,7 @@ function GenericTool(props: ToolProps) {
             {(value) => (
               <box gap={1}>
                 <text>
-                  <span style={{ bg: themeV2.background.action.secondary("focused"), fg: themeV2.text.subdued() }}> Output </span>
+                  <span style={{ bg: themeV2.background.action("focused"), fg: themeV2.text.subdued() }}> Output </span>
                 </text>
                 <box paddingLeft={1}>
                   <text fg={themeV2.text()} wrapMode="word">
@@ -2301,7 +2301,7 @@ function InlineToolLabel(props: { color?: RGBA; denied?: boolean; status: JSX.El
 function StatusBadge(props: { children: string }) {
   const { themeV2 } = useTheme()
   return (
-    <text flexShrink={0} bg={themeV2.background.action.secondary("focused")} fg={themeV2.text.subdued()}>
+    <text flexShrink={0} bg={themeV2.background.action("focused")} fg={themeV2.text.subdued()}>
       {" "}
       {props.children}{" "}
     </text>
@@ -2335,7 +2335,7 @@ function BlockTool(props: {
       paddingLeft={2}
       gap={1}
       backgroundColor={
-        hover() ? themeV2.background.action.secondary("focused") : themeV2.background()
+        hover() ? themeV2.background.action("focused") : themeV2.background()
       }
       customBorderChars={SplitBorder.customBorderChars}
       borderColor={themeV2.background()}

+ 5 - 5
packages/tui/src/routes/session/permission.tsx

@@ -516,7 +516,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
         paddingLeft={2}
         paddingRight={3}
         paddingBottom={1}
-        backgroundColor={themeV2.background.action.secondary("focused")}
+        backgroundColor={themeV2.background.action("focused")}
         justifyContent={narrow() ? "flex-start" : "space-between"}
         alignItems={narrow() ? "flex-start" : "center"}
         gap={1}
@@ -656,7 +656,7 @@ function Prompt<const T extends Record<string, string>>(props: {
     <box
       backgroundColor={themeV2.background()}
       border={["left"]}
-      borderColor={themeV2.background.action.primary("focused")}
+      borderColor={themeV2.background.action("focused")}
       customBorderChars={SplitBorder.customBorderChars}
       {...(store.expanded
         ? { top: dimensions().height * -1 + 1, bottom: 1, left: 2, right: 2, position: "absolute" }
@@ -693,7 +693,7 @@ function Prompt<const T extends Record<string, string>>(props: {
         paddingLeft={2}
         paddingRight={3}
         paddingBottom={1}
-        backgroundColor={themeV2.background.action.secondary("focused")}
+        backgroundColor={themeV2.background.action("focused")}
         justifyContent={narrow() ? "flex-start" : "space-between"}
         alignItems={narrow() ? "flex-start" : "center"}
       >
@@ -703,7 +703,7 @@ function Prompt<const T extends Record<string, string>>(props: {
               <box
                 paddingLeft={1}
                 paddingRight={1}
-                backgroundColor={themeV2.background.action.primary(
+                backgroundColor={themeV2.background.action(
                   option === store.selected ? "focused" : "default",
                 )}
                 onMouseOver={() => setStore("selected", option)}
@@ -713,7 +713,7 @@ function Prompt<const T extends Record<string, string>>(props: {
                 }}
               >
                 <text
-                  fg={themeV2.text.action.primary(
+                  fg={themeV2.text.action(
                     option === store.selected ? "focused" : "default",
                   )}
                 >

+ 3 - 3
packages/tui/src/routes/session/subagent-footer.tsx

@@ -83,7 +83,7 @@ export function SubagentFooter() {
               onMouseOver={() => setHover("parent")}
               onMouseOut={() => setHover(null)}
               onMouseUp={() => keymap.dispatch("session.parent")}
-              backgroundColor={hover() === "parent" ? themeV2.background.action.secondary("focused") : themeV2.background()}
+              backgroundColor={hover() === "parent" ? themeV2.background.action("focused") : themeV2.background()}
             >
               <text fg={themeV2.text()}>
                 Parent <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.parent")}</span>
@@ -93,7 +93,7 @@ export function SubagentFooter() {
               onMouseOver={() => setHover("prev")}
               onMouseOut={() => setHover(null)}
               onMouseUp={() => keymap.dispatch("session.child.previous")}
-              backgroundColor={hover() === "prev" ? themeV2.background.action.secondary("focused") : themeV2.background()}
+              backgroundColor={hover() === "prev" ? themeV2.background.action("focused") : themeV2.background()}
             >
               <text fg={themeV2.text()}>
                 Prev <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.child.previous")}</span>
@@ -103,7 +103,7 @@ export function SubagentFooter() {
               onMouseOver={() => setHover("next")}
               onMouseOut={() => setHover(null)}
               onMouseUp={() => keymap.dispatch("session.child.next")}
-              backgroundColor={hover() === "next" ? themeV2.background.action.secondary("focused") : themeV2.background()}
+              backgroundColor={hover() === "next" ? themeV2.background.action("focused") : themeV2.background()}
             >
               <text fg={themeV2.text()}>
                 Next <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.child.next")}</span>

+ 14 - 4
packages/tui/src/theme/v2/component.ts

@@ -6,9 +6,8 @@ import type {
   ResolvedActionState,
   ResolvedFormfieldState,
   ResolvedThemeView,
-  HueStep,
 } from "./index"
-import { ActionState } from "./schema"
+import { ActionState, HueStep } from "./schema"
 
 export type ActionStates = Partial<Record<ActionState, boolean>>
 
@@ -79,6 +78,8 @@ export function createComponentTheme(current: Accessor<ResolvedThemeView>) {
 
   return {
     hue,
+    increase: (color: RGBA, amount = 1) => shiftHue(current(), color, amount),
+    decrease: (color: RGBA, amount = 1) => shiftHue(current(), color, -amount),
     text,
     background,
     border: () => current().border.default,
@@ -122,6 +123,17 @@ export function createComponentTheme(current: Accessor<ResolvedThemeView>) {
   }
 }
 
+function shiftHue(theme: ResolvedThemeView, color: RGBA, amount: number) {
+  const colors = Object.values(theme.hue).flatMap((scale) =>
+    HueStep.literals.map((step, index) => ({ color: scale[step], index, scale })),
+  )
+  const match = colors.find((entry) => entry.color === color) ?? colors.find((entry) => entry.color.equals(color))
+  if (!match) return color
+  const offset = Number.isFinite(amount) ? Math.trunc(amount) : 0
+  const index = Math.max(0, Math.min(HueStep.literals.length - 1, match.index + offset))
+  return match.scale[HueStep.literals[index]]
+}
+
 function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RGBA) {
   const action = (variant: ActionVariant) => (states: ActionState | "default" | ActionStates = "default") => {
     if (typeof states === "string") return get(variant, states)
@@ -129,8 +141,6 @@ function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RG
   }
   const primary = action("primary")
   return Object.assign(primary, {
-    primary,
-    secondary: action("secondary"),
     destructive: action("destructive"),
   })
 }

+ 0 - 16
packages/tui/src/theme/v2/defaults.ts

@@ -101,7 +101,6 @@ export const DEFAULT_THEME = {
       subdued: "$hue.neutral.600",
       action: {
         primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
-        secondary: { default: "$hue.neutral.900", $disabled: "$hue.neutral.500" },
         destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
       },
       formfield: {
@@ -132,13 +131,6 @@ export const DEFAULT_THEME = {
           $selected: "$hue.interactive.700",
           $disabled: "$hue.neutral.300",
         },
-        secondary: {
-          default: "$hue.neutral.200",
-          $focused: "$hue.neutral.300",
-          $pressed: "$hue.neutral.400",
-          $selected: "$hue.neutral.300",
-          $disabled: "$hue.neutral.200",
-        },
         destructive: {
           default: "$hue.red.600",
           $focused: "$hue.red.700",
@@ -318,7 +310,6 @@ export const DEFAULT_THEME = {
       subdued: "$hue.neutral.400",
       action: {
         primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
-        secondary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
         destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
       },
       formfield: {
@@ -349,13 +340,6 @@ export const DEFAULT_THEME = {
           $selected: "$hue.interactive.600",
           $disabled: "$hue.neutral.800",
         },
-        secondary: {
-          default: "$hue.neutral.800",
-          $focused: "$hue.neutral.700",
-          $pressed: "$hue.neutral.900",
-          $selected: "$hue.neutral.700",
-          $disabled: "$hue.neutral.900",
-        },
         destructive: {
           default: "$hue.red.600",
           $focused: "$hue.red.700",

+ 1 - 2
packages/tui/src/theme/v2/schema.ts

@@ -9,7 +9,7 @@ export type BaseHue = Schema.Schema.Type<typeof BaseHue>
 export const HueAlias = Schema.Literals(["accent", "interactive", "neutral"])
 export type HueAlias = Schema.Schema.Type<typeof HueAlias>
 
-export const ActionVariant = Schema.Literals(["primary", "secondary", "destructive"])
+export const ActionVariant = Schema.Literals(["primary", "destructive"])
 export type ActionVariant = Schema.Schema.Type<typeof ActionVariant>
 
 export const ActionState = Schema.Literals(["disabled", "pressed", "focused", "selected"])
@@ -93,7 +93,6 @@ export type FormfieldColorDefinition = Schema.Schema.Type<typeof FormfieldColorD
 
 const ActionColorDefinition = Schema.Struct({
   primary: Schema.optional(StatefulColorDefinition),
-  secondary: Schema.optional(StatefulColorDefinition),
   destructive: Schema.optional(StatefulColorDefinition),
 })
 

+ 0 - 9
packages/tui/src/theme/v2/v1-migrate.ts

@@ -56,10 +56,6 @@ function migrateMode(theme: Theme, mode: "light" | "dark"): ThemeFile["light"] {
           $focused: selected,
           $selected: primary,
         },
-        secondary: {
-          default: "$text.default",
-          $disabled: textMuted,
-        },
         destructive: { default: destructive, $disabled: textMuted },
       },
       formfield: {
@@ -84,11 +80,6 @@ function migrateMode(theme: Theme, mode: "light" | "dark"): ThemeFile["light"] {
       },
       action: {
         primary: { default: "transparent", $focused: primary, $selected: primary },
-        secondary: {
-          default: "$background.default",
-          $focused: color("backgroundElement"),
-          $pressed: color("backgroundElement"),
-        },
         destructive: { default: color("error") },
       },
       formfield: {

+ 20 - 11
packages/tui/test/theme/v2/component.test.ts

@@ -1,5 +1,6 @@
 import { expect, test } from "bun:test"
 import { createSignal } from "solid-js"
+import { RGBA } from "@opentui/core"
 import { createComponentTheme } from "../../../src/theme/v2/component"
 import { DEFAULT_THEME } from "../../../src/theme/v2/defaults"
 import { resolveTheme } from "../../../src/theme/v2/resolve"
@@ -18,26 +19,34 @@ test("provides reactive property, variant, state, and context accessors", () =>
   expect(theme.hue.accent(500)).toBe(resolved().hue.accent[500])
   expect(theme.hue.interactive(500)).toBe(resolved().hue.interactive[500])
   expect(theme.hue.gray(200)).toBe(resolved().hue.gray[200])
+  expect(theme.increase(theme.background.surface.offset(), 1)).toBe(resolved().hue.neutral[300])
+  expect(theme.decrease(theme.hue.red(300), 2)).toBe(resolved().hue.red[100])
+  expect(theme.increase(theme.hue.red(900), 3)).toBe(resolved().hue.red[900])
+  expect(theme.decrease(theme.hue.red(100), 3)).toBe(resolved().hue.red[100])
+  const equivalent = RGBA.fromInts(...resolved().hue.green[500].toInts())
+  expect(theme.increase(equivalent, 1)).toBe(resolved().hue.green[600])
+  const unmatched = RGBA.fromInts(1, 2, 3)
+  expect(theme.increase(unmatched, 1)).toBe(unmatched)
   expect(theme.text.subdued()).toBe(resolved().text.subdued)
   expect(theme.text.action()).toBe(resolved().text.action.primary.default)
-  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(
+  expect(theme.text.action("pressed")).toBe(resolved().text.action.primary.pressed)
+  expect(theme.text.action("selected")).toBe(resolved().text.action.primary.selected)
+  expect(theme.background.action("selected")).toBe(resolved().background.action.primary.selected)
+  expect(theme.background.action({ selected: true })).toBe(resolved().background.action.primary.selected)
+  expect(theme.background.action({ focused: true, selected: true })).toBe(
     resolved().background.action.primary.focused,
   )
-  expect(theme.background.action.primary({ pressed: true, focused: true, selected: true })).toBe(
+  expect(theme.background.action({ 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(
+  expect(theme.background.action({ disabled: true, pressed: true, focused: true, selected: true })).toBe(
     resolved().background.action.primary.disabled,
   )
-  expect(theme.background.action.primary({ disabled: false, selected: false })).toBe(
+  expect(theme.background.action({ disabled: false, selected: false })).toBe(
     resolved().background.action.primary.default,
   )
-  expect(theme.background.action.secondary("disabled")).toBe(
-    resolved().background.action.secondary.disabled,
+  expect(theme.background.action.destructive("disabled")).toBe(
+    resolved().background.action.destructive.disabled,
   )
   expect(theme.background.surface.offset()).toBe(resolved().background.surface.offset)
   expect(theme.background.surface.overlay()).toBe(resolved().background.surface.overlay)
@@ -46,7 +55,7 @@ test("provides reactive property, variant, state, and context accessors", () =>
 
   setContext("@context:elevated")
   expect(theme.text()).toBe(resolved().contexts["@context:elevated"]!.text.default)
-  expect(theme.background.action.primary("focused")).toBe(
+  expect(theme.background.action("focused")).toBe(
     resolved().contexts["@context:elevated"]!.background.action.primary.focused,
   )
   expect(theme.background.formfield("selected")).toBe(

+ 0 - 3
packages/tui/test/theme/v2/resolve.test.ts

@@ -142,7 +142,6 @@ test("resolves matched action variants and states", () => {
   expect(theme.text.action.primary.selected).toBeInstanceOf(RGBA)
   expect(theme.background.action.primary.pressed).toBeInstanceOf(RGBA)
   expect(theme.background.action.primary.selected).toBeInstanceOf(RGBA)
-  expect(theme.text.action.secondary.default).toBeInstanceOf(RGBA)
   expect(theme.background.action.destructive.disabled).toBeInstanceOf(RGBA)
 })
 
@@ -175,7 +174,6 @@ test("context overrides rewire semantic references and apply state precedence",
       default: "#111111",
       action: {
         primary: { default: "$text.default", $pressed: "#222222" },
-        secondary: { default: "$text.default" },
       },
     },
     "@context:elevated": {
@@ -189,7 +187,6 @@ test("context overrides rewire semantic references and apply state precedence",
   const overlay = theme.contexts["@context:elevated"]!
 
   expect(overlay.text.default.toInts()).toEqual([51, 51, 51, 255])
-  expect(overlay.text.action.secondary.default.toInts()).toEqual([51, 51, 51, 255])
   expect(overlay.text.action.primary.pressed.toInts()).toEqual([68, 68, 68, 255])
   expect(overlay.text.action.primary.focused.toInts()).toEqual([85, 85, 85, 255])
 })

+ 0 - 2
packages/tui/test/theme/v2/types.test.ts

@@ -6,7 +6,6 @@ const text = {
   subdued: "$hue.neutral.600",
   action: {
     primary: { default: "$hue.neutral.100", $pressed: "$hue.neutral.200" },
-    secondary: { default: "$hue.neutral.900" },
     destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
   },
   formfield: { default: "$hue.neutral.600", $selected: "$hue.neutral.100" },
@@ -24,7 +23,6 @@ const background = {
       $pressed: "$hue.interactive.800",
       $selected: "$hue.interactive.700",
     },
-    secondary: { default: "$hue.neutral.200" },
     destructive: { default: "$hue.red.600" },
   },
   formfield: { default: "$hue.neutral.100", $selected: "$hue.interactive.600" },

+ 0 - 4
packages/tui/test/theme/v2/v1-migrate.test.ts

@@ -30,7 +30,6 @@ test("migrates resolved V1 modes into literal V2 tokens", () => {
   expect(migrated.light.scrollbar?.default).toBe(hex(legacy.borderActive))
   expect(migrated.light.diff?.lineNumber?.background?.removed).toBe(hex(legacy.diffRemovedLineNumberBg))
   expect(migrated.light.markdown?.emphasis).toBe(hex(legacy.markdownEmph))
-  expect(resolved.background.action.secondary.focused.toInts()).toEqual(legacy.backgroundElement.toInts())
   expect(resolved.background.surface.offset.toInts()).toEqual(legacy.backgroundPanel.toInts())
   expect(resolved.background.surface.overlay.toInts()).toEqual(legacy.backgroundMenu.toInts())
   expect(resolved.background.formfield.selected.toInts()).toEqual(legacy.background.toInts())
@@ -46,9 +45,6 @@ test("migrates resolved V1 modes into literal V2 tokens", () => {
   expect(resolved.contexts["@context:elevated"]?.background.default.toInts()).toEqual(
     legacy.backgroundPanel.toInts(),
   )
-  expect(resolved.contexts["@context:elevated"]?.background.action.secondary.default.toInts()).toEqual(
-    legacy.backgroundPanel.toInts(),
-  )
   expect(resolved.contexts["@context:elevated"]?.background.action.primary.default.toInts()).toEqual([0, 0, 0, 0])
   expect(resolved.contexts["@context:elevated"]?.text.action.primary.default.toInts()).toEqual(
     legacy.text.toInts(),