Refine goal UI around memory theme tokens

Goal status needed to read like persistent agent state, not a success alert. Reuse the Memory surface language and expose goal-specific aliases so the visual treatment follows each supported desktop theme.\n\nConstraint: Must keep /goal visible in both active-session chrome and transcript events.\nRejected: Continue using success tokens | they made the UI look like a warning/success card and clashed with white/dark themes.\nConfidence: high\nScope-risk: narrow\nTested: cd desktop && bun run test -- --run src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx src/theme/globals.test.ts\nTested: cd desktop && bun run lint\nTested: cd desktop && bun run build\nTested: Chrome screenshot fixture for light, white, and dark goal UI tokens.
This commit is contained in:
程序员阿江(Relakkes) 2026-05-16 01:44:42 +08:00
parent d668a8e0d1
commit 291dd3e07d
4 changed files with 68 additions and 20 deletions

View File

@ -153,8 +153,7 @@ function GoalEventCard({ message }: { message: GoalEvent }) {
const t = useTranslation()
const titleKey = `chat.goalEvent.${message.action === 'status' ? 'statusTitle' : message.action}` as TranslationKey
const title = t(titleKey) === titleKey ? t('chat.goalEvent.message') : t(titleKey)
const details = [
message.objective ? t('chat.goalEvent.objective', { value: message.objective }) : null,
const metaDetails = [
message.status ? t('chat.goalEvent.statusValue', { value: message.status }) : null,
message.budget ? t('chat.goalEvent.budget', { value: message.budget }) : null,
message.continuations ? t('chat.goalEvent.continuations', { value: message.continuations }) : null,
@ -162,23 +161,33 @@ function GoalEventCard({ message }: { message: GoalEvent }) {
return (
<div className="mb-3 flex justify-center">
<div className="flex max-w-[min(680px,100%)] items-start gap-3 rounded-[8px] border border-[var(--color-success)]/25 bg-[var(--color-success-container)]/28 px-3.5 py-3 text-left shadow-sm">
<div className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] bg-[var(--color-success)]/12 text-[var(--color-success)]">
<div className="flex w-full max-w-[680px] items-start gap-3 rounded-lg border border-[var(--color-goal-border)] bg-[var(--color-goal-surface)] px-3 py-2.5 text-left">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-[var(--color-goal-border)] bg-[var(--color-goal-icon-bg)] text-[var(--color-goal-accent)]">
<Target size={15} strokeWidth={2.25} aria-hidden="true" />
</div>
<div className="min-w-0">
<div className="text-sm font-semibold text-[var(--color-text-primary)]">{title}</div>
{details.length > 0 ? (
<div className="mt-1 space-y-0.5 text-xs leading-5 text-[var(--color-text-secondary)]">
{details.map((detail) => (
<div key={detail} className="break-words">{detail}</div>
))}
{message.objective ? (
<div className="mt-1 line-clamp-2 text-xs leading-5 text-[var(--color-text-secondary)]">
{t('chat.goalEvent.objective', { value: message.objective })}
</div>
) : message.message ? (
<div className="mt-1 whitespace-pre-wrap text-xs leading-5 text-[var(--color-text-secondary)]">
{message.message}
</div>
) : null}
{metaDetails.length > 0 && (
<div className="mt-2 flex flex-wrap items-center gap-1.5">
{metaDetails.map((detail) => (
<span
key={detail}
className="rounded-[var(--radius-sm)] border border-[var(--color-goal-chip-border)] bg-[var(--color-goal-chip-bg)] px-1.5 py-0.5 text-[11px] font-medium text-[var(--color-text-secondary)]"
>
{detail}
</span>
))}
</div>
)}
</div>
</div>
</div>

View File

@ -223,31 +223,46 @@ function GoalStatusPanel({
data-testid="active-goal-panel"
className={
compact
? 'border-b border-[var(--color-success)]/20 bg-[var(--color-success)]/6 px-4 py-2'
: 'mx-auto w-full max-w-[860px] border-b border-[var(--color-success)]/20 px-8 py-2'
? 'border-b border-[var(--color-border)] bg-[var(--color-surface-container-lowest)] px-4 py-2'
: 'mx-auto w-full max-w-[900px] px-8 py-2.5'
}
>
<div className="flex min-w-0 items-center gap-3 rounded-md border border-[var(--color-success)]/25 bg-[var(--color-success)]/8 px-3 py-2">
<span className="material-symbols-outlined shrink-0 text-[18px] text-[var(--color-success)]">track_changes</span>
<div className="flex min-w-0 items-center gap-3 rounded-lg border border-[var(--color-goal-border)] bg-[var(--color-goal-surface)] px-3 py-2.5">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-[var(--color-goal-border)] bg-[var(--color-goal-icon-bg)] text-[var(--color-goal-accent)]">
<span className="material-symbols-outlined text-[18px]">track_changes</span>
</span>
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-2">
<span className="shrink-0 text-[11px] font-semibold uppercase tracking-normal text-[var(--color-success)]">
<span className="shrink-0 text-[12px] font-semibold text-[var(--color-text-primary)]">
{t('chat.activeGoal.title')}
</span>
<span className="shrink-0 rounded-sm bg-[var(--color-success)]/12 px-1.5 py-0.5 text-[10px] font-semibold text-[var(--color-success)]">
<span className="inline-flex shrink-0 items-center gap-1 rounded-[var(--radius-sm)] border border-[var(--color-goal-border)] bg-[var(--color-goal-chip-bg)] px-1.5 py-0.5 text-[11px] font-semibold text-[var(--color-goal-accent)]">
<span className="h-1.5 w-1.5 rounded-full bg-[var(--color-goal-accent)]" aria-hidden="true" />
{stateLabel}
</span>
{goal.objective && (
<span className="truncate text-xs font-semibold text-[var(--color-text-primary)]">
<span className="truncate text-sm font-semibold text-[var(--color-text-primary)]">
{goal.objective}
</span>
)}
</div>
{(goal.budget || goal.continuations || goal.elapsed) && (
<div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-[10px] text-[var(--color-text-secondary)]">
{goal.budget && <span>{t('chat.activeGoal.budget', { value: goal.budget })}</span>}
{goal.continuations && <span>{t('chat.activeGoal.continuations', { value: goal.continuations })}</span>}
{goal.elapsed && <span>{t('chat.activeGoal.elapsed', { value: goal.elapsed })}</span>}
<div className="mt-1.5 flex min-w-0 flex-wrap items-center gap-1.5">
{goal.budget && (
<span className="rounded-[var(--radius-sm)] border border-[var(--color-goal-chip-border)] bg-[var(--color-goal-chip-bg)] px-1.5 py-0.5 text-[11px] font-medium text-[var(--color-text-secondary)]">
{t('chat.activeGoal.budget', { value: goal.budget })}
</span>
)}
{goal.continuations && (
<span className="rounded-[var(--radius-sm)] border border-[var(--color-goal-chip-border)] bg-[var(--color-goal-chip-bg)] px-1.5 py-0.5 text-[11px] font-medium text-[var(--color-text-secondary)]">
{t('chat.activeGoal.continuations', { value: goal.continuations })}
</span>
)}
{goal.elapsed && (
<span className="rounded-[var(--radius-sm)] border border-[var(--color-goal-chip-border)] bg-[var(--color-goal-chip-bg)] px-1.5 py-0.5 text-[11px] font-medium text-[var(--color-text-secondary)]">
{t('chat.activeGoal.elapsed', { value: goal.elapsed })}
</span>
)}
</div>
)}
</div>

View File

@ -370,6 +370,12 @@
--color-memory-surface: #F6FAF8;
--color-memory-border: #BCCDC8;
--color-memory-icon-bg: #F3F8F7;
--color-goal-accent: var(--color-memory-accent);
--color-goal-surface: var(--color-memory-surface);
--color-goal-border: var(--color-memory-border);
--color-goal-icon-bg: var(--color-memory-icon-bg);
--color-goal-chip-bg: var(--color-surface);
--color-goal-chip-border: var(--color-border);
--color-model-option-selected-bg: var(--color-primary-fixed);
--color-model-option-selected-border: rgba(143, 72, 47, 0.2);
--color-activity-heat-0: var(--color-surface-container);
@ -571,6 +577,12 @@
--color-memory-surface: #F3F8F7;
--color-memory-border: #AECBC9;
--color-memory-icon-bg: #EDF7F6;
--color-goal-accent: var(--color-memory-accent);
--color-goal-surface: var(--color-memory-surface);
--color-goal-border: var(--color-memory-border);
--color-goal-icon-bg: var(--color-memory-icon-bg);
--color-goal-chip-bg: var(--color-surface);
--color-goal-chip-border: var(--color-border);
--color-model-option-selected-bg: #FFF0EA;
--color-model-option-selected-border: rgba(143, 72, 47, 0.2);
--color-activity-heat-0: #EEF2F6;
@ -749,6 +761,12 @@
--color-memory-surface: #1D2423;
--color-memory-border: #334743;
--color-memory-icon-bg: #22302E;
--color-goal-accent: var(--color-memory-accent);
--color-goal-surface: var(--color-memory-surface);
--color-goal-border: var(--color-memory-border);
--color-goal-icon-bg: var(--color-memory-icon-bg);
--color-goal-chip-bg: var(--color-surface-container-low);
--color-goal-chip-border: var(--color-border);
--color-model-option-selected-bg: rgba(255, 181, 159, 0.13);
--color-model-option-selected-border: rgba(255, 181, 159, 0.34);
--color-activity-heat-0: #242322;

View File

@ -49,6 +49,12 @@ describe('desktop theme tokens', () => {
'--color-info',
'--color-info-container',
'--color-warning-container',
'--color-goal-accent',
'--color-goal-surface',
'--color-goal-border',
'--color-goal-icon-bg',
'--color-goal-chip-bg',
'--color-goal-chip-border',
'--color-text-secondary-a72',
'--color-text-secondary-a68',
'--color-text-primary-a88',