From 1c425eea107abc2b7a3594579553e9a32fee194f Mon Sep 17 00:00:00 2001 From: Marcel Wege Date: Sat, 13 Jun 2026 09:43:18 +0200 Subject: [PATCH 1/5] feat(web-ui): canonical Lume Button on Framer Motion (#282 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit finding: 308 buttons, none using a shared component — each was hand-styled className soup relying on the auto-material CSS matcher, and only 3 of 308 had any motion. The spec defines five normative button variants (§4.2) plus focus and a busy recipe (§7.3); without a component those drift per call site. New app/_components/ui/Button.tsx — a motion.button with: - variants primary / secondary / ghost / danger (§4.2), sizes sm/md/lg/icon - §7.3 busy state: verb + animated dots (lume-busy-dots), never a spinner - tactile press/hover via Framer whileTap/whileHover (§6.4), gated on useReducedMotion so it respects prefers-reduced-motion - real - + diff --git a/web-ui/app/_components/ui/Button.tsx b/web-ui/app/_components/ui/Button.tsx new file mode 100644 index 000000000..e5ffb0cee --- /dev/null +++ b/web-ui/app/_components/ui/Button.tsx @@ -0,0 +1,119 @@ +'use client'; + +import { type HTMLMotionProps, motion, useReducedMotion } from 'framer-motion'; +import { forwardRef } from 'react'; + +import { cn } from '../../_lib/cn'; + +/** + * Lume Button — the canonical interactive button (omadia visual-spec §4.2). + * + * Why a component instead of per-call className soup: the spec defines five + * normative button variants plus a focus and busy recipe. Encoding them once + * keeps every button on-spec, gives real press/hover motion (§6.4) through + * Framer Motion's `whileTap` / `whileHover` instead of CSS-only state, and + * keeps the markup a plain ` + )} diff --git a/web-ui/app/page.tsx b/web-ui/app/page.tsx index 4cc8c1229..1733d03a8 100644 --- a/web-ui/app/page.tsx +++ b/web-ui/app/page.tsx @@ -12,6 +12,7 @@ import { import { useTranslations } from 'next-intl'; import { Eraser, GitBranch, Navigation, Network } from 'lucide-react'; import { ChatTabs } from './_components/ChatTabs'; +import { Button } from './_components/ui/Button'; import { AgentPicker } from './_components/AgentPicker'; import { AgentUnavailableBanner } from './_components/AgentUnavailableBanner'; import { AgentUsagePills } from './_components/chat/AgentUsagePills'; @@ -648,25 +649,19 @@ export default function ChatPage(): React.ReactElement { {t('steerButton')} - + ) : ( - + )} From b6d32fbe61ff123c8beaafab7d15f8be045eab62 Mon Sep 17 00:00:00 2001 From: Marcel Wege Date: Sat, 13 Jun 2026 09:50:54 +0200 Subject: [PATCH 2/5] feat(web-ui): Button pill + fullWidth props for the migration (#282 follow-up) Audit showed many CTAs are pill-shaped (rounded-full) with icons. Add pill and fullWidth props so those migrate to the canonical Button without a shape regression. twMerge resolves the rounded-md/rounded-full conflict. --- web-ui/app/_components/ui/Button.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/web-ui/app/_components/ui/Button.tsx b/web-ui/app/_components/ui/Button.tsx index e5ffb0cee..4d5e44511 100644 --- a/web-ui/app/_components/ui/Button.tsx +++ b/web-ui/app/_components/ui/Button.tsx @@ -35,6 +35,10 @@ export type ButtonSize = 'sm' | 'md' | 'lg' | 'icon'; export interface ButtonProps extends Omit, 'children'> { variant?: ButtonVariant; size?: ButtonSize; + /** Pill radius (badge/CTA chips) instead of the default radius.md corner. */ + pill?: boolean; + /** Stretch to the container width (form submits, full-width CTAs). */ + fullWidth?: boolean; /** §7.3 in-flight: replaces the label with `busyLabel` + animated dots. */ busy?: boolean; busyLabel?: string; @@ -78,6 +82,8 @@ export const Button = forwardRef(function Button { variant = 'primary', size = 'md', + pill = false, + fullWidth = false, busy = false, busyLabel, disabled, @@ -103,7 +109,14 @@ export const Button = forwardRef(function Button whileTap={animate ? TAP : undefined} whileHover={animate ? HOVER : undefined} transition={FEEL_TRANSITION} - className={cn(BASE, VARIANT[variant], SIZE[size], className)} + className={cn( + BASE, + VARIANT[variant], + SIZE[size], + pill ? 'rounded-full' : 'rounded-md', + fullWidth && 'w-full', + className, + )} {...rest} > {busy ? ( From be6eab65c050df1126a3c7ce2af1ae3341f205df Mon Sep 17 00:00:00 2001 From: Marcel Wege Date: Sat, 13 Jun 2026 16:32:14 +0200 Subject: [PATCH 3/5] feat(web-ui): migrate ~80 buttons to the Lume Button across all areas (#290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six sub-agents migrated raw + ))} diff --git a/web-ui/app/_components/IssueReportCard.tsx b/web-ui/app/_components/IssueReportCard.tsx index 4b4b54043..701a68399 100644 --- a/web-ui/app/_components/IssueReportCard.tsx +++ b/web-ui/app/_components/IssueReportCard.tsx @@ -4,6 +4,7 @@ import type React from 'react'; import { useState } from 'react'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; import { ApiError, confirmBuilderIssue, @@ -247,13 +248,8 @@ function SecondaryButton({ onClick: () => void; }): React.ReactElement { return ( - + ); } diff --git a/web-ui/app/_components/StreamToasts.tsx b/web-ui/app/_components/StreamToasts.tsx index f1cccfc33..1f900369a 100644 --- a/web-ui/app/_components/StreamToasts.tsx +++ b/web-ui/app/_components/StreamToasts.tsx @@ -7,6 +7,7 @@ import { useTranslations } from 'next-intl'; import { AnimatePresence, motion } from 'framer-motion'; import { Ban, X } from 'lucide-react'; +import { Button } from '@/app/_components/ui/Button'; import { useChatSessionsCtx } from '../_lib/chatSessionsContext'; import { type StreamPhase, @@ -325,16 +326,16 @@ function AbortConfirmModal({ > {t('abortConfirmKeep')} - + , diff --git a/web-ui/app/_components/store/CredentialsEditor.tsx b/web-ui/app/_components/store/CredentialsEditor.tsx index 1ce35733f..cdb385d56 100644 --- a/web-ui/app/_components/store/CredentialsEditor.tsx +++ b/web-ui/app/_components/store/CredentialsEditor.tsx @@ -25,6 +25,7 @@ import { patchInstalledSecrets, } from '../../_lib/api'; import type { PluginSetupField } from '../../_lib/storeTypes'; +import { Button } from '@/app/_components/ui/Button'; interface CredentialsEditorProps { pluginId: string; @@ -354,19 +355,16 @@ export function CredentialsEditor({
- + {savedAt !== null ? ( ✓ Gespeichert diff --git a/web-ui/app/_components/store/EditFromStoreButton.tsx b/web-ui/app/_components/store/EditFromStoreButton.tsx index e33e0d6cb..7fe816a51 100644 --- a/web-ui/app/_components/store/EditFromStoreButton.tsx +++ b/web-ui/app/_components/store/EditFromStoreButton.tsx @@ -5,8 +5,8 @@ import { useRouter } from 'next/navigation'; import { AlertTriangle, Pencil } from 'lucide-react'; import { cloneBuilderDraftFromInstalled } from '../../_lib/api'; -import { cn } from '../../_lib/cn'; import type { CloneFromInstalledResponse } from '../../_lib/builderTypes'; +import { Button } from '@/app/_components/ui/Button'; /** * Edit-from-Store action (B.6-3). Surfaces on the plugin detail page when @@ -71,30 +71,18 @@ export function EditFromStoreButton({ return (
- + + Im Builder bearbeiten + {phase.kind === 'failed' ? (
diff --git a/web-ui/app/_components/store/InstallButton.tsx b/web-ui/app/_components/store/InstallButton.tsx index 10d931c70..651e52454 100644 --- a/web-ui/app/_components/store/InstallButton.tsx +++ b/web-ui/app/_components/store/InstallButton.tsx @@ -34,6 +34,7 @@ import { pickLocalized } from '../../_lib/localized'; import { RequiresWizard } from './RequiresWizard'; import { FieldRow, extractValues } from './setupForm'; import { Markdown } from '../Markdown'; +import { Button } from '@/app/_components/ui/Button'; interface InstallButtonProps { pluginId: string; @@ -426,15 +427,16 @@ function InstalledPanel({ {update.from ? `${update.from} → ${update.to}` : `→ ${update.to}`} - + Aktualisieren +
{updateError ? (

@@ -693,24 +695,19 @@ function InstallDrawer({
Andere Werte landen in der Instanz-Konfiguration.

- + )} diff --git a/web-ui/app/_components/store/RequiresWizard.tsx b/web-ui/app/_components/store/RequiresWizard.tsx index d2a8dafe5..15573273c 100644 --- a/web-ui/app/_components/store/RequiresWizard.tsx +++ b/web-ui/app/_components/store/RequiresWizard.tsx @@ -19,6 +19,7 @@ import type { } from '../../_lib/storeTypes'; import { Chip } from './Chip'; import { FieldRow, extractValues } from './setupForm'; +import { Button } from '@/app/_components/ui/Button'; /** * RequiresWizard — modal-dialog operator-flow for an @@ -202,47 +203,38 @@ export function RequiresWizard({ > Abbrechen - + ) : phase.kind === 'installing' && phase.requiresForm ? ( - + ) : phase.kind === 'success' ? ( - + ) : phase.kind === 'error' ? ( + {t('propose')} +
{/* template compose (standalone plugins that expose selfExtend templates) */} @@ -265,15 +268,17 @@ export function SelfExtensionPanel({ agentId }: { agentId: string }): React.Reac {tplError ? (

{tplError}

) : null} - + {t('proposeTemplate')} +
) : null} @@ -363,15 +368,18 @@ export function SelfExtensionPanel({ agentId }: { agentId: string }): React.Reac
) : (
- + + {t('install')} + ) : null} ))} diff --git a/web-ui/app/_components/store/UploadDropzone.tsx b/web-ui/app/_components/store/UploadDropzone.tsx index 5d3a76baa..94ee435a4 100644 --- a/web-ui/app/_components/store/UploadDropzone.tsx +++ b/web-ui/app/_components/store/UploadDropzone.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation'; import { ApiError, uploadPackage } from '../../_lib/api'; import type { UploadedPackage } from '../../_lib/storeTypes'; import { cn } from '../../_lib/cn'; +import { Button } from '@/app/_components/ui/Button'; type UploadState = | { kind: 'idle' } @@ -159,18 +160,13 @@ export function UploadDropzone(): React.ReactElement { {state.kind === 'idle' && (
- + oder per Drag & Drop diff --git a/web-ui/app/admin/builder/panels/InspectorControls.tsx b/web-ui/app/admin/builder/panels/InspectorControls.tsx index d49194235..0a84a3450 100644 --- a/web-ui/app/admin/builder/panels/InspectorControls.tsx +++ b/web-ui/app/admin/builder/panels/InspectorControls.tsx @@ -1,5 +1,7 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; + export const inputCls = 'w-full rounded-md border border-[color:var(--border)] bg-transparent px-3 py-2 text-sm outline-none focus:border-[color:var(--accent)]'; @@ -30,13 +32,12 @@ export function SaveButton({ label: string; }): React.ReactElement { return ( - + ); } diff --git a/web-ui/app/admin/builder/panels/InspectorPanel.tsx b/web-ui/app/admin/builder/panels/InspectorPanel.tsx index 54b9625cb..daf7f2d52 100644 --- a/web-ui/app/admin/builder/panels/InspectorPanel.tsx +++ b/web-ui/app/admin/builder/panels/InspectorPanel.tsx @@ -16,6 +16,7 @@ import { type SkillNode, type SubAgentNode, } from '../../../_lib/agentBuilder'; +import { Button } from '@/app/_components/ui/Button'; import type { BuilderNodeData } from '../nodes/types'; import { Field, inputCls, SaveButton } from './InspectorControls'; @@ -40,13 +41,9 @@ export function InspectorPanel(props: InspectorPanelProps): React.ReactElement {

{t('inspector.title')}

- +
diff --git a/web-ui/app/admin/bulk-promote/page.tsx b/web-ui/app/admin/bulk-promote/page.tsx index f6ef24d87..f6d84b1e4 100644 --- a/web-ui/app/admin/bulk-promote/page.tsx +++ b/web-ui/app/admin/bulk-promote/page.tsx @@ -3,6 +3,8 @@ import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; + import { ApiError, previewBulkPromote, @@ -185,14 +187,14 @@ export default function BulkPromotePage(): React.ReactElement {
- + +
@@ -316,14 +318,13 @@ export default function DangerZonePage(): React.ReactElement { className="w-full rounded border border-[color:var(--danger-edge)] px-2 py-2 text-sm font-mono" />
- +
)} diff --git a/web-ui/app/admin/duplicates/page.tsx b/web-ui/app/admin/duplicates/page.tsx index df8610db2..03e2e407b 100644 --- a/web-ui/app/admin/duplicates/page.tsx +++ b/web-ui/app/admin/duplicates/page.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; import { listExcerptMergeCandidates, listMergeCandidates, @@ -372,14 +373,15 @@ export default function DuplicatesListPage(): React.ReactElement { {s === 'all' ? 'alle' : s} ))} - + {error !== null && ( @@ -568,14 +570,15 @@ export default function DuplicatesListPage(): React.ReactElement { {s === 'all' ? 'alle' : s} ))} - + {excerptError !== null && ( diff --git a/web-ui/app/admin/inconsistencies/page.tsx b/web-ui/app/admin/inconsistencies/page.tsx index 3335db0fa..8bfd7f1d0 100644 --- a/web-ui/app/admin/inconsistencies/page.tsx +++ b/web-ui/app/admin/inconsistencies/page.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; import { listInconsistencies, previewBulkInconsistencyDetect, @@ -302,14 +303,15 @@ export default function InconsistenciesListPage(): React.ReactElement { {s === 'all' ? 'alle' : s} ))} - + {error !== null && ( diff --git a/web-ui/app/admin/kg-lifecycle/page.tsx b/web-ui/app/admin/kg-lifecycle/page.tsx index 44826a29e..b32044063 100644 --- a/web-ui/app/admin/kg-lifecycle/page.tsx +++ b/web-ui/app/admin/kg-lifecycle/page.tsx @@ -3,6 +3,8 @@ import { useCallback, useEffect, useState } from 'react'; import type { JSX } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + /** * Admin → Knowledge-Graph Lifecycle (palaia Phase 4 / OB-73, Slice D). * @@ -141,13 +143,9 @@ export default function KgLifecyclePage(): JSX.Element { cron schedule; the buttons below trigger them on demand.

- + {error ? ( diff --git a/web-ui/app/admin/kg-priorities/page.tsx b/web-ui/app/admin/kg-priorities/page.tsx index ce86c81ec..2252012cb 100644 --- a/web-ui/app/admin/kg-priorities/page.tsx +++ b/web-ui/app/admin/kg-priorities/page.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + /** * Admin → Knowledge-Graph Priorities (palaia Phase 5 / OB-74 Slice 5). * @@ -161,13 +163,9 @@ export default function KgPrioritiesPage(): React.ReactElement { placeholder="de.byte5.agent.calendar oder orchestrator-default" className="flex-1 rounded-md border border-[color:var(--border)] bg-[color:var(--bg)] px-3 py-2 text-sm text-[color:var(--fg-strong)]" /> - + @@ -214,8 +212,8 @@ export default function KgPrioritiesPage(): React.ReactElement { placeholder="Reason (optional)" className="lg:col-span-3 rounded-md border border-[color:var(--border)] bg-[color:var(--bg)] px-3 py-2 text-sm text-[color:var(--fg-strong)]" /> - + @@ -285,14 +283,14 @@ export default function KgPrioritiesPage(): React.ReactElement { {new Date(r.updatedAt).toLocaleString('de-DE')} - + )) diff --git a/web-ui/app/admin/memory-backend/page.tsx b/web-ui/app/admin/memory-backend/page.tsx index e1b727dcb..4304c62fd 100644 --- a/web-ui/app/admin/memory-backend/page.tsx +++ b/web-ui/app/admin/memory-backend/page.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; import { ApiError, getMemoryBackend, @@ -216,14 +217,13 @@ export default function MemoryBackendPage(): React.ReactElement {
- +
diff --git a/web-ui/app/admin/registries/page.tsx b/web-ui/app/admin/registries/page.tsx index d0ca28663..7e57aab65 100644 --- a/web-ui/app/admin/registries/page.tsx +++ b/web-ui/app/admin/registries/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import { ApiError, StoredRegistry, @@ -178,14 +179,13 @@ export default function AdminRegistriesPage(): React.ReactElement { className={inputCls} /> - + @@ -217,16 +217,15 @@ export default function AdminRegistriesPage(): React.ReactElement {
- + + {r.has_token && ( - + )}
diff --git a/web-ui/app/admin/settings/page.tsx b/web-ui/app/admin/settings/page.tsx index 022e45f06..37c1b6c7d 100644 --- a/web-ui/app/admin/settings/page.tsx +++ b/web-ui/app/admin/settings/page.tsx @@ -5,6 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import Link from 'next/link'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; import { getSettings, patchSettings, @@ -330,14 +331,13 @@ function SettingRow({ className={`${inputCls} flex-1 sm:min-w-[260px]`} /> {s.isSet && ( - + )} ) : ( diff --git a/web-ui/app/admin/users/[id]/page.tsx b/web-ui/app/admin/users/[id]/page.tsx index 3aae45e56..977cd2a41 100644 --- a/web-ui/app/admin/users/[id]/page.tsx +++ b/web-ui/app/admin/users/[id]/page.tsx @@ -4,6 +4,7 @@ import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import { AdminUser, ApiError, @@ -210,13 +211,14 @@ export default function AdminUserEditPage(): React.ReactElement {

{patchMessage}

)} {patchError &&

{patchError}

} - + @@ -240,14 +242,13 @@ export default function AdminUserEditPage(): React.ReactElement { className="rounded-md border border-[color:var(--border)] bg-transparent px-3 py-2 text-sm outline-none focus:border-[color:var(--accent)]" /> - + {resetMessage && (

{resetMessage}

@@ -265,14 +266,13 @@ export default function AdminUserEditPage(): React.ReactElement {

Löschen entfernt den Nutzer permanent. Audit-Log behält den Eintrag.

- + {deleteError && (

{deleteError}

)} diff --git a/web-ui/app/admin/users/page.tsx b/web-ui/app/admin/users/page.tsx index 04c90a5d6..03cd080c6 100644 --- a/web-ui/app/admin/users/page.tsx +++ b/web-ui/app/admin/users/page.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import { AdminUser, ApiError, @@ -100,13 +101,12 @@ export default function AdminUsersPage(): React.ReactElement { Nutzer sich einmal über den IdP angemeldet hat.

- + {showCreate && ( @@ -147,13 +147,14 @@ export default function AdminUsersPage(): React.ReactElement { {createError && (

{createError}

)} - + )} diff --git a/web-ui/app/error.tsx b/web-ui/app/error.tsx index 39f19695d..4832a93a9 100644 --- a/web-ui/app/error.tsx +++ b/web-ui/app/error.tsx @@ -3,6 +3,8 @@ import { useEffect } from 'react'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; + /** * Route-level error boundary. Without it, a thrown error during render of the * chat page (e.g. a chat session persisted by an older schema, whose drifted @@ -62,13 +64,9 @@ export default function Error({ > {t('reload')} - +

{t('resetDataHint')} diff --git a/web-ui/app/graph/_components/MemoryAclSection.tsx b/web-ui/app/graph/_components/MemoryAclSection.tsx index 638bbf3cd..833feea1b 100644 --- a/web-ui/app/graph/_components/MemoryAclSection.tsx +++ b/web-ui/app/graph/_components/MemoryAclSection.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import type { GraphNode } from './graphTypes'; interface AclAuditEntry { @@ -276,15 +277,15 @@ export default function MemoryAclSection({ > + Owner hinzufügen - + diff --git a/web-ui/app/memories/[id]/page.tsx b/web-ui/app/memories/[id]/page.tsx index fa16c1efe..e60d0f704 100644 --- a/web-ui/app/memories/[id]/page.tsx +++ b/web-ui/app/memories/[id]/page.tsx @@ -4,6 +4,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; + import { deleteMemory, getMemory, @@ -475,14 +477,14 @@ export default function MemoryDetailPage(): React.ReactElement { ) : ( <> - + - +

- - - +
@@ -593,9 +594,9 @@ function AgentCard(props: { > {t('actionDrain')} - +
@@ -711,22 +712,22 @@ function BindingsEditor(props: {

{t('bindingsHeading')} - - +

{rows.length === 0 ? ( @@ -775,14 +776,14 @@ function BindingsEditor(props: { className="flex-1 rounded border border-[color:var(--border)] px-2 py-1 font-mono text-xs" placeholder="28:bot-id-or-@username" /> - + ))} diff --git a/web-ui/app/operator/agents/_components/PluginsDnd.tsx b/web-ui/app/operator/agents/_components/PluginsDnd.tsx index 650d17ee9..fbc1a489a 100644 --- a/web-ui/app/operator/agents/_components/PluginsDnd.tsx +++ b/web-ui/app/operator/agents/_components/PluginsDnd.tsx @@ -23,6 +23,7 @@ import { GripVertical } from 'lucide-react'; import { useMemo, useState } from 'react'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; import type { OperatorAgentDto, PluginCatalogEntryDto, @@ -288,14 +289,14 @@ export function PluginsDnd(props: PluginsDndProps): React.ReactElement {

{t('pluginsHeading')} - +

{!attached ? ( - + ) : ( <> {hasFields && ( - + )} - + )}
diff --git a/web-ui/app/page.tsx b/web-ui/app/page.tsx index 1733d03a8..bc81e350e 100644 --- a/web-ui/app/page.tsx +++ b/web-ui/app/page.tsx @@ -1480,18 +1480,19 @@ function FollowUpButtons({
{options.map((opt, idx) => ( - + ))}
diff --git a/web-ui/app/routines/_components/RoutineActions.tsx b/web-ui/app/routines/_components/RoutineActions.tsx index fe892b387..52a73e701 100644 --- a/web-ui/app/routines/_components/RoutineActions.tsx +++ b/web-ui/app/routines/_components/RoutineActions.tsx @@ -3,6 +3,7 @@ import { useState, useTransition } from 'react'; import { useRouter } from 'next/navigation'; +import { Button } from '@/app/_components/ui/Button'; import { deleteRoutine, setRoutineStatus, @@ -97,14 +98,15 @@ export function RoutineActions({ routine }: Props): React.ReactElement { > Jetzt - + + {error ? (
diff --git a/web-ui/app/routines/_components/RoutineTemplateEditor.tsx b/web-ui/app/routines/_components/RoutineTemplateEditor.tsx index 1a4eb5b53..d6044617c 100644 --- a/web-ui/app/routines/_components/RoutineTemplateEditor.tsx +++ b/web-ui/app/routines/_components/RoutineTemplateEditor.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/navigation'; import { useMemo, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import { previewRoutineTemplate, setRoutineTemplate, @@ -259,14 +260,16 @@ export function RoutineTemplateEditor({ routine }: Props): React.ReactElement { > {saving ? 'Speichert…' : 'Speichern'} - + + ); diff --git a/web-ui/app/store/builder/[id]/_components/BoundariesSection.tsx b/web-ui/app/store/builder/[id]/_components/BoundariesSection.tsx index 5847950e8..8c90a466d 100644 --- a/web-ui/app/store/builder/[id]/_components/BoundariesSection.tsx +++ b/web-ui/app/store/builder/[id]/_components/BoundariesSection.tsx @@ -3,6 +3,8 @@ import { useTranslations } from 'next-intl'; import { useCallback, useMemo, useState, useTransition } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { setQualityConfig } from '../../../../_lib/api'; import { BOUNDARY_CATEGORY_LABELS_DE, @@ -178,15 +180,16 @@ export function BoundariesSection({ )}
- + {savedAt && ( {t('saved')} diff --git a/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx b/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx index 4e9fee4f4..3a8410a18 100644 --- a/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx +++ b/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; import { useCallback, useEffect, @@ -602,15 +603,15 @@ export function BuilderChatPane({ {t('button.stop')} ) : ( - + )}
{inflight ? ( diff --git a/web-ui/app/store/builder/[id]/_components/CulturePresetDropdown.tsx b/web-ui/app/store/builder/[id]/_components/CulturePresetDropdown.tsx index a95644e04..45490a715 100644 --- a/web-ui/app/store/builder/[id]/_components/CulturePresetDropdown.tsx +++ b/web-ui/app/store/builder/[id]/_components/CulturePresetDropdown.tsx @@ -3,6 +3,8 @@ import { useTranslations } from 'next-intl'; import { useCallback, useMemo, useState, useTransition } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { setPersonaConfig } from '../../../../_lib/api'; import { CULTURE_PRESETS, @@ -121,15 +123,16 @@ export function CulturePresetDropdown({
)}
- + + ) : null} +
); } diff --git a/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx b/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx index fbda281ed..7d87e188c 100644 --- a/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx +++ b/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx @@ -3,6 +3,8 @@ import { useTranslations } from 'next-intl'; import { useCallback, useState, useTransition } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { patchBuilderSpec, setPersonaConfig } from '../../../../_lib/api'; import { PERSONA_TEMPLATES, @@ -192,15 +194,16 @@ export function PersonaTemplateGallery({ > {t('cancel')} - + diff --git a/web-ui/app/store/builder/[id]/_components/PreviewChatPane.tsx b/web-ui/app/store/builder/[id]/_components/PreviewChatPane.tsx index 3f1e7ef4d..b882bcdc9 100644 --- a/web-ui/app/store/builder/[id]/_components/PreviewChatPane.tsx +++ b/web-ui/app/store/builder/[id]/_components/PreviewChatPane.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; import { useCallback, useEffect, @@ -587,15 +588,15 @@ export function PreviewChatPane({ {t('stop')} ) : ( - + )} {inflight ? ( diff --git a/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx b/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx index 8ecde05cb..06eec66db 100644 --- a/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx +++ b/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; + +import { Button } from '@/app/_components/ui/Button'; import { AnimatePresence, motion } from 'framer-motion'; import { CheckCircle2, KeyRound, Trash2, X } from 'lucide-react'; @@ -215,11 +217,11 @@ export function SecretsDrawer({ {t('clearAll')} - + diff --git a/web-ui/app/store/builder/[id]/_components/SetupFieldsEditor.tsx b/web-ui/app/store/builder/[id]/_components/SetupFieldsEditor.tsx index 92736c17a..4cae8b200 100644 --- a/web-ui/app/store/builder/[id]/_components/SetupFieldsEditor.tsx +++ b/web-ui/app/store/builder/[id]/_components/SetupFieldsEditor.tsx @@ -2,6 +2,8 @@ import { useCallback, useId, useMemo, useState } from 'react'; import { useTranslations } from 'next-intl'; + +import { Button } from '@/app/_components/ui/Button'; import { Plus, Trash2 } from 'lucide-react'; import { ApiError, patchBuilderSpec } from '../../../../_lib/api'; @@ -314,8 +316,8 @@ function NewFieldForm({ {t('required.on')} - + {key && !valid ? (

diff --git a/web-ui/app/store/builder/[id]/_components/SimpleIntakePane.tsx b/web-ui/app/store/builder/[id]/_components/SimpleIntakePane.tsx index 642c56330..201eb80bb 100644 --- a/web-ui/app/store/builder/[id]/_components/SimpleIntakePane.tsx +++ b/web-ui/app/store/builder/[id]/_components/SimpleIntakePane.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useId, useRef, useState } from 'react'; import { useTranslations } from 'next-intl'; + +import { Button } from '@/app/_components/ui/Button'; import { motion } from 'framer-motion'; import { Send, Sparkles, StopCircle } from 'lucide-react'; @@ -316,15 +318,16 @@ export function SimpleIntakePane({ {t('stop')} ) : ( - + )} diff --git a/web-ui/app/store/builder/[id]/_components/SlotEditor.tsx b/web-ui/app/store/builder/[id]/_components/SlotEditor.tsx index 2be3497d8..b57ae6452 100644 --- a/web-ui/app/store/builder/[id]/_components/SlotEditor.tsx +++ b/web-ui/app/store/builder/[id]/_components/SlotEditor.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; import { useCallback, useEffect, @@ -401,15 +402,16 @@ export function SlotEditor({ ))} - +

diff --git a/web-ui/app/store/builder/[id]/_components/SpecEditor.tsx b/web-ui/app/store/builder/[id]/_components/SpecEditor.tsx index dfb20a99a..d13abb974 100644 --- a/web-ui/app/store/builder/[id]/_components/SpecEditor.tsx +++ b/web-ui/app/store/builder/[id]/_components/SpecEditor.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; import { useTranslations } from 'next-intl'; + +import { Button } from '@/app/_components/ui/Button'; import { AlertCircle, Check, Plus, X } from 'lucide-react'; import { ApiError, patchBuilderSpec } from '../../../../_lib/api'; @@ -540,15 +542,15 @@ function ArrayField({ mono && 'font-mono-num', )} /> - +
); diff --git a/web-ui/app/store/builder/[id]/_components/ToolBulkImportModal.tsx b/web-ui/app/store/builder/[id]/_components/ToolBulkImportModal.tsx index 1f249f455..d0b8eb92e 100644 --- a/web-ui/app/store/builder/[id]/_components/ToolBulkImportModal.tsx +++ b/web-ui/app/store/builder/[id]/_components/ToolBulkImportModal.tsx @@ -4,6 +4,7 @@ import { AlertTriangle, Check, Upload, X } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useMemo, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import type { JsonPatch, ToolSpec } from '../../../../_lib/builderTypes'; import { cn } from '../../../../_lib/cn'; import { @@ -158,8 +159,9 @@ export function ToolBulkImportModal({

{t('collisionNote')}

- + diff --git a/web-ui/app/store/builder/[id]/_components/ToolInputSchemaRow.tsx b/web-ui/app/store/builder/[id]/_components/ToolInputSchemaRow.tsx index c6944f709..11c8d2ee1 100644 --- a/web-ui/app/store/builder/[id]/_components/ToolInputSchemaRow.tsx +++ b/web-ui/app/store/builder/[id]/_components/ToolInputSchemaRow.tsx @@ -2,6 +2,8 @@ import { ChevronDown, ChevronRight, X } from 'lucide-react'; import { useTranslations } from 'next-intl'; + +import { Button } from '@/app/_components/ui/Button'; import { useEffect, useId, useState } from 'react'; import { cn } from '../../../../_lib/cn'; @@ -454,18 +456,19 @@ function EnumValuesEditor({ placeholder={t('valueEnterPlaceholder')} className="flex-1 rounded border border-[color:var(--border)] bg-[color:var(--bg)] px-2 py-1 font-mono-num text-[11px] text-[color:var(--fg-strong)] focus:border-[color:var(--accent)] focus:outline-none" /> - + ); diff --git a/web-ui/app/store/builder/[id]/_components/ToolList.tsx b/web-ui/app/store/builder/[id]/_components/ToolList.tsx index 3daf511f0..a739984cf 100644 --- a/web-ui/app/store/builder/[id]/_components/ToolList.tsx +++ b/web-ui/app/store/builder/[id]/_components/ToolList.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; import { closestCenter, DndContext, @@ -149,14 +150,14 @@ export function ToolList({ {t('emptyState')}

- + ) : null} - +
diff --git a/web-ui/app/store/builder/[id]/_components/VersionsTab.tsx b/web-ui/app/store/builder/[id]/_components/VersionsTab.tsx index 5daefffc6..8fb763f0a 100644 --- a/web-ui/app/store/builder/[id]/_components/VersionsTab.tsx +++ b/web-ui/app/store/builder/[id]/_components/VersionsTab.tsx @@ -3,6 +3,8 @@ import { useTranslations } from 'next-intl'; import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { captureSnapshot, getSnapshotDiff, @@ -96,13 +98,13 @@ export function VersionsTab({ draftId }: VersionsTabProps): React.ReactElement { return (
- + - +
); diff --git a/web-ui/app/store/builder/_components/CreateDraftButton.tsx b/web-ui/app/store/builder/_components/CreateDraftButton.tsx index 7ffdfe2b8..ae59a64b2 100644 --- a/web-ui/app/store/builder/_components/CreateDraftButton.tsx +++ b/web-ui/app/store/builder/_components/CreateDraftButton.tsx @@ -7,7 +7,7 @@ import { Plus } from 'lucide-react'; import { ApiError, createBuilderDraft } from '../../../_lib/api'; import type { DraftQuotaSnapshot } from '../../../_lib/builderTypes'; -import { cn } from '../../../_lib/cn'; +import { Button } from '@/app/_components/ui/Button'; /** * Primary "neuer Agent"-Action. One click creates a draft row via POST @@ -50,22 +50,16 @@ export function CreateDraftButton({ return (
- + {error ? (

{error} diff --git a/web-ui/app/store/builder/_components/ImportBundleButton.tsx b/web-ui/app/store/builder/_components/ImportBundleButton.tsx index ffcb2da19..ef74c7801 100644 --- a/web-ui/app/store/builder/_components/ImportBundleButton.tsx +++ b/web-ui/app/store/builder/_components/ImportBundleButton.tsx @@ -8,6 +8,7 @@ import { Upload, FileArchive } from 'lucide-react'; import { ApiError, importProfileBundle } from '../../../_lib/api'; import type { ImportBundleSuccess } from '../../../_lib/profileTypes'; import { cn } from '../../../_lib/cn'; +import { Button } from '@/app/_components/ui/Button'; /** * Phase 2.4 (OB-66) — Bundle-Import-Button + Drag-Drop-Modal. @@ -28,20 +29,10 @@ export function ImportBundleButton(): React.ReactElement { return ( <> - + {open ? ( setOpen(false)} @@ -234,24 +225,18 @@ function ImportBundleModal({ ) : null}

- - + {t('modal.import')} +
diff --git a/web-ui/vitest.config.ts b/web-ui/vitest.config.ts index a36467e64..88c473be2 100644 --- a/web-ui/vitest.config.ts +++ b/web-ui/vitest.config.ts @@ -22,7 +22,11 @@ export default defineConfig({ }, resolve: { alias: { - '@': path.resolve(__dirname, './app'), + // Match tsconfig's `@/*` -> `./*` (web-ui root), so `@/app/...` resolves + // the same in vitest as in tsc and Next. (The previous `@` -> ./app + // diverged from tsconfig; it surfaced once the first `@/app/...` imports + // landed.) + '@': path.resolve(__dirname, '.'), }, }, }); From c883f5949b30dcf6fc2b4e2c87f1d39653fad7f7 Mon Sep 17 00:00:00 2001 From: Marcel Wege Date: Sat, 13 Jun 2026 16:49:59 +0200 Subject: [PATCH 4/5] =?UTF-8?q?feat(web-ui):=20pass=202=20=E2=80=94=20broa?= =?UTF-8?q?den=20button=20migration=20to=20spec=20variants=20(#290)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mapped bespoke patterns onto the 4 spec variants (no new variants, per §10): bg-inverse fills -> primary, neutral-bordered-no-bg -> secondary, danger-with-/NN-border -> danger. ~68 more buttons across builder/store/ admin/operator/graph. typecheck 0, build green, lint 0 errors, vitest 157/162. Warning fills and solid-danger fills left for manual per-intent mapping next. --- .../app/_components/store/InstallButton.tsx | 41 +++++------ .../app/_components/store/RequiresWizard.tsx | 20 +++--- .../_components/store/SelfExtensionPanel.tsx | 10 +-- web-ui/app/admin/auth/page.tsx | 22 ++---- web-ui/app/admin/bulk-promote/page.tsx | 18 +++-- web-ui/app/admin/duplicates/page.tsx | 24 ++++--- web-ui/app/admin/inconsistencies/page.tsx | 12 ++-- web-ui/app/admin/registries/page.tsx | 12 ++-- web-ui/app/admin/topics/page.tsx | 14 ++-- web-ui/app/graph/_components/DetailPanel.tsx | 33 +++++---- web-ui/app/graph/_components/ListView.tsx | 11 +-- .../graph/_components/MemoryAclSection.tsx | 9 +-- web-ui/app/memories/[id]/page.tsx | 68 +++++++++++-------- web-ui/app/memories/page.tsx | 9 +-- web-ui/app/memory/page.tsx | 10 +-- .../agents/_components/AgentsDashboard.tsx | 16 +++-- .../routines/_components/RoutineActions.tsx | 20 +++--- .../_components/RoutineTemplateEditor.tsx | 28 +++++--- .../[id]/_components/AuditTimelinePane.tsx | 10 +-- .../[id]/_components/BuilderChatPane.tsx | 8 +-- .../_components/CulturePresetDropdown.tsx | 9 +-- .../[id]/_components/InstallDiffModal.tsx | 22 +++--- .../builder/[id]/_components/PagesList.tsx | 8 +-- .../[id]/_components/PersonaPillar.tsx | 30 ++++---- .../_components/PersonaTemplateGallery.tsx | 18 ++--- .../[id]/_components/PreviewChatPane.tsx | 17 ++--- .../[id]/_components/PreviewPromptPanel.tsx | 18 ++--- .../builder/[id]/_components/QualityPanel.tsx | 10 +-- .../[id]/_components/SecretsDrawer.tsx | 8 +-- .../[id]/_components/SimpleIntakePane.tsx | 18 ++--- .../builder/[id]/_components/ToolForm.tsx | 10 +-- .../_components/ToolInputSchemaBuilder.tsx | 10 ++- .../builder/[id]/_components/ToolList.tsx | 36 +++++----- .../[id]/_components/ToolTestModal.tsx | 9 +-- .../builder/[id]/_components/VersionsTab.tsx | 24 ++----- .../builder/[id]/_components/Workspace.tsx | 24 +++---- .../store/builder/_components/DraftRow.tsx | 10 +-- 37 files changed, 352 insertions(+), 324 deletions(-) diff --git a/web-ui/app/_components/store/InstallButton.tsx b/web-ui/app/_components/store/InstallButton.tsx index 651e52454..2f5ee9ed4 100644 --- a/web-ui/app/_components/store/InstallButton.tsx +++ b/web-ui/app/_components/store/InstallButton.tsx @@ -109,14 +109,12 @@ export function InstallButton({ if (!enabled) { return (
- + {blockingReasons?.length ? (
    - + {drawerOpen ? ( - +
)} diff --git a/web-ui/app/_components/store/RequiresWizard.tsx b/web-ui/app/_components/store/RequiresWizard.tsx index 15573273c..353cfc304 100644 --- a/web-ui/app/_components/store/RequiresWizard.tsx +++ b/web-ui/app/_components/store/RequiresWizard.tsx @@ -196,13 +196,15 @@ export function RequiresWizard({
{phase.kind === 'review' ? ( <> - + + ) : null}
diff --git a/web-ui/app/_components/store/SelfExtensionPanel.tsx b/web-ui/app/_components/store/SelfExtensionPanel.tsx index 4465c53ec..4989bc711 100644 --- a/web-ui/app/_components/store/SelfExtensionPanel.tsx +++ b/web-ui/app/_components/store/SelfExtensionPanel.tsx @@ -380,15 +380,17 @@ export function SelfExtensionPanel({ agentId }: { agentId: string }): React.Reac {t('approve')} - + ) ) : null} diff --git a/web-ui/app/admin/auth/page.tsx b/web-ui/app/admin/auth/page.tsx index d0885c5f7..babe671db 100644 --- a/web-ui/app/admin/auth/page.tsx +++ b/web-ui/app/admin/auth/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import { AdminAuthProvider, ApiError, @@ -126,23 +127,14 @@ export default function AdminAuthPage(): React.ReactElement { {p.active ? 'aktiv' : 'inaktiv'} - + {p.active ? 'Deaktivieren' : 'Aktivieren'} + ))} diff --git a/web-ui/app/admin/bulk-promote/page.tsx b/web-ui/app/admin/bulk-promote/page.tsx index f6d84b1e4..a4f046d87 100644 --- a/web-ui/app/admin/bulk-promote/page.tsx +++ b/web-ui/app/admin/bulk-promote/page.tsx @@ -195,18 +195,16 @@ export default function BulkPromotePage(): React.ReactElement { > Vorschau aktualisieren - + Bulk-Job starten + diff --git a/web-ui/app/admin/duplicates/page.tsx b/web-ui/app/admin/duplicates/page.tsx index 03e2e407b..eb8b005a4 100644 --- a/web-ui/app/admin/duplicates/page.tsx +++ b/web-ui/app/admin/duplicates/page.tsx @@ -301,16 +301,18 @@ export default function DuplicatesListPage(): React.ReactElement { }} className="w-20 rounded border border-[color:var(--border)] bg-transparent px-2 py-1 text-xs" /> - + Bulk-Detect starten + {bulkLimit > BULK_CONFIRM_THRESHOLD && ( ⚠️ Confirm bei Limit > {BULK_CONFIRM_THRESHOLD} @@ -500,17 +502,19 @@ export default function DuplicatesListPage(): React.ReactElement { }} className="w-20 rounded border border-[color:var(--border)] bg-transparent px-2 py-1 text-xs" /> - + Bulk-Detect starten + {excerptBulkLimit > BULK_CONFIRM_THRESHOLD && ( ⚠️ Confirm bei Limit > {BULK_CONFIRM_THRESHOLD} diff --git a/web-ui/app/admin/inconsistencies/page.tsx b/web-ui/app/admin/inconsistencies/page.tsx index 8bfd7f1d0..f663cd6e5 100644 --- a/web-ui/app/admin/inconsistencies/page.tsx +++ b/web-ui/app/admin/inconsistencies/page.tsx @@ -229,18 +229,20 @@ export default function InconsistenciesListPage(): React.ReactElement { }} className="w-20 rounded border border-[color:var(--border)] bg-transparent px-2 py-1 text-xs" /> - + Bulk-Detect starten + {bulkLimit > BULK_CONFIRM_THRESHOLD && ( ⚠️ Cost-Confirm bei Limit > {BULK_CONFIRM_THRESHOLD} diff --git a/web-ui/app/admin/registries/page.tsx b/web-ui/app/admin/registries/page.tsx index 7e57aab65..11c9164cd 100644 --- a/web-ui/app/admin/registries/page.tsx +++ b/web-ui/app/admin/registries/page.tsx @@ -226,14 +226,14 @@ export default function AdminRegistriesPage(): React.ReactElement { > {editing === r.name ? 'Abbrechen' : 'Bearbeiten'} - + Entfernen + diff --git a/web-ui/app/admin/topics/page.tsx b/web-ui/app/admin/topics/page.tsx index 0e7e48e11..65f167506 100644 --- a/web-ui/app/admin/topics/page.tsx +++ b/web-ui/app/admin/topics/page.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; import { listTopics, reclusterTopics, @@ -130,14 +131,17 @@ export default function TopicsListPage(): React.ReactElement { /> - + Re-Cluster starten + {runError !== null && (

Fehler: {runError} diff --git a/web-ui/app/graph/_components/DetailPanel.tsx b/web-ui/app/graph/_components/DetailPanel.tsx index 7f23b7063..dc741ec1c 100644 --- a/web-ui/app/graph/_components/DetailPanel.tsx +++ b/web-ui/app/graph/_components/DetailPanel.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; import { type GraphNode, type MemoryWithAncestors, @@ -92,14 +93,18 @@ export default function DetailPanel({ ))} - + ↔ Nachbarn expandieren + {node.type === 'MemorableKnowledge' && (

{neighbors.map((n) => ( - + ))}
@@ -258,11 +264,12 @@ function ProvenanceList({
{nodes.map((n) => ( - + ))}
diff --git a/web-ui/app/graph/_components/ListView.tsx b/web-ui/app/graph/_components/ListView.tsx index f38478c48..269ccead7 100644 --- a/web-ui/app/graph/_components/ListView.tsx +++ b/web-ui/app/graph/_components/ListView.tsx @@ -1,6 +1,8 @@ 'use client'; import { useState } from 'react'; + +import { Button } from '@/app/_components/ui/Button'; import type { GraphNode, NodeType, @@ -72,13 +74,14 @@ function TurnCard({
{time} {tools !== undefined && tools={String(tools)}} - +
{user} diff --git a/web-ui/app/graph/_components/MemoryAclSection.tsx b/web-ui/app/graph/_components/MemoryAclSection.tsx index 833feea1b..8ae17af67 100644 --- a/web-ui/app/graph/_components/MemoryAclSection.tsx +++ b/web-ui/app/graph/_components/MemoryAclSection.tsx @@ -269,14 +269,15 @@ export default function MemoryAclSection({ disabled={busy} />
- + - + Speichern + ) : ( <> @@ -485,14 +487,14 @@ export default function MemoryDetailPage(): React.ReactElement { > Löschen - + )}
@@ -552,21 +554,23 @@ export default function MemoryDetailPage(): React.ReactElement {
{!isEditing && (
- - +
)} @@ -608,24 +612,28 @@ export default function MemoryDetailPage(): React.ReactElement { ))}
- - + Speichern +
diff --git a/web-ui/app/memories/page.tsx b/web-ui/app/memories/page.tsx index 95ade3ef7..4a5ed1e28 100644 --- a/web-ui/app/memories/page.tsx +++ b/web-ui/app/memories/page.tsx @@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; import { listMemories, type MemorableKind, @@ -80,13 +81,13 @@ export default function MemoriesPage(): React.ReactElement { Kuratiertes Wissen — ACL-gefiltert auf dich als Owner.

- +
{KIND_FILTERS.map((k) => { diff --git a/web-ui/app/memory/page.tsx b/web-ui/app/memory/page.tsx index c8cb31f58..2f7fc7196 100644 --- a/web-ui/app/memory/page.tsx +++ b/web-ui/app/memory/page.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Markdown } from '../_components/Markdown'; +import { Button } from '@/app/_components/ui/Button'; import { getMemoryBackend, type MemoryBackend } from '../_lib/api'; interface Entry { @@ -246,13 +247,14 @@ export default function MemoryPage(): React.ReactElement { {selected}
- +
diff --git a/web-ui/app/operator/agents/_components/AgentsDashboard.tsx b/web-ui/app/operator/agents/_components/AgentsDashboard.tsx index 4c59d3d30..5d28f385a 100644 --- a/web-ui/app/operator/agents/_components/AgentsDashboard.tsx +++ b/web-ui/app/operator/agents/_components/AgentsDashboard.tsx @@ -344,13 +344,15 @@ function RoutingTester(props: { className="rounded border border-[color:var(--border)] px-2 py-1 text-sm" /> - + {t('routingTesterSubmit')} + {error && (

{error}

@@ -449,13 +451,13 @@ function CreateAgentForm(props: {
- +
diff --git a/web-ui/app/routines/_components/RoutineActions.tsx b/web-ui/app/routines/_components/RoutineActions.tsx index 52a73e701..1c64a7d58 100644 --- a/web-ui/app/routines/_components/RoutineActions.tsx +++ b/web-ui/app/routines/_components/RoutineActions.tsx @@ -89,15 +89,17 @@ export function RoutineActions({ routine }: Props): React.ReactElement { return (
- + - +
{error ? (
diff --git a/web-ui/app/routines/_components/RoutineTemplateEditor.tsx b/web-ui/app/routines/_components/RoutineTemplateEditor.tsx index d6044617c..41f55040d 100644 --- a/web-ui/app/routines/_components/RoutineTemplateEditor.tsx +++ b/web-ui/app/routines/_components/RoutineTemplateEditor.tsx @@ -250,16 +250,20 @@ export function RoutineTemplateEditor({ routine }: Props): React.ReactElement { ) : null}
- + Speichern +
- + Preview +
{previewError ? (
diff --git a/web-ui/app/store/builder/[id]/_components/AuditTimelinePane.tsx b/web-ui/app/store/builder/[id]/_components/AuditTimelinePane.tsx index 84ec8e270..157459bbc 100644 --- a/web-ui/app/store/builder/[id]/_components/AuditTimelinePane.tsx +++ b/web-ui/app/store/builder/[id]/_components/AuditTimelinePane.tsx @@ -3,6 +3,8 @@ import { useTranslations } from 'next-intl'; import { useCallback, useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { listBuilderAudit, type BuilderAuditEvent } from '../../../../_lib/api'; /** @@ -164,17 +166,17 @@ export function AuditTimelinePane({ draftId }: AuditTimelinePaneProps): React.Re {hasMore && ( - + )} ); diff --git a/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx b/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx index 3a8410a18..a59d42d93 100644 --- a/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx +++ b/web-ui/app/store/builder/[id]/_components/BuilderChatPane.tsx @@ -594,14 +594,14 @@ export function BuilderChatPane({ className="min-h-[44px] flex-1 resize-none rounded-md border border-[color:var(--border)] bg-[color:var(--bg)] px-3 py-2 text-[13px] leading-snug text-[color:var(--fg-strong)] placeholder:text-[color:var(--fg-subtle)] focus:border-[color:var(--accent)] focus:outline-none disabled:opacity-60" /> {inflight ? ( - + ) : ( - +
)} diff --git a/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx b/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx index abc2d6f1d..5222bae47 100644 --- a/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx +++ b/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx @@ -262,16 +262,11 @@ export function InstallDiffModal({ > {t('back')} - +
@@ -564,13 +559,14 @@ function FailureBanner({ {t('bumpAndPublish', { current: currentVersion, next: bumped ?? '' })} ) : null} - + diff --git a/web-ui/app/store/builder/[id]/_components/PagesList.tsx b/web-ui/app/store/builder/[id]/_components/PagesList.tsx index 8e88eb5a8..37829b0fa 100644 --- a/web-ui/app/store/builder/[id]/_components/PagesList.tsx +++ b/web-ui/app/store/builder/[id]/_components/PagesList.tsx @@ -192,14 +192,10 @@ export function PagesList({ - + ); } diff --git a/web-ui/app/store/builder/[id]/_components/PersonaPillar.tsx b/web-ui/app/store/builder/[id]/_components/PersonaPillar.tsx index 4b8bdf08f..e2033bc13 100644 --- a/web-ui/app/store/builder/[id]/_components/PersonaPillar.tsx +++ b/web-ui/app/store/builder/[id]/_components/PersonaPillar.tsx @@ -4,6 +4,8 @@ import { useTranslations } from 'next-intl'; import { useCallback, useMemo, useState, useTransition } from 'react'; import { ChevronDown, ChevronRight, Sparkles } from 'lucide-react'; +import { Button } from '@/app/_components/ui/Button'; + import { ApiError, setPersonaConfig } from '../../../../_lib/api'; import type { QualityConfig } from '../../../../_lib/builderTypes'; import { cn } from '../../../../_lib/cn'; @@ -201,15 +203,15 @@ export function PersonaPillar({ : t('templateBadge', { template: persona.template }) : t('templateBadgeNone')}
- + {galleryOpen && (
- - +
diff --git a/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx b/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx index 7d87e188c..5ef8bc4ee 100644 --- a/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx +++ b/web-ui/app/store/builder/[id]/_components/PersonaTemplateGallery.tsx @@ -101,15 +101,16 @@ export function PersonaTemplateGallery({

{t('title')}

- +
@@ -185,15 +186,16 @@ export function PersonaTemplateGallery({ )}
- + + ) : ( + ) : null}
diff --git a/web-ui/app/store/builder/[id]/_components/PreviewPromptPanel.tsx b/web-ui/app/store/builder/[id]/_components/PreviewPromptPanel.tsx index 2a3df3171..1af129fcb 100644 --- a/web-ui/app/store/builder/[id]/_components/PreviewPromptPanel.tsx +++ b/web-ui/app/store/builder/[id]/_components/PreviewPromptPanel.tsx @@ -3,6 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; + import { fetchBuilderPreviewPrompt, type BuilderPreviewPrompt, @@ -133,26 +135,26 @@ export function PreviewPromptPanel({ })}
)} - - + diff --git a/web-ui/app/store/builder/[id]/_components/QualityPanel.tsx b/web-ui/app/store/builder/[id]/_components/QualityPanel.tsx index 0c2ae7b95..55cd24b28 100644 --- a/web-ui/app/store/builder/[id]/_components/QualityPanel.tsx +++ b/web-ui/app/store/builder/[id]/_components/QualityPanel.tsx @@ -3,6 +3,8 @@ import { useTranslations } from 'next-intl'; import { useCallback, useEffect, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { fetchBuilderQuality, type BuilderQualityResult, @@ -95,17 +97,17 @@ export function QualityPanel({ draftId, refetchKey }: QualityPanelProps): React. {t('tokensPrefix')} {tokenHealthLabel(data.tokenHealth)}
)} - + diff --git a/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx b/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx index 06eec66db..40fc3af34 100644 --- a/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx +++ b/web-ui/app/store/builder/[id]/_components/SecretsDrawer.tsx @@ -208,15 +208,15 @@ export function SecretsDrawer({