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 815f303f..611fb75f 100644 --- a/apps/mcp/src/widget/design/tokens.css +++ b/apps/mcp/src/widget/design/tokens.css @@ -275,3 +275,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 (