diff --git a/apps/mcp/src/widget/App.tsx b/apps/mcp/src/widget/App.tsx
index 52d16d69..e5ec8192 100644
--- a/apps/mcp/src/widget/App.tsx
+++ b/apps/mcp/src/widget/App.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from "react"
+import { type ReactNode, useEffect } from "react"
import type { ViewMessage } from "../shared/types"
import { useApplyHostTheme } from "./hooks/useApplyHostTheme"
import { useLog } from "./hooks/useLog"
@@ -25,15 +25,56 @@ export function App() {
}
}, [state, log])
- if (state.kind === "loading") return
- if (state.kind === "error") return
+ if (state.kind === "loading") {
+ return (
+
+
+
+ )
+ }
+ if (state.kind === "error") {
+ return (
+
+
+
+ )
+ }
if (state.kind === "raw") {
return (
-
+
+
+
)
}
- return renderView(state.message, setView, setError)
+ return {renderView(state.message, setView, setError)}
+}
+
+function WidgetShell({ children }: { children: ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ supermemory
+ MCP
+
+
+
{children}
+
+ )
}
function renderView(
diff --git a/apps/mcp/src/widget/design/globals.css b/apps/mcp/src/widget/design/globals.css
index f159f5e5..d7c12cfc 100644
--- a/apps/mcp/src/widget/design/globals.css
+++ b/apps/mcp/src/widget/design/globals.css
@@ -85,9 +85,17 @@
}
body {
- background: var(--bg-primary);
+ background:
+ radial-gradient(
+ ellipse at 50% 112%,
+ rgba(75, 160, 250, 0.34) 0%,
+ rgba(20, 16, 255, 0.22) 28%,
+ rgba(11, 17, 25, 0) 62%
+ ),
+ linear-gradient(180deg, #0b1119 0%, #070b11 100%);
color: var(--text-primary);
font-family: var(--font-sans);
+ letter-spacing: -0.01em;
line-height: var(--leading-normal);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -107,14 +115,114 @@
position: relative;
}
+.mcp-widget-shell {
+ position: relative;
+ isolation: isolate;
+ min-height: 280px;
+ overflow: hidden;
+ background:
+ linear-gradient(rgba(75, 160, 250, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(75, 160, 250, 0.035) 1px, transparent 1px),
+ radial-gradient(
+ circle at 50% 108%,
+ rgba(75, 160, 250, 0.22),
+ transparent 42%
+ ),
+ linear-gradient(180deg, #0b1119 0%, #070b11 100%);
+ background-size:
+ 36px 36px,
+ 36px 36px,
+ 100% 100%,
+ 100% 100%;
+ color: var(--text-primary);
+}
+
+.mcp-widget-glow {
+ position: absolute;
+ right: -18%;
+ bottom: -42%;
+ left: -18%;
+ z-index: -1;
+ height: 70%;
+ pointer-events: none;
+ background: radial-gradient(
+ ellipse at 50% 48%,
+ rgba(255, 255, 255, 0.34) 0%,
+ rgba(71, 168, 253, 0.42) 11%,
+ rgba(0, 153, 255, 0.32) 28%,
+ rgba(20, 16, 255, 0.28) 48%,
+ transparent 74%
+ );
+ filter: blur(18px);
+ opacity: 0.9;
+}
+
+.mcp-widget-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ margin: var(--space-4) var(--page-header-px) 0;
+ padding: 7px 10px 7px 8px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ background: rgba(13, 18, 26, 0.78);
+ backdrop-filter: blur(18px);
+ box-shadow: var(--shadow-sm);
+}
+
+.mcp-widget-brand-mark {
+ display: grid;
+ width: 26px;
+ height: 26px;
+ place-items: center;
+ color: var(--accent);
+ border-radius: var(--radius-md);
+ background: #080b0f;
+ box-shadow:
+ inset 1.5px 1.5px 4.5px rgba(0, 0, 0, 0.7),
+ inset 0 1px 1px rgba(255, 255, 255, 0.03);
+}
+
+.mcp-widget-brand-mark svg {
+ width: 17px;
+ height: 14px;
+}
+
+.mcp-widget-brand-copy {
+ display: flex;
+ min-width: 0;
+ flex-direction: column;
+ gap: 1px;
+}
+
+.mcp-widget-brand-name {
+ color: var(--text-primary);
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.mcp-widget-brand-mode {
+ color: var(--text-muted);
+ font-size: 10px;
+ font-weight: 500;
+ line-height: 1.15;
+}
+
+.mcp-widget-content {
+ position: relative;
+ z-index: 1;
+}
+
/* Spinner — kept as global because primitives can't easily express a CSS animation */
.spinner {
width: 24px;
height: 24px;
- border: 2px solid var(--border);
+ border: 2px solid rgba(255, 255, 255, 0.12);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
+ box-shadow: 0 0 20px rgba(75, 160, 250, 0.42);
}
@keyframes spin {
@@ -141,7 +249,20 @@
* reads as a re-layout). The floor keeps the size continuous. */
min-height: 280px;
position: relative;
- background: var(--bg-primary);
+ background:
+ linear-gradient(rgba(75, 160, 250, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(75, 160, 250, 0.035) 1px, transparent 1px),
+ radial-gradient(
+ circle at 50% 108%,
+ rgba(75, 160, 250, 0.22),
+ transparent 42%
+ ),
+ var(--bg-primary);
+ background-size:
+ 36px 36px,
+ 36px 36px,
+ 100% 100%,
+ 100% 100%;
}
.graph-view.fullscreen {
diff --git a/apps/mcp/src/widget/design/tokens.css b/apps/mcp/src/widget/design/tokens.css
index 750cb117..d505eb04 100644
--- a/apps/mcp/src/widget/design/tokens.css
+++ b/apps/mcp/src/widget/design/tokens.css
@@ -277,3 +277,103 @@
--graph-control-border: #1a2333;
}
}
+
+/* Nova widget theme
+ *
+ * PR-1120's widget screens run inside agent cells, so the light/dark host
+ * theme can vary independently from the Supermemory product theme. Force the
+ * MCP app surfaces onto Nova's dark product palette so all widget tools match
+ * apps/web: DM Sans, dark cards, soft borders, and the #4ba0fa accent.
+ */
+:root,
+[data-theme="light"],
+[data-theme="dark"] {
+ color-scheme: dark;
+
+ --accent-start: #369bfd;
+ --accent-mid: #4ba0fa;
+ --accent-end: #36fdfd;
+ --accent-gradient: linear-gradient(
+ 94deg,
+ var(--accent-start),
+ var(--accent-end)
+ );
+
+ --accent: #4ba0fa;
+ --accent-foreground: #ffffff;
+ --accent-hover: #369bfd;
+ --accent-muted: rgba(75, 160, 250, 0.12);
+
+ --bg-primary: #0b1119;
+ --bg-secondary: #101822;
+ --bg-muted: #131b28;
+ --bg-elevated: #1b1f24;
+ --bg-overlay: rgba(13, 18, 26, 0.78);
+
+ --border: rgba(255, 255, 255, 0.12);
+ --border-muted: rgba(255, 255, 255, 0.07);
+ --border-accent: rgba(34, 97, 202, 0.45);
+
+ --text-primary: #fafafa;
+ --text-secondary: #d0dae7;
+ --text-muted: #a0aec4;
+ --text-inverse: #0b1119;
+
+ --success: #4ade80;
+ --success-muted: rgba(74, 222, 128, 0.14);
+ --error: #fb7185;
+ --error-muted: rgba(251, 113, 133, 0.14);
+ --warning: #fbbf24;
+ --warning-muted: rgba(251, 191, 36, 0.14);
+ --info: #60a5fa;
+ --info-muted: rgba(96, 165, 250, 0.14);
+ --danger: #ef4444;
+
+ --font-sans:
+ "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
+ --font-display:
+ "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
+ --font-brand:
+ "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
+
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+
+ --shadow-sm:
+ 0 2px 10px rgba(0, 0, 0, 0.18),
+ inset 0 1px 1px rgba(255, 255, 255, 0.06);
+ --shadow-md:
+ 0 10px 30px rgba(0, 0, 0, 0.28),
+ inset 0 1px 1px rgba(255, 255, 255, 0.08);
+ --shadow-lg:
+ 0 20px 60px rgba(0, 0, 0, 0.42),
+ inset 0 1px 1px rgba(255, 255, 255, 0.08);
+
+ --graph-bg: #0b1119;
+ --graph-doc-fill: #1b1f24;
+ --graph-doc-stroke: rgba(255, 255, 255, 0.12);
+ --graph-doc-inner: #0d121a;
+ --graph-mem-fill: #0d2034;
+ --graph-mem-fill-hover: #112840;
+ --graph-mem-stroke: #4ba0fa;
+ --graph-accent: #4ba0fa;
+ --graph-text-primary: #fafafa;
+ --graph-text-secondary: #d0dae7;
+ --graph-text-muted: #a0aec4;
+ --graph-edge-derives: #fbbf24;
+ --graph-edge-updates: #4ba0fa;
+ --graph-edge-extends: #36fdfd;
+ --graph-mem-border-forgotten: #ef4444;
+ --graph-mem-border-expiring: #f59e0b;
+ --graph-mem-border-recent: #10b981;
+ --graph-glow: #4ba0fa;
+ --graph-icon: #4ba0fa;
+ --graph-popover-bg: rgba(27, 31, 36, 0.94);
+ --graph-popover-border: rgba(255, 255, 255, 0.12);
+ --graph-popover-text-primary: #fafafa;
+ --graph-popover-text-secondary: #d0dae7;
+ --graph-popover-text-muted: #a0aec4;
+ --graph-control-bg: rgba(13, 18, 26, 0.78);
+ --graph-control-border: rgba(255, 255, 255, 0.12);
+}
diff --git a/apps/mcp/src/widget/design/ui/Button.tsx b/apps/mcp/src/widget/design/ui/Button.tsx
index c641215c..07819d68 100644
--- a/apps/mcp/src/widget/design/ui/Button.tsx
+++ b/apps/mcp/src/widget/design/ui/Button.tsx
@@ -9,9 +9,9 @@ import { cn } from "../lib/cn"
const buttonVariants = cva(
[
"inline-flex items-center justify-center gap-2",
- "uppercase tracking-[0.075em]",
+ "font-semibold",
"rounded-[var(--radius-md)]",
- "transition-colors cursor-pointer",
+ "transition-all cursor-pointer",
"disabled:pointer-events-none disabled:opacity-50",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)]/20 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-primary)]",
"[&_svg:not([class*='size-'])]:size-4 shrink-0",
@@ -21,12 +21,13 @@ const buttonVariants = cva(
variant: {
primary: [
"bg-[var(--accent)] text-[var(--accent-foreground)]",
- "hover:bg-[var(--accent)]/90",
+ "shadow-[0_10px_28px_rgba(75,160,250,0.24)]",
+ "hover:bg-[var(--accent-hover)] hover:shadow-[0_12px_32px_rgba(75,160,250,0.32)]",
].join(" "),
secondary: [
- "bg-transparent text-[var(--text-primary)]",
+ "bg-[var(--bg-overlay)] text-[var(--text-primary)]",
"border border-[var(--border)]",
- "hover:bg-[var(--bg-muted)]",
+ "hover:bg-[var(--bg-muted)] hover:border-[var(--border-accent)]",
].join(" "),
ghost: [
"text-[var(--text-primary)]",
@@ -38,7 +39,7 @@ const buttonVariants = cva(
].join(" "),
},
size: {
- sm: "h-[var(--height-sm)] px-[var(--space-4)] text-[length:var(--text-xs)]",
+ sm: "h-[var(--height-md)] px-[var(--space-4)] text-[length:var(--text-sm)]",
icon: "size-[var(--height-sm)] p-0",
},
},
diff --git a/apps/mcp/src/widget/design/ui/Card.tsx b/apps/mcp/src/widget/design/ui/Card.tsx
index 06a15a66..f1159f9c 100644
--- a/apps/mcp/src/widget/design/ui/Card.tsx
+++ b/apps/mcp/src/widget/design/ui/Card.tsx
@@ -7,14 +7,18 @@ import {
import { cn } from "../lib/cn"
const cardStyles = cva(
- "text-left bg-bg-elevated border border-border rounded-lg p-4 transition-colors duration-150",
+ [
+ "text-left bg-bg-elevated/90 border border-border rounded-lg p-4",
+ "shadow-md backdrop-blur transition-colors duration-150",
+ ].join(" "),
{
variants: {
variant: {
default: "",
interactive:
"cursor-pointer hover:bg-bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary",
- active: "cursor-pointer border-accent bg-accent-muted",
+ active:
+ "cursor-pointer border-border-accent bg-accent-muted shadow-[0_0_30px_rgba(75,160,250,0.16)]",
},
},
defaultVariants: { variant: "default" },
diff --git a/apps/mcp/src/widget/design/ui/FileUpload.tsx b/apps/mcp/src/widget/design/ui/FileUpload.tsx
index 753eda68..33e87d51 100644
--- a/apps/mcp/src/widget/design/ui/FileUpload.tsx
+++ b/apps/mcp/src/widget/design/ui/FileUpload.tsx
@@ -58,10 +58,10 @@ export function FileUpload({
"flex min-h-[180px] flex-col items-center justify-center gap-(--space-3)",
"py-(--space-12) px-(--space-6)",
"border-2 border-dashed rounded-(--radius-lg)",
- "cursor-pointer transition-colors",
+ "bg-[var(--bg-overlay)] cursor-pointer shadow-md backdrop-blur transition-colors",
dragOver
- ? "border-accent bg-accent-muted/30"
- : "border-border hover:bg-bg-muted/40",
+ ? "border-accent bg-accent-muted"
+ : "border-border hover:bg-bg-muted/80",
disabled && "pointer-events-none opacity-50",
className,
)}
diff --git a/apps/mcp/src/widget/design/ui/Input.tsx b/apps/mcp/src/widget/design/ui/Input.tsx
index 3687419a..17c00fac 100644
--- a/apps/mcp/src/widget/design/ui/Input.tsx
+++ b/apps/mcp/src/widget/design/ui/Input.tsx
@@ -7,13 +7,13 @@ import { cn } from "../lib/cn"
const inputVariants = cva(
[
"flex w-full",
- "bg-transparent text-[var(--text-primary)]",
+ "bg-[var(--bg-overlay)] text-[var(--text-primary)]",
"border border-[var(--border)]",
"rounded-[var(--radius-lg)]",
"placeholder:text-[var(--text-muted)]",
"transition-colors",
- "hover:bg-[var(--text-muted)]/10",
- "focus-visible:outline-none focus-visible:shadow-[0_0_0_1px_var(--border)]",
+ "hover:bg-[var(--bg-muted)]",
+ "focus-visible:outline-none focus-visible:border-[var(--border-accent)] focus-visible:shadow-[0_0_0_1px_var(--border-accent)]",
"disabled:cursor-not-allowed disabled:opacity-50",
"aria-invalid:border-[var(--error)] aria-invalid:ring-[var(--error)]",
"file:border-0 file:bg-transparent file:text-[length:var(--text-sm)] file:font-medium",
diff --git a/apps/mcp/src/widget/design/ui/Popover.tsx b/apps/mcp/src/widget/design/ui/Popover.tsx
index 5ec0ec60..53d33d83 100644
--- a/apps/mcp/src/widget/design/ui/Popover.tsx
+++ b/apps/mcp/src/widget/design/ui/Popover.tsx
@@ -16,9 +16,9 @@ const PopoverContent = forwardRef<
align={align}
className={cn(
"z-50 w-72 p-(--space-4)",
- "bg-bg-elevated border border-border",
- "rounded-(--radius-md)",
- "shadow-md",
+ "bg-bg-elevated/95 border border-border",
+ "rounded-(--radius-lg)",
+ "shadow-lg backdrop-blur",
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
className,
diff --git a/apps/mcp/src/widget/design/ui/TextArea.tsx b/apps/mcp/src/widget/design/ui/TextArea.tsx
index 5cdddc2e..b3e356d7 100644
--- a/apps/mcp/src/widget/design/ui/TextArea.tsx
+++ b/apps/mcp/src/widget/design/ui/TextArea.tsx
@@ -6,15 +6,15 @@ import { cn } from "../lib/cn"
// controls min-height via className.
const textAreaClass = [
"flex w-full",
- "bg-transparent text-[var(--text-primary)]",
+ "bg-[var(--bg-overlay)] text-[var(--text-primary)]",
"border border-[var(--border)]",
"rounded-[var(--radius-lg)]",
"px-[var(--space-3)] py-[var(--space-2)]",
"text-[length:var(--text-sm)] leading-normal font-sans",
"placeholder:text-[var(--text-muted)]",
"transition-colors resize-y",
- "hover:bg-[var(--text-muted)]/10",
- "focus-visible:outline-none focus-visible:shadow-[0_0_0_1px_var(--border)]",
+ "hover:bg-[var(--bg-muted)]",
+ "focus-visible:outline-none focus-visible:border-[var(--border-accent)] focus-visible:shadow-[0_0_0_1px_var(--border-accent)]",
"disabled:cursor-not-allowed disabled:opacity-50",
"aria-invalid:border-[var(--error)] aria-invalid:ring-[var(--error)]",
].join(" ")
diff --git a/apps/mcp/src/widget/index.html b/apps/mcp/src/widget/index.html
index 6266b3b9..760bda1c 100644
--- a/apps/mcp/src/widget/index.html
+++ b/apps/mcp/src/widget/index.html
@@ -6,7 +6,7 @@
Supermemory MCP
-
+
diff --git a/apps/mcp/src/widget/studio.html b/apps/mcp/src/widget/studio.html
index 159c8ff7..4120f24b 100644
--- a/apps/mcp/src/widget/studio.html
+++ b/apps/mcp/src/widget/studio.html
@@ -6,7 +6,7 @@
Supermemory MCP · Studio
-
+
diff --git a/apps/mcp/src/widget/views/Confirmation.tsx b/apps/mcp/src/widget/views/Confirmation.tsx
index 354fceba..919d34df 100644
--- a/apps/mcp/src/widget/views/Confirmation.tsx
+++ b/apps/mcp/src/widget/views/Confirmation.tsx
@@ -9,7 +9,7 @@ export function Confirmation({ containerTag }: Props) {
return (
diff --git a/apps/mcp/src/widget/views/Error.tsx b/apps/mcp/src/widget/views/Error.tsx
index 9f2cc82e..b77637e4 100644
--- a/apps/mcp/src/widget/views/Error.tsx
+++ b/apps/mcp/src/widget/views/Error.tsx
@@ -9,7 +9,7 @@ export function ErrorView({ message }: Props) {
return (
diff --git a/apps/mcp/src/widget/views/Graph.tsx b/apps/mcp/src/widget/views/Graph.tsx
index 21ab8537..c48122af 100644
--- a/apps/mcp/src/widget/views/Graph.tsx
+++ b/apps/mcp/src/widget/views/Graph.tsx
@@ -185,8 +185,13 @@ export function Graph({ documents, totalCount }: Props) {
}, [mode, toggleFullscreen])
return (
-
-
+
+
{/* No remount on toggle. The CSS just resizes the container; the
package's ResizeObserver re-lays-out and its node cache keeps
positions stable, so expand/minimize is instant with no reload.
diff --git a/apps/mcp/src/widget/views/Picker.tsx b/apps/mcp/src/widget/views/Picker.tsx
index 45c69605..30c9cf97 100644
--- a/apps/mcp/src/widget/views/Picker.tsx
+++ b/apps/mcp/src/widget/views/Picker.tsx
@@ -82,7 +82,7 @@ export function Picker({
{/* Bounded, scrollable list — keeps a stable height so filtering or a
long workspace list doesn't resize (jump) the whole widget. */}
-
+
{filtered.length === 0 ? (
No workspaces match “{query}”.
diff --git a/apps/mcp/src/widget/views/Success.tsx b/apps/mcp/src/widget/views/Success.tsx
index 9ab17683..8db23a6c 100644
--- a/apps/mcp/src/widget/views/Success.tsx
+++ b/apps/mcp/src/widget/views/Success.tsx
@@ -21,7 +21,7 @@ export function Success(props: Props) {
return (