diff --git a/.changeset/attention-accent-color.md b/.changeset/attention-accent-color.md new file mode 100644 index 00000000000..0db5dd56564 --- /dev/null +++ b/.changeset/attention-accent-color.md @@ -0,0 +1,5 @@ +--- +"@opencode-ai/theme": patch +--- + +Use the unread accent color by default for question and permission status indicators, while preserving explicit theme overrides. diff --git a/packages/theme/src/tui/defaults.ts b/packages/theme/src/tui/defaults.ts index 9c6cb1cee1b..268bf6593c5 100644 --- a/packages/theme/src/tui/defaults.ts +++ b/packages/theme/src/tui/defaults.ts @@ -123,8 +123,8 @@ export const DEFAULT_THEME = { }, status: { running: "$hue.interactive.800", - question: "$text.feedback.info.default", - permission: "$text.feedback.warning.default", + question: "$text.status.unread", + permission: "$text.status.unread", unread: "$hue.accent.800", }, feedback: { @@ -344,8 +344,8 @@ export const DEFAULT_THEME = { }, status: { running: "$hue.interactive.200", - question: "$text.feedback.info.default", - permission: "$text.feedback.warning.default", + question: "$text.status.unread", + permission: "$text.status.unread", unread: "$hue.accent.200", }, feedback: { diff --git a/packages/theme/test/status.test.ts b/packages/theme/test/status.test.ts index f5308b69b93..c47d6489dd2 100644 --- a/packages/theme/test/status.test.ts +++ b/packages/theme/test/status.test.ts @@ -11,13 +11,33 @@ test.each(["light", "dark"] as const)("built-in %s themes resolve status colors" for (const document of [DEFAULT_THEME, migrateV1(source)]) { const theme = resolveThemeDocument(document, mode) expect(theme.text.status.running.equals(theme.hue.interactive[mode === "light" ? 800 : 200])).toBeTrue() - expect(theme.text.status.question.equals(theme.text.feedback.info.default)).toBeTrue() - expect(theme.text.status.permission.equals(theme.text.feedback.warning.default)).toBeTrue() + expect(theme.text.status.question.equals(theme.text.status.unread)).toBeTrue() + expect(theme.text.status.permission.equals(theme.text.status.unread)).toBeTrue() expect(theme.text.status.unread.equals(theme.hue.accent[mode === "light" ? 800 : 200])).toBeTrue() expect(theme.contextual.elevated.text.status).toEqual(theme.text.status) } }) +test.each(["light", "dark"] as const)("custom %s themes inherit the unread attention color", (mode) => { + for (const standalone of [false, true]) { + const theme = resolveThemeDocument( + Schema.decodeUnknownSync(ThemeDocument)({ + version: 2, + standalone, + [mode]: { + hue: { ...DEFAULT_THEME[mode].hue, accent: "$hue.purple" }, + text: { status: { unread: "#abcdef" } }, + }, + }), + mode, + ) + expect(theme.text.status.unread.equals(RGBA.fromHex("#abcdef"))).toBeTrue() + expect(theme.text.status.question.equals(theme.text.status.unread)).toBeTrue() + expect(theme.text.status.permission.equals(theme.text.status.unread)).toBeTrue() + expect(theme.contextual.elevated.text.status).toEqual(theme.text.status) + } +}) + test.each(["light", "dark"] as const)("custom %s themes inherit and override status colors", (mode) => { for (const standalone of [false, true]) { const theme = resolveThemeDocument( @@ -27,8 +47,7 @@ test.each(["light", "dark"] as const)("custom %s themes inherit and override sta [mode]: { hue: { ...DEFAULT_THEME[mode].hue, interactive: "$hue.purple", accent: "$hue.orange" }, text: { - feedback: { warning: { default: "#654321" } }, - status: { question: "#123456" }, + status: { question: "#123456", permission: "#654321" }, }, }, }), diff --git a/packages/tui/test/component/session-tabs-status.test.tsx b/packages/tui/test/component/session-tabs-status.test.tsx index 1e3eeebe6f7..57eb6516a3b 100644 --- a/packages/tui/test/component/session-tabs-status.test.tsx +++ b/packages/tui/test/component/session-tabs-status.test.tsx @@ -124,6 +124,12 @@ for (const orientation of ["horizontal", "vertical"] as const) { setActive("second") setStatus({ ...EMPTY_SESSION_TAB_STATUS, busy: true, attention }) await app.renderOnce() + const indicatorColor = () => + app + .captureSpans() + .lines.flatMap((line) => line.spans) + .find((span) => span.text.trim() === (attention === "question" ? "?" : "!"))?.fg + expect(indicatorColor()?.toInts()).toEqual(theme.text.status[attention].toInts()) const glow = () => { const colors = app .captureSpans() @@ -140,6 +146,7 @@ for (const orientation of ["horizontal", "vertical"] as const) { expect(full).toBeGreaterThan(0) setActive("first") await app.renderOnce() + expect(indicatorColor()?.toInts()).toEqual(theme.text.status[attention].toInts()) const dim = glow() expect(dim).toBeGreaterThan(0) expect(dim).toBeLessThan(full)