[aidan] ux/font-stack: unify font families via shared typography module (#116)

This commit is contained in:
Aidan
2026-06-30 19:39:02 -07:00
committed by GitHub
parent 0cdd1cbd6e
commit 14a08a0d8c
23 changed files with 115 additions and 131 deletions
+3
View File
@@ -38,6 +38,9 @@
<link rel="dns-prefetch" href="https://api.openswarm.com" />
<link rel="dns-prefetch" href="https://api.github.com" />
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Newsreader:opsz,wght@6..72,400;6..72,500;6..72,600;6..72,700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
</head>
<body>
+17 -6
View File
@@ -100,12 +100,13 @@ function buildMuiTheme(c: ClaudeTokens, mode: 'light' | 'dark') {
},
typography: {
fontFamily: c.font.sans,
h1: { fontWeight: 600 },
h2: { fontWeight: 600 },
h3: { fontWeight: 600 },
h5: { fontWeight: 600 },
h6: { fontWeight: 600 },
button: { textTransform: 'none' as const, fontWeight: 500 },
h1: { fontFamily: c.font.sans, fontWeight: 700 },
h2: { fontFamily: c.font.sans, fontWeight: 700 },
h3: { fontFamily: c.font.sans, fontWeight: 600 },
h4: { fontFamily: c.font.sans, fontWeight: 600 },
h5: { fontFamily: c.font.sans, fontWeight: 600 },
h6: { fontFamily: c.font.sans, fontWeight: 600 },
button: { textTransform: 'none' as const, fontWeight: 600 },
},
shape: {
borderRadius: c.radius.xl,
@@ -113,12 +114,22 @@ function buildMuiTheme(c: ClaudeTokens, mode: 'light' | 'dark') {
components: {
MuiCssBaseline: {
styleOverrides: {
html: {
fontFamily: c.font.sans,
},
body: {
backgroundColor: c.bg.page,
color: c.text.primary,
fontFamily: c.font.sans,
scrollbarWidth: 'thin',
scrollbarColor: `${c.border.strong} transparent`,
},
'#root': {
fontFamily: c.font.sans,
},
'button, input, textarea, select': {
fontFamily: c.font.sans,
},
'*': {
scrollbarWidth: 'thin',
scrollbarColor: `${c.border.strong} transparent`,
@@ -47,6 +47,7 @@ import { setInstalling } from '@/shared/state/updateSlice';
import { findBrowserByWebContentsId } from '@/shared/browserRegistry';
import { byPreviewRecency } from '@/shared/previewOrder';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { ErrorSlime } from '@/app/components/feedback/ErrorSlime';
const SIDEBAR_MIN = 160;
@@ -584,9 +585,10 @@ const AppShell: React.FC = () => {
<Typography
sx={{
color: c.text.secondary,
fontFamily: font.detail,
fontSize: '0.9rem',
fontWeight: 600,
letterSpacing: 0.2,
letterSpacing: 0,
lineHeight: 1,
}}
>
@@ -10,6 +10,7 @@ import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CloseIcon from '@mui/icons-material/Close';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { useOnboardingProgress } from './hooks/useOnboardingProgress';
import { hasAnyAgentCompleted } from './steps/skipPredicates';
@@ -425,7 +426,7 @@ const StepCardBody: React.FC<StepCardProps> = ({
fontWeight: 600,
color: c.text.primary,
mb: 0.4,
fontFamily: '"Charter", Georgia, serif',
fontFamily: font.heading,
}}
>
{step.title}
@@ -669,7 +670,7 @@ const CelebrationView: React.FC<CelebrationProps> = ({ step, accent }) => {
fontSize: 16,
fontWeight: 600,
color: c.text.primary,
fontFamily: '"Charter", Georgia, serif',
fontFamily: font.heading,
position: 'relative',
display: 'inline-block',
}}
@@ -723,7 +724,7 @@ const AllDoneView: React.FC<{ accent: string; tokens: ReturnType<typeof useClaud
fontSize: 16,
fontWeight: 600,
color: c.text.primary,
fontFamily: '"Charter", Georgia, serif',
fontFamily: font.heading,
}}
>
You're all set up
@@ -8,6 +8,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import LockIcon from '@mui/icons-material/Lock';
import CloseIcon from '@mui/icons-material/Close';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { useOnboardingProgress } from './hooks/useOnboardingProgress';
import { STAGE_GROUPS, STEPS, findStepById } from './steps';
import { useUnlockedStepIds, unlockHintFor } from './steps/stepUnlock';
@@ -101,7 +102,7 @@ const OnboardingRoadmapModal: React.FC = () => {
sx={{
fontSize: 16,
fontWeight: 600,
fontFamily: '"Charter", Georgia, serif',
fontFamily: font.heading,
}}
>
Your roadmap
@@ -51,9 +51,8 @@ const ACTIONS: ActionResult[] = [
{ kind: 'action', id: 'new-dashboard', name: 'New dashboard', keywords: 'create board canvas workspace' },
{ kind: 'action', id: 'settings', name: 'Open Settings', keywords: 'preferences general theme options' },
{ kind: 'action', id: 'settings-models', name: 'Connect a model', keywords: 'settings models api key provider subscription' },
{ kind: 'action', id: 'go-skills', name: 'Go to Skills', keywords: 'customize skills' },
{ kind: 'action', id: 'go-actions', name: 'Go to Actions', keywords: 'customize tools actions mcp' },
{ kind: 'action', id: 'go-modes', name: 'Go to Modes', keywords: 'customize modes' },
{ kind: 'action', id: 'go-skills', name: 'Go to Skills', keywords: 'customize skills settings' },
{ kind: 'action', id: 'go-actions', name: 'Go to Tools', keywords: 'customize tools actions mcp settings' },
{ kind: 'action', id: 'go-apps', name: 'Go to Apps', keywords: 'apps mini app' },
{ kind: 'action', id: 'all-dashboards', name: 'All dashboards', keywords: 'overview picker browse boards' },
];
@@ -150,9 +149,8 @@ const GlobalSearchPalette: React.FC<Props> = ({ open, onClose }) => {
break;
case 'settings': dispatch(openSettingsModal()); break;
case 'settings-models': dispatch(openSettingsModal('models')); break;
case 'go-skills': navigate('/skills'); break;
case 'go-actions': navigate('/actions'); break;
case 'go-modes': navigate('/modes'); break;
case 'go-skills': dispatch(openSettingsModal('skills')); break;
case 'go-actions': dispatch(openSettingsModal('tools')); break;
case 'go-apps': navigate('/apps'); break;
case 'all-dashboards': navigate('/'); break;
}
@@ -17,6 +17,7 @@ import CloseIcon from '@mui/icons-material/Close';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { activateSignin, fetchSettings } from '@/shared/state/settingsSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { OPENSWARM_DEFAULT_PROXY_URL } from '@/shared/config';
import { report } from '@/shared/serviceClient';
@@ -187,7 +188,7 @@ export default function SignInDialog({ onClose }: { onClose: () => void }): JSX.
<>
<Typography
variant="h5"
sx={{ fontFamily: '"Charter", Georgia, serif', fontWeight: 500, mb: 1 }}
sx={{ fontFamily: font.heading, fontWeight: 500, mb: 1 }}
>
Check your inbox
</Typography>
@@ -272,7 +273,7 @@ export default function SignInDialog({ onClose }: { onClose: () => void }): JSX.
<>
<Typography
variant="h5"
sx={{ fontFamily: '"Charter", Georgia, serif', fontWeight: 500, mb: 1 }}
sx={{ fontFamily: font.heading, fontWeight: 500, mb: 1 }}
>
Sign in to OpenSwarm
</Typography>
@@ -1168,7 +1168,7 @@ const MessageBubble: React.FC<Props> = React.memo(({ message, editing = false, o
{message.images && message.images.length > 0 && (
<MessageImageThumbnails images={message.images} c={c} />
)}
<Typography sx={{ color: c.text.primary, fontSize: '0.875rem', lineHeight: 1.6, overflowWrap: 'anywhere', wordBreak: 'break-word', whiteSpace: 'pre-wrap' }}>
<Typography sx={{ color: c.text.primary, fontSize: '0.875rem', fontFamily: c.font.serif, lineHeight: 1.6, overflowWrap: 'anywhere', wordBreak: 'break-word', whiteSpace: 'pre-wrap' }}>
{renderUserTextWithPills(displayText, c)}
</Typography>
<AttachedContextSection elements={selectedElements} message={message} c={c} />
@@ -1180,9 +1180,11 @@ const MessageBubble: React.FC<Props> = React.memo(({ message, editing = false, o
sx={{
color: c.text.secondary,
fontSize: '0.875rem',
fontFamily: c.font.serif,
lineHeight: 1.7,
overflowWrap: 'anywhere',
wordBreak: 'break-word',
'& h1, & h2, & h3, & h4, & h5, & h6': { fontFamily: c.font.sans },
'& p': { m: 0, mb: 1, '&:last-child': { mb: 0 } },
'& pre': {
bgcolor: c.bg.secondary,
@@ -18,7 +18,7 @@ export const AgentResponseBody: React.FC<{ open: boolean; markdown: string }> =
overflowY: 'auto',
overflowX: 'hidden',
color: c.text.secondary,
fontFamily: c.font.sans,
fontFamily: c.font.serif,
fontSize: '0.78rem',
lineHeight: 1.65,
overflowWrap: 'anywhere',
@@ -4,6 +4,7 @@ import Typography from '@mui/material/Typography';
import { motion, AnimatePresence } from 'framer-motion';
import { ArrowLeft } from 'lucide-react';
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
import { font } from '@/shared/styles/typography';
import { useAppSelector } from '@/shared/hooks';
import {
hasModelConnected,
@@ -54,7 +55,7 @@ const DashboardEmptyState: React.FC<{
pointerEvents: 'none',
}}
>
<Typography sx={{ color: c.text.secondary, fontSize: '1.3rem', fontWeight: 500, mb: 2.2, textAlign: 'center' }}>
<Typography sx={{ color: c.text.primary, fontFamily: font.display, fontSize: '1.5rem', fontWeight: 500, lineHeight: 1.1, letterSpacing: 0, mb: 2.5, textAlign: 'center' }}>
What do you want done?
</Typography>
@@ -70,9 +71,6 @@ const DashboardEmptyState: React.FC<{
transition={{ duration: 0.2 }}
style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}
>
<Typography sx={{ color: c.text.ghost, fontSize: '0.9rem', mb: 1.4 }}>
pick one, or just tell me below
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.2, width: '100%', maxWidth: 460 }}>
{STARTER_CATEGORIES.map((cat) => (
<Box
@@ -85,9 +83,9 @@ const DashboardEmptyState: React.FC<{
borderRadius: 2.5,
border: `1px solid ${c.border.medium}`,
background: c.bg.surface,
color: c.text.secondary,
fontSize: '0.98rem', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit',
color: c.text.primary,
fontSize: '1.05rem', fontWeight: 400, lineHeight: 1.25,
cursor: 'pointer', fontFamily: font.paragraph,
transition: 'background 150ms, border-color 150ms',
'&:hover': { background: c.bg.elevated, borderColor: c.border.strong },
}}
@@ -134,11 +132,13 @@ const DashboardEmptyState: React.FC<{
borderRadius: 2,
border: `1px solid ${c.border.medium}`,
background: c.bg.surface,
color: c.text.secondary,
fontSize: '0.95rem',
color: c.text.primary,
fontSize: '1.02rem',
fontWeight: 400,
lineHeight: 1.35,
cursor: launching ? 'default' : 'pointer',
opacity: launching ? 0.5 : 1,
fontFamily: 'inherit',
fontFamily: font.paragraph,
transition: 'background 150ms, border-color 150ms',
'&:hover': launching ? {} : { background: c.bg.elevated, borderColor: c.border.strong },
}}
@@ -27,6 +27,7 @@ import {
Dashboard,
} from '@/shared/state/dashboardsSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { byPreviewRecency } from '@/shared/previewOrder';
// Module-scope: auto-enter fires once per app boot; revisiting "/" later shows the picker normally.
@@ -212,7 +213,7 @@ const DashboardSelection: React.FC = () => {
</Box>
) : dashboards.length === 0 ? (
<Box sx={{ textAlign: 'center', py: 10, color: c.text.muted }}>
<Typography sx={{ fontSize: '1.1rem', mb: 1 }}>
<Typography sx={{ fontFamily: font.heading, fontSize: '1.1rem', mb: 1 }}>
{search ? 'No dashboards match your search' : 'No dashboards yet'}
</Typography>
<Typography sx={{ fontSize: '0.85rem', color: c.text.tertiary }}>
+8 -7
View File
@@ -35,6 +35,7 @@ import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import {
fetchSkills,
@@ -288,9 +289,9 @@ const Skills: React.FC = () => {
overflow: 'auto',
'&::-webkit-scrollbar': { width: 5 },
'&::-webkit-scrollbar-thumb': { background: c.border.medium, borderRadius: 3 },
'& h1': { fontSize: '1.3rem', fontWeight: 700, color: c.text.primary, mt: 0, mb: 1.5, fontFamily: c.font.sans },
'& h2': { fontSize: '1.1rem', fontWeight: 600, color: c.text.primary, mt: 2, mb: 1, fontFamily: c.font.sans },
'& h3': { fontSize: '0.95rem', fontWeight: 600, color: c.text.primary, mt: 1.5, mb: 0.75, fontFamily: c.font.sans },
'& h1': { fontSize: '1.3rem', fontWeight: 700, color: c.text.primary, mt: 0, mb: 1.5, fontFamily: font.heading },
'& h2': { fontSize: '1.1rem', fontWeight: 600, color: c.text.primary, mt: 2, mb: 1, fontFamily: font.heading },
'& h3': { fontSize: '0.95rem', fontWeight: 600, color: c.text.primary, mt: 1.5, mb: 0.75, fontFamily: font.heading },
'& p': { fontSize: '0.88rem', color: c.text.secondary, lineHeight: 1.7, mb: 1.5 },
'& ul, & ol': { pl: 2.5, mb: 1.5, '& li': { fontSize: '0.88rem', color: c.text.secondary, lineHeight: 1.7, mb: 0.5 } },
'& code': { fontFamily: c.font.mono, fontSize: '0.82rem', bgcolor: 'rgba(0,0,0,0.04)', px: 0.5, py: 0.15, borderRadius: `${c.radius.xs}px` },
@@ -532,7 +533,7 @@ const Skills: React.FC = () => {
<Box sx={{ p: 4, pb: 3, maxWidth: 1100, display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5, flexShrink: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography sx={{ fontSize: '1.4rem', fontWeight: 700, color: c.text.primary, fontFamily: c.font.sans }}>
<Typography sx={{ fontSize: '1.4rem', fontWeight: 700, color: c.text.primary, fontFamily: font.heading }}>
{builderPreview.name || 'Untitled Skill'}
</Typography>
<Chip
@@ -593,7 +594,7 @@ const Skills: React.FC = () => {
) : selectedReg ? (
<Box sx={{ p: 4, pb: 3, maxWidth: 1100, display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5, flexShrink: 0 }}>
<Typography sx={{ fontSize: '1.4rem', fontWeight: 700, color: c.text.primary, fontFamily: c.font.sans }}>
<Typography sx={{ fontSize: '1.4rem', fontWeight: 700, color: c.text.primary, fontFamily: font.heading }}>
{selectedReg.name}
</Typography>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
@@ -658,7 +659,7 @@ const Skills: React.FC = () => {
<Box sx={{ p: 4, pb: 3, maxWidth: 1100, display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5, flexShrink: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography sx={{ fontSize: '1.4rem', fontWeight: 700, color: c.text.primary, fontFamily: c.font.sans }}>
<Typography sx={{ fontSize: '1.4rem', fontWeight: 700, color: c.text.primary, fontFamily: font.heading }}>
{selectedLocal.name}
</Typography>
{selectedLocal.built_in && (
@@ -752,7 +753,7 @@ const Skills: React.FC = () => {
sx: { bgcolor: c.bg.surface, backgroundImage: 'none', borderRadius: `${c.radius.lg}px`, border: `${c.border.width} solid ${c.border.subtle}`, boxShadow: c.shadow.lg },
}}
>
<DialogTitle sx={{ color: c.text.primary, fontWeight: 600, fontFamily: c.font.sans }}>
<DialogTitle sx={{ color: c.text.primary, fontWeight: 600, fontFamily: font.heading }}>
{editingId ? 'Edit Skill' : 'New Skill'}
</DialogTitle>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: '8px !important' }}>
+2 -1
View File
@@ -4,6 +4,7 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress';
import PixelBlast from '@/app/components/feedback/PixelBlast';
import { font } from '@/shared/styles/typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import TextField from '@mui/material/TextField';
@@ -70,7 +71,7 @@ const InstallPlaceholder: React.FC = () => {
>
<Typography
sx={{
fontFamily: 'Charter, Georgia, serif',
fontFamily: font.heading,
fontSize: '2rem',
fontWeight: 500,
color: '#f5f5f5',
+2 -1
View File
@@ -10,6 +10,7 @@ import AddIcon from '@mui/icons-material/Add';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { fetchOutputs, deleteOutput, Output } from '@/shared/state/outputsSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { font } from '@/shared/styles/typography';
import { byPreviewRecency } from '@/shared/previewOrder';
import ViewCard from './ViewCard';
import { Skeleton } from '@/app/components/feedback/Loading';
@@ -143,7 +144,7 @@ const Views: React.FC = () => {
color: c.text.muted,
}}
>
<Typography sx={{ fontSize: '1.1rem', mb: 1 }}>
<Typography sx={{ fontFamily: font.heading, fontSize: '1.1rem', mb: 1 }}>
No apps yet
</Typography>
<Typography sx={{ fontSize: '0.85rem', color: c.text.tertiary }}>
@@ -4,7 +4,7 @@ import type { CSSProperties } from 'react';
import { useAppSelector } from '@/shared/hooks';
import { startOfWeek, startOfMonthGrid, addDays, sameDay } from '@/app/pages/Workflows/scheduleUtils';
import { useCalendarOccurrences } from './useCalendarOccurrences';
import { colorForWorkflow, useWC, type WCPalette } from './uiKit';
import { colorForWorkflow, useWC, FONT_SERIF, type WCPalette } from './uiKit';
import type { AppNav } from './types';
interface Occ { wfId: string; title: string; at: Date; color: string; }
@@ -102,7 +102,7 @@ const CalendarView: React.FC<{ nav: AppNav }> = ({ nav }) => {
<button onClick={() => step(-1)} style={{ width: 30, height: 30, borderRadius: 8, border: `1px solid rgba(${WC.inkRGB},0.12)`, background: WC.paper, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: WC.ink3 }}><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M15 6l-6 6 6 6" /></svg></button>
<button onClick={() => step(1)} style={{ width: 30, height: 30, borderRadius: 8, border: `1px solid rgba(${WC.inkRGB},0.12)`, background: WC.paper, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: WC.ink3 }}><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M9 6l6 6-6 6" /></svg></button>
</div>
<h1 style={{ margin: 0, fontFamily: "'Newsreader',serif", fontSize: 22, fontWeight: 600, color: WC.ink, letterSpacing: '-0.01em' }}>{title}</h1>
<h1 style={{ margin: 0, fontFamily: FONT_SERIF, fontSize: 22, fontWeight: 600, color: WC.ink, letterSpacing: '-0.01em' }}>{title}</h1>
<div style={{ flex: 1 }} />
<div style={{ display: 'flex', background: WC.inset, border: `1px solid ${WC.line}`, borderRadius: 9, padding: 3, gap: 2 }}>
<button onClick={() => nav.setCalView('week')} style={tabBtn(nav.calView === 'week', WC)}>Week</button>
@@ -117,7 +117,7 @@ const CalendarView: React.FC<{ nav: AppNav }> = ({ nav }) => {
{dayPop && createPortal(
<div onClick={() => setDayPop(null)} style={{ position: 'fixed', inset: 0, zIndex: 2147483600 }}>
<div onClick={(e) => e.stopPropagation()} style={{ position: 'fixed', left: dayPop.x, top: dayPop.y, width: POP_W, maxHeight: 320, overflowY: 'auto', background: WC.paper, border: `1px solid ${WC.line2}`, borderRadius: WC.radius.lg, boxShadow: WC.shadow.lg, padding: 12 }}>
<div style={{ fontFamily: "'Newsreader',serif", fontSize: 15, fontWeight: 500, color: WC.ink, marginBottom: 10, padding: '0 2px' }}>{dayPop.title}</div>
<div style={{ fontFamily: FONT_SERIF, fontSize: 15, fontWeight: 500, color: WC.ink, marginBottom: 10, padding: '0 2px' }}>{dayPop.title}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{dayPop.runs.map((r, i) => (
<div key={i} onClick={() => selectFromPop(r.wfId)} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '8px 7px', borderRadius: 8, cursor: 'pointer' }}>
@@ -259,7 +259,7 @@ const WeekGrid: React.FC<GridProps> = ({ ref0, now, occByDay, dayKey, onSelect,
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '8px 0 7px', borderLeft: `1px solid rgba(${WC.inkRGB},0.06)` }}>
<span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10, letterSpacing: '0.04em', color: WC.muted2 }}>{DOW[d.getDay()]}</span>
<div style={{ width: 30, height: 30, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: 3, flex: 'none', background: isToday ? WC.accent : 'transparent' }}>
<span style={{ fontFamily: "'Newsreader',serif", fontSize: 18, fontWeight: 500, lineHeight: 1, color: isToday ? '#fff' : WC.ink }}>{d.getDate()}</span>
<span style={{ fontFamily: FONT_SERIF, fontSize: 18, fontWeight: 500, lineHeight: 1, color: isToday ? '#fff' : WC.ink }}>{d.getDate()}</span>
</div>
</div>
);
@@ -90,7 +90,7 @@ const ComposeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', background: WC.page }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 14, height: 14, borderRadius: '50%', border: `2px solid rgba(${WC.inkRGB},0.15)`, borderTopColor: WC.accent, animation: 'os-spin 0.7s linear infinite' }} />
<span style={{ fontFamily: "'Newsreader',serif", fontStyle: 'italic', fontSize: 14, color: WC.ink4 }}>Setting up your workflow</span>
<span style={{ fontFamily: FONT_SERIF, fontStyle: 'italic', fontSize: 14, color: WC.ink4 }}>Setting up your workflow</span>
</div>
</div>
);
@@ -105,11 +105,11 @@ const ComposeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
value={workflow.title || ''}
onCommit={(t) => patch(workflow, { title: t, auto_named: false })}
placeholder="Untitled workflow"
sx={{ flex: 1, minWidth: 0, fontFamily: "'Newsreader',serif", fontSize: 21, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}
sx={{ flex: 1, minWidth: 0, fontFamily: FONT_SERIF, fontSize: 21, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}
>
<Typewriter value={workflow.title || 'Untitled workflow'} enabled={workflow.auto_named !== false}>
{(t) => (
<span style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: "'Newsreader',serif", fontSize: 21, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}>{t}</span>
<span style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: FONT_SERIF, fontSize: 21, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}>{t}</span>
)}
</Typewriter>
</InlineEditableTitle>
@@ -7,7 +7,7 @@ import { askRun } from './api';
import AgentChat from '@/app/pages/AgentChat/AgentChat';
import InlineEditableTitle from '@/app/components/InlineEditableTitle';
import { Typewriter } from '@/app/components/feedback/Animated';
import { useWC, colorForWorkflow, statusChip } from './uiKit';
import { useWC, colorForWorkflow, statusChip, FONT_SERIF } from './uiKit';
import { isRunning, runContextChip } from './model';
import { useEditAgentSession } from './useEditAgentSession';
import { useWorkflowPatch } from './useWorkflowPatch';
@@ -63,11 +63,11 @@ const DetailView: React.FC<{ workflowId: string; nav: AppNav }> = ({ workflowId
value={workflow.title || ''}
onCommit={(t) => patch(workflow, { title: t, auto_named: false })}
placeholder="Untitled workflow"
sx={{ flex: 1, minWidth: 0, fontFamily: "'Newsreader',serif", fontSize: 25, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}
sx={{ flex: 1, minWidth: 0, fontFamily: FONT_SERIF, fontSize: 25, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}
>
<Typewriter value={workflow.title || 'Untitled workflow'} enabled={workflow.auto_named !== false}>
{(t) => (
<span style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: "'Newsreader',serif", fontSize: 25, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}>{t}</span>
<span style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: FONT_SERIF, fontSize: 25, fontWeight: 500, color: WC.ink, letterSpacing: '-0.01em' }}>{t}</span>
)}
</Typewriter>
</InlineEditableTitle>
@@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { runMissedRuns, dismissMissedRuns } from '@/shared/state/missedRunsSlice';
import { openWorkflowMonitor } from '@/shared/state/dashboardLayoutSlice';
import { useCalendarOccurrences } from './useCalendarOccurrences';
import { colorForWorkflow, useWC, statusChip, statusDot } from './uiKit';
import { colorForWorkflow, useWC, statusChip, statusDot, FONT_SERIF } from './uiKit';
import { clockOf, whenText } from './model';
import WorkflowTitle from './WorkflowTitle';
import type { AppNav } from './types';
@@ -98,7 +98,7 @@ const HomeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
{ongoing.length > 0 && (
<div style={{ marginTop: 22 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 11 }}>
<span style={{ fontFamily: "'Newsreader',serif", fontSize: 18, fontWeight: 500, color: WC.ink }}>Ongoing runs</span>
<span style={{ fontFamily: FONT_SERIF, fontSize: 18, fontWeight: 500, color: WC.ink }}>Ongoing runs</span>
<div style={{ width: 7, height: 7, borderRadius: '50%', background: WC.accent, animation: 'os-pulse 1.1s ease-in-out infinite' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
@@ -131,7 +131,7 @@ const HomeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
{missed.length > 0 && (
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 11 }}>
<span style={{ fontFamily: "'Newsreader',serif", fontSize: 18, fontWeight: 500, color: WC.ink }}>Missed</span>
<span style={{ fontFamily: FONT_SERIF, fontSize: 18, fontWeight: 500, color: WC.ink }}>Missed</span>
<span style={{ fontSize: 11, fontWeight: 600, color: WC.danger, background: WC.dangerBg, padding: '2px 8px', borderRadius: 999 }}>{missed.length}</span>
<div style={{ flex: 1 }} />
<button onClick={reRunAll} style={{ background: 'transparent', border: `1px solid rgba(${WC.inkRGB},0.14)`, borderRadius: 8, padding: '6px 12px', fontSize: 12, fontWeight: 600, color: WC.ink3, cursor: 'pointer' }}>Re-run all</button>
@@ -164,7 +164,7 @@ const HomeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
)}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontFamily: "'Newsreader',serif", fontSize: 18, fontWeight: 500, color: WC.ink, marginBottom: 12 }}>Coming up</div>
<div style={{ fontFamily: FONT_SERIF, fontSize: 18, fontWeight: 500, color: WC.ink, marginBottom: 12 }}>Coming up</div>
{comingGroups.length === 0 && (
<div style={{ fontSize: 13, color: WC.muted }}>Nothing scheduled in the next 7 days.</div>
)}
@@ -172,7 +172,7 @@ const HomeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
{comingGroups.map((g) => (
<div key={g.key} style={{ display: 'flex', gap: 18, padding: '4px 0 16px' }}>
<div style={{ width: 62, flex: 'none', textAlign: 'right', paddingTop: 2 }}>
<div style={{ fontFamily: "'Newsreader',serif", fontSize: 26, fontWeight: 500, color: WC.ink, lineHeight: 1 }}>{g.dayNum}</div>
<div style={{ fontFamily: FONT_SERIF, fontSize: 26, fontWeight: 500, color: WC.ink, lineHeight: 1 }}>{g.dayNum}</div>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5, letterSpacing: '0.04em', textTransform: 'uppercase', color: WC.muted2, marginTop: 4 }}>{g.dow}</div>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10, color: WC.faint, marginTop: 3 }}>{g.countLabel}</div>
</div>
@@ -201,7 +201,7 @@ const HomeView: React.FC<{ nav: AppNav }> = ({ nav }) => {
</div>
<div style={{ marginTop: 18 }}>
<div style={{ fontFamily: "'Newsreader',serif", fontSize: 18, fontWeight: 500, color: WC.ink, marginBottom: 12 }}>Recents</div>
<div style={{ fontFamily: FONT_SERIF, fontSize: 18, fontWeight: 500, color: WC.ink, marginBottom: 12 }}>Recents</div>
{recents.length === 0 && <div style={{ fontSize: 13, color: WC.muted }}>No runs yet.</div>}
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
{recents.map((r) => (
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { fetchDeletedWorkflows, restoreWorkflow, purgeWorkflow } from '@/shared/state/workflowsSlice';
import { colorForWorkflow, useWC } from './uiKit';
import { colorForWorkflow, useWC, FONT_SERIF } from './uiKit';
import { whenText } from './model';
const TrashView: React.FC = () => {
@@ -23,7 +23,7 @@ const TrashView: React.FC = () => {
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', background: WC.paper }}>
<div style={{ flex: 'none', padding: '22px 30px 14px', borderBottom: `1px solid ${WC.line}` }}>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 11, letterSpacing: '0.06em', textTransform: 'uppercase', color: WC.muted2, marginBottom: 5 }}>Deleted workflows</div>
<h1 style={{ margin: 0, fontFamily: "'Newsreader',serif", fontSize: 29, fontWeight: 500, color: WC.ink, letterSpacing: '-0.015em' }}>Trash</h1>
<h1 style={{ margin: 0, fontFamily: FONT_SERIF, fontSize: 29, fontWeight: 500, color: WC.ink, letterSpacing: '-0.015em' }}>Trash</h1>
</div>
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0, padding: '18px 30px 32px' }}>
@@ -47,7 +47,7 @@ const TrashView: React.FC = () => {
<div style={{ width: 46, height: 46, borderRadius: 12, background: WC.inset, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 14 }}>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={WC.faint} strokeWidth="1.7"><path d="M4 7h16M9 7V5h6v2M6 7l1 13h10l1-13" /></svg>
</div>
<div style={{ fontFamily: "'Newsreader',serif", fontSize: 18, color: WC.ink, marginBottom: 4 }}>Trash is empty</div>
<div style={{ fontFamily: FONT_SERIF, fontSize: 18, color: WC.ink, marginBottom: 4 }}>Trash is empty</div>
<div style={{ fontSize: 13, color: WC.muted, maxWidth: 300, lineHeight: 1.5 }}>Deleted workflows appear here. Restore them or remove them permanently.</div>
</div>
)
@@ -104,9 +104,7 @@ export function useWC(): WCPalette {
};
}
export const FONT_SERIF = "'Newsreader', Georgia, serif";
export const FONT_SANS = "'Hanken Grotesk', system-ui, sans-serif";
export const FONT_MONO = "'JetBrains Mono', ui-monospace, monospace";
export { FONT_SERIF, FONT_SANS, FONT_MONO } from '@/shared/styles/typography';
// Stable per-workflow color: the backend has no color field, so derive a vivid-but-deterministic swatch from the id. Same id always lands the same hue, so dots/bars stay consistent across panes without persistence.
export const WORKFLOW_PALETTE = [
+9 -5
View File
@@ -1,3 +1,5 @@
import { FONT_SANS, FONT_SERIF, FONT_MONO } from './typography';
export interface ClaudeTokens {
bg: { page: string; surface: string; elevated: string; secondary: string; inverse: string };
text: { primary: string; secondary: string; tertiary: string; muted: string; inverse: string; ghost: string };
@@ -7,7 +9,7 @@ export interface ClaudeTokens {
radius: { xs: number; sm: number; md: number; lg: number; xl: number; full: number };
status: { success: string; successBg: string; warning: string; warningBg: string; error: string; errorBg: string; info: string; infoBg: string };
user: { bubble: string };
font: { sans: string; mono: string };
font: { sans: string; serif: string; mono: string };
transition: string;
}
@@ -56,8 +58,9 @@ export const lightTokens: ClaudeTokens = {
},
user: { bubble: '#DDD9CE' },
font: {
sans: '"Anthropic Sans", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
sans: FONT_SANS,
serif: FONT_SERIF,
mono: FONT_MONO,
},
transition: 'all 150ms cubic-bezier(0.165, 0.85, 0.45, 1)',
};
@@ -107,8 +110,9 @@ export const darkTokens: ClaudeTokens = {
},
user: { bubble: '#393937' },
font: {
sans: '"Anthropic Sans", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
sans: FONT_SANS,
serif: FONT_SERIF,
mono: FONT_MONO,
},
transition: 'all 150ms cubic-bezier(0.165, 0.85, 0.45, 1)',
};
@@ -1,63 +0,0 @@
@use '@/shared/styles/color.module.scss' as g-color;
@use '@/shared/styles/utils.module.scss' as utils;
$font-map: (
'default': 'Inter',
'secondary': 'Times New Roman'
);
@function font($mode: 'default') {
@return utils.get-style(
$function-map: $font-map,
$mode: $mode
);
}
$size-map: (
'small': 12px,
'small-medium': 14px,
'medium': 16px,
'large': 20px,
'title': 30px,
);
@function size($mode: 'default') {
@return utils.get-style(
$function-map: $size-map,
$mode: $mode
);
}
$weight-map: (
'small': 400,
'medium': 500,
'large': 600,
'title': 700,
);
@function weight($mode: 'default') {
@return utils.get-style(
$function-map: $weight-map,
$mode: $mode
);
}
$text-map: (
'default': (
font-family: 'Inter',
font-size: 16px,
font-weight: 400,
color: g-color.text('light-1'),
),
'title': (
font-family: 'Inter',
font-size: 40px,
font-weight: 700,
color: g-color.accent('blue-1'),
line-height: 100%,
)
);
@mixin text($mode: 'default') {
@include utils.apply-style-map(
$mixin-map: $text-map,
$mode: $mode
);
}
+22
View File
@@ -0,0 +1,22 @@
// The single source of truth for font families. If a family string lives anywhere
// else in the app, that's a bug waiting to drift; point it here instead.
export const FONT_SANS = "'Hanken Grotesk', Arial, sans-serif";
export const FONT_SERIF = "'Newsreader', Georgia, serif";
export const FONT_MONO = "'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
// Ask for text by ROLE, not by family. Sizes and weights stay at the call site.
export const font: {
display: string;
detail: string;
paragraph: string;
heading: string;
body: string;
mono: string;
} = {
display: FONT_SANS,
detail: FONT_SANS,
paragraph: FONT_SERIF,
heading: FONT_SANS,
body: FONT_SANS,
mono: FONT_MONO,
};