mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-25 19:14:53 +02:00
[eric] tool-ui: the ShowUI host header colours its own title instead of inheriting it into a dark card
This commit is contained in:
@@ -6,10 +6,12 @@ import LinksWidget from './LinksWidget';
|
||||
import VendoredToolUi from '@toolui/VendoredToolUi';
|
||||
import type { ShowUiPayload } from './showUiPayload';
|
||||
import { useOpenUrlInBrowserCard } from './useOpenUrlInBrowserCard';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
|
||||
/** One switch for every surface that renders a ShowUI payload (chat bubble, pill artifact); ambient = low-cost render for resting surfaces. */
|
||||
function ShowUiWidgetView({ payload, ambient }: { payload: ShowUiPayload; ambient?: boolean }): React.ReactElement | null {
|
||||
const openUrl = useOpenUrlInBrowserCard();
|
||||
const c = useClaudeTokens();
|
||||
if (payload.component === 'weather') return <WeatherWidget props={payload.props} ambient={ambient} />;
|
||||
if (payload.component === 'plan') return <PlanWidget props={payload.props} />;
|
||||
if (payload.component === 'stats') return <StatsWidget props={payload.props} />;
|
||||
@@ -50,8 +52,13 @@ function ShowUiWidgetView({ payload, ambient }: { payload: ShowUiPayload; ambien
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 6, paddingLeft: 4, paddingRight: 4 }}>
|
||||
{title && <div style={{ fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.35 }}>{title}</div>}
|
||||
{desc && <div style={{ fontSize: '0.8125rem', opacity: 0.72, marginTop: 2, lineHeight: 1.4 }}>{desc}</div>}
|
||||
{/* Colour explicitly, never inherited. This header sits above a vendored card whose own
|
||||
surface is dark, and with no colour of its own it took whatever the ancestor happened to
|
||||
carry: on a light-mode app that is near-black text on a dark card, i.e. an invisible
|
||||
title (screenshot 2026-08-31). Same class as ENG-419: a surface that fixes its own
|
||||
background owes its text a token in the same place. */}
|
||||
{title && <div style={{ fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.35, color: c.text.primary }}>{title}</div>}
|
||||
{desc && <div style={{ fontSize: '0.8125rem', color: c.text.secondary, marginTop: 2, lineHeight: 1.4 }}>{desc}</div>}
|
||||
</div>
|
||||
{widget}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// The ShowUI host header (ENG-227) renders an agent-supplied title/description ABOVE a vendored
|
||||
// card. It set size and weight but no COLOUR, so it inherited whatever the ancestor carried: on a
|
||||
// light-mode app the inherited text is near-black, and the card underneath is dark, which is a
|
||||
// title you can only find by selecting it (Eric's screenshot, 2026-08-31).
|
||||
//
|
||||
// Same class as ENG-419: a surface that fixes its own background owes its text an explicit token in
|
||||
// the same place. Inheriting is the bug, not the styling.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const SRC = readFileSync('src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx', 'utf8');
|
||||
// The header block only; the rest of the file legitimately has uncoloured wrappers.
|
||||
const HEADER = SRC.slice(SRC.indexOf('if (!title && !desc) return widget;'), SRC.indexOf('return widget;', SRC.indexOf('if (!title && !desc)') + 40));
|
||||
|
||||
test('the host header title sets its own colour from the theme', () => {
|
||||
const title = HEADER.slice(HEADER.indexOf('{title &&'), HEADER.indexOf('{desc &&'));
|
||||
assert.match(title, /color:\s*c\.text\.primary/, 'the title must not inherit its colour');
|
||||
});
|
||||
|
||||
test('the description does too, and does not fake contrast with opacity', () => {
|
||||
const desc = HEADER.slice(HEADER.indexOf('{desc &&'));
|
||||
assert.match(desc, /color:\s*c\.text\.secondary/, 'the description must not inherit its colour');
|
||||
assert.doesNotMatch(desc, /opacity:/,
|
||||
'opacity over an inherited colour keeps the bug and only dims it; use the secondary token');
|
||||
});
|
||||
|
||||
test('the tokens actually come from the theme hook, not a literal', () => {
|
||||
assert.match(SRC, /useClaudeTokens/, 'the component must read live theme tokens');
|
||||
assert.doesNotMatch(HEADER, /color:\s*['"#]/, 'no hardcoded colour: it would break the other mode');
|
||||
});
|
||||
Reference in New Issue
Block a user