From 6570a0c9041c8c906dcf355aaee61258a0c67502 Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Wed, 20 May 2026 04:09:15 +0300 Subject: [PATCH] feat(dashboard): implement ResolveKit design system - Design tokens: canvas-2, ink-headline, border-2, radius, shadow, motion tokens - Full console dark theme under [data-theme="console"] with dark glass-panel override - Dark mode toggle in nav with localStorage persistence - Brand assets: 19 SVGs in public/brand/, dark logo variant for nav - Logo/favicon updated to match website (logo-horizontal.svg, favicon.svg) - New components: TraceMark, InlineAlert, SegmentControl, ErrorPage - Updated components: Toast (severity rail), MetricTile, EmptyState, Input - 404/error boundary pages - AuditLog: severity rails, actor-verb-entity format, inline diff, SegmentControl filter - Functions: severity icon tinting, origin pill, severity selector, loading skeletons - KnowledgeBases: stats strip, amber impact dialog, add-source SegmentControl, skeletons - Sessions: ToolCallCard accent tint, KbSearchCard warning rail, mono role chips - Playbooks: grip handles, step connector lines - DB: severity column on registered_functions (migration 022 + Prisma schema) - API: PATCH /functions/:id now accepts severity field - crawl4ai added to deps for kb-service playwright support Co-Authored-By: Claude Sonnet 4.6 --- .../versions/022_add_severity_to_functions.py | 32 ++ dashboard/prisma/schema.prisma | 1 + dashboard/public/brand/app-icon-A.svg | 14 + dashboard/public/brand/app-icon-dark.svg | 14 + dashboard/public/brand/app-icon.svg | 14 + dashboard/public/brand/avatar-accent.svg | 9 + dashboard/public/brand/avatar-brand.svg | 20 + dashboard/public/brand/avatar-console.svg | 19 + dashboard/public/brand/avatar-mono.svg | 9 + dashboard/public/brand/favicon-16.svg | 4 + dashboard/public/brand/favicon.svg | 5 + dashboard/public/brand/glyph-prompt.svg | 5 + dashboard/public/brand/glyph-resolved.svg | 5 + dashboard/public/brand/glyph-trace.svg | 5 + .../public/brand/logo-horizontal-dark.svg | 12 + dashboard/public/brand/logo-horizontal.svg | 12 + dashboard/public/brand/logo-stacked.svg | 12 + .../public/brand/mark-A-resolved-mono.svg | 4 + dashboard/public/brand/mark-A-resolved.svg | 6 + dashboard/public/brand/mark-B-ktrace.svg | 8 + dashboard/public/brand/mark-C-pathcheck.svg | 7 + dashboard/public/brand/social-cover.svg | 38 ++ dashboard/public/brand/wordmark.svg | 7 + dashboard/src/app/error.tsx | 19 + dashboard/src/app/icon.svg | 16 +- dashboard/src/app/not-found.tsx | 18 + .../[appId]/functions/[functionId]/route.ts | 3 + dashboard/src/components/Layout.tsx | 51 +- .../src/components/ResolveKitWordmark.tsx | 20 +- dashboard/src/components/TraceMark.tsx | 94 +++ dashboard/src/components/ui/ConfirmDialog.tsx | 5 +- dashboard/src/components/ui/EmptyState.tsx | 12 +- dashboard/src/components/ui/ErrorPage.tsx | 142 +++++ dashboard/src/components/ui/InlineAlert.tsx | 73 +++ dashboard/src/components/ui/Input.tsx | 6 +- dashboard/src/components/ui/MetricTile.tsx | 15 +- .../src/components/ui/SegmentControl.tsx | 32 ++ dashboard/src/components/ui/Toast.tsx | 84 ++- dashboard/src/components/ui/index.ts | 3 + dashboard/src/dashboard_pages/AuditLog.tsx | 296 ++++++---- dashboard/src/dashboard_pages/Functions.tsx | 541 +++++++++++------- .../src/dashboard_pages/KnowledgeBases.tsx | 277 ++++++--- dashboard/src/dashboard_pages/Login.tsx | 5 +- dashboard/src/dashboard_pages/Playbooks.tsx | 64 ++- dashboard/src/dashboard_pages/Sessions.tsx | 153 ++--- dashboard/src/index.css | 125 ++++ dashboard/src/lib/server/serializers.ts | 2 + pyproject.toml | 1 + uv.lock | 538 ++++++++++++++++- 49 files changed, 2267 insertions(+), 590 deletions(-) create mode 100644 alembic/versions/022_add_severity_to_functions.py create mode 100644 dashboard/public/brand/app-icon-A.svg create mode 100644 dashboard/public/brand/app-icon-dark.svg create mode 100644 dashboard/public/brand/app-icon.svg create mode 100644 dashboard/public/brand/avatar-accent.svg create mode 100644 dashboard/public/brand/avatar-brand.svg create mode 100644 dashboard/public/brand/avatar-console.svg create mode 100644 dashboard/public/brand/avatar-mono.svg create mode 100644 dashboard/public/brand/favicon-16.svg create mode 100644 dashboard/public/brand/favicon.svg create mode 100644 dashboard/public/brand/glyph-prompt.svg create mode 100644 dashboard/public/brand/glyph-resolved.svg create mode 100644 dashboard/public/brand/glyph-trace.svg create mode 100644 dashboard/public/brand/logo-horizontal-dark.svg create mode 100644 dashboard/public/brand/logo-horizontal.svg create mode 100644 dashboard/public/brand/logo-stacked.svg create mode 100644 dashboard/public/brand/mark-A-resolved-mono.svg create mode 100644 dashboard/public/brand/mark-A-resolved.svg create mode 100644 dashboard/public/brand/mark-B-ktrace.svg create mode 100644 dashboard/public/brand/mark-C-pathcheck.svg create mode 100644 dashboard/public/brand/social-cover.svg create mode 100644 dashboard/public/brand/wordmark.svg create mode 100644 dashboard/src/app/error.tsx create mode 100644 dashboard/src/app/not-found.tsx create mode 100644 dashboard/src/components/TraceMark.tsx create mode 100644 dashboard/src/components/ui/ErrorPage.tsx create mode 100644 dashboard/src/components/ui/InlineAlert.tsx create mode 100644 dashboard/src/components/ui/SegmentControl.tsx diff --git a/alembic/versions/022_add_severity_to_functions.py b/alembic/versions/022_add_severity_to_functions.py new file mode 100644 index 0000000..f69e48b --- /dev/null +++ b/alembic/versions/022_add_severity_to_functions.py @@ -0,0 +1,32 @@ +"""Add severity field to registered_functions + +Revision ID: 022 +Revises: 021 +Create Date: 2026-05-20 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +revision: str = "022" +down_revision: Union[str, None] = "021" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "registered_functions", + sa.Column( + "severity", + sa.String(16), + nullable=False, + server_default="read", + ), + ) + + +def downgrade() -> None: + op.drop_column("registered_functions", "severity") diff --git a/dashboard/prisma/schema.prisma b/dashboard/prisma/schema.prisma index 93a3f4f..ab7a68e 100644 --- a/dashboard/prisma/schema.prisma +++ b/dashboard/prisma/schema.prisma @@ -143,6 +143,7 @@ model RegisteredFunction { availability Json @default("{}") source String @default("app_inline") @db.VarChar(32) packName String? @map("pack_name") @db.VarChar(255) + severity String @default("read") @db.VarChar(16) app App @relation(fields: [appId], references: [id], onDelete: Cascade) playbookFunctions PlaybookFunction[] diff --git a/dashboard/public/brand/app-icon-A.svg b/dashboard/public/brand/app-icon-A.svg new file mode 100644 index 0000000..c7540ee --- /dev/null +++ b/dashboard/public/brand/app-icon-A.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/app-icon-dark.svg b/dashboard/public/brand/app-icon-dark.svg new file mode 100644 index 0000000..38c615c --- /dev/null +++ b/dashboard/public/brand/app-icon-dark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/app-icon.svg b/dashboard/public/brand/app-icon.svg new file mode 100644 index 0000000..2b83081 --- /dev/null +++ b/dashboard/public/brand/app-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/avatar-accent.svg b/dashboard/public/brand/avatar-accent.svg new file mode 100644 index 0000000..2a07641 --- /dev/null +++ b/dashboard/public/brand/avatar-accent.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/avatar-brand.svg b/dashboard/public/brand/avatar-brand.svg new file mode 100644 index 0000000..502e26c --- /dev/null +++ b/dashboard/public/brand/avatar-brand.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/avatar-console.svg b/dashboard/public/brand/avatar-console.svg new file mode 100644 index 0000000..1650a71 --- /dev/null +++ b/dashboard/public/brand/avatar-console.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/avatar-mono.svg b/dashboard/public/brand/avatar-mono.svg new file mode 100644 index 0000000..760bf4f --- /dev/null +++ b/dashboard/public/brand/avatar-mono.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/favicon-16.svg b/dashboard/public/brand/favicon-16.svg new file mode 100644 index 0000000..6a8a92a --- /dev/null +++ b/dashboard/public/brand/favicon-16.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dashboard/public/brand/favicon.svg b/dashboard/public/brand/favicon.svg new file mode 100644 index 0000000..6c0a7f9 --- /dev/null +++ b/dashboard/public/brand/favicon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/glyph-prompt.svg b/dashboard/public/brand/glyph-prompt.svg new file mode 100644 index 0000000..cb2f78e --- /dev/null +++ b/dashboard/public/brand/glyph-prompt.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/glyph-resolved.svg b/dashboard/public/brand/glyph-resolved.svg new file mode 100644 index 0000000..b87f636 --- /dev/null +++ b/dashboard/public/brand/glyph-resolved.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/glyph-trace.svg b/dashboard/public/brand/glyph-trace.svg new file mode 100644 index 0000000..d7c0802 --- /dev/null +++ b/dashboard/public/brand/glyph-trace.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/logo-horizontal-dark.svg b/dashboard/public/brand/logo-horizontal-dark.svg new file mode 100644 index 0000000..dbf7806 --- /dev/null +++ b/dashboard/public/brand/logo-horizontal-dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + RESOLVE + kit + + diff --git a/dashboard/public/brand/logo-horizontal.svg b/dashboard/public/brand/logo-horizontal.svg new file mode 100644 index 0000000..0ce8776 --- /dev/null +++ b/dashboard/public/brand/logo-horizontal.svg @@ -0,0 +1,12 @@ + + + + + + + + + RESOLVE + kit + + diff --git a/dashboard/public/brand/logo-stacked.svg b/dashboard/public/brand/logo-stacked.svg new file mode 100644 index 0000000..c00cc08 --- /dev/null +++ b/dashboard/public/brand/logo-stacked.svg @@ -0,0 +1,12 @@ + + + + + + + + + RESOLVE + kit + + \ No newline at end of file diff --git a/dashboard/public/brand/mark-A-resolved-mono.svg b/dashboard/public/brand/mark-A-resolved-mono.svg new file mode 100644 index 0000000..1af20d4 --- /dev/null +++ b/dashboard/public/brand/mark-A-resolved-mono.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dashboard/public/brand/mark-A-resolved.svg b/dashboard/public/brand/mark-A-resolved.svg new file mode 100644 index 0000000..83bbd5f --- /dev/null +++ b/dashboard/public/brand/mark-A-resolved.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/mark-B-ktrace.svg b/dashboard/public/brand/mark-B-ktrace.svg new file mode 100644 index 0000000..5601988 --- /dev/null +++ b/dashboard/public/brand/mark-B-ktrace.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/mark-C-pathcheck.svg b/dashboard/public/brand/mark-C-pathcheck.svg new file mode 100644 index 0000000..17658b3 --- /dev/null +++ b/dashboard/public/brand/mark-C-pathcheck.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/dashboard/public/brand/social-cover.svg b/dashboard/public/brand/social-cover.svg new file mode 100644 index 0000000..f7a30c0 --- /dev/null +++ b/dashboard/public/brand/social-cover.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RESOLVE + kit + + + Support that ships inside the product. + OPEN-SOURCE SDK · IN-APP AI SUPPORT + github.com/resolve-kit + \ No newline at end of file diff --git a/dashboard/public/brand/wordmark.svg b/dashboard/public/brand/wordmark.svg new file mode 100644 index 0000000..0651825 --- /dev/null +++ b/dashboard/public/brand/wordmark.svg @@ -0,0 +1,7 @@ + + + + RESOLVE + kit + + \ No newline at end of file diff --git a/dashboard/src/app/error.tsx b/dashboard/src/app/error.tsx new file mode 100644 index 0000000..b0b6d4d --- /dev/null +++ b/dashboard/src/app/error.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { ErrorPage } from "@/components/ui/ErrorPage"; + +export default function GlobalError({ reset }: { error: Error & { digest?: string }; reset: () => void }) { + return ( + + Try again + + } + /> + ); +} diff --git a/dashboard/src/app/icon.svg b/dashboard/src/app/icon.svg index 71f7c29..6f76694 100644 --- a/dashboard/src/app/icon.svg +++ b/dashboard/src/app/icon.svg @@ -1,13 +1,5 @@ - - - - - - - - - + + + + diff --git a/dashboard/src/app/not-found.tsx b/dashboard/src/app/not-found.tsx new file mode 100644 index 0000000..4a316c4 --- /dev/null +++ b/dashboard/src/app/not-found.tsx @@ -0,0 +1,18 @@ +import Link from "next/link"; +import { ErrorPage } from "@/components/ui/ErrorPage"; + +export default function NotFound() { + return ( + + Back to Apps + + } + /> + ); +} diff --git a/dashboard/src/app/v1/apps/[appId]/functions/[functionId]/route.ts b/dashboard/src/app/v1/apps/[appId]/functions/[functionId]/route.ts index d5b1b57..b8a2aae 100644 --- a/dashboard/src/app/v1/apps/[appId]/functions/[functionId]/route.ts +++ b/dashboard/src/app/v1/apps/[appId]/functions/[functionId]/route.ts @@ -19,6 +19,7 @@ type FunctionUpdatePayload = { availability?: Record; source?: "app_inline" | "playbook_pack"; pack_name?: string | null; + severity?: "read" | "write" | "destructive"; }; export async function PATCH( @@ -56,6 +57,7 @@ export async function PATCH( availability?: Prisma.InputJsonValue; source?: "app_inline" | "playbook_pack"; packName?: string | null; + severity?: "read" | "write" | "destructive"; } = {}; if (typeof body.description === "string") data.description = body.description; @@ -70,6 +72,7 @@ export async function PATCH( } if (body.source === "app_inline" || body.source === "playbook_pack") data.source = body.source; if (Object.prototype.hasOwnProperty.call(body, "pack_name")) data.packName = body.pack_name ?? null; + if (body.severity === "read" || body.severity === "write" || body.severity === "destructive") data.severity = body.severity; const updated = await prisma.registeredFunction.update({ where: { id: functionId }, diff --git a/dashboard/src/components/Layout.tsx b/dashboard/src/components/Layout.tsx index 7bb98ff..72eca85 100644 --- a/dashboard/src/components/Layout.tsx +++ b/dashboard/src/components/Layout.tsx @@ -7,7 +7,6 @@ import { OnboardingProvider } from "../context/OnboardingContext"; import { useKnowledgeBaseStatus } from "../hooks/useKnowledgeBaseStatus"; import AppSidebar from "./AppSidebar"; import OnboardingGuideRail from "./OnboardingGuideRail"; -import ResolveKitWordmark from "./ResolveKitWordmark"; export default function Layout() { const navigate = useNavigate(); @@ -16,6 +15,19 @@ export default function Layout() { const [authReady, setAuthReady] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false); const { status: kbStatus } = useKnowledgeBaseStatus(); + const [isDark, setIsDark] = useState( + () => localStorage.getItem("rk-theme") === "console" + ); + + useEffect(() => { + if (isDark) { + document.documentElement.setAttribute("data-theme", "console"); + localStorage.setItem("rk-theme", "console"); + } else { + document.documentElement.removeAttribute("data-theme"); + localStorage.removeItem("rk-theme"); + } + }, [isDark]); const handleSignOut = useCallback(async () => { await logout(); @@ -59,8 +71,8 @@ export default function Layout() {
diff --git a/dashboard/src/components/ResolveKitWordmark.tsx b/dashboard/src/components/ResolveKitWordmark.tsx index 3cbbf55..56cbd0c 100644 --- a/dashboard/src/components/ResolveKitWordmark.tsx +++ b/dashboard/src/components/ResolveKitWordmark.tsx @@ -7,16 +7,26 @@ interface ResolveKitWordmarkProps { export default function ResolveKitWordmark({ className = "", resolveClassName = "text-strong", - kitClassName = "text-strong", + kitClassName = "text-dim", }: ResolveKitWordmarkProps) { return ( - RESOLVE - kit + + RESOLVE + + + kit + ); } diff --git a/dashboard/src/components/TraceMark.tsx b/dashboard/src/components/TraceMark.tsx new file mode 100644 index 0000000..cfca490 --- /dev/null +++ b/dashboard/src/components/TraceMark.tsx @@ -0,0 +1,94 @@ +interface TraceMarkProps { + size?: number; + className?: string; + animated?: boolean; + variant?: "prompt" | "trace" | "resolved"; +} + +export default function TraceMark({ + size = 24, + className = "", + animated = false, + variant = "prompt", +}: TraceMarkProps) { + if (variant === "trace") { + return ( + + + + + + ); + } + + if (variant === "resolved") { + return ( + + + + + + ); + } + + // Default: prompt-trace (canonical mark) + return ( + + {/* Vertical cursor/prompt bar */} + + {/* Top trace line */} + + {/* Bottom trace line (dimmer) */} + + + ); +} diff --git a/dashboard/src/components/ui/ConfirmDialog.tsx b/dashboard/src/components/ui/ConfirmDialog.tsx index 5893657..5d4383d 100644 --- a/dashboard/src/components/ui/ConfirmDialog.tsx +++ b/dashboard/src/components/ui/ConfirmDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { type ReactNode, useCallback, useEffect, useState } from "react"; import { Button } from "./Button"; type ConfirmVariant = "danger" | "primary" | "outline"; @@ -7,6 +7,7 @@ interface ConfirmDialogProps { open: boolean; title: string; description?: string; + warningBox?: ReactNode; confirmLabel?: string; confirmVariant?: ConfirmVariant; confirmTextRequired?: string; @@ -20,6 +21,7 @@ export function ConfirmDialog({ open, title, description, + warningBox, confirmLabel = "Confirm", confirmVariant = "danger", confirmTextRequired, @@ -76,6 +78,7 @@ export function ConfirmDialog({

Confirm action

{title}

{description &&

{description}

} + {warningBox &&
{warningBox}
} {requiresTypedConfirm && (