Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 57 additions & 33 deletions program/plugins/mermaid.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { uiStyles } from './ui/appearance.js'
import type { Mermaid, MermaidConfig, RenderResult } from 'mermaid'
import type { Plugin } from 'cordis'
import type { MarkdownCodeBlockProps } from './markdown-api.js'
import { DEFAULT_THEME, normalizeAccent } from './theme-api.js'
import { useThemeAppearance, type ClientThemeAppearance } from './theme-runtime.js'
import styles from './mermaid.css'

export interface MermaidDiagramConfig {
Expand All @@ -38,7 +40,27 @@ function clamp(value: number | undefined, fallback: number, min: number, max: nu
return Math.max(min, Math.min(max, value ?? fallback))
}

export function mermaidConfiguration(config: MermaidDiagramConfig): MermaidConfig {
function mixColor(accent: string, surface: string, amount: number): string {
return '#' + [1, 3, 5].map((offset) => (
Math.round(Number.parseInt(accent.slice(offset, offset + 2), 16) * amount
+ Number.parseInt(surface.slice(offset, offset + 2), 16) * (1 - amount))
.toString(16).padStart(2, '0')
)).join('')
}

export function mermaidConfiguration(
config: MermaidDiagramConfig,
appearance: ClientThemeAppearance = { mode: 'light', accent: DEFAULT_THEME.accent },
): MermaidConfig {
const dark = appearance.mode === 'dark'
const accent = normalizeAccent(appearance.accent)
const surface = dark ? '#262931' : '#f7f7fa'
const text = dark ? '#c8ccd8' : '#42475d'
// Mermaid derives other colors from these values, so supply concrete hex
// colors rather than CSS variables. Neutral ink keeps extreme accents legible.
const primary = mixColor(accent, surface, dark ? 0.18 : 0.08)
const primaryBorder = mixColor(accent, text, 0.35)
const border = dark ? '#565c6d' : '#c7cad5'
return {
startOnLoad: false,
securityLevel: 'strict',
Expand All @@ -48,44 +70,45 @@ export function mermaidConfiguration(config: MermaidDiagramConfig): MermaidConfi
theme: 'base',
look: 'classic',
themeVariables: {
background: '#f7f7fa',
darkMode: dark,
background: surface,
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", Inter, sans-serif',
fontSize: '14px',
primaryColor: '#f0edff',
primaryBorderColor: '#a993f1',
primaryTextColor: '#42475d',
secondaryColor: '#edf2ff',
secondaryBorderColor: '#aebce5',
secondaryTextColor: '#42475d',
tertiaryColor: '#f2f3f7',
tertiaryBorderColor: '#c7cad5',
tertiaryTextColor: '#42475d',
lineColor: '#858ba0',
textColor: '#42475d',
mainBkg: '#f0edff',
nodeBorder: '#a993f1',
clusterBkg: '#f5f4fa',
clusterBorder: '#d8d5e2',
edgeLabelBackground: '#f7f7fa',
noteBkgColor: '#fff8d9',
noteBorderColor: '#d8c980',
noteTextColor: '#4d4b43',
actorBkg: '#f0edff',
actorBorder: '#a993f1',
actorTextColor: '#42475d',
signalColor: '#73798f',
signalTextColor: '#42475d',
labelBoxBkgColor: '#f7f7fa',
labelBoxBorderColor: '#c7cad5',
labelTextColor: '#42475d',
primaryColor: primary,
primaryBorderColor: primaryBorder,
primaryTextColor: text,
secondaryColor: dark ? '#2c3447' : '#edf2ff',
secondaryBorderColor: dark ? '#5c6a8b' : '#aebce5',
secondaryTextColor: text,
tertiaryColor: dark ? '#2b2e37' : '#f2f3f7',
tertiaryBorderColor: border,
tertiaryTextColor: text,
lineColor: dark ? '#989fb2' : '#858ba0',
textColor: text,
mainBkg: primary,
nodeBorder: primaryBorder,
clusterBkg: dark ? '#23262e' : '#f5f4fa',
clusterBorder: dark ? border : '#d8d5e2',
edgeLabelBackground: surface,
noteBkgColor: dark ? '#403a24' : '#fff8d9',
noteBorderColor: dark ? '#9e8948' : '#d8c980',
noteTextColor: dark ? '#e9ddb6' : '#4d4b43',
actorBkg: primary,
actorBorder: primaryBorder,
actorTextColor: text,
signalColor: dark ? '#989fb2' : '#73798f',
signalTextColor: text,
labelBoxBkgColor: surface,
labelBoxBorderColor: border,
labelTextColor: text,
},
}
}

async function mermaidRuntime(config: MermaidDiagramConfig): Promise<Mermaid> {
async function mermaidRuntime(config: MermaidDiagramConfig, appearance: ClientThemeAppearance): Promise<Mermaid> {
runtimePromise ??= import('mermaid').then((module) => module.default)
const runtime = await runtimePromise
const siteConfig = mermaidConfiguration(config)
const siteConfig = mermaidConfiguration(config, appearance)
const signature = JSON.stringify(siteConfig)
if (signature !== activeConfiguration) {
runtime.initialize(siteConfig)
Expand Down Expand Up @@ -228,6 +251,7 @@ export function MermaidDiagram({
config,
overlays,
}: MarkdownCodeBlockProps & { config: MermaidDiagramConfig; overlays?: ClientOverlays }): ReactNode {
const { mode, accent } = useThemeAppearance()
const renderId = `alto-mermaid-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`
const stageRef = useRef<HTMLDivElement>(null)
const [expandedHeight, setExpandedHeight] = useState<number>()
Expand Down Expand Up @@ -255,7 +279,7 @@ export function MermaidDiagram({
const timer = window.setTimeout(() => {
void (async () => {
try {
const runtime = await mermaidRuntime(config)
const runtime = await mermaidRuntime(config, { mode, accent })
const valid = await runtime.parse(source, { suppressErrors: true })
if (!valid) throw new Error('Invalid Mermaid diagram')
// Mermaid uses this id while building the SVG. A fresh id avoids
Expand All @@ -278,7 +302,7 @@ export function MermaidDiagram({
active = false
window.clearTimeout(timer)
}
}, [code, config, maxSourceCharacters, renderId])
}, [code, config, maxSourceCharacters, renderId, mode, accent])

useEffect(() => { if (!render.svg) closeExpanded() }, [render.svg, closeExpanded])

Expand Down
7 changes: 2 additions & 5 deletions program/plugins/mermaid.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
color: var(--text);
border: 1px solid color-mix(in srgb, var(--border) 78%, transparent);
border-radius: 12px;
background: color-mix(in srgb, var(--panel-solid) 88%, white);
background: var(--ui-surface);
}

.cordis-mermaid figcaption {
Expand Down Expand Up @@ -154,10 +154,7 @@
min-width: 0;
overflow: auto;
overscroll-behavior: contain;
/* Match the renderer’s light diagram palette in either application theme. */
color-scheme: light;
--ui-focus: CanvasText;
background: Canvas;
background: var(--ui-surface);
}

.cordis-mermaid-expanded-viewport:focus-visible {
Expand Down
2 changes: 1 addition & 1 deletion program/plugins/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ html[data-alto-theme='dark'] .ghostty-terminal-surface {

html[data-alto-theme='dark'] .shell-kernel main:not(.program-modal-content) {
background:
radial-gradient(circle at 51% -18%, rgba(76, 66, 112, 0.16), transparent 40%),
radial-gradient(circle at 51% -18%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 40%),
linear-gradient(135deg, #1f2229 0%, #1c1f26 54%, #191c22 100%);
}

Expand Down
1 change: 1 addition & 0 deletions program/plugins/ui/appearance-tokens.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* App-wide roles inherit the selected palette, including body-mounted portals.
The window's decorative accent and native material remain independent. */
:root {
accent-color: var(--ui-action);
--ui-font: var(--composer-font);
--sans: var(--ui-font);
--display: var(--sans);
Expand Down
25 changes: 19 additions & 6 deletions program/plugins/ui/controls.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,22 @@

:is(.alto-button, .button).ghost:active:not(:disabled) { background: var(--ui-pressed); }

:is(.alto-button, .button).primary,
.composer .send-button:not(.stop):not(:disabled) {
:is(.alto-button, .button).primary {
color: var(--ui-on-action);
border-color: transparent;
background: var(--ui-action);
box-shadow: none;
}

:is(.alto-button, .button).primary:hover:not(:disabled),
.composer .send-button:not(.stop):not(:disabled):hover {
:is(.alto-button, .button).primary:hover:not(:disabled) {
color: var(--ui-on-action);
border-color: transparent;
background: var(--ui-action-hover);
box-shadow: none;
filter: none;
}

:is(.alto-button, .button).primary:active:not(:disabled),
.composer .send-button:not(.stop):not(:disabled):active { background: var(--ui-action-pressed); }
:is(.alto-button, .button).primary:active:not(:disabled) { background: var(--ui-action-pressed); }

:is(.alto-button, .button).danger { color: var(--ui-danger); }
:is(.alto-button, .button).danger:hover:not(:disabled) { background: color-mix(in srgb, var(--ui-danger) 9%, transparent); }
Expand Down Expand Up @@ -111,6 +108,22 @@
--composer-action-hover-fill: var(--ui-hover);
}

/* A quiet accent wash keeps Send distinct without overpowering the editor. */
.composer .send-button:not(.stop):not(:disabled) {
color: var(--ui-action);
border-color: transparent;
background: color-mix(in srgb, var(--ui-action) 12%, transparent);
box-shadow: none;
transition: none;
}
.composer .send-button:not(.stop):not(:disabled):hover {
background: color-mix(in srgb, var(--ui-action) 18%, transparent);
filter: none;
}
.composer .send-button:not(.stop):not(:disabled):active {
background: color-mix(in srgb, var(--ui-action) 24%, transparent);
}

.composer :is(.composer-attach, button.composer-workspace-trigger, .composer-permission-trigger, .composer-model-trigger) {
--icon-scale: 1;
--icon-opacity: 1;
Expand Down
14 changes: 7 additions & 7 deletions program/plugins/ui/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ html[data-desktop-platform="darwin"] [role="tab"] {
}

.history-entry:hover:not(:disabled) { color: var(--text-strong); background: rgba(79, 85, 105, 0.06); }
.history-entry.active { color: var(--text-strong); background: rgba(111, 102, 154, 0.1); }
.history-entry.active { color: var(--text-strong); background: var(--ui-selected); }
.history-entry:disabled { opacity: 1; cursor: default; }
.history-entry > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.history-entry-meta { display: inline-flex; align-items: center; gap: 5px; }
Expand Down Expand Up @@ -2035,10 +2035,12 @@ details pre {

.composer {
--composer-outline: light-dark(#e2e4ec, #363a46);
--composer-focus-outline: light-dark(#d9caf8, #63527d);
--composer-action-ink: light-dark(var(--accent), #b59af9);
--composer-action-fill: light-dark(#ede5ff, #3a3053);
--composer-action-hover-fill: light-dark(#e6dafb, #473665);
--composer-focus-outline: light-dark(
color-mix(in srgb, var(--ui-action, var(--accent)) 24%, var(--composer-surface)),
color-mix(in srgb, var(--ui-action, var(--accent)) 40%, var(--composer-surface)));
--composer-action-ink: var(--ui-action, var(--accent));
--composer-action-fill: color-mix(in srgb, var(--ui-action, var(--accent)) 12%, var(--composer-surface));
--composer-action-hover-fill: color-mix(in srgb, var(--ui-action, var(--accent)) 18%, var(--composer-surface));
--composer-shadow-tint: light-dark(#33354916, #00000042);
--composer-elevation: 0 3px 6px var(--composer-shadow-tint), 0 10px 24px var(--composer-shadow-tint);
--composer-resting-shadow: 0 0 0 1px var(--composer-outline), var(--composer-elevation);
Expand Down Expand Up @@ -2718,8 +2720,6 @@ button.composer-workspace-trigger:focus-visible { color: var(--text); background
}

.send-button:hover:not(:disabled) { background: var(--composer-action-hover-fill); }
.composer .send-button:not(.stop):not(:disabled) { color: var(--accent-text); background: var(--accent); }
.composer .send-button:not(.stop):not(:disabled):hover { background: color-mix(in srgb, var(--accent) 86%, var(--text-strong)); }
.send-button:disabled { opacity: 0.32; cursor: not-allowed; }
.send-button > :is(svg, .stop-glyph) { transform: translate(-3px, -3px); }
.stop-glyph {
Expand Down
10 changes: 8 additions & 2 deletions program/plugins/workspace-tabs-appearance.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ html:not([data-desktop-platform='darwin']) .vertical-tab-rail:not(.is-collapsed)
}
.vertical-tab-rail:not(.is-collapsed) .workspace-tabs {
--workspace-tab-group-line-inset: var(--ui-row-inline-padding);
--workspace-tab-group-member-indent: calc(var(--ui-row-inline-padding) + var(--ui-row-gap));
padding-inline: var(--ui-panel-inset);
}

Expand Down Expand Up @@ -112,8 +113,13 @@ html:not([data-desktop-platform='darwin']) .vertical-tab-rail:not(.is-collapsed)
:is(.workspace-tabbar, .vertical-tab-rail:not(.is-collapsed)) .workspace-tab > :is(.workspace-tab-thread-status, .workspace-tab-group-count) {
inset-inline-end: calc(var(--workspace-tab-action-size) + var(--workspace-tab-action-inset));
}
.vertical-tab-rail:not(.is-collapsed) .workspace-tabs-with-groups[aria-orientation='vertical'] .workspace-tab-group-member:not(.is-group-collapsed) {
padding-inline-start: calc(var(--ui-row-inline-padding) + var(--space-1) / 2 + var(--ui-row-gap));
/* Keep the continuous group line in a gutter outside the rounded tab surface.
Shared hover, selection, and focus states then all clear the line. */
.vertical-tab-rail:not(.is-collapsed) .workspace-tabs-with-groups[aria-orientation='vertical'] .workspace-tab.workspace-tab-group-member {
--workspace-tab-group-line-inset: calc(-1 * var(--ui-row-gap));
width: calc(100% - var(--workspace-tab-group-member-indent));
margin-inline-start: var(--workspace-tab-group-member-indent);
padding-inline-start: var(--ui-row-inline-padding);
}

.workspace-tabbar .workspace-tab-add {
Expand Down
2 changes: 1 addition & 1 deletion scripts/design-system-audit-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"program/plugins/thread-status.css": {"rawGeometry": 6},
"program/plugins/turn-progress.css": {"rawFontSize": 3, "rawFontWeight": 1, "rawRadius": 1, "rawSpacing": 10, "rawGeometry": 10, "rawShadow": 2},
"program/plugins/ui/composer.tsx": {"inlineStyle": 1},
"program/plugins/ui/default.css": {"rawFontSize": 60, "rawFontWeight": 30, "rawRadius": 47, "rawSpacing": 270, "rawGeometry": 137, "rawColor": 68, "rawShadow": 16, "negativeTracking": 8},
"program/plugins/ui/default.css": {"rawFontSize": 60, "rawFontWeight": 30, "rawRadius": 47, "rawSpacing": 270, "rawGeometry": 137, "rawColor": 65, "rawShadow": 16, "negativeTracking": 8},
"program/plugins/ui/markdown.tsx": {"inlineStyle": 2},
"program/plugins/work-targets.client.tsx": {"inlineStyle": 1},
"program/plugins/work-targets.css": {"rawSpacing": 26, "rawGeometry": 9},
Expand Down
Loading
Loading