refactor(tui): finish V2 theme migration (#38383)
This commit is contained in:
parent
36979c9641
commit
b6e14b5a74
15 changed files with 46 additions and 119 deletions
|
|
@ -8,8 +8,6 @@ export type ModelRef = { id: string; providerID: string; variant?: string }
|
|||
|
||||
export type ProviderSettings = { [x: string]: JsonValue }
|
||||
|
||||
export type AgentColor = string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info"
|
||||
|
||||
export type PermissionV2Effect = "allow" | "deny" | "ask"
|
||||
|
||||
export type PluginInfo = { id: string }
|
||||
|
|
@ -2005,7 +2003,7 @@ export type AgentInfo = {
|
|||
description?: string
|
||||
mode: "subagent" | "primary" | "all"
|
||||
hidden: boolean
|
||||
color?: AgentColor
|
||||
color?: string
|
||||
steps?: number
|
||||
permissions: PermissionV2Ruleset
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10561,26 +10561,10 @@
|
|||
"additionalProperties": false
|
||||
},
|
||||
"Agent.Color": {
|
||||
"anyOf": [
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"primary",
|
||||
"secondary",
|
||||
"accent",
|
||||
"success",
|
||||
"warning",
|
||||
"error",
|
||||
"info"
|
||||
]
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import { ConfigProvider } from "./provider"
|
|||
import { ConfigModel } from "./model"
|
||||
import { PositiveInt } from "../schema"
|
||||
|
||||
export const Color = Schema.Union([
|
||||
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
|
||||
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
|
||||
])
|
||||
export const Color = Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/))
|
||||
|
||||
export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
|
||||
model: ConfigModel.Selection.pipe(Schema.optional),
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ import { Schema, SchemaGetter } from "effect"
|
|||
import { PositiveInt } from "../../schema"
|
||||
import { ConfigPermissionV1 } from "./permission"
|
||||
|
||||
const Color = Schema.Union([
|
||||
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
|
||||
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
|
||||
])
|
||||
const Color = Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/))
|
||||
|
||||
const AgentSchema = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
|
|
@ -29,7 +26,7 @@ const AgentSchema = Schema.StructWithRest(
|
|||
}),
|
||||
options: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||
color: Schema.optional(Color).annotate({
|
||||
description: "Hex color code (e.g., #FF5733) or theme color (e.g., primary)",
|
||||
description: "Hex color code (e.g., #FF5733)",
|
||||
}),
|
||||
steps: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum number of agentic iterations before forcing text-only response",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Effect, Schema } from "effect"
|
||||
|
|
@ -23,6 +23,10 @@ const defaultPermissions = [
|
|||
{ action: "external_directory", resource: "*", effect: "ask" },
|
||||
] satisfies PermissionV2.Ruleset
|
||||
|
||||
test("rejects named agent color tokens", () => {
|
||||
expect(() => decode({ agents: { reviewer: { color: "warning" } } })).toThrow()
|
||||
})
|
||||
|
||||
describe("ConfigAgentPlugin.Plugin", () => {
|
||||
it.effect("matches POSIX paths against home-relative permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -160,7 +164,7 @@ describe("ConfigAgentPlugin.Plugin", () => {
|
|||
description: "Reviews changes",
|
||||
mode: "subagent",
|
||||
hidden: true,
|
||||
color: "warning",
|
||||
color: "#ff6b6b",
|
||||
steps: 12,
|
||||
request: {
|
||||
headers: { first: "one", shared: "first" },
|
||||
|
|
@ -197,7 +201,7 @@ describe("ConfigAgentPlugin.Plugin", () => {
|
|||
description: "Reviews changes",
|
||||
mode: "subagent",
|
||||
hidden: true,
|
||||
color: "warning",
|
||||
color: "#ff6b6b",
|
||||
steps: 12,
|
||||
model: { providerID: "anthropic", id: "claude-sonnet" },
|
||||
})
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@ describe("Config", () => {
|
|||
system: "Find regressions.",
|
||||
mode: "subagent",
|
||||
hidden: false,
|
||||
color: "warning",
|
||||
color: "#ff6b6b",
|
||||
steps: 12,
|
||||
disabled: false,
|
||||
permissions: [{ action: "edit", resource: "*", effect: "deny" }],
|
||||
|
|
@ -824,7 +824,7 @@ describe("Config", () => {
|
|||
expect(reviewer?.system).toBe("Find regressions.")
|
||||
expect(reviewer?.mode).toBe("subagent")
|
||||
expect(reviewer?.hidden).toBe(false)
|
||||
expect(reviewer?.color).toBe("warning")
|
||||
expect(reviewer?.color).toBe("#ff6b6b")
|
||||
expect(reviewer?.steps).toBe(12)
|
||||
expect(reviewer?.disabled).toBe(false)
|
||||
expect(reviewer?.permissions).toEqual([{ action: "edit", resource: "*", effect: "deny" }])
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ becomes `system`:
|
|||
description: Reviews changes without modifying files
|
||||
mode: subagent
|
||||
model: anthropic/claude-sonnet-4-5#high
|
||||
color: warning
|
||||
color: "#ff6b6b"
|
||||
steps: 8
|
||||
permissions:
|
||||
- action: edit
|
||||
|
|
@ -118,7 +118,7 @@ Use the `agents` field in any [OpenCode configuration file](/config):
|
|||
"mode": "all",
|
||||
"model": "anthropic/claude-sonnet-4-5#high",
|
||||
"system": "Review the current changes. Report findings before any summary.",
|
||||
"color": "warning",
|
||||
"color": "#ff6b6b",
|
||||
"steps": 8,
|
||||
"permissions": [
|
||||
{ "action": "edit", "resource": "*", "effect": "deny" },
|
||||
|
|
@ -250,8 +250,7 @@ security boundary.
|
|||
|
||||
### `color`
|
||||
|
||||
Sets the agent's UI color. Use a six-digit hex color such as `#ff6b6b`, or one
|
||||
of `primary`, `secondary`, `accent`, `success`, `warning`, `error`, or `info`.
|
||||
Sets the agent's UI color. Use a six-digit hex color such as `#ff6b6b`.
|
||||
|
||||
### `disabled`
|
||||
|
||||
|
|
|
|||
|
|
@ -10561,26 +10561,10 @@
|
|||
"additionalProperties": false
|
||||
},
|
||||
"Agent.Color": {
|
||||
"anyOf": [
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"primary",
|
||||
"secondary",
|
||||
"accent",
|
||||
"success",
|
||||
"warning",
|
||||
"error",
|
||||
"info"
|
||||
]
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@ export type ID = typeof ID.Type
|
|||
export const Name = Schema.String.pipe(Schema.brand("Agent.Name"))
|
||||
export type Name = typeof Name.Type
|
||||
|
||||
export const Color = Schema.Union([
|
||||
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
|
||||
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
|
||||
]).annotate({ identifier: "Agent.Color" })
|
||||
export const Color = Schema.String.annotate({ identifier: "Agent.Color" }).check(
|
||||
Schema.isPattern(/^#[0-9a-fA-F]{6}$/),
|
||||
)
|
||||
export type Color = typeof Color.Type
|
||||
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ import { PersistedRevert } from "../src/session-revert.js"
|
|||
import { optional } from "../src/schema.js"
|
||||
|
||||
describe("contract hygiene", () => {
|
||||
test("restricts agent colors to six-digit hex values", () => {
|
||||
const decode = Schema.decodeUnknownSync(Agent.Color)
|
||||
expect(decode("#ff6b6b")).toBe("#ff6b6b")
|
||||
expect(() => decode("warning")).toThrow()
|
||||
})
|
||||
|
||||
test("keeps absolute costs distinct from model rates", () => {
|
||||
const usd = Money.USD.make(1)
|
||||
const rate = Money.USDPerMillionTokens.make(1)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ declare module "@opentui/solid" {
|
|||
extend({ go_upsell_art: GoUpsellArtRenderable })
|
||||
|
||||
export function BgPulse() {
|
||||
const { theme } = useTheme()
|
||||
const { themeV2, mode } = useTheme().contextual("elevated")
|
||||
const renderer = useRenderer()
|
||||
let targetFps = renderer.targetFps
|
||||
let maxFps = renderer.maxFps
|
||||
|
|
@ -91,9 +91,9 @@ export function BgPulse() {
|
|||
<go_upsell_art
|
||||
width="100%"
|
||||
height="100%"
|
||||
backgroundPanel={theme.backgroundPanel}
|
||||
primary={theme.primary}
|
||||
logoBase={tint(theme.background, theme.text, 0.62)}
|
||||
backgroundPanel={themeV2.background.default}
|
||||
primary={themeV2.hue.interactive[mode() === "light" ? 800 : 200]}
|
||||
logoBase={tint(themeV2.background.default, themeV2.text.default, 0.62)}
|
||||
live
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,16 +23,6 @@ import { useRoute } from "./route"
|
|||
import { useData } from "./data"
|
||||
import { usePermission } from "./permission"
|
||||
|
||||
export type LocalTheme = {
|
||||
secondary: RGBA
|
||||
accent: RGBA
|
||||
success: RGBA
|
||||
warning: RGBA
|
||||
primary: RGBA
|
||||
error: RGBA
|
||||
info: RGBA
|
||||
}
|
||||
|
||||
export function parseModel(model: string) {
|
||||
const [providerID, ...rest] = model.split("/")
|
||||
return {
|
||||
|
|
@ -60,7 +50,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
const data = useData()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { theme, themeV2, mode } = useTheme()
|
||||
const { themeV2, mode } = useTheme()
|
||||
const route = useRoute()
|
||||
const paths = useTuiPaths()
|
||||
const args = useArgs()
|
||||
|
|
@ -128,12 +118,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
if (index === -1) return colors()[0]
|
||||
const agent = visibleAgents()[index]
|
||||
|
||||
if (agent?.color) {
|
||||
const color = agent.color
|
||||
if (color.startsWith("#")) return RGBA.fromHex(color)
|
||||
// already validated by config, just satisfying TS here
|
||||
return theme[color as keyof typeof theme] as RGBA
|
||||
}
|
||||
if (agent?.color) return RGBA.fromHex(agent.color)
|
||||
return colors()[index % colors().length]
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ type State = {
|
|||
|
||||
type ContextName = "elevated" | "overlay"
|
||||
type ThemeService = {
|
||||
theme: Theme
|
||||
themeV2: ComponentTheme
|
||||
contextual(context: ContextName): ThemeService
|
||||
readonly selected: string
|
||||
|
|
@ -280,7 +279,7 @@ const themeContext = createSimpleContext({
|
|||
if (supported.includes(store.mode)) return store.mode
|
||||
return supported[0] ?? store.mode
|
||||
}
|
||||
const values = createMemo(() => resolveTheme(source(), mode()))
|
||||
const legacySyntaxTheme = createMemo(() => resolveTheme(source(), mode()))
|
||||
const valuesV2 = createMemo(() => resolveThemeFile(file(), mode(), sourceName()))
|
||||
valuesV2()
|
||||
themePerformance.set("Init", `${(performance.now() - initStarted).toFixed(2)} ms`)
|
||||
|
|
@ -298,21 +297,13 @@ const themeContext = createSimpleContext({
|
|||
}, mode),
|
||||
}
|
||||
|
||||
createEffect(() => renderer.setBackgroundColor(values().background))
|
||||
createEffect(() => renderer.setBackgroundColor(valuesV2().background.default))
|
||||
|
||||
const syntax = createSyntaxStyleMemo(() => generateSyntax(values()))
|
||||
|
||||
const theme = new Proxy(values(), {
|
||||
get(_target, prop) {
|
||||
// @ts-expect-error Properties are forwarded to the current reactive value.
|
||||
return values()[prop]
|
||||
},
|
||||
})
|
||||
const syntax = createSyntaxStyleMemo(() => generateSyntax(legacySyntaxTheme()))
|
||||
function contextual(context: ContextName) {
|
||||
return contextualServices[context]
|
||||
}
|
||||
const service: ThemeService = {
|
||||
theme,
|
||||
themeV2,
|
||||
contextual,
|
||||
get selected() {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ becomes `system`:
|
|||
description: Reviews changes without modifying files
|
||||
mode: subagent
|
||||
model: anthropic/claude-sonnet-4-5#high
|
||||
color: warning
|
||||
color: "#ff6b6b"
|
||||
steps: 8
|
||||
permissions:
|
||||
- action: edit
|
||||
|
|
@ -118,7 +118,7 @@ Use the `agents` field in any [OpenCode configuration file](/docs/config):
|
|||
"mode": "all",
|
||||
"model": "anthropic/claude-sonnet-4-5#high",
|
||||
"system": "Review the current changes. Report findings before any summary.",
|
||||
"color": "warning",
|
||||
"color": "#ff6b6b",
|
||||
"steps": 8,
|
||||
"permissions": [
|
||||
{ "action": "edit", "resource": "*", "effect": "deny" },
|
||||
|
|
@ -250,8 +250,7 @@ security boundary.
|
|||
|
||||
### `color`
|
||||
|
||||
Sets the agent's UI color. Use a six-digit hex color such as `#ff6b6b`, or one
|
||||
of `primary`, `secondary`, `accent`, `success`, `warning`, `error`, or `info`.
|
||||
Sets the agent's UI color. Use a six-digit hex color such as `#ff6b6b`.
|
||||
|
||||
### `disabled`
|
||||
|
||||
|
|
|
|||
|
|
@ -10561,26 +10561,10 @@
|
|||
"additionalProperties": false
|
||||
},
|
||||
"Agent.Color": {
|
||||
"anyOf": [
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"primary",
|
||||
"secondary",
|
||||
"accent",
|
||||
"success",
|
||||
"warning",
|
||||
"error",
|
||||
"info"
|
||||
]
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue