diff --git a/webui/src/pages/Cron.tsx b/webui/src/pages/Cron.tsx
index 47ad8d6..2c5b873 100644
--- a/webui/src/pages/Cron.tsx
+++ b/webui/src/pages/Cron.tsx
@@ -4,6 +4,7 @@ import { motion, AnimatePresence } from 'motion/react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import { useUI } from '../context/UIContext';
+import { Button, FixedButton } from '../components/Button';
import { CronJob } from '../types';
import { formatLocalDateTime } from '../utils/time';
@@ -172,22 +173,12 @@ const Cron: React.FC = () => {
{t('cronJobs')}
-
-
+
@@ -223,26 +214,20 @@ const Cron: React.FC = () => {
-
);
@@ -273,9 +258,9 @@ const Cron: React.FC = () => {
>
{editingCron ? t('editJob') : t('addJob')}
- setIsCronModalOpen(false)} className="ui-button ui-button-neutral p-2 rounded-full">
+ setIsCronModalOpen(false)} radius="full" label={t('close')}>
-
+
@@ -376,18 +361,10 @@ const Cron: React.FC = () => {
- setIsCronModalOpen(false)}
- className="ui-button ui-button-neutral px-4 py-2 text-sm font-medium"
- >
- {t('cancel')}
-
-
+ setIsCronModalOpen(false)}>{t('cancel')}
+
{editingCron ? t('update') : t('create')}
-
+
diff --git a/webui/src/pages/Dashboard.tsx b/webui/src/pages/Dashboard.tsx
index ecf2746..f327f82 100644
--- a/webui/src/pages/Dashboard.tsx
+++ b/webui/src/pages/Dashboard.tsx
@@ -3,6 +3,7 @@ import { RefreshCw, Activity, MessageSquare, Wrench, Sparkles, AlertTriangle, Wo
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import StatCard from '../components/StatCard';
+import { FixedButton } from '../components/Button';
function formatRuntimeTime(value: unknown) {
const raw = String(value || '').trim();
@@ -122,14 +123,9 @@ const Dashboard: React.FC = () => {
{t('webui')}: {webuiVersion}
-
+
-
+
diff --git a/webui/src/pages/EKG.tsx b/webui/src/pages/EKG.tsx
index caf7485..0590116 100644
--- a/webui/src/pages/EKG.tsx
+++ b/webui/src/pages/EKG.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { AlertTriangle, RefreshCw, Route, ServerCrash, Workflow } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
+import { FixedButton } from '../components/Button';
type EKGKV = { key?: string; score?: number; count?: number };
@@ -164,14 +165,14 @@ const EKG: React.FC = () => {
-
-
+
diff --git a/webui/src/pages/Logs.tsx b/webui/src/pages/Logs.tsx
index b7e832b..01712c9 100644
--- a/webui/src/pages/Logs.tsx
+++ b/webui/src/pages/Logs.tsx
@@ -5,6 +5,7 @@ import { useAppContext } from '../context/AppContext';
import { useUI } from '../context/UIContext';
import { LogEntry } from '../types';
import { formatLocalTime } from '../utils/time';
+import { Button } from '../components/Button';
const Logs: React.FC = () => {
const { t } = useTranslation();
@@ -174,23 +175,15 @@ const Logs: React.FC = () => {
-
setShowRaw(!showRaw)}
- className="ui-button ui-button-neutral flex items-center gap-2 px-4 py-2 text-sm font-medium"
- >
+ setShowRaw(!showRaw)} gap="2">
{showRaw ? t('pretty') : t('raw')}
-
- setIsStreaming(!isStreaming)}
- className={`ui-button flex items-center gap-2 px-4 py-2 text-sm font-medium ${
- isStreaming ? 'ui-button-neutral' : 'ui-button-primary'
- }`}
- >
+
+ setIsStreaming(!isStreaming)} variant={isStreaming ? 'neutral' : 'primary'} gap="2">
{isStreaming ? <> {t('pause')}> : <> {t('resume')}>}
-
-
+
+
{t('clear')}
-
+
diff --git a/webui/src/pages/MCP.tsx b/webui/src/pages/MCP.tsx
index 8fb134c..d0a0202 100644
--- a/webui/src/pages/MCP.tsx
+++ b/webui/src/pages/MCP.tsx
@@ -4,6 +4,7 @@ import { Package, Pencil, Plus, RefreshCw, Save, Trash2, Wrench, X } from 'lucid
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import { useUI } from '../context/UIContext';
+import { Button, FixedButton } from '../components/Button';
type MCPDraftServer = {
enabled: boolean;
@@ -356,22 +357,12 @@ const MCP: React.FC = () => {
{t('mcpServicesHint')}
-
{ await loadConfig(true); await refreshMCPTools(); }}
- className="ui-button ui-button-neutral ui-button-icon"
- title={t('reload')}
- aria-label={t('reload')}
- >
+ { await loadConfig(true); await refreshMCPTools(); }} label={t('reload')}>
-
-
+
+
-
+
@@ -405,12 +396,12 @@ const MCP: React.FC = () => {
)}
-
openEditModal(name, server)} className="ui-button ui-button-neutral p-2 rounded-xl" title={t('edit')}>
+ openEditModal(name, server)} radius="xl" label={t('edit')}>
-
-
removeServer(name)} className="ui-button ui-button-danger p-2 rounded-xl" title={t('delete')}>
+
+ removeServer(name)} variant="danger" radius="xl" label={t('delete')}>
-
+
@@ -476,9 +467,9 @@ const MCP: React.FC = () => {
{t('mcpServicesHint')}
-
+
-
+
@@ -551,9 +542,9 @@ const MCP: React.FC = () => {
Args
{t('configMCPArgsEnterHint')}
-
+
{t('install')}
-
+
@@ -587,9 +578,9 @@ const MCP: React.FC = () => {
{t('configMCPInstallSuggested', { pkg: activeCheck.package })}
)}
{activeCheck.installable && (
-
installCheckPackage(activeCheck)} className="ui-button ui-button-warning px-3 py-2 text-xs">
+ installCheckPackage(activeCheck)} variant="warning" size="xs_tall">
{t('install')}
-
+
)}
)}
@@ -601,14 +592,14 @@ const MCP: React.FC = () => {
{editingName && (
- removeServer(editingName)} className="ui-button ui-button-danger flex items-center gap-2 px-3 py-2 text-sm">
+ removeServer(editingName)} variant="danger" gap="2">
{t('delete')}
-
+
)}
- {t('cancel')}
-
+ {t('cancel')}
+
{t('saveChanges')}
-
+
diff --git a/webui/src/pages/Memory.tsx b/webui/src/pages/Memory.tsx
index b5539c1..17f498c 100644
--- a/webui/src/pages/Memory.tsx
+++ b/webui/src/pages/Memory.tsx
@@ -3,6 +3,7 @@ import { Trash2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import { useUI } from '../context/UIContext';
+import { Button, FixedButton } from '../components/Button';
const Memory: React.FC = () => {
const { t } = useTranslation();
@@ -118,7 +119,9 @@ const Memory: React.FC = () => {
{t('memoryFiles')}
- +
+
+ +
+
{files.map((f) => (
@@ -141,7 +144,7 @@ const Memory: React.FC = () => {
{active || t('noFileSelected')}
- {t('save')}
+ {t('save')}
diff --git a/webui/src/pages/NodeArtifacts.tsx b/webui/src/pages/NodeArtifacts.tsx
index 08295df..53684ac 100644
--- a/webui/src/pages/NodeArtifacts.tsx
+++ b/webui/src/pages/NodeArtifacts.tsx
@@ -3,6 +3,7 @@ import { RefreshCw } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useSearchParams } from 'react-router-dom';
import { useAppContext } from '../context/AppContext';
+import { Button, FixedButton, LinkButton } from '../components/Button';
import { formatLocalDateTime } from '../utils/time';
function dataUrlForArtifact(artifact: any) {
@@ -142,17 +143,10 @@ const NodeArtifacts: React.FC = () => {
{t('nodeArtifactsHint')}
@@ -188,13 +182,9 @@ const NodeArtifacts: React.FC = () => {
placeholder={t('nodeArtifactsKeepLatest')}
className="ui-input rounded-xl px-3 py-2 text-xs"
/>
-
+
{prunePending ? t('loading') : t('nodeArtifactsPrune')}
-
+
@@ -230,12 +220,8 @@ const NodeArtifacts: React.FC = () => {
{String(selected?.node || '-')} · {String(selected?.action || '-')} · {formatLocalDateTime(selected?.time)}
-
- {t('download')}
-
-
deleteArtifact(String(selected?.id || ''))} className="ui-button ui-button-danger px-3 py-1.5 text-xs">
- {t('delete')}
-
+
{t('download')}
+
deleteArtifact(String(selected?.id || ''))} variant="danger" size="xs">{t('delete')}
diff --git a/webui/src/pages/Nodes.tsx b/webui/src/pages/Nodes.tsx
index 3bdf4f0..bd7917e 100644
--- a/webui/src/pages/Nodes.tsx
+++ b/webui/src/pages/Nodes.tsx
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import { Check, RefreshCw } from 'lucide-react';
import { useAppContext } from '../context/AppContext';
import { formatLocalDateTime } from '../utils/time';
+import { Button, FixedButton, LinkButton } from '../components/Button';
function dataUrlForArtifact(artifact: any) {
const mime = String(artifact?.mime_type || '').trim() || 'application/octet-stream';
@@ -221,14 +222,13 @@ const Nodes: React.FC = () => {
{t('nodes')}
{t('nodesDetailHint')}
- { refreshNodes(); setReloadTick((value) => value + 1); }}
- className="ui-button ui-button-primary ui-button-icon"
- title={loading ? t('loading') : t('refresh')}
- aria-label={loading ? t('loading') : t('refresh')}
+ variant="primary"
+ label={loading ? t('loading') : t('refresh')}
>
-
+
@@ -298,18 +298,12 @@ const Nodes: React.FC = () => {
@@ -455,20 +449,10 @@ const Nodes: React.FC = () => {
{t('nodeDispatchDetail')}
-
- {t('resetReplayDraft')}
-
-
+ {t('resetReplayDraft')}
+
{replayPending ? t('replaying') : t('replayDispatch')}
-
+
diff --git a/webui/src/pages/Skills.tsx b/webui/src/pages/Skills.tsx
index 413544d..2e50f2e 100644
--- a/webui/src/pages/Skills.tsx
+++ b/webui/src/pages/Skills.tsx
@@ -4,6 +4,7 @@ import { motion, AnimatePresence } from 'motion/react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import { useUI } from '../context/UIContext';
+import { Button, FixedButton } from '../components/Button';
const Skills: React.FC = () => {
const { t } = useTranslation();
@@ -190,7 +191,7 @@ const Skills: React.FC = () => {
{t('skills')}
setInstallName(e.target.value)} placeholder={t('skillsNamePlaceholder')} className="w-full sm:w-72 px-3 py-2 bg-zinc-950/70 border border-zinc-800 rounded-xl text-sm disabled:opacity-60" />
- {installingSkill ? t('loading') : t('install')}
+ {installingSkill ? t('loading') : t('install')}
{!clawhubInstalled && (
-
+
{t('skillsInstallNow')}
-
+
)}
-
refreshSkills()}
- className="ui-button ui-button-neutral ui-button-icon"
- title={t('refresh')}
- aria-label={t('refresh')}
- >
+ refreshSkills()} label={t('refresh')}>
-
-
+
+
-
+
@@ -276,19 +264,12 @@ const Skills: React.FC = () => {
- openFileManager(s.id)}
- className="flex-1 flex items-center justify-center gap-2 py-2 bg-indigo-500/10 text-indigo-300 hover:bg-indigo-500/20 rounded-lg text-xs font-medium transition-colors"
- title={t('files')}
- >
+ openFileManager(s.id)} variant="accent" size="xs_tall" radius="lg" gap="2" grow title={t('files')}>
{t('skillsFileEdit')}
-
- deleteSkill(s.id)}
- className="ui-pill ui-pill-danger p-2 rounded-lg transition-colors"
- >
+
+ deleteSkill(s.id)} variant="danger" radius="lg" label={t('delete')}>
-
+
))}
@@ -311,8 +292,10 @@ const Skills: React.FC = () => {
{activeFile || t('noFileSelected')}
- {t('save')}
- setIsFileModalOpen(false)} className="p-2 hover:bg-zinc-800 rounded-full transition-colors text-zinc-400">
+ {t('save')}
+ setIsFileModalOpen(false)} radius="full" label={t('close')}>
+
+