From d010220588baa1c7fe4326d7086ae651f301c716 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Thu, 23 Jul 2026 01:13:18 -0500 Subject: [PATCH] fix(desktop): de-dupe credit toast icon, band-color the figure, split detail Three fixes to how agent credit notices render as toasts: - Strip the leading severity glyph (the toast already draws a kind icon, so the raw text doubled it). Native OS notifications keep the glyph (no icon there). - Icon top-margin is now 0.42ch (font-relative) instead of a fixed rem. - Band-color the $used figure (semibold) by $used/$cap: muted <75%, --ui-orange >=75%, --ui-red >=90% (depleted red, restored green), reusing the existing --ui-* usage palette. Icon shares the accent. - Split a trailing '. detail' into a muted secondary line (title+description convention) instead of an inline middot. Generic 'accentColor' + 'meta' slots on the notification; degrades gracefully when a notice has no figure. --- apps/desktop/src/components/notifications.tsx | 41 ++++++- apps/desktop/src/store/agent-notices.test.ts | 88 +++++++++++++-- apps/desktop/src/store/agent-notices.ts | 105 +++++++++++++++++- apps/desktop/src/store/notifications.ts | 8 ++ 4 files changed, 225 insertions(+), 17 deletions(-) diff --git a/apps/desktop/src/components/notifications.tsx b/apps/desktop/src/components/notifications.tsx index ec6051843cd..465e501f54c 100644 --- a/apps/desktop/src/components/notifications.tsx +++ b/apps/desktop/src/components/notifications.tsx @@ -1,5 +1,5 @@ import { useStore } from '@nanostores/react' -import { type ReactNode, useEffect, useRef, useState } from 'react' +import { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' @@ -162,6 +162,30 @@ function BottomRightStack({ ) } +// Emphasize only the leading money figure ("$16.00" — the amount used) with the +// accent color (semibold), leaving the rest of the line in its default muted +// tone. No accent, or no figure in the message → render the text untouched. +function renderMessage(message: string, accent?: string): ReactNode { + const match = accent ? /\$\d+(?:\.\d{2})?/.exec(message) : null + + if (!match) { + return message + } + + const start = match.index + const end = start + match[0].length + + return ( + <> + {message.slice(0, start)} + + {match[0]} + + {message.slice(end)} + + ) +} + function NotificationItem({ notification }: { notification: AppNotification }) { const styles = tone[notification.kind] const Icon = styles.icon @@ -169,6 +193,12 @@ function NotificationItem({ notification }: { notification: AppNotification }) { const { t } = useI18n() const copy = t.notifications + // Nudge the icon down to sit on the first text line, in `ch` so it tracks the + // toast's font size instead of a fixed rem. `accentColor` (when set) tints the + // icon + message as a severity ramp, overriding the kind's default color. + const accent = notification.accentColor + const iconStyle: CSSProperties = { marginTop: '0.42ch', ...(accent ? { color: accent } : {}) } + return ( {notification.icon ? ( - + ) : ( - + )}
{notification.title && {notification.title}} -

{notification.message}

+

{renderMessage(notification.message, accent)}

+ {notification.meta && ( +

{notification.meta}

+ )} {hasDetail && } {notification.action && (