Improve desktop runtime UX by surfacing inference readiness in gateway status and hardening WSL link opening.

This also stabilizes markdown code/table block spacing and adds root-install guards so desktop dev runs use a healthy workspace dependency tree.
This commit is contained in:
Brooklyn Nicholson 2026-05-15 16:33:04 -05:00
parent d0c20708ce
commit c30550c552
12 changed files with 188 additions and 95 deletions

View file

@ -352,6 +352,44 @@ function rememberLog(chunk) {
scheduleDesktopLogFlush()
}
function openExternalUrl(rawUrl) {
const raw = String(rawUrl || '').trim()
if (!raw) return false
let parsed
try {
parsed = new URL(raw)
} catch {
return false
}
if (!['http:', 'https:', 'mailto:'].includes(parsed.protocol)) {
return false
}
const url = parsed.toString()
if (IS_WSL) {
rememberLog(`[link] opening via WSL→Windows: ${url}`)
const proc = spawn('cmd.exe', ['/c', 'start', '""', url], {
detached: true,
stdio: 'ignore',
windowsHide: true
})
proc.on('error', error => {
rememberLog(`[link] cmd.exe start failed: ${error.message}; falling back to xdg-open`)
shell.openExternal(url).catch(fallback => rememberLog(`[link] xdg-open failed: ${fallback.message}`))
})
proc.unref()
return true
}
shell.openExternal(url).catch(error => rememberLog(`[link] openExternal failed: ${error.message}`))
return true
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
@ -2067,7 +2105,7 @@ function installContextMenu(window) {
label: 'Open Image',
click: () => {
if (params.srcURL && !params.srcURL.startsWith('data:')) {
void shell.openExternal(params.srcURL)
openExternalUrl(params.srcURL)
}
},
enabled: !params.srcURL.startsWith('data:')
@ -2096,7 +2134,7 @@ function installContextMenu(window) {
template.push(
{
label: 'Open Link',
click: () => void shell.openExternal(params.linkURL)
click: () => openExternalUrl(params.linkURL)
},
{
label: 'Copy Link',
@ -2570,6 +2608,19 @@ function createWindow() {
installPreviewShortcut(mainWindow)
installDevToolsShortcut(mainWindow)
installContextMenu(mainWindow)
mainWindow.webContents.setWindowOpenHandler(details => {
openExternalUrl(details.url)
return { action: 'deny' }
})
mainWindow.webContents.on('will-navigate', (event, url) => {
if ((DEV_SERVER && url.startsWith(DEV_SERVER)) || (!DEV_SERVER && url.startsWith('file:'))) {
return
}
event.preventDefault()
openExternalUrl(url)
})
if (DEV_SERVER) {
mainWindow.loadURL(DEV_SERVER)
@ -2731,7 +2782,11 @@ ipcMain.on('hermes:titlebar-theme', (_event, payload) => {
mainWindow?.setTitleBarOverlay?.(getTitleBarOverlayOptions())
})
ipcMain.handle('hermes:openExternal', (_event, url) => shell.openExternal(url))
ipcMain.handle('hermes:openExternal', (_event, url) => {
if (!openExternalUrl(url)) {
throw new Error('Invalid external URL')
}
})
ipcMain.handle('hermes:fetchLinkTitle', (_event, url) => fetchLinkTitle(url))

View file

@ -10,12 +10,12 @@
"scripts": {
"dev": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"",
"dev:fake-boot": "cross-env HERMES_DESKTOP_BOOT_FAKE=1 HERMES_DESKTOP_BOOT_FAKE_STEP_MS=650 npm run dev",
"dev:renderer": "vite --host 127.0.0.1 --port 5174",
"dev:electron": "wait-on http://127.0.0.1:5174 && cross-env HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
"profile:main": "wait-on http://127.0.0.1:5174 && cross-env HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron --inspect=9229 .",
"profile:main:cpu": "wait-on http://127.0.0.1:5174 && cross-env NODE_OPTIONS=--cpu-prof HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
"dev:renderer": "node scripts/assert-root-install.cjs && vite --host 127.0.0.1 --port 5174",
"dev:electron": "wait-on http://127.0.0.1:5174 && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
"profile:main": "wait-on http://127.0.0.1:5174 && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron --inspect=9229 .",
"profile:main:cpu": "wait-on http://127.0.0.1:5174 && cross-env XCURSOR_SIZE=24 NODE_OPTIONS=--cpu-prof HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
"start": "npm run build && electron .",
"build": "tsc -b && vite build",
"build": "node scripts/assert-root-install.cjs && tsc -b && vite build",
"stage:hermes": "node scripts/stage-hermes-payload.mjs",
"builder": "cross-env NODE_OPTIONS=--max-old-space-size=16384 electron-builder",
"pack": "npm run build && npm run stage:hermes && npm run builder -- --dir",
@ -38,7 +38,7 @@
"fmt": "prettier --write 'src/**/*.{ts,tsx}' 'electron/**/*.{js,cjs}' 'vite.config.ts'",
"fix": "npm run lint:fix && npm run fmt",
"test:ui": "vitest run --environment jsdom",
"preview": "vite preview --host 127.0.0.1 --port 4174"
"preview": "node scripts/assert-root-install.cjs && vite preview --host 127.0.0.1 --port 4174"
},
"dependencies": {
"@assistant-ui/react": "^0.12.28",

View file

@ -0,0 +1,13 @@
"use strict"
const fs = require("fs")
const path = require("path")
const root = path.resolve(__dirname, "..", "..", "..")
try {
fs.accessSync(path.join(root, "node_modules", "vite", "package.json"))
} catch {
console.error(`Run from repo root: cd ${root} && npm ci`)
process.exit(1)
}

View file

@ -867,7 +867,7 @@ export function CommandCenterView({
)}
/>
<span className="font-medium text-foreground">
{status.gateway_running ? 'Gateway running' : 'Gateway not running'}
{status.gateway_running ? 'Messaging gateway running' : 'Messaging gateway stopped'}
</span>
</div>
<div className="mt-1 text-xs text-muted-foreground">
@ -876,7 +876,7 @@ export function CommandCenterView({
</div>
<div className="flex shrink-0 items-center gap-1.5 whitespace-nowrap">
<OverlayActionButton className="h-7 px-2.5" onClick={() => void runSystemAction('restart')}>
Restart gateway
Restart messaging
</OverlayActionButton>
<OverlayActionButton className="h-7 px-2.5" onClick={() => void runSystemAction('update')}>
Update Hermes

View file

@ -208,7 +208,7 @@ export function DesktopController() {
}
}, [])
const { gatewayLogLines, statusSnapshot } = useStatusSnapshot(gatewayState)
const { gatewayLogLines, inferenceStatus, statusSnapshot } = useStatusSnapshot(gatewayState, requestGateway)
const { browseSessionCwd, changeSessionCwd, refreshProjectBranch } = useCwdActions({
activeSessionId,
@ -393,6 +393,8 @@ export function DesktopController() {
extraLeftItems: statusbarItemGroups.flat.left,
extraRightItems: statusbarItemGroups.flat.right,
gatewayLogLines,
gatewayState,
inferenceStatus,
openAgents,
openCommandCenterSection,
statusSnapshot,

View file

@ -33,7 +33,7 @@ const STATE_LABELS: Record<string, string> = {
connecting: 'Connecting',
disabled: 'Disabled',
fatal: 'Error',
gateway_stopped: 'Gateway stopped',
gateway_stopped: 'Messaging gateway stopped',
not_configured: 'Needs setup',
pending_restart: 'Restart needed',
retrying: 'Retrying',
@ -502,7 +502,7 @@ function PlatformDetail({
<SetupPill active={platform.configured}>
{platform.configured ? 'Credentials set' : 'Needs setup'}
</SetupPill>
{!platform.gateway_running && <SetupPill active={false}>Gateway stopped</SetupPill>}
{!platform.gateway_running && <SetupPill active={false}>Messaging gateway stopped</SetupPill>}
</div>
<PlatformHint platform={platform} />
</div>

View file

@ -2,15 +2,16 @@ import { IconLayoutDashboard } from '@tabler/icons-react'
import { StatusDot, type StatusTone } from '@/components/status-dot'
import { Button } from '@/components/ui/button'
import { Activity, AlertCircle, RefreshCw } from '@/lib/icons'
import { Activity, AlertCircle } from '@/lib/icons'
import type { RuntimeReadinessResult } from '@/lib/runtime-readiness'
import { cn } from '@/lib/utils'
import type { StatusResponse } from '@/types/hermes'
interface GatewayMenuPanelProps {
gatewayState: string
inferenceStatus: RuntimeReadinessResult | null
logLines: readonly string[]
onOpenSystem: () => void
onRestart: () => void
restarting: boolean
statusSnapshot: StatusResponse | null
}
@ -32,44 +33,41 @@ const RUNTIME_BRACKET_RE = /^\[[^\]]+]\s+/
const trimLogLine = (raw: string) => raw.trim().replace(TIMESTAMP_RE, '').replace(RUNTIME_BRACKET_RE, '')
export function GatewayMenuPanel({
gatewayState,
inferenceStatus,
logLines,
onOpenSystem,
onRestart,
restarting,
statusSnapshot
}: GatewayMenuPanelProps) {
const gatewayRunning = Boolean(statusSnapshot?.gateway_running)
const gatewayOpen = gatewayState === 'open'
const inferenceReady = gatewayOpen && inferenceStatus?.ready === true
const connectionLabel = gatewayOpen ? 'Connected' : prettyState(gatewayState || 'offline')
const inferenceLabel = gatewayOpen
? inferenceStatus
? inferenceReady
? 'Inference ready'
: 'Inference not ready'
: 'Checking inference'
: 'Disconnected'
const platforms = Object.entries(statusSnapshot?.gateway_platforms || {}).sort(([l], [r]) => l.localeCompare(r))
const stateLabel = gatewayRunning ? prettyState(statusSnapshot?.gateway_state || 'online') : 'Offline'
const recentLogs = logLines.slice(-5)
return (
<div className="text-sm">
<div className="flex items-center justify-between gap-2 px-3 py-2.5">
<div className="flex min-w-0 items-center gap-2">
{gatewayRunning ? (
{inferenceReady ? (
<Activity className="size-3.5 text-primary" />
) : (
<AlertCircle className="size-3.5 text-destructive" />
<AlertCircle className={cn('size-3.5', gatewayOpen ? 'text-amber-600' : 'text-destructive')} />
)}
<span className="font-medium">Gateway</span>
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<StatusDot tone={gatewayRunning ? 'good' : 'bad'} />
{stateLabel}
<StatusDot tone={inferenceReady ? 'good' : gatewayOpen ? 'warn' : 'bad'} />
{inferenceLabel}
</span>
</div>
<div className="flex items-center">
<Button
aria-label={restarting ? 'Restarting gateway' : 'Restart gateway'}
className="size-7 text-muted-foreground hover:text-foreground"
disabled={restarting}
onClick={onRestart}
size="icon-sm"
title={restarting ? 'Restarting gateway' : 'Restart gateway'}
variant="ghost"
>
<RefreshCw className={cn(restarting && 'animate-spin')} />
</Button>
<Button
aria-label="Open system panel"
className="size-7 text-muted-foreground hover:text-foreground"
@ -83,6 +81,11 @@ export function GatewayMenuPanel({
</div>
</div>
<div className="border-t border-border/50 px-3 py-2 text-xs text-muted-foreground">
<div>Connection: {connectionLabel}</div>
{inferenceStatus?.reason && <div className="mt-1 line-clamp-3">{inferenceStatus.reason}</div>}
</div>
{recentLogs.length > 0 && (
<div className="border-t border-border/50 px-3 py-2">
<SectionLabel>Recent activity</SectionLabel>
@ -109,7 +112,7 @@ export function GatewayMenuPanel({
{platforms.length > 0 && (
<div className="border-t border-border/50 px-3 py-2">
<SectionLabel>Platforms</SectionLabel>
<SectionLabel>Messaging platforms</SectionLabel>
<ul className="mt-1.5 space-y-1">
{platforms.map(([name, platform]) => (
<li className="flex items-center justify-between gap-2 text-xs" key={name}>

View file

@ -1,23 +1,35 @@
import { useEffect, useState } from 'react'
import { getLogs, getStatus } from '@/hermes'
import { evaluateRuntimeReadiness, type RuntimeReadinessResult } from '@/lib/runtime-readiness'
import type { StatusResponse } from '@/types/hermes'
const REFRESH_MS = 15_000
const LOG_TAIL = 12
export function useStatusSnapshot(gatewayState: string | undefined) {
type GatewayRequester = <T = unknown>(method: string, params?: Record<string, unknown>) => Promise<T>
export function useStatusSnapshot(gatewayState: string | undefined, requestGateway: GatewayRequester) {
const [statusSnapshot, setStatusSnapshot] = useState<StatusResponse | null>(null)
const [gatewayLogLines, setGatewayLogLines] = useState<string[]>([])
const [inferenceStatus, setInferenceStatus] = useState<RuntimeReadinessResult | null>(null)
useEffect(() => {
let cancelled = false
const refresh = async () => {
try {
const [next, logs] = await Promise.all([
const [next, logs, inference] = await Promise.all([
getStatus(),
getLogs({ file: 'gateway', lines: LOG_TAIL }).catch(() => ({ lines: [] }))
getLogs({ file: 'gui', lines: LOG_TAIL }).catch(() => ({ lines: [] })),
gatewayState === 'open'
? evaluateRuntimeReadiness(requestGateway).catch(error => ({
checksDisagree: false,
ready: false,
reason: error instanceof Error ? error.message : String(error),
source: 'fallback' as const
}))
: Promise.resolve(null)
])
if (cancelled) {
@ -26,6 +38,7 @@ export function useStatusSnapshot(gatewayState: string | undefined) {
setStatusSnapshot(next)
setGatewayLogLines(logs.lines.map(line => line.trim()).filter(Boolean))
setInferenceStatus(inference)
} catch {
// Keep last snapshot through transient gateway flaps.
}
@ -38,7 +51,7 @@ export function useStatusSnapshot(gatewayState: string | undefined) {
cancelled = true
window.clearInterval(timer)
}
}, [gatewayState])
}, [gatewayState, requestGateway])
return { gatewayLogLines, statusSnapshot }
return { gatewayLogLines, inferenceStatus, statusSnapshot }
}

View file

@ -1,14 +1,13 @@
import { useStore } from '@nanostores/react'
import { useCallback, useMemo, useState } from 'react'
import { useMemo } from 'react'
import type { CommandCenterSection } from '@/app/command-center'
import { GatewayMenuPanel } from '@/app/shell/gateway-menu-panel'
import { restartGateway } from '@/hermes'
import { Activity, AlertCircle, Clock, Command, Cpu, FolderOpen, GitBranch, Hash, Loader2, Sparkles } from '@/lib/icons'
import type { RuntimeReadinessResult } from '@/lib/runtime-readiness'
import { compactPath, contextBarLabel, LiveDuration, usageContextLabel } from '@/lib/statusbar'
import { cn } from '@/lib/utils'
import { $desktopActionTasks } from '@/store/activity'
import { notify, notifyError } from '@/store/notifications'
import { $previewServerRestartStatus } from '@/store/preview'
import {
$busy,
@ -36,6 +35,8 @@ interface StatusbarItemsOptions {
extraLeftItems: readonly StatusbarItem[]
extraRightItems: readonly StatusbarItem[]
gatewayLogLines: readonly string[]
gatewayState: string
inferenceStatus: RuntimeReadinessResult | null
openAgents: () => void
openCommandCenterSection: (section: CommandCenterSection) => void
statusSnapshot: StatusResponse | null
@ -49,6 +50,8 @@ export function useStatusbarItems({
extraLeftItems,
extraRightItems,
gatewayLogLines,
gatewayState,
inferenceStatus,
openAgents,
openCommandCenterSection,
statusSnapshot,
@ -73,40 +76,17 @@ export function useStatusbarItems({
const contextUsage = useMemo(() => usageContextLabel(currentUsage), [currentUsage])
const contextBar = useMemo(() => contextBarLabel(currentUsage), [currentUsage])
const [restartingGateway, setRestartingGateway] = useState(false)
const handleRestartGateway = useCallback(async () => {
if (restartingGateway) {
return
}
setRestartingGateway(true)
try {
await restartGateway()
notify({
kind: 'success',
title: 'Gateway restart requested',
message: 'Status will update once the gateway reconnects.'
})
} catch (err) {
notifyError(err, 'Failed to restart gateway')
} finally {
setRestartingGateway(false)
}
}, [restartingGateway])
const gatewayMenuContent = useMemo(
() => (
<GatewayMenuPanel
logLines={gatewayLogLines}
onOpenSystem={() => openCommandCenterSection('system')}
onRestart={() => void handleRestartGateway()}
restarting={restartingGateway}
gatewayState={gatewayState}
inferenceStatus={inferenceStatus}
statusSnapshot={statusSnapshot}
/>
),
[gatewayLogLines, handleRestartGateway, openCommandCenterSection, restartingGateway, statusSnapshot]
[gatewayLogLines, gatewayState, inferenceStatus, openCommandCenterSection, statusSnapshot]
)
const { bgFailed, bgRunning, subagentsRunning } = useMemo(() => {
@ -124,7 +104,22 @@ export function useStatusbarItems({
}
}, [desktopActionTasks, previewServerRestartStatus, subagentsBySession, workingSessionIds])
const gatewayUp = Boolean(statusSnapshot?.gateway_running)
const gatewayOpen = gatewayState === 'open'
const inferenceReady = gatewayOpen && inferenceStatus?.ready === true
const gatewayDetail = gatewayOpen
? inferenceStatus
? inferenceReady
? 'ready'
: 'needs setup'
: 'checking'
: gatewayState === 'connecting'
? 'connecting'
: 'offline'
const gatewayClassName = inferenceReady
? undefined
: gatewayOpen || gatewayState === 'connecting'
? 'text-amber-600 hover:text-amber-600'
: 'text-destructive hover:text-destructive'
const versionItem = useMemo<StatusbarItem>(() => {
const appVersion = desktopVersion?.appVersion
@ -182,14 +177,14 @@ export function useStatusbarItems({
variant: 'action'
},
{
className: gatewayUp ? undefined : 'text-destructive hover:text-destructive',
detail: gatewayUp ? statusSnapshot?.gateway_state || 'online' : 'offline',
icon: gatewayUp ? <Activity className="size-3" /> : <AlertCircle className="size-3" />,
className: gatewayClassName,
detail: gatewayDetail,
icon: inferenceReady ? <Activity className="size-3" /> : <AlertCircle className="size-3" />,
id: 'gateway-health',
label: 'Gateway',
menuClassName: 'w-72',
menuContent: gatewayMenuContent,
title: 'Gateway and platform health',
title: inferenceStatus?.reason || 'Hermes inference gateway status',
variant: 'menu'
},
{
@ -234,9 +229,11 @@ export function useStatusbarItems({
bgRunning,
commandCenterOpen,
gatewayMenuContent,
gatewayUp,
gatewayClassName,
gatewayDetail,
inferenceReady,
inferenceStatus?.reason,
openAgents,
statusSnapshot?.gateway_state,
subagentsRunning,
toggleCommandCenter
]

View file

@ -53,9 +53,8 @@ function CodeHeader({ language, code }: { language?: string; code?: string }) {
const label = cleanLanguage && cleanLanguage !== 'unknown' ? cleanLanguage : ''
return (
<div className="m-0 flex items-stretch justify-between gap-2 rounded-t-md border border-b-0 border-border bg-muted/60 pr-3 text-xs text-muted-foreground">
<span className="flex items-center gap-2.5 py-1.5 pl-0 font-mono uppercase tracking-[0.16em]">
<span aria-hidden="true" className="self-stretch w-[2px] -my-1.5 bg-midground/60" />
<div className="aui-code-header m-0 flex items-stretch justify-between gap-2 rounded-t-md border border-b-0 border-border bg-muted/60 pr-3 text-xs text-muted-foreground">
<span className="flex items-center gap-2.5 py-1.5 pl-3 font-mono uppercase tracking-[0.16em]">
<span className="text-midground/85">{label || 'code'}</span>
</span>
<CopyButton appearance="inline" iconClassName="size-3" label="Copy code" text={normalizedCode}>
@ -288,10 +287,10 @@ const MarkdownTextImpl = () => {
<li className={cn('leading-(--dt-line-height)', className)} {...props} />
),
table: ({ className, ...props }: ComponentProps<'table'>) => (
<div className="max-w-full overflow-x-auto rounded-md border border-border">
<div className="aui-md-table my-3 max-w-full overflow-x-auto rounded-md border border-border">
<table
className={cn(
'w-full border-collapse text-sm [&_tr]:border-b [&_tr]:border-border last:[&_tr]:border-0',
'm-0 w-full border-collapse text-sm [&_tr]:border-b [&_tr]:border-border last:[&_tr]:border-0',
className
)}
{...props}
@ -299,12 +298,12 @@ const MarkdownTextImpl = () => {
</div>
),
thead: ({ className, ...props }: ComponentProps<'thead'>) => (
<thead className={cn('bg-muted/50 text-foreground', className)} {...props} />
<thead className={cn('m-0 bg-muted/50 text-foreground', className)} {...props} />
),
th: ({ className, ...props }: ComponentProps<'th'>) => (
<th
className={cn(
'h-9 px-3 text-left align-middle text-xs font-semibold uppercase tracking-[0.16em] text-midground/75',
'px-3 py-1.5 text-left align-middle text-xs font-semibold uppercase tracking-[0.16em] text-midground/75',
className
)}
{...props}

View file

@ -29,6 +29,9 @@ export const SyntaxHighlighter: FC<HermesSyntaxHighlighterProps> = ({
code,
defer = false
}) => {
const preClassName =
'aui-shiki m-0 overflow-hidden rounded-b-md border border-t-0 border-border bg-card font-mono text-sm leading-relaxed [&_pre]:m-0 [&_pre]:overflow-x-auto [&_pre]:bg-transparent! [&_pre]:px-4 [&_pre]:py-3 [&_pre]:font-mono [&_pre]:leading-relaxed'
// Streamdown may hand us fence contents with edge newlines. Strip blank
// fence padding without touching indentation on the first real line.
const trimmed = (code ?? '').replace(/^\n+/, '').trimEnd()
@ -45,14 +48,14 @@ export const SyntaxHighlighter: FC<HermesSyntaxHighlighterProps> = ({
if (defer) {
return (
<Pre className="aui-shiki m-0 overflow-hidden rounded-b-md border border-t-0 border-border bg-card font-mono text-sm leading-relaxed [&_pre]:m-0 [&_pre]:overflow-x-auto [&_pre]:bg-transparent! [&_pre]:px-4 [&_pre]:py-3 [&_pre]:font-mono [&_pre]:leading-relaxed">
<Pre className={preClassName}>
<code className="block whitespace-pre">{trimmed}</code>
</Pre>
)
}
return (
<Pre className="aui-shiki m-0 overflow-hidden rounded-b-md border border-t-0 border-border bg-card font-mono text-sm leading-relaxed [&_pre]:m-0 [&_pre]:overflow-x-auto [&_pre]:bg-transparent! [&_pre]:px-4 [&_pre]:py-3 [&_pre]:font-mono [&_pre]:leading-relaxed">
<Pre className={preClassName}>
<ShikiHighlighter
addDefaultStyles={false}
as="div"

View file

@ -457,17 +457,25 @@ canvas {
white-space: inherit;
}
[data-slot='aui_assistant-message-content'] .aui-md [data-streamdown='code-block'] {
padding: 0 !important;
gap: 0 !important;
border: 0 !important;
background: transparent !important;
border-radius: 0 !important;
margin: 1rem 0 !important;
[data-slot='aui_assistant-message-content'] .aui-md :where(.aui-code-header, .aui-shiki, .aui-shiki > pre) {
margin: 0 !important;
margin-block-start: 0 !important;
margin-block-end: 0 !important;
}
[data-slot='aui_assistant-message-content'] .aui-md [data-streamdown='code-block'] > * {
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table {
border-spacing: 0;
}
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table > table,
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table thead,
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table tbody,
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table tr,
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table th,
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table td {
margin: 0 !important;
margin-block-start: 0 !important;
margin-block-end: 0 !important;
}
[data-slot='tool-block'] + [data-slot='tool-block'] {