diff --git a/web-ui/app/_components/ChoiceCard.tsx b/web-ui/app/_components/ChoiceCard.tsx index 2dcb9400f..43b04e4da 100644 --- a/web-ui/app/_components/ChoiceCard.tsx +++ b/web-ui/app/_components/ChoiceCard.tsx @@ -3,6 +3,7 @@ import type React from 'react'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; import type { PendingUserChoice } from '../_lib/chatSessions'; export type { PendingUserChoice }; @@ -42,23 +43,17 @@ export function ChoiceCard({ )}
{choice.options.map((opt, idx) => ( - + ))}
diff --git a/web-ui/app/_components/ConfirmDialog.tsx b/web-ui/app/_components/ConfirmDialog.tsx index 6dbfe7ea2..5dca904a0 100644 --- a/web-ui/app/_components/ConfirmDialog.tsx +++ b/web-ui/app/_components/ConfirmDialog.tsx @@ -2,6 +2,8 @@ import { useEffect, useRef } from 'react'; +import { Button } from './ui/Button'; + /** * Minimal modal-confirm. No portal, no library — just a fixed-position * overlay with focus-trap basics. We keep it generic (title/body/labels) @@ -56,14 +58,6 @@ export function ConfirmDialog({ if (!open) return null; - // §4.2 button variants: danger is transparent + error edge + error text - // (the error is the signal — no fill, no glow); neutral confirm is the - // Lume primary (accent fill → gradient + glow via the material layer). - const confirmCls = - tone === 'danger' - ? 'border-[color:var(--danger-edge)] bg-transparent text-[color:var(--danger)] hover:bg-[color:var(--danger)]/8' - : 'border-transparent bg-[color:var(--accent)]'; - return (
)}
- - +
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..5becfc6fa 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; @@ -108,14 +109,12 @@ export function InstallButton({ if (!enabled) { return (
- + {blockingReasons?.length ? (
    - + {drawerOpen ? ( - + Aktualisieren +
{updateError ? (

@@ -507,22 +502,20 @@ function InstalledPanel({

- - +
)} @@ -693,24 +686,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..353cfc304 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 @@ -195,62 +196,57 @@ export function RequiresWizard({
{phase.kind === 'review' ? ( <> - - + ) : phase.kind === 'installing' && phase.requiresForm ? ( - + ) : phase.kind === 'success' ? ( - + ) : phase.kind === 'error' ? ( - + ) : null}
diff --git a/web-ui/app/_components/store/SelfExtensionPanel.tsx b/web-ui/app/_components/store/SelfExtensionPanel.tsx index 8f8505fe6..ebdb4d13f 100644 --- a/web-ui/app/_components/store/SelfExtensionPanel.tsx +++ b/web-ui/app/_components/store/SelfExtensionPanel.tsx @@ -6,6 +6,7 @@ import { useTranslations } from 'next-intl'; import { AlertTriangle, Check, ShieldQuestion, Sparkles, X } from 'lucide-react'; import { cn } from '../../_lib/cn'; +import { Button } from '@/app/_components/ui/Button'; import { approveSelfExtensionProposal, denySelfExtensionProposal, @@ -214,15 +215,17 @@ export function SelfExtensionPanel({ agentId }: { agentId: string }): React.Reac {composeError ? (

{composeError}

) : null} - + {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} @@ -342,14 +347,15 @@ export function SelfExtensionPanel({ agentId }: { agentId: string }): React.Reac placeholder={t('denyReasonPlaceholder')} className="flex-1 rounded border border-[color:var(--border)] bg-[color:var(--bg-soft)] px-2 py-1 text-[12px] focus:outline-none focus:ring-1 focus:ring-[color:var(--accent)]" /> - +
) : (
- - +
) ) : null} {p.status === 'approved' ? ( - + {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/_components/ui/Button.tsx b/web-ui/app/_components/ui/Button.tsx new file mode 100644 index 000000000..4d5e44511 --- /dev/null +++ b/web-ui/app/_components/ui/Button.tsx @@ -0,0 +1,132 @@ +'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 `
- + {p.active ? 'Deaktivieren' : 'Aktivieren'} + ))} 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..a4f046d87 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,26 +187,24 @@ export default function BulkPromotePage(): React.ReactElement {
- - + Bulk-Job starten +
diff --git a/web-ui/app/admin/danger-zone/page.tsx b/web-ui/app/admin/danger-zone/page.tsx index d1e419ceb..47d42f46d 100644 --- a/web-ui/app/admin/danger-zone/page.tsx +++ b/web-ui/app/admin/danger-zone/page.tsx @@ -3,6 +3,8 @@ import { useCallback, useMemo, useState } from 'react'; import Link from 'next/link'; +import { Button } from '@/app/_components/ui/Button'; + import { ApiError, previewMemoryPurge, @@ -248,18 +250,18 @@ export default function DangerZonePage(): 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..eb8b005a4 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, @@ -300,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} @@ -372,14 +375,15 @@ export default function DuplicatesListPage(): React.ReactElement { {s === 'all' ? 'alle' : s} ))} - + {error !== null && ( @@ -498,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} @@ -568,14 +574,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..f663cd6e5 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, @@ -228,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} @@ -302,14 +305,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..11c9164cd 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,24 +217,23 @@ export default function AdminRegistriesPage(): React.ReactElement {
- - + Entfernen +
@@ -257,23 +256,21 @@ 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/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/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/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 638bbf3cd..8ae17af67 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 { @@ -268,23 +269,24 @@ export default function MemoryAclSection({ disabled={busy} />
- - +
diff --git a/web-ui/app/login/page.tsx b/web-ui/app/login/page.tsx index 9aabf7d84..07437c466 100644 --- a/web-ui/app/login/page.tsx +++ b/web-ui/app/login/page.tsx @@ -4,6 +4,8 @@ import { Suspense, useEffect, useMemo, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; +import { Button } from '../_components/ui/Button'; + import { ApiError, getAuthProviders, @@ -192,13 +194,9 @@ function LoginPageInner(): React.ReactElement { {submitError && (

{submitError}

)} - + )} diff --git a/web-ui/app/memories/[id]/page.tsx b/web-ui/app/memories/[id]/page.tsx index fa16c1efe..653a8d58f 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, @@ -456,41 +458,43 @@ export default function MemoryDetailPage(): React.ReactElement {
{editing ? ( <> - - + Speichern + ) : ( <> - - + )}
@@ -550,21 +554,23 @@ export default function MemoryDetailPage(): React.ReactElement { {!isEditing && (
- - +
)} @@ -606,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 dba8ee59e..5d28f385a 100644 --- a/web-ui/app/operator/agents/_components/AgentsDashboard.tsx +++ b/web-ui/app/operator/agents/_components/AgentsDashboard.tsx @@ -6,6 +6,7 @@ import { useTranslations } from 'next-intl'; import { ChevronDown, ChevronRight } from 'lucide-react'; +import { Button } from '@/app/_components/ui/Button'; import { ApiError } from '../../../_lib/api'; import { createOperatorAgent, @@ -121,9 +122,9 @@ export function AgentsDashboard({

{t('platformHeading')}

- - +
- + {t('routingTesterSubmit')} + {error && (

{error}

@@ -448,13 +451,13 @@ function CreateAgentForm(props: {
- +
@@ -534,9 +537,9 @@ function AgentCard(props: {
- - - +
@@ -593,9 +596,9 @@ function AgentCard(props: { > {t('actionDrain')} - +
@@ -711,22 +714,22 @@ function BindingsEditor(props: {

{t('bindingsHeading')} - - +

{rows.length === 0 ? ( @@ -775,14 +778,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 4cc8c1229..bc81e350e 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')} - + ) : ( - + )}
@@ -1485,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..1c64a7d58 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, @@ -88,31 +89,36 @@ export function RoutineActions({ routine }: Props): React.ReactElement { return (
- - - +
{error ? (
diff --git a/web-ui/app/routines/_components/RoutineRow.tsx b/web-ui/app/routines/_components/RoutineRow.tsx index 7a3df2075..22e7371a7 100644 --- a/web-ui/app/routines/_components/RoutineRow.tsx +++ b/web-ui/app/routines/_components/RoutineRow.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { useCallback, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; import { listRoutineRuns, type RoutineDto, @@ -295,14 +296,16 @@ function RunHistoryPanel({
Letzte {HISTORY_LIMIT} Runs
- +
{error ? (
diff --git a/web-ui/app/routines/_components/RoutineTemplateEditor.tsx b/web-ui/app/routines/_components/RoutineTemplateEditor.tsx index 1a4eb5b53..41f55040d 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, @@ -249,24 +250,30 @@ export function RoutineTemplateEditor({ routine }: Props): React.ReactElement { ) : null}
- - +
- + Preview +
{previewError ? (
diff --git a/web-ui/app/setup/page.tsx b/web-ui/app/setup/page.tsx index 7ea469f59..f2170734b 100644 --- a/web-ui/app/setup/page.tsx +++ b/web-ui/app/setup/page.tsx @@ -4,6 +4,8 @@ import { Suspense, useEffect, useMemo, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; +import { Button } from '@/app/_components/ui/Button'; + import { ApiError, getAuthProviders, @@ -247,13 +249,9 @@ function SetupPageInner(): React.ReactElement { {submitError && (

{submitError}

)} - + ); 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/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..a59d42d93 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, @@ -593,24 +594,24 @@ 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 ? ( - + ) : ( - + )}
{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..a5a212c8d 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,24 +123,26 @@ export function CulturePresetDropdown({
)}
- - +
)} diff --git a/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx b/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx index f7a9e274c..5222bae47 100644 --- a/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx +++ b/web-ui/app/store/builder/[id]/_components/InstallDiffModal.tsx @@ -2,6 +2,8 @@ import { useState } from 'react'; import { useTranslations } from 'next-intl'; + +import { Button } from '@/app/_components/ui/Button'; import { useRouter } from 'next/navigation'; import { AlertTriangle, CheckCircle2, ShieldCheck, X } from 'lucide-react'; @@ -260,16 +262,11 @@ export function InstallDiffModal({ > {t('back')} - +
@@ -553,21 +550,23 @@ function FailureBanner({
{canBump ? ( - + ) : null} - +
diff --git a/web-ui/app/store/builder/[id]/_components/PagesList.tsx b/web-ui/app/store/builder/[id]/_components/PagesList.tsx index 4d0610296..37829b0fa 100644 --- a/web-ui/app/store/builder/[id]/_components/PagesList.tsx +++ b/web-ui/app/store/builder/[id]/_components/PagesList.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button } from '@/app/_components/ui/Button'; import { closestCenter, DndContext, @@ -138,14 +139,14 @@ export function PagesList({ code: (chunks) => {chunks}, })}

- + ); } @@ -191,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 fbda281ed..5ef8bc4ee 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, @@ -99,15 +101,16 @@ export function PersonaTemplateGallery({

{t('title')}

- +
@@ -183,24 +186,26 @@ export function PersonaTemplateGallery({ )}
- - +
diff --git a/web-ui/app/store/builder/[id]/_components/PreviewChatPane.tsx b/web-ui/app/store/builder/[id]/_components/PreviewChatPane.tsx index 3f1e7ef4d..fde9e19bf 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, @@ -578,24 +579,24 @@ export function PreviewChatPane({ 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 ? ( - + ) : ( - + )} {inflight ? ( @@ -684,14 +685,15 @@ function RuntimeSmokeStrip({ ) : null} {snap.phase === 'failed' && onFixWithBuilder ? ( - + ) : 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 8ecde05cb..40fc3af34 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'; @@ -206,20 +208,20 @@ export function SecretsDrawer({ 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..71b22a0a3 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'; @@ -307,24 +309,26 @@ export function SimpleIntakePane({ className="min-h-[50px] flex-1 resize-none rounded-lg border border-[color:var(--border)] bg-[color:var(--bg)] px-4 py-3 text-[14.5px] leading-snug text-[color:var(--fg-strong)] placeholder:text-[color:var(--fg-subtle)] transition-colors focus:border-[color:var(--accent)] focus:outline-none focus:ring-4 focus:ring-[color:var(--accent)]/10 disabled:opacity-60" /> {inflight ? ( - + ) : ( - + )} @@ -437,14 +441,15 @@ function IntakeHero({ {t('examplesLabel')}

{examples.map((prompt) => ( - + ))} 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/ToolForm.tsx b/web-ui/app/store/builder/[id]/_components/ToolForm.tsx index 6e2313523..6a93af9d6 100644 --- a/web-ui/app/store/builder/[id]/_components/ToolForm.tsx +++ b/web-ui/app/store/builder/[id]/_components/ToolForm.tsx @@ -4,6 +4,8 @@ import { Save } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useEffect, useId, useRef, useState } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import type { JsonPatch, ToolSpec } from '../../../../_lib/builderTypes'; import { cn } from '../../../../_lib/cn'; import { savePersonalTemplate } from '../../../../_lib/toolTemplates'; @@ -255,8 +257,9 @@ function SavePersonalTemplateButton({ const [stamp, setStamp] = useState<'idle' | 'saved'>('idle'); return (
- + {stamp === 'saved' ? ( {t('saved')} diff --git a/web-ui/app/store/builder/[id]/_components/ToolInputSchemaBuilder.tsx b/web-ui/app/store/builder/[id]/_components/ToolInputSchemaBuilder.tsx index b27235459..ae3746158 100644 --- a/web-ui/app/store/builder/[id]/_components/ToolInputSchemaBuilder.tsx +++ b/web-ui/app/store/builder/[id]/_components/ToolInputSchemaBuilder.tsx @@ -4,6 +4,8 @@ import { Plus } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useCallback, useMemo } from 'react'; +import { Button } from '@/app/_components/ui/Button'; + import { detectType, ensureTopLevelObject, @@ -167,14 +169,10 @@ export function ToolInputSchemaBuilder({ ); }) )} - +
); } 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..d70cf77fc 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,22 +150,23 @@ export function ToolList({ {t('emptyState')}

- - +
{templatesOpen ? ( @@ -211,30 +213,33 @@ export function ToolList({
- - - +
{templatesOpen ? (
{onSaveTestCase && outcome && !outcome.isError ? ( - + ) : 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..e4d739bb8 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,20 +98,16 @@ export function VersionsTab({ draftId }: VersionsTabProps): React.ReactElement { return (
- - + +
{state.kind === 'loading' &&

{t('loading')}

} @@ -381,21 +379,17 @@ function CaptureModal({ {t('vendorBundleLabel')}
- - +
); @@ -571,13 +565,9 @@ function RollbackModal({ autoComplete="off" />
- + + ); } @@ -1992,14 +1989,15 @@ function BuildStatusStrip({ ) : null} {onFixWithBuilder ? ( - + ) : null} 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/DraftRow.tsx b/web-ui/app/store/builder/_components/DraftRow.tsx index 358f9dc6b..d8eb1d24b 100644 --- a/web-ui/app/store/builder/_components/DraftRow.tsx +++ b/web-ui/app/store/builder/_components/DraftRow.tsx @@ -17,6 +17,7 @@ import type { DraftSummary, } from '../../../_lib/builderTypes'; import { cn } from '../../../_lib/cn'; +import { Button } from '@/app/_components/ui/Button'; import { ExportDraftButton } from './ExportDraftButton'; interface DraftRowProps { @@ -196,15 +197,16 @@ export function DraftRow({ draft, deleted = false }: DraftRowProps): React.React

{deleted ? ( - + ) : ( <> {draft.status === 'published' ? ( 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, '.'), }, }, });