diff --git a/apps/mcp/src/widget/App.tsx b/apps/mcp/src/widget/App.tsx index 0d9f552a..158fa364 100644 --- a/apps/mcp/src/widget/App.tsx +++ b/apps/mcp/src/widget/App.tsx @@ -68,7 +68,6 @@ export function WidgetShell({ return (
-
- {isWrite ? "Read/write" : "Read only"} + + {isWrite ? "Read / write" : "Read only"} ) } diff --git a/apps/mcp/src/widget/components/Spinner.tsx b/apps/mcp/src/widget/components/Spinner.tsx index 39b8dc2d..0d3de414 100644 --- a/apps/mcp/src/widget/components/Spinner.tsx +++ b/apps/mcp/src/widget/components/Spinner.tsx @@ -1,3 +1,5 @@ +import { Loader2 } from "../lib/icons" + export function Spinner() { - return
+ return } diff --git a/apps/mcp/src/widget/components/WorkspaceCard.tsx b/apps/mcp/src/widget/components/WorkspaceCard.tsx index 8af1697a..18b96f9d 100644 --- a/apps/mcp/src/widget/components/WorkspaceCard.tsx +++ b/apps/mcp/src/widget/components/WorkspaceCard.tsx @@ -1,6 +1,5 @@ import type { ContainerTag, ContainerTagAccess } from "../../shared/types" -import { Card } from "../design/ui" -import { Check } from "../lib/icons" +import { cn } from "../design/lib/cn" import { formatTagLabel } from "../lib/formatTag" import { PermissionBadge } from "./PermissionBadge" @@ -18,51 +17,38 @@ export function WorkspaceCard({ onClick, }: Props) { const name = containerTag.name || formatTagLabel(containerTag.containerTag) + const docs = containerTag.documentCount + const mems = containerTag.memoryCount + const meta = + docs > 0 || mems > 0 + ? `${docs} doc${docs === 1 ? "" : "s"} · ${mems} ${mems === 1 ? "memory" : "memories"}` + : "No memories yet" + return ( - onClick(containerTag.containerTag)} - variant={active ? "active" : "interactive"} + type="button" > -
-
-
- - {name} - - {active ? ( - - ) : null} -
- {(containerTag.documentCount > 0 || containerTag.memoryCount > 0) && ( -
- - {containerTag.documentCount} doc - {containerTag.documentCount === 1 ? "" : "s"} - - - · - - - {containerTag.memoryCount}{" "} - {containerTag.memoryCount === 1 ? "memory" : "memories"} - -
- )} -
- {access ? ( -
- -
- ) : null} -
-
+ + + + {name} + + + {meta} + + + + {access ? : null} + + + ) } diff --git a/apps/mcp/src/widget/components/WorkspaceChip.tsx b/apps/mcp/src/widget/components/WorkspaceChip.tsx new file mode 100644 index 00000000..e397f75d --- /dev/null +++ b/apps/mcp/src/widget/components/WorkspaceChip.tsx @@ -0,0 +1,12 @@ +import { formatTagLabel } from "../lib/formatTag" + +// A calm workspace pill: a small accent dot + the workspace name on a neutral +// surface. Reads like a "space" tag rather than a blue-on-blue system badge. +export function WorkspaceChip({ containerTag }: { containerTag: string }) { + return ( + + + {formatTagLabel(containerTag)} + + ) +} diff --git a/apps/mcp/src/widget/design/globals.css b/apps/mcp/src/widget/design/globals.css index fff43ab5..e63673d5 100644 --- a/apps/mcp/src/widget/design/globals.css +++ b/apps/mcp/src/widget/design/globals.css @@ -3,11 +3,9 @@ @custom-variant dark (&:is([data-theme="dark"] *)); -/* Map design tokens to Tailwind utilities. Mirrors console-v2's mapping - * so utility class names like `bg-bg-primary`, `text-text-primary`, - * `border-border-accent`, `font-brand`, `text-xs`, `shadow-md` etc. - * resolve from one source of truth. - */ +/* Map design tokens to Tailwind utilities so class names like `bg-bg-primary`, + * `text-text-primary`, `border-border-accent`, `text-xs`, `shadow-md` resolve + * from one source of truth. */ @theme inline { --color-bg-primary: var(--bg-primary); --color-bg-secondary: var(--bg-secondary); @@ -20,6 +18,7 @@ --color-border: var(--border); --color-border-muted: var(--border-muted); + --color-border-control: var(--border-control); --color-border-accent: var(--border-accent); --color-text-primary: var(--text-primary); @@ -63,6 +62,7 @@ --radius-sm: var(--radius-sm); --radius-md: var(--radius-md); --radius-lg: var(--radius-lg); + --radius-xl: var(--radius-xl); --radius-full: var(--radius-full); --shadow-sm: var(--shadow-sm); @@ -70,15 +70,13 @@ --shadow-lg: var(--shadow-lg); } -/* Excalidraw pattern: NO heights on root containers. The MCP host owns - * the iframe height; ext-apps' App.autoResize observes documentElement - * and reports content size back so the host right-sizes the iframe. - * Setting any height (vh, %, etc.) here breaks that feedback loop. +/* Excalidraw pattern: NO heights on root containers. The MCP host owns the + * iframe height; ext-apps' App.autoResize observes documentElement and reports + * content size back so the host right-sizes the iframe. Any height here (vh, %, + * etc.) breaks that feedback loop. * - * The reset lives INSIDE @layer base so Tailwind's @layer utilities (which - * come after base) win — an unlayered universal padding:0 reset would override - * every padding utility in the widget. box-sizing/margin are belt-and-braces - * on top of Tailwind preflight. */ + * The reset lives INSIDE @layer base so Tailwind's @layer utilities win — an + * unlayered universal padding:0 would override every padding utility. */ @layer base { * { margin: 0; @@ -111,10 +109,13 @@ position: relative; } +/* ── Shell ────────────────────────────────────────────────────────────────── */ .mcp-widget-shell { position: relative; isolation: isolate; - min-height: 280px; + display: flex; + flex-direction: column; + min-height: 220px; overflow: hidden; background: var(--widget-shell-bg); color: var(--text-primary); @@ -125,510 +126,153 @@ background: var(--widget-shell-graph-bg); } -.mcp-widget-shell-graph::before, -.mcp-widget-shell-graph::after { - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - content: ""; -} - -.mcp-widget-shell-graph::before { - background-image: radial-gradient( - circle at center, - rgba(105, 167, 240, 0.25) 1px, - transparent 1px - ); - background-size: 32px 32px; - mask-image: radial-gradient(ellipse at center, black 60%, transparent 100%); -} - -.mcp-widget-shell-graph::after { - background: - radial-gradient( - ellipse at 50% 112%, - rgba(75, 160, 250, 0.42) 0%, - rgba(20, 16, 255, 0.3) 34%, - transparent 66% - ), - linear-gradient(180deg, rgba(5, 8, 13, 0) 0%, rgba(5, 8, 13, 0.2) 100%); -} - -.mcp-widget-glow { - position: absolute; - right: -16%; - bottom: -44%; - left: -16%; - z-index: -1; - height: 68%; - pointer-events: none; - background: radial-gradient( - ellipse at 50% 42%, - var(--widget-glow-inner) 0%, - var(--widget-glow-mid) 22%, - var(--widget-glow-outer) 46%, - transparent 74% - ); - filter: blur(18px); - opacity: 0.82; -} - -/* Studio-only visual variants. Production widgets do not set this attribute. */ -[data-widget-version="v1"] .mcp-widget-shell { - --brand-accent: var(--accent); - --brand-opacity: 0.86; - --widget-glow-inner: rgba(255, 255, 255, 0.22); - --widget-glow-mid: rgba(71, 168, 253, 0.3); - --widget-glow-outer: rgba(20, 16, 255, 0.18); - background: - linear-gradient(rgba(75, 160, 250, 0.045) 1px, transparent 1px), - linear-gradient(90deg, rgba(75, 160, 250, 0.045) 1px, transparent 1px), - radial-gradient( - circle at 50% 108%, - rgba(75, 160, 250, 0.16), - transparent 42% - ), - var(--widget-shell-bg); - background-size: - 36px 36px, - 36px 36px, - 100% 100%, - 100% 100%; -} - -[data-widget-version="v1"] .super-loader, -[data-widget-version="v1"] .spinner { - color: var(--accent); -} - -[data-widget-version="v1"] .spinner { - border-top-color: var(--accent); - box-shadow: 0 0 20px rgba(75, 160, 250, 0.42); -} -[data-widget-version="v2"] .mcp-widget-shell { - --brand-accent: var(--accent); - --brand-opacity: 1; - --widget-shell-bg: color-mix( - in srgb, - var(--widget-shell-bg) 86%, - var(--bg-muted) - ); - --card-bg: color-mix(in srgb, var(--card-bg) 88%, var(--bg-muted)); - --panel-bg: color-mix(in srgb, var(--panel-bg) 88%, var(--bg-muted)); - --widget-glow-mid: color-mix( - in srgb, - var(--widget-glow-mid) 70%, - transparent - ); - --widget-glow-outer: color-mix( - in srgb, - var(--widget-glow-outer) 70%, - transparent - ); -} - -[data-widget-version="v2"] .mcp-widget-shell-graph::before { - background-image: - linear-gradient(rgba(105, 167, 240, 0.12) 1px, transparent 1px), - linear-gradient(90deg, rgba(105, 167, 240, 0.12) 1px, transparent 1px); - background-size: 32px 32px; - mask-image: radial-gradient(ellipse at center, black 60%, transparent 100%); -} - -[data-theme="light"] [data-widget-version="v2"] .mcp-widget-shell-graph::before, -:root:not([data-theme="dark"]) - [data-widget-version="v2"] - .mcp-widget-shell-graph::before { - background-image: - linear-gradient(rgba(75, 160, 250, 0.045) 1px, transparent 1px), - linear-gradient(90deg, rgba(75, 160, 250, 0.045) 1px, transparent 1px); -} - -[data-theme="light"] [data-widget-version="v2"] .mcp-widget-shell-graph::after, -:root:not([data-theme="dark"]) - [data-widget-version="v2"] - .mcp-widget-shell-graph::after { - content: none; -} - -[data-theme="light"] [data-widget-version="v2"] .mcp-soft-button, -:root:not([data-theme="dark"]) [data-widget-version="v2"] .mcp-soft-button { - background: var(--button-secondary-bg); - color: var(--button-secondary-text); - border: 1px solid var(--button-secondary-border); - box-shadow: var(--shadow-sm); -} - -[data-theme="light"] [data-widget-version="v2"] .mcp-soft-button:hover, -:root:not([data-theme="dark"]) - [data-widget-version="v2"] - .mcp-soft-button:hover { - background: var(--button-secondary-hover); -} - -/* Card/panel glow — hidden by default, activated by v3/v4. */ -.card-glow, -.panel-glow { - display: none; -} - -.mcp-panel { - position: relative; - overflow: hidden; -} - -.mcp-panel > :not(.panel-glow) { - position: relative; - z-index: 1; -} - -/* Workspace picker cards — grid layout (default). */ -.workspace-card { - min-height: 104px; -} - -.workspace-card-inner { - position: relative; - display: flex; - flex-direction: column; - height: 100%; - min-height: 56px; -} - -.workspace-card-main { - display: flex; - flex-direction: column; - gap: var(--space-1); - min-width: 0; - padding-right: 72px; -} - -.workspace-card-heading { - display: flex; - align-items: center; - gap: var(--space-2); - min-width: 0; -} - -.workspace-card-title { - flex: 1; - min-width: 0; -} - -.workspace-card-trailing { - position: absolute; - top: 0; - right: 0; - font-size: 11px; - font-weight: 500; - line-height: 1; -} - -/* v3 — Bold inner glow on cards/panels (web-app BgGrad-inspired), plain shell background. */ -[data-widget-version="v3"] .mcp-widget-shell { - --brand-accent: var(--accent); - --brand-opacity: 1; - background: var(--widget-shell-bg); -} - -[data-widget-version="v3"] .mcp-widget-glow { - display: none; -} - -[data-widget-version="v3"] .card-glow, -[data-widget-version="v3"] .panel-glow { - display: block; - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - background: - radial-gradient( - ellipse 80% 50% at 50% 100%, - rgba(20, 16, 255, 0.12) 0%, - rgba(0, 144, 255, 0.08) 30%, - transparent 70% - ), - radial-gradient( - ellipse 60% 40% at 30% 90%, - rgba(71, 168, 253, 0.1) 0%, - transparent 60% - ), - radial-gradient( - ellipse 60% 40% at 70% 90%, - rgba(75, 160, 250, 0.1) 0%, - transparent 60% - ); -} - -/* v4 — Fully refined for white/light + proper dark mode support. */ -[data-widget-version="v4"] .mcp-widget-shell { - --brand-accent: var(--accent); - --brand-opacity: 1; - display: flex; - flex-direction: column; - background: var(--widget-shell-bg); -} - -[data-widget-version="v4"] .mcp-widget-glow { - display: none; -} - -[data-theme="light"] [data-widget-version="v4"] .mcp-widget-shell, -:root:not([data-theme="dark"]) [data-widget-version="v4"] .mcp-widget-shell { - --widget-shell-bg: #faf9f7; - --card-bg: #ffffff; - --card-border: rgba(0, 0, 0, 0.06); - --card-border-hover: rgba(0, 0, 0, 0.1); - --card-active-border: rgba(63, 126, 232, 0.32); - --card-active-bg: #fafcff; - --panel-bg: #ffffff; - --panel-border: rgba(0, 0, 0, 0.06); - --panel-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 6px 16px rgba(0, 0, 0, 0.03); - --bg-control: #fafafa; - --bg-control-hover: #f5f5f5; - --border-control: rgba(0, 0, 0, 0.08); - --text-secondary: #6b7280; - --text-muted: #9ca3af; -} - -[data-theme="dark"] [data-widget-version="v4"] .mcp-widget-shell { - --widget-shell-bg: #111518; - --card-bg: #181d22; - --card-border: rgba(255, 255, 255, 0.06); - --card-border-hover: rgba(255, 255, 255, 0.1); - --card-active-border: rgba(106, 166, 248, 0.36); - --card-active-bg: #161c24; - --panel-bg: #181d22; - --panel-border: rgba(255, 255, 255, 0.06); - --panel-shadow: 0 1px 3px rgba(0, 0, 0, 0.16), 0 6px 16px rgba(0, 0, 0, 0.14); - --bg-control: #14191f; - --bg-control-hover: #1b222a; - --border-control: rgba(255, 255, 255, 0.08); - --text-secondary: #a0a8b4; - --text-muted: #6b7580; -} - -[data-widget-version="v4"] .card-glow, -[data-widget-version="v4"] .panel-glow { - display: block; - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - background: radial-gradient( - ellipse 80% 50% at 50% 100%, - rgba(20, 16, 255, 0.07) 0%, - rgba(0, 144, 255, 0.04) 30%, - transparent 70% - ); -} - -/* v4 — Compact inline wordmark: logo + supermemory, no MCP subtitle. */ -[data-widget-version="v4"] .mcp-widget-brand { - display: flex; - align-items: center; - gap: var(--space-2); - margin: var(--space-3) var(--page-header-px) 0; - padding-bottom: var(--space-3); - border-bottom: 1px solid var(--border-muted); - opacity: 1; -} - -[data-widget-version="v4"] .mcp-widget-brand-mark { - width: 18px; - height: 18px; -} - -[data-widget-version="v4"] .mcp-widget-brand-mark svg { - width: 15px; - height: 12px; -} - -[data-widget-version="v4"] .mcp-widget-brand-copy { - flex-direction: row; - align-items: center; -} - -[data-widget-version="v4"] .mcp-widget-brand-name { - font-family: var(--font-brand); - font-size: 13px; - font-weight: 600; - letter-spacing: -0.02em; - line-height: 1; -} - -[data-widget-version="v4"] .mcp-widget-brand-mode { - display: none; -} - -/* v4 — Vertically center loading in the shell below the header. */ -[data-widget-version="v4"] .mcp-widget-content:has(.mcp-widget-loading) { - flex: 1; - display: flex; - flex-direction: column; - min-height: 0; -} - -[data-widget-version="v4"] .mcp-widget-loading { - flex: 1; - min-height: 180px; - padding: 0; -} - -/* v4 — Workspace picker as compact list rows. */ -[data-widget-version="v4"] .workspace-picker-grid { - display: flex; - flex-direction: column; - gap: 0; - overflow: hidden; - border: 1px solid var(--card-border); - border-radius: var(--radius-lg); - background: var(--card-bg); -} - -[data-widget-version="v4"] .workspace-card { - min-height: 0; - padding: var(--space-2) var(--space-3); - border: 0; - border-radius: 0; - background: transparent; - box-shadow: none; -} - -[data-widget-version="v4"] .workspace-card:not(:last-child) { - border-bottom: 1px solid var(--card-border); -} - -[data-widget-version="v4"] .workspace-card:hover { - background: var(--card-bg-hover); -} - -[data-widget-version="v4"] .workspace-card .card-glow { - display: none; -} - -[data-widget-version="v4"] .workspace-card-inner { - flex-direction: row; - align-items: center; - justify-content: space-between; - gap: var(--space-3); - height: auto; - min-height: 0; -} - -[data-widget-version="v4"] .workspace-card-main { - flex: 1; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - gap: var(--space-3); - min-width: 0; - padding-right: 0; - overflow: hidden; -} - -[data-widget-version="v4"] .workspace-card-heading { - flex: 1; - min-width: 0; -} - -[data-widget-version="v4"] .workspace-card-title { - min-width: 0; -} - -[data-widget-version="v4"] .workspace-card-meta { - flex-shrink: 0; - white-space: nowrap; -} - -[data-widget-version="v4"] .workspace-card-trailing { - position: static; - flex-shrink: 0; -} -.mcp-widget-shell-graph .mcp-widget-glow { - display: none; -} - -.mcp-widget-brand { - display: inline-flex; - align-items: center; - gap: var(--space-2); - position: relative; - z-index: 1; - margin: var(--space-4) var(--page-header-px) 0; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; - opacity: var(--brand-opacity); -} - -.mcp-widget-brand-mark { - display: grid; - width: 20px; - height: 20px; - place-items: center; - color: var(--brand-accent); -} - -.mcp-widget-brand-mark svg { - width: 16px; - height: 13px; -} - -.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: 650; - line-height: 1; -} - -.mcp-widget-brand-mode { - color: var(--text-muted); - font-size: 10px; - font-weight: 500; - line-height: 1.15; -} - -.shadow-inside-out { - box-shadow: - inset 0 2px 4px rgba(0, 0, 0, 0.3), - inset 0 1px 2px rgba(0, 0, 0, 0.1); -} - .mcp-widget-content { position: relative; z-index: 1; } .mcp-widget-shell-graph .mcp-widget-content { + flex: 1; min-height: inherit; } -/* Spinner — kept as global because primitives can't easily express a CSS animation */ -.spinner { - width: 24px; - height: 24px; - border: 2px solid rgba(255, 255, 255, 0.12); - border-top-color: var(--brand-accent); - border-radius: 50%; - animation: spin 0.8s linear infinite; - box-shadow: 0 0 20px rgba(75, 160, 250, 0.42); +/* ── Brand header — compact inline wordmark with a hairline divider ───────── */ +.mcp-widget-brand { + display: flex; + align-items: center; + gap: var(--space-2); + margin: var(--space-3) var(--page-header-px) 0; + padding-bottom: var(--space-3); + border-bottom: 1px solid var(--border-muted); } -@keyframes spin { - to { - transform: rotate(360deg); - } +.mcp-widget-brand-mark { + display: grid; + width: 18px; + height: 18px; + place-items: center; + color: var(--accent); +} + +.mcp-widget-brand-mark svg { + width: 15px; + height: 12px; +} + +.mcp-widget-brand-copy { + display: flex; + align-items: baseline; + gap: var(--space-2); + min-width: 0; +} + +.mcp-widget-brand-name { + color: var(--text-primary); + font-size: 13px; + font-weight: 600; + letter-spacing: -0.02em; + line-height: 1; +} + +.mcp-widget-brand-mode { + color: var(--text-muted); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1; +} + +/* ── Workspace picker — a framed, scrollable list of rows ─────────────────── */ +.workspace-picker-grid { + display: flex; + flex-direction: column; + max-height: 320px; + overflow-y: auto; + overflow-x: hidden; + border: 1px solid var(--card-border); + border-radius: var(--radius-lg); + background: var(--card-bg); + /* Hide scrollbar chrome — the framed list keeps a stable height. */ + scrollbar-width: none; +} + +.workspace-picker-grid::-webkit-scrollbar { + display: none; +} + +.workspace-card { + position: relative; + display: block; + width: 100%; + padding: var(--space-3) var(--space-3); + text-align: left; + background: transparent; + border: 0; + cursor: pointer; + transition: background-color 0.15s ease; +} + +.workspace-card:not(:last-child) { + border-bottom: 1px solid var(--border-muted); +} + +.workspace-card:hover { + background: var(--card-bg-hover); +} + +.workspace-card:focus-visible { + outline: none; + background: var(--card-bg-hover); + box-shadow: inset 0 0 0 2px var(--accent-ring); +} + +/* Active workspace is marked by an accent-tinted row + accent-colored name + * (see WorkspaceCard) — no rail, no check, so the trailing badge stays aligned. */ +.workspace-card[data-active="true"] { + background: var(--card-active-bg); +} + +.workspace-card-inner { + display: flex; + align-items: center; + gap: var(--space-3); + min-width: 0; +} + +.workspace-card-main { + display: flex; + flex-direction: column; + gap: 2px; + flex: 1; + min-width: 0; +} + +.workspace-card-title { + min-width: 0; +} + +.workspace-card-trailing { + display: flex; + align-items: center; + gap: var(--space-3); + flex-shrink: 0; +} + +/* ── Loading — draw-in wordmark, no glow ──────────────────────────────────── */ +.mcp-widget-content:has(.mcp-widget-loading) { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +.mcp-widget-loading { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-height: 180px; } .super-loader { @@ -636,7 +280,7 @@ min-width: calc(var(--super-loader-size, 42px) + 10px); align-items: center; gap: var(--space-2); - color: var(--brand-accent); + color: var(--accent); } .super-loader-mark { @@ -689,22 +333,22 @@ } } -/* The graph canvas needs a definite height in both modes. +/* ── Graph canvas ─────────────────────────────────────────────────────────── */ +/* The graph needs a definite height in both modes. * * Inline: derive height from the (stable) width via aspect-ratio — width is - * fixed by the chat column, so this breaks the autoResize feedback loop. + * fixed by the chat column, so this doesn't feed the autoResize loop. * * Fullscreen: the host expands the iframe, so 100dvh = the fullscreen height. - * We stay IN FLOW (no position:fixed) — taking the element out of flow - * collapses the document, autoResize shrinks the iframe, and a fixed inset:0 - * box in a collapsed iframe ends up with zero height (and never recovers on - * minimize). Keeping it in flow with an explicit height avoids that entirely. */ + * We stay IN FLOW (no position:fixed) — taking the element out of flow collapses + * the document, autoResize shrinks the iframe, and a fixed inset:0 box in a + * collapsed iframe ends up with zero height. Keeping it in flow with an explicit + * height avoids that. */ .graph-view { width: 100%; aspect-ratio: 16 / 9; - /* Never collapse to 0 during a resize/transition — a 0-size frame makes the - * package drop its "already fitted" flag and re-fit (a camera animation that - * reads as a re-layout). The floor keeps the size continuous. */ + /* Never collapse to 0 during a resize — a 0-size frame makes the package + * re-fit (a camera animation that reads as a re-layout). */ min-height: 420px; position: relative; background: transparent; diff --git a/apps/mcp/src/widget/design/tokens.css b/apps/mcp/src/widget/design/tokens.css index 1124010c..221e3130 100644 --- a/apps/mcp/src/widget/design/tokens.css +++ b/apps/mcp/src/widget/design/tokens.css @@ -1,64 +1,80 @@ -/* Design tokens for Supermemory MCP widget. +/* Design tokens for the Supermemory MCP widget. * - * Mirrors console-v2's tokens 1:1 so brand identity is consistent across - * surfaces. Diverge only when the widget's iframed context demands it. + * The widget renders inside a host chat surface (Cursor, Claude, ChatGPT). It + * should read as a native extension of the Supermemory console, whose language + * is: flat cool-neutral surfaces, a signature recessed inset-shadow on inputs, + * one restrained blue accent, and DM Sans. No gradients, no glows, no grids. * - * Dark mode: MCP host applies `data-theme="dark"` on via - * `applyDocumentTheme`. Tailwind's `dark:` variant is wired in globals.css - * via `@custom-variant dark (&:is([data-theme="dark"] *))`. + * The host applies `data-theme="light" | "dark"` on the document via ext-apps' + * `applyDocumentTheme`; Tailwind's `dark:` variant is wired in globals.css via + * `@custom-variant dark (&:is([data-theme="dark"] *))`. When no host is present + * (standalone / Studio) we fall back to the OS preference. */ +/* ── Light (default) ──────────────────────────────────────────────────────── */ :root, [data-theme="light"] { color-scheme: light; - /* Accent gradient (Supermemory brand) */ - --accent-start: #1148f7; - --accent-mid: #117dff; - --accent-end: #cdf4ff; - --accent-gradient: linear-gradient( - 135deg, - var(--accent-start), - var(--accent-mid), - var(--accent-end) - ); - - /* Accent solid */ - --accent: #117dff; + /* Accent — one restrained blue. Deeper than the dark accent so it stays + * legible as text/border on white. */ + --accent: #2e6fe6; --accent-foreground: #ffffff; - --accent-hover: #1148f7; - --accent-muted: #cdf4ff; + --accent-hover: #2560d0; + --accent-muted: rgba(46, 111, 230, 0.1); + --accent-ring: rgba(46, 111, 230, 0.35); - /* Backgrounds (warm subtle) */ - --bg-primary: #faf9f4; - --bg-secondary: #f3f1ec; - --bg-muted: #e8e5e0; - --bg-elevated: #f8f7f3; - --bg-overlay: rgba(250, 249, 244, 0.7); + /* Surfaces — cool light neutrals. The shell is the widget's own canvas; + * cards/panels sit a step brighter (white) so borders + shadow read. */ + --app-bg: #eef1f6; + --widget-shell-bg: #f5f7fa; + --widget-shell-graph-bg: #f5f7fa; - /* Borders */ - --border: #e3e0db; - --border-muted: #f0ede8; - --border-accent: var(--accent); + --bg-primary: #f5f7fa; + --bg-secondary: #eef1f6; + --bg-muted: #e7ebf1; + --bg-elevated: #ffffff; + --bg-overlay: rgba(245, 247, 250, 0.8); + --bg-panel: #ffffff; + --bg-control: #ffffff; + --bg-control-hover: #f4f6fa; - /* Text */ - --text-primary: #0a0a0a; - --text-secondary: #525252; - --text-muted: #a3a3a3; + --border: #e2e7ef; + --border-muted: #edf0f5; + --border-control: #dce2ea; + --border-accent: rgba(46, 111, 230, 0.45); + + --card-bg: #ffffff; + --card-bg-hover: #f6f8fb; + --card-border: #e2e7ef; + --card-border-hover: #d2dae5; + --card-active-bg: rgba(46, 111, 230, 0.06); + --card-active-border: rgba(46, 111, 230, 0.4); + + --panel-bg: #ffffff; + --panel-border: #e2e7ef; + --panel-shadow: + 0 1px 2px rgba(15, 23, 42, 0.04), 0 10px 26px rgba(15, 23, 42, 0.05); + + /* Signature recessed shadow — subtle in light so fields read as inset + * without going muddy. */ + --shadow-inset: inset 0 1px 2px rgba(15, 23, 42, 0.06); + --shadow-inset-strong: inset 1px 1px 3px rgba(15, 23, 42, 0.08); + + --text-primary: #0f1729; + --text-secondary: #55617a; + --text-muted: #8b95a7; --text-inverse: #ffffff; - /* Status colors (soft pastel) */ - --success: #4ade80; - --success-muted: rgba(74, 222, 128, 0.18); - --error: #f87171; - --error-muted: rgba(248, 113, 113, 0.18); - --warning: #fbbf24; - --warning-muted: rgba(251, 191, 36, 0.18); - --info: #60a5fa; - --info-muted: rgba(96, 165, 250, 0.18); - - /* Action colors */ - --danger: #ef4444; + --success: #15935a; + --success-muted: rgba(21, 147, 90, 0.12); + --error: #d64545; + --error-muted: rgba(214, 69, 69, 0.12); + --warning: #b7791f; + --warning-muted: rgba(183, 121, 31, 0.12); + --info: #2e6fe6; + --info-muted: rgba(46, 111, 230, 0.12); + --danger: #d64545; /* Spacing scale (4px base) */ --space-1: 4px; @@ -72,11 +88,11 @@ --space-12: 48px; --space-16: 64px; - /* Typography */ - --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif; - --font-display: - "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif; - --font-brand: "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif; + /* Typography — DM Sans throughout, mirroring the console. */ + --font-sans: + "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-display: var(--font-sans); + --font-brand: var(--font-sans); --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; @@ -94,13 +110,13 @@ --font-bold: 700; --leading-tight: 1.25; - --leading-normal: 1.35; - --leading-relaxed: 1.35; + --leading-normal: 1.4; + --leading-relaxed: 1.55; /* Unified component heights */ --height-xs: 28px; - --height-sm: 32px; - --height-md: 36px; + --height-sm: 34px; + --height-md: 38px; --height-lg: 44px; /* Icon sizes */ @@ -109,415 +125,242 @@ --icon-md: 20px; --icon-lg: 24px; - /* Radius */ - --radius-sm: 3px; - --radius-md: 4px; - --radius-lg: 6px; + /* Radius — a touch rounder than the console for a consumer feel. */ + --radius-sm: 6px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; --radius-full: 9999px; - /* Border width */ --border-width: 1px; - /* Page layout (mirrors console-v2 — widget iframes are narrow so we - * use the mobile values throughout: 16px horizontal, 16-24px vertical). */ + /* Page layout — widget iframes are narrow, so use compact values. */ --page-header-px: var(--space-4); --page-header-py: var(--space-4); --page-title-size: var(--text-lg); --page-title-weight: var(--font-semibold); - --page-title-mt: var(--space-5); + --page-title-mt: var(--space-4); - /* Shadows */ - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: - 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); - --shadow-lg: - 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + /* Panel shadow family (also exposed to Tailwind as shadow-sm/md/lg). */ + --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06); + --shadow-md: 0 6px 18px rgba(15, 23, 42, 0.08); + --shadow-lg: 0 18px 40px rgba(15, 23, 42, 0.12); /* Host-provided iframe dimensions; fallback for standalone dev. */ --host-height: 100vh; --host-width: 100vw; - /* Memory graph canvas — read by @supermemory/memory-graph's useGraphTheme. - * Mirrors console-v2 so the widget graph matches the console exactly. */ - --graph-bg: var(--bg-secondary); - --graph-doc-fill: #e8e5e0; - --graph-doc-stroke: #d4d0cb; - --graph-doc-inner: #f3f1ec; - --graph-mem-fill: #dbeafe; - --graph-mem-fill-hover: #bfdbfe; - --graph-mem-stroke: #3b82f6; - --graph-accent: var(--accent); - --graph-text-primary: var(--text-primary); - --graph-text-secondary: var(--text-secondary); - --graph-text-muted: var(--text-muted); - /* Edge / legend colors — bright + "hot" so derives/updates/extends read - * clearly against the light canvas (dark mode keeps its own hot set below). */ - --graph-edge-derives: #f59e0b; - --graph-edge-updates: #8b5cf6; - --graph-edge-extends: #0ea5e9; - --graph-mem-border-forgotten: #dc2626; - --graph-mem-border-expiring: #d97706; - --graph-mem-border-recent: #059669; - --graph-glow: #3b82f6; - --graph-icon: #6b7280; - --graph-popover-bg: var(--bg-elevated); - --graph-popover-border: var(--border); - --graph-popover-text-primary: var(--text-primary); - --graph-popover-text-secondary: var(--text-secondary); - --graph-popover-text-muted: var(--text-muted); - --graph-control-bg: var(--bg-elevated); - --graph-control-border: var(--border); + /* Memory graph canvas — read by @supermemory/memory-graph's theme. + * Neutral fills, one accent, semantic (not decorative) relation edges. */ + --graph-bg: transparent; + --graph-doc-fill: #f2f5f9; + --graph-doc-stroke: #dce2ea; + --graph-doc-inner: #ffffff; + --graph-mem-fill: #eaf1fc; + --graph-mem-fill-hover: #dde9fb; + --graph-mem-stroke: #2e6fe6; + --graph-accent: #2e6fe6; + --graph-text-primary: #0f1729; + --graph-text-secondary: #55617a; + --graph-text-muted: #8b95a7; + --graph-edge-derives: #b0b7c3; + --graph-edge-updates: #2e6fe6; + --graph-edge-extends: #0e9488; + --graph-mem-border-forgotten: #d64545; + --graph-mem-border-expiring: #b7791f; + --graph-mem-border-recent: #15935a; + --graph-glow: rgba(46, 111, 230, 0.35); + --graph-icon: #8b95a7; + --graph-popover-bg: #ffffff; + --graph-popover-border: #e2e7ef; + --graph-popover-text-primary: #0f1729; + --graph-popover-text-secondary: #55617a; + --graph-popover-text-muted: #8b95a7; + --graph-control-bg: #ffffff; + --graph-control-border: #e2e7ef; } +/* ── Dark ─────────────────────────────────────────────────────────────────── */ [data-theme="dark"] { color-scheme: dark; - --bg-primary: #0a0a0a; - --bg-secondary: #171717; - --bg-muted: #262626; - --bg-elevated: #1c1c1c; - --bg-overlay: rgba(10, 10, 10, 0.7); + --accent: #4ba0fa; + --accent-foreground: #0a0f16; + --accent-hover: #66b0ff; + --accent-muted: rgba(75, 160, 250, 0.14); + --accent-ring: rgba(75, 160, 250, 0.4); - --border: #2e2e2e; - --border-muted: #1f1f1f; + --app-bg: #080c11; + --widget-shell-bg: #0b1119; + --widget-shell-graph-bg: #0b1119; - --text-primary: #fafafa; - --text-secondary: #a3a3a3; - --text-muted: #8a8a8a; - --text-inverse: #0a0a0a; + --bg-primary: #0b1119; + --bg-secondary: #0e1620; + --bg-muted: #17212f; + --bg-elevated: #101822; + --bg-overlay: rgba(11, 17, 25, 0.8); + --bg-panel: #101822; + --bg-control: #0d141d; + --bg-control-hover: #131d2a; - --success-muted: rgba(74, 222, 128, 0.2); - --error-muted: rgba(248, 113, 113, 0.2); - --warning-muted: rgba(251, 191, 36, 0.2); - --info-muted: rgba(96, 165, 250, 0.2); + --border: #1f2b3b; + --border-muted: #172230; + --border-control: #263141; + --border-accent: rgba(75, 160, 250, 0.4); - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); - --shadow-md: - 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3); - --shadow-lg: - 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4); + --card-bg: #101822; + --card-bg-hover: #131d2a; + --card-border: #1f2b3b; + --card-border-hover: #2b3a4e; + --card-active-bg: rgba(75, 160, 250, 0.1); + --card-active-border: rgba(75, 160, 250, 0.45); - /* Memory graph canvas — dark (mirrors console-v2) */ - --graph-bg: #0f1419; - --graph-doc-fill: #1b1f24; - --graph-doc-stroke: #2a2f36; - --graph-doc-inner: #13161a; - --graph-mem-fill: #0d2034; - --graph-mem-fill-hover: #112840; - --graph-mem-stroke: #3b73b8; - --graph-edge-derives: #fbbf24; - --graph-edge-updates: #a78bfa; - --graph-edge-extends: #38bdf8; - --graph-mem-border-forgotten: #ef4444; - --graph-mem-border-expiring: #f59e0b; - --graph-mem-border-recent: #10b981; - --graph-glow: #3b73b8; - --graph-icon: #3b73b8; - --graph-popover-bg: #0c1829; - --graph-popover-border: #1a2333; - --graph-popover-text-primary: #ffffff; - --graph-popover-text-secondary: #e2e8f0; - --graph-popover-text-muted: #94a3b8; - --graph-control-bg: #0c1829; - --graph-control-border: #1a2333; + --panel-bg: #101822; + --panel-border: #1f2b3b; + --panel-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 1px 2px rgba(0, 0, 0, 0.4), + 0 14px 34px rgba(0, 0, 0, 0.35); + + /* Signature inset — the console's recessed emboss. */ + --shadow-inset: inset 2px 2px 5px rgba(4, 8, 13, 0.55); + --shadow-inset-strong: inset 2.4px 2.4px 4.5px rgba(6, 10, 16, 0.7); + + --text-primary: #eef2f7; + --text-secondary: #a9b4c2; + --text-muted: #6f7d8f; + --text-inverse: #0b1119; + + --success: #34d399; + --success-muted: rgba(52, 211, 153, 0.14); + --error: #f87171; + --error-muted: rgba(248, 113, 113, 0.14); + --warning: #fbbf24; + --warning-muted: rgba(251, 191, 36, 0.14); + --info: #4ba0fa; + --info-muted: rgba(75, 160, 250, 0.14); + --danger: #f87171; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 18px 44px rgba(0, 0, 0, 0.5); + + --graph-bg: transparent; + --graph-doc-fill: #131d2a; + --graph-doc-stroke: #2b3a4e; + --graph-doc-inner: #0d141d; + --graph-mem-fill: #122437; + --graph-mem-fill-hover: #172d44; + --graph-mem-stroke: #4ba0fa; + --graph-accent: #4ba0fa; + --graph-text-primary: #eef2f7; + --graph-text-secondary: #a9b4c2; + --graph-text-muted: #6f7d8f; + --graph-edge-derives: #5b6675; + --graph-edge-updates: #4ba0fa; + --graph-edge-extends: #2dd4bf; + --graph-mem-border-forgotten: #f87171; + --graph-mem-border-expiring: #fbbf24; + --graph-mem-border-recent: #34d399; + --graph-glow: rgba(75, 160, 250, 0.45); + --graph-icon: #6f7d8f; + --graph-popover-bg: #101822; + --graph-popover-border: #1f2b3b; + --graph-popover-text-primary: #eef2f7; + --graph-popover-text-secondary: #a9b4c2; + --graph-popover-text-muted: #6f7d8f; + --graph-control-bg: #101822; + --graph-control-border: #1f2b3b; } +/* ── Standalone / no host: follow the OS preference ───────────────────────── */ @media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { + :root:not([data-theme="light"]):not([data-theme="dark"]) { color-scheme: dark; - --bg-primary: #0a0a0a; - --bg-secondary: #171717; - --bg-muted: #262626; - --bg-elevated: #1c1c1c; - --bg-overlay: rgba(10, 10, 10, 0.7); + --accent: #4ba0fa; + --accent-foreground: #0a0f16; + --accent-hover: #66b0ff; + --accent-muted: rgba(75, 160, 250, 0.14); + --accent-ring: rgba(75, 160, 250, 0.4); - --border: #2e2e2e; - --border-muted: #1f1f1f; + --app-bg: #080c11; + --widget-shell-bg: #0b1119; + --widget-shell-graph-bg: #0b1119; - --text-primary: #fafafa; - --text-secondary: #a3a3a3; - --text-muted: #8a8a8a; - --text-inverse: #0a0a0a; + --bg-primary: #0b1119; + --bg-secondary: #0e1620; + --bg-muted: #17212f; + --bg-elevated: #101822; + --bg-overlay: rgba(11, 17, 25, 0.8); + --bg-panel: #101822; + --bg-control: #0d141d; + --bg-control-hover: #131d2a; - --success-muted: rgba(74, 222, 128, 0.2); - --error-muted: rgba(248, 113, 113, 0.2); - --warning-muted: rgba(251, 191, 36, 0.2); - --info-muted: rgba(96, 165, 250, 0.2); + --border: #1f2b3b; + --border-muted: #172230; + --border-control: #263141; + --border-accent: rgba(75, 160, 250, 0.4); - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); - --shadow-md: - 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3); - --shadow-lg: - 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4); + --card-bg: #101822; + --card-bg-hover: #131d2a; + --card-border: #1f2b3b; + --card-border-hover: #2b3a4e; + --card-active-bg: rgba(75, 160, 250, 0.1); + --card-active-border: rgba(75, 160, 250, 0.45); - --graph-bg: #0f1419; - --graph-doc-fill: #1b1f24; - --graph-doc-stroke: #2a2f36; - --graph-doc-inner: #13161a; - --graph-mem-fill: #0d2034; - --graph-mem-fill-hover: #112840; - --graph-mem-stroke: #3b73b8; - --graph-edge-derives: #fbbf24; - --graph-edge-updates: #a78bfa; - --graph-edge-extends: #38bdf8; - --graph-mem-border-forgotten: #ef4444; - --graph-mem-border-expiring: #f59e0b; - --graph-mem-border-recent: #10b981; - --graph-glow: #3b73b8; - --graph-icon: #3b73b8; - --graph-popover-bg: #0c1829; - --graph-popover-border: #1a2333; - --graph-popover-text-primary: #ffffff; - --graph-popover-text-secondary: #e2e8f0; - --graph-popover-text-muted: #94a3b8; - --graph-control-bg: #0c1829; - --graph-control-border: #1a2333; + --panel-bg: #101822; + --panel-border: #1f2b3b; + --panel-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 1px 2px rgba(0, 0, 0, 0.4), + 0 14px 34px rgba(0, 0, 0, 0.35); + + --shadow-inset: inset 2px 2px 5px rgba(4, 8, 13, 0.55); + --shadow-inset-strong: inset 2.4px 2.4px 4.5px rgba(6, 10, 16, 0.7); + + --text-primary: #eef2f7; + --text-secondary: #a9b4c2; + --text-muted: #6f7d8f; + --text-inverse: #0b1119; + + --success: #34d399; + --success-muted: rgba(52, 211, 153, 0.14); + --error: #f87171; + --error-muted: rgba(248, 113, 113, 0.14); + --warning: #fbbf24; + --warning-muted: rgba(251, 191, 36, 0.14); + --info: #4ba0fa; + --info-muted: rgba(75, 160, 250, 0.14); + --danger: #f87171; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 18px 44px rgba(0, 0, 0, 0.5); + + --graph-bg: transparent; + --graph-doc-fill: #131d2a; + --graph-doc-stroke: #2b3a4e; + --graph-doc-inner: #0d141d; + --graph-mem-fill: #122437; + --graph-mem-fill-hover: #172d44; + --graph-mem-stroke: #4ba0fa; + --graph-accent: #4ba0fa; + --graph-text-primary: #eef2f7; + --graph-text-secondary: #a9b4c2; + --graph-text-muted: #6f7d8f; + --graph-edge-derives: #5b6675; + --graph-edge-updates: #4ba0fa; + --graph-edge-extends: #2dd4bf; + --graph-mem-border-forgotten: #f87171; + --graph-mem-border-expiring: #fbbf24; + --graph-mem-border-recent: #34d399; + --graph-glow: rgba(75, 160, 250, 0.45); + --graph-icon: #6f7d8f; + --graph-popover-bg: #101822; + --graph-popover-border: #1f2b3b; + --graph-popover-text-primary: #eef2f7; + --graph-popover-text-secondary: #a9b4c2; + --graph-popover-text-muted: #6f7d8f; + --graph-control-bg: #101822; + --graph-control-border: #1f2b3b; } } - -/* Cursor MCP widget theme - * - * The web app palette is a useful reference, but these widgets live inside - * Cursor's chat surface. Keep the brand typography, then soften both themes so - * the widget feels embedded instead of like a separate high-contrast app frame. - */ -:root, -[data-theme="light"] { - color-scheme: light; - - --accent-start: #4f8df7; - --accent-mid: #4f8df7; - --accent-end: #8ec8ff; - --accent-gradient: linear-gradient( - 94deg, - var(--accent-start), - var(--accent-end) - ); - --accent: #3f7ee8; - --accent-foreground: #ffffff; - --accent-hover: #2f6fd8; - --accent-muted: rgba(63, 126, 232, 0.1); - - --app-bg: #f3f1ec; - --widget-shell-bg: #f6f4ef; - --widget-shell-graph-bg: #f4f2ec; - --brand-accent: #78746c; - --brand-opacity: 0.66; - --widget-glow-inner: rgba(255, 255, 255, 0.62); - --widget-glow-mid: rgba(110, 154, 220, 0.2); - --widget-glow-outer: rgba(63, 126, 232, 0.08); - - --bg-primary: #f6f4ef; - --bg-secondary: #efede7; - --bg-muted: #e7e3da; - --bg-elevated: #f8f7f3; - --bg-overlay: rgba(246, 244, 239, 0.74); - --bg-panel: #f1efe8; - --bg-control: #fbfaf7; - --bg-control-hover: #f4f2ec; - - --border: #ddd8ce; - --border-muted: #e9e5dc; - --border-control: #dcd6ca; - --border-accent: rgba(63, 126, 232, 0.42); - - --card-bg: #f8f7f3; - --card-bg-hover: #f3f1eb; - --card-border: #ddd8ce; - --card-border-hover: #cfc7b8; - --card-active-bg: #f8f7f3; - --card-active-border: rgba(63, 126, 232, 0.42); - - --panel-bg: #f1efe8; - --panel-border: #ddd8ce; - --panel-shadow: 0 10px 24px rgba(74, 68, 58, 0.08); - - --button-primary-bg: #111827; - --button-primary-hover: #1f2937; - --button-primary-active: #0b1120; - --button-primary-text: #ffffff; - --button-secondary-bg: #f8f7f3; - --button-secondary-hover: #f1eee7; - --button-secondary-text: #111827; - --button-secondary-border: #dcd6ca; - --button-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 1px 2px rgba(15, 23, 42, 0.1); - - --text-primary: #111827; - --text-secondary: #4b5563; - --text-muted: #8a94a3; - --text-inverse: #ffffff; - - --success: #16a34a; - --success-muted: rgba(22, 163, 74, 0.1); - --error: #dc2626; - --error-muted: rgba(220, 38, 38, 0.1); - --warning: #d97706; - --warning-muted: rgba(217, 119, 6, 0.12); - --info: #2563eb; - --info-muted: rgba(37, 99, 235, 0.1); - --danger: #dc2626; - - --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 1px 2px rgba(15, 23, 42, 0.06); - --shadow-md: 0 12px 28px rgba(15, 23, 42, 0.09); - --shadow-lg: 0 20px 44px rgba(15, 23, 42, 0.12); - - --graph-bg: #f3f1ec; - --graph-doc-fill: #f8f7f3; - --graph-doc-stroke: #ddd8ce; - --graph-doc-inner: #efede7; - --graph-mem-fill: #edf2f7; - --graph-mem-fill-hover: #e3ebf3; - --graph-mem-stroke: #6aa6f8; - --graph-accent: #3f7ee8; - --graph-text-primary: #111827; - --graph-text-secondary: #4b5563; - --graph-text-muted: #8a94a3; - --graph-edge-derives: #c08403; - --graph-edge-updates: #3f7ee8; - --graph-edge-extends: #0891b2; - --graph-mem-border-forgotten: #dc2626; - --graph-mem-border-expiring: #d97706; - --graph-mem-border-recent: #16a34a; - --graph-glow: #6aa6f8; - --graph-icon: #6b7280; - --graph-popover-bg: #f8f7f3; - --graph-popover-border: #ddd8ce; - --graph-popover-text-primary: #111827; - --graph-popover-text-secondary: #4b5563; - --graph-popover-text-muted: #8a94a3; - --graph-control-bg: #f8f7f3; - --graph-control-border: #ddd8ce; -} - -[data-theme="dark"] { - color-scheme: dark; - - --accent-start: #6aa6f8; - --accent-mid: #6aa6f8; - --accent-end: #8bd4ff; - --accent-gradient: linear-gradient( - 94deg, - var(--accent-start), - var(--accent-end) - ); - --accent: #6aa6f8; - --accent-foreground: #0f141a; - --accent-hover: #82b7ff; - --accent-muted: rgba(106, 166, 248, 0.12); - - --app-bg: #12161b; - --widget-shell-bg: #151a20; - --widget-shell-graph-bg: #11161c; - --brand-accent: #7f8996; - --brand-opacity: 0.56; - --widget-glow-inner: rgba(255, 255, 255, 0.12); - --widget-glow-mid: rgba(106, 166, 248, 0.16); - --widget-glow-outer: rgba(106, 166, 248, 0.08); - - --bg-primary: #151a20; - --bg-secondary: #181e25; - --bg-muted: #20262e; - --bg-elevated: #1a2027; - --bg-overlay: rgba(21, 26, 32, 0.72); - --bg-panel: #1d232b; - --bg-control: #14191f; - --bg-control-hover: #1b222a; - - --border: #2b333d; - --border-muted: #232a33; - --border-control: #303946; - --border-accent: rgba(106, 166, 248, 0.38); - - --card-bg: #1a2026; - --card-bg-hover: #202731; - --card-border: #2b333d; - --card-border-hover: #3a4654; - --card-active-bg: #1b222b; - --card-active-border: rgba(106, 166, 248, 0.44); - - --panel-bg: #1c222a; - --panel-border: #2b333d; - --panel-shadow: - 0 14px 34px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.035); - - --button-primary-bg: #eef4ff; - --button-primary-hover: #ffffff; - --button-primary-active: #dceaff; - --button-primary-text: #101820; - --button-secondary-bg: #202731; - --button-secondary-hover: #27303b; - --button-secondary-text: #eef2f7; - --button-secondary-border: #34404d; - --button-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 1px 2px rgba(0, 0, 0, 0.22); - - --text-primary: #f4f6f8; - --text-secondary: #c1c9d2; - --text-muted: #87909c; - --text-inverse: #111827; - - --success: #4ade80; - --success-muted: rgba(74, 222, 128, 0.12); - --error: #fb7185; - --error-muted: rgba(251, 113, 133, 0.12); - --warning: #fbbf24; - --warning-muted: rgba(251, 191, 36, 0.12); - --info: #8bbcff; - --info-muted: rgba(139, 188, 255, 0.12); - --danger: #fb7185; - - --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 1px 2px rgba(0, 0, 0, 0.18); - --shadow-md: 0 12px 30px rgba(0, 0, 0, 0.22); - --shadow-lg: 0 20px 48px rgba(0, 0, 0, 0.28); - - --graph-bg: #151a20; - --graph-doc-fill: #202731; - --graph-doc-stroke: #35404c; - --graph-doc-inner: #171d24; - --graph-mem-fill: #172536; - --graph-mem-fill-hover: #1b2d42; - --graph-mem-stroke: #6aa6f8; - --graph-accent: #6aa6f8; - --graph-text-primary: #f4f6f8; - --graph-text-secondary: #c1c9d2; - --graph-text-muted: #87909c; - --graph-edge-derives: #fbbf24; - --graph-edge-updates: #6aa6f8; - --graph-edge-extends: #67e8f9; - --graph-mem-border-forgotten: #fb7185; - --graph-mem-border-expiring: #f59e0b; - --graph-mem-border-recent: #4ade80; - --graph-glow: #6aa6f8; - --graph-icon: #7ab2ff; - --graph-popover-bg: #1c222a; - --graph-popover-border: #34404d; - --graph-popover-text-primary: #f4f6f8; - --graph-popover-text-secondary: #c1c9d2; - --graph-popover-text-muted: #87909c; - --graph-control-bg: #1c222a; - --graph-control-border: #34404d; -} diff --git a/apps/mcp/src/widget/design/ui/Button.tsx b/apps/mcp/src/widget/design/ui/Button.tsx index e65d3a33..d1a42fc7 100644 --- a/apps/mcp/src/widget/design/ui/Button.tsx +++ b/apps/mcp/src/widget/design/ui/Button.tsx @@ -3,35 +3,42 @@ import { type ButtonHTMLAttributes, forwardRef, type ReactNode } from "react" import { Loader2 } from "../../lib/icons" import { cn } from "../lib/cn" -// Mirrors apps/web's pill-shaped inside-out controls while keeping the widget -// variants small and dependency-free for MCP iframes. +// Token-driven, theme-aware pill controls. Primary is a high-contrast slab that +// inverts per theme (dark-on-light / light-on-dark); secondary is a neutral, +// input-like surface so it never reads as a heavy block on light backgrounds. const buttonVariants = cva( [ "inline-flex items-center justify-center gap-2", - "font-semibold", + "font-semibold whitespace-nowrap", "rounded-full", - "transition-all cursor-pointer", + "transition-colors duration-150 cursor-pointer", "disabled:pointer-events-none disabled:opacity-50", - "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)]/35 focus-visible:ring-offset-0", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-ring)] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--bg-primary)]", "[&_svg:not([class*='size-'])]:size-4 shrink-0", ].join(" "), { variants: { variant: { + // Nova's "insideOut" primary button: a near-black pill with the + // shadow-inside-out recessed inset. Same in both themes, exactly as + // the console renders it. primary: [ - "bg-[#0D121A] text-[#FAFAFA] shadow-inside-out", - "hover:bg-[#121820] active:bg-[#080B0F]", + "bg-[#0D121A] text-[#FAFAFA]", + "shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)]", + "hover:bg-[#121820] active:bg-[#0A0E14]", ].join(" "), secondary: [ - "bg-[#0D121A] text-[#FAFAFA] shadow-inside-out", - "hover:bg-[#121820]", + "bg-[var(--bg-control)] text-[var(--text-primary)]", + "border border-[var(--border-control)]", + "hover:bg-[var(--bg-control-hover)] hover:border-[var(--card-border-hover)]", + ].join(" "), + ghost: [ + "text-[var(--text-secondary)]", + "hover:bg-[var(--bg-muted)] hover:text-[var(--text-primary)]", ].join(" "), - ghost: ["text-[#737373]", "hover:bg-white/5 hover:text-[#FAFAFA]"].join( - " ", - ), danger: [ - "bg-[#EF4444]/15 text-[#EF4444]", - "hover:bg-[#EF4444]/25", + "bg-[var(--error-muted)] text-[var(--error)]", + "hover:bg-[var(--error-muted)] hover:brightness-95", ].join(" "), }, size: { diff --git a/apps/mcp/src/widget/design/ui/Card.tsx b/apps/mcp/src/widget/design/ui/Card.tsx index 07028c5e..edcb9d95 100644 --- a/apps/mcp/src/widget/design/ui/Card.tsx +++ b/apps/mcp/src/widget/design/ui/Card.tsx @@ -8,18 +8,18 @@ import { cn } from "../lib/cn" const cardStyles = cva( [ - "group relative overflow-hidden text-left", - "rounded-lg border border-[var(--card-border)] bg-[var(--card-bg)] p-4", - "transition-all duration-200", + "group relative text-left", + "rounded-xl border border-[var(--card-border)] bg-[var(--card-bg)] p-4", + "transition-colors duration-150", ].join(" "), { variants: { variant: { default: "", interactive: - "cursor-pointer hover:border-[var(--card-border-hover)] hover:bg-[var(--card-bg-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary", + "cursor-pointer hover:border-[var(--card-border-hover)] hover:bg-[var(--card-bg-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/30 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary", active: - "cursor-pointer border-[var(--card-active-border)] bg-[var(--card-active-bg)] hover:border-[var(--card-border-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary", + "cursor-pointer border-[var(--card-active-border)] bg-[var(--card-active-bg)] hover:border-[var(--card-active-border)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/30 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary", }, }, defaultVariants: { variant: "default" }, @@ -39,12 +39,6 @@ export type CardProps = DivProps | ButtonElementProps export const Card = forwardRef( ({ className, variant, as = "div", children, ...props }, ref) => { const cls = cn(cardStyles({ variant }), className) - const inner = ( - <> -
- {children} - - ) if (as === "button") { return ( ) } @@ -63,7 +57,7 @@ export const Card = forwardRef( ref={ref as React.Ref} {...(props as HTMLAttributes)} > - {inner} + {children}
) }, diff --git a/apps/mcp/src/widget/design/ui/Field.tsx b/apps/mcp/src/widget/design/ui/Field.tsx index 89adc266..9f970922 100644 --- a/apps/mcp/src/widget/design/ui/Field.tsx +++ b/apps/mcp/src/widget/design/ui/Field.tsx @@ -14,7 +14,7 @@ export function Field({ label, hint, error, className, children }: Props) {
{label ? ( // biome-ignore lint/a11y/noLabelWithoutControl: generic field wrapper where children provide the input -
-
- - Version - - setVersion("v1")} selected={version === "v1"}> - v1 - - setVersion("v2")} selected={version === "v2"}> - v2 - - setVersion("v3")} selected={version === "v3"}> - v3 - - setVersion("v4")} selected={version === "v4"}> - v4 - -
Width @@ -365,10 +352,7 @@ export function Studio() { description="The same widget shell Cursor renders, with mock MCP tool data." title="Cursor widgets" > -
+
- + @@ -425,7 +405,6 @@ export function Studio() { diff --git a/apps/mcp/src/widget/views/Confirmation.tsx b/apps/mcp/src/widget/views/Confirmation.tsx index 32660f06..f6a8c7f3 100644 --- a/apps/mcp/src/widget/views/Confirmation.tsx +++ b/apps/mcp/src/widget/views/Confirmation.tsx @@ -1,6 +1,6 @@ -import { Badge, Stack } from "../design/ui" -import { CheckCircle } from "../lib/icons" -import { formatTagLabel } from "../lib/formatTag" +import { WorkspaceChip } from "../components/WorkspaceChip" +import { Stack } from "../design/ui" +import { Check } from "../lib/icons" interface Props { containerTag: string @@ -10,20 +10,20 @@ export function Confirmation({ containerTag }: Props) { return ( -
- + + + -
+
Active workspace set
- {formatTagLabel(containerTag)} + -

- All subsequent saves and recalls will use this workspace until you - change it. +

+ Saves and recalls will use this workspace until you change it.

) diff --git a/apps/mcp/src/widget/views/Error.tsx b/apps/mcp/src/widget/views/Error.tsx index 5dfb5f40..ae518773 100644 --- a/apps/mcp/src/widget/views/Error.tsx +++ b/apps/mcp/src/widget/views/Error.tsx @@ -9,11 +9,13 @@ export function ErrorView({ message }: Props) { return ( - -
+ + + +
Something went wrong

diff --git a/apps/mcp/src/widget/views/Graph.tsx b/apps/mcp/src/widget/views/Graph.tsx index 5c621b2c..33925ee4 100644 --- a/apps/mcp/src/widget/views/Graph.tsx +++ b/apps/mcp/src/widget/views/Graph.tsx @@ -58,19 +58,25 @@ function readGraphColors(): GraphThemeColors { return out } -// Read the graph palette from CSS, and re-read after a theme switch. We can't -// rely on the package's own useGraphTheme: it watches the `class` attribute, -// but the MCP host themes via `data-theme`, applied in a passive effect that -// runs AFTER children render. requestAnimationFrame fires after that effect, -// so by then `data-theme` (hence the --graph-* values) is current. +// Read the graph palette from CSS, and re-read whenever the document theme +// flips. We can't rely on the package's own useGraphTheme (it watches `class`), +// nor on the `theme` prop alone: the host themes via `data-theme`, and in +// standalone/Studio the attribute can change without the prop changing. So we +// observe `data-theme`/`class` on directly and re-read after the next +// paint (rAF), by which point the --graph-* values are current. function useGraphColors(theme: string): GraphThemeColors { const [colors, setColors] = useState(readGraphColors) useEffect(() => { - // `theme` change is the trigger; re-read after the host applies - // data-theme (rAF fires after that passive effect). if (!theme) return - const id = requestAnimationFrame(() => setColors(readGraphColors())) - return () => cancelAnimationFrame(id) + const reread = () => + requestAnimationFrame(() => setColors(readGraphColors())) + reread() + const observer = new MutationObserver(reread) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme", "class"], + }) + return () => observer.disconnect() }, [theme]) return colors } diff --git a/apps/mcp/src/widget/views/Picker.tsx b/apps/mcp/src/widget/views/Picker.tsx index 40ccfa25..fd3f3944 100644 --- a/apps/mcp/src/widget/views/Picker.tsx +++ b/apps/mcp/src/widget/views/Picker.tsx @@ -9,7 +9,7 @@ import { Input, PageHeader } from "../design/ui" import { useApp } from "../hooks/useApp" import { useLog } from "../hooks/useLog" import { formatTagLabel } from "../lib/formatTag" -import { Search } from "../lib/icons" +import { Package, Search } from "../lib/icons" interface Props { containerTags: ContainerTag[] @@ -62,50 +62,64 @@ export function Picker({ const count = containerTags.length const description = count === 0 - ? "No workspaces available." - : `${count} workspace${count === 1 ? "" : "s"} available` + ? "Create a workspace in Supermemory to get started." + : "Pick the workspace to save and recall from." return (

-
- {count >= SEARCH_THRESHOLD ? ( -
- - setQuery(e.target.value)} - placeholder="Search workspaces…" - value={query} - /> -
- ) : null} - - {/* Bounded, scrollable list — keeps a stable height without showing scrollbar chrome. */} -
- {filtered.length === 0 ? ( -

- No workspaces match “{query}”. +

+ {count === 0 ? ( +
+ +

+ No workspaces yet

- ) : ( -
- {filtered.map((tag) => { - const access = assignedTags?.find( - (t) => t.containerTag === tag.containerTag, - ) - return ( - - ) - })} -
- )} -
+

+ Workspaces you create in Supermemory show up here, ready to save + and recall from. +

+
+ ) : ( + <> + {count >= SEARCH_THRESHOLD ? ( +
+ + setQuery(e.target.value)} + placeholder="Search workspaces…" + value={query} + /> +
+ ) : null} + + {filtered.length === 0 ? ( +
+

+ No workspaces match “{query}”. +

+
+ ) : ( +
+ {filtered.map((tag) => { + const access = assignedTags?.find( + (t) => t.containerTag === tag.containerTag, + ) + return ( + + ) + })} +
+ )} + + )} {pending ? (

diff --git a/apps/mcp/src/widget/views/Save.tsx b/apps/mcp/src/widget/views/Save.tsx index 10e2683b..e9f4d17b 100644 --- a/apps/mcp/src/widget/views/Save.tsx +++ b/apps/mcp/src/widget/views/Save.tsx @@ -79,12 +79,11 @@ export function Save({ title="Add Memory" />

-
-
+