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 (
-
@@ -138,13 +137,12 @@ function renderView(
case "confirmation":
return
case "save-success":
- return
+ return
case "upload-success":
return (
)
diff --git a/apps/mcp/src/widget/components/PermissionBadge.tsx b/apps/mcp/src/widget/components/PermissionBadge.tsx
index 3ce84b9a..a248467d 100644
--- a/apps/mcp/src/widget/components/PermissionBadge.tsx
+++ b/apps/mcp/src/widget/components/PermissionBadge.tsx
@@ -5,11 +5,8 @@ interface Props {
export function PermissionBadge({ permission }: Props) {
const isWrite = permission === "write"
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 (
(
type="button"
{...(props as ButtonHTMLAttributes)}
>
- {inner}
+ {children}
)
}
@@ -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
-
+
{label}
) : null}
diff --git a/apps/mcp/src/widget/design/ui/FileUpload.tsx b/apps/mcp/src/widget/design/ui/FileUpload.tsx
index fb97e2d5..b8bbbe64 100644
--- a/apps/mcp/src/widget/design/ui/FileUpload.tsx
+++ b/apps/mcp/src/widget/design/ui/FileUpload.tsx
@@ -57,10 +57,10 @@ export function FileUpload({
className={cn(
"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)",
- "bg-[var(--bg-control)] cursor-pointer shadow-sm transition-all",
+ "border border-dashed rounded-(--radius-lg)",
+ "bg-[var(--bg-control)] cursor-pointer shadow-[var(--shadow-inset)] transition-colors",
dragOver
- ? "border-accent bg-accent-muted"
+ ? "border-accent bg-[var(--accent-muted)]"
: "border-[var(--border-control)] hover:border-[var(--card-border-hover)] hover:bg-[var(--bg-control-hover)]",
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 61025f4a..f91c412b 100644
--- a/apps/mcp/src/widget/design/ui/Input.tsx
+++ b/apps/mcp/src/widget/design/ui/Input.tsx
@@ -2,20 +2,21 @@ import { cva, type VariantProps } from "class-variance-authority"
import { forwardRef, type InputHTMLAttributes } from "react"
import { cn } from "../lib/cn"
-// Mirrors console-v2's Input with a solid widget surface so the Nova grid stays
-// behind the form instead of bleeding through controls.
+// A recessed field: solid control surface, hairline border, and the console's
+// signature inset shadow. Focus is a clean accent ring — no glow.
const inputVariants = cva(
[
"flex w-full",
"bg-[var(--bg-control)] text-[var(--text-primary)]",
"border border-[var(--border-control)]",
"rounded-[var(--radius-lg)]",
+ "shadow-[var(--shadow-inset)]",
"placeholder:text-[var(--text-muted)]",
- "transition-all",
- "hover:bg-[var(--bg-control-hover)]",
- "focus-visible:outline-none focus-visible:border-[var(--border-accent)] focus-visible:shadow-[0_0_0_1px_var(--border-accent),0_0_24px_rgba(75,160,250,0.12)]",
+ "transition-colors",
+ "hover:border-[var(--card-border-hover)]",
+ "focus-visible:outline-none focus-visible:border-[var(--border-accent)] focus-visible:shadow-[var(--shadow-inset),0_0_0_2px_var(--accent-ring)]",
"disabled:cursor-not-allowed disabled:opacity-50",
- "aria-invalid:border-[var(--error)] aria-invalid:ring-[var(--error)]",
+ "aria-invalid:border-[var(--error)]",
"file:border-0 file:bg-transparent file:text-[length:var(--text-sm)] file:font-medium",
].join(" "),
{
diff --git a/apps/mcp/src/widget/design/ui/TextArea.tsx b/apps/mcp/src/widget/design/ui/TextArea.tsx
index 19b473ce..7ee84f74 100644
--- a/apps/mcp/src/widget/design/ui/TextArea.tsx
+++ b/apps/mcp/src/widget/design/ui/TextArea.tsx
@@ -1,21 +1,22 @@
import { forwardRef, type TextareaHTMLAttributes } from "react"
import { cn } from "../lib/cn"
-// Multi-line counterpart to Input. Solid surface keeps host/product background
-// treatments from showing through text entry areas.
+// Multi-line counterpart to Input — same recessed field, inset shadow, and clean
+// accent focus ring.
const textAreaClass = [
"flex w-full",
"bg-[var(--bg-control)] text-[var(--text-primary)]",
"border border-[var(--border-control)]",
"rounded-[var(--radius-lg)]",
- "px-[var(--space-3)] py-[var(--space-2)]",
+ "shadow-[var(--shadow-inset)]",
+ "px-[var(--space-3)] py-[var(--space-3)]",
"text-[length:var(--text-sm)] leading-normal font-sans",
"placeholder:text-[var(--text-muted)]",
- "transition-all resize-y",
- "hover:bg-[var(--bg-control-hover)]",
- "focus-visible:outline-none focus-visible:border-[var(--border-accent)] focus-visible:shadow-[0_0_0_1px_var(--border-accent),0_0_24px_rgba(75,160,250,0.12)]",
+ "transition-colors resize-y",
+ "hover:border-[var(--card-border-hover)]",
+ "focus-visible:outline-none focus-visible:border-[var(--border-accent)] focus-visible:shadow-[var(--shadow-inset),0_0_0_2px_var(--accent-ring)]",
"disabled:cursor-not-allowed disabled:opacity-50",
- "aria-invalid:border-[var(--error)] aria-invalid:ring-[var(--error)]",
+ "aria-invalid:border-[var(--error)]",
].join(" ")
export interface TextAreaProps
diff --git a/apps/mcp/src/widget/design/ui/WorkspaceSelect.tsx b/apps/mcp/src/widget/design/ui/WorkspaceSelect.tsx
index 168b7e7c..290e80f1 100644
--- a/apps/mcp/src/widget/design/ui/WorkspaceSelect.tsx
+++ b/apps/mcp/src/widget/design/ui/WorkspaceSelect.tsx
@@ -1,5 +1,5 @@
import { type ReactNode, useEffect, useMemo, useState } from "react"
-import { Check, ChevronDown, Search } from "../../lib/icons"
+import { ChevronDown, Search } from "../../lib/icons"
import { cn } from "../lib/cn"
import { Button } from "./Button"
import { Popover, PopoverContent, PopoverTrigger } from "./Popover"
@@ -63,9 +63,9 @@ export function WorkspaceSelect({
{
@@ -127,7 +128,14 @@ export function WorkspaceSelect({
{option.icon ?? null}
-
+
{option.label}
@@ -137,9 +145,6 @@ export function WorkspaceSelect({
) : null}
- {isSelected ? (
-
- ) : null}
)
})
diff --git a/apps/mcp/src/widget/studio/Studio.tsx b/apps/mcp/src/widget/studio/Studio.tsx
index 2500a38f..b499a29e 100644
--- a/apps/mcp/src/widget/studio/Studio.tsx
+++ b/apps/mcp/src/widget/studio/Studio.tsx
@@ -1,4 +1,4 @@
-import { type ReactNode, useState } from "react"
+import { type ReactNode, useEffect, useState } from "react"
import { WidgetShell } from "../App"
import { WorkspaceCard } from "../components/WorkspaceCard"
import {
@@ -32,7 +32,6 @@ import {
type Theme = "light" | "dark"
type Width = "narrow" | "wide" | "cursor"
-type Version = "v1" | "v2" | "v3" | "v4"
const NARROW = 420
const WIDE = 720
@@ -121,9 +120,8 @@ function Swatch({ name, varName }: { name: string; varName: string }) {
}
export function Studio() {
- const [theme, setTheme] = useState("light")
- const [width, setWidth] = useState("cursor")
- const [version, setVersion] = useState("v1")
+ const [theme, setTheme] = useState("dark")
+ const [width, setWidth] = useState("narrow")
const frameWidth =
width === "narrow" ? NARROW : width === "wide" ? WIDE : CURSOR
@@ -132,6 +130,12 @@ export function Studio() {
document.documentElement.setAttribute("data-theme", t)
}
+ // Keep the document theme in sync so the gallery matches the toggle,
+ // including on first paint.
+ useEffect(() => {
+ document.documentElement.setAttribute("data-theme", theme)
+ }, [theme])
+
const saveStub = useStubHandlers("Save")
const uploadStub = useStubHandlers("Upload")
const pickerStub = useStubHandlers("Picker")
@@ -167,23 +171,6 @@ export function Studio() {
Dark
-
-
- 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"
/>
-
-
+
) : null}
-
+
-
-
+
+
+
-
+
{isUpload ? `Uploaded ${props.fileName}` : "Memory saved"}
-
{formatTagLabel(props.containerTag)}
+
-
- {props.id}
-
)
}
diff --git a/apps/mcp/src/widget/views/Upload.tsx b/apps/mcp/src/widget/views/Upload.tsx
index dbb7c271..54b85a00 100644
--- a/apps/mcp/src/widget/views/Upload.tsx
+++ b/apps/mcp/src/widget/views/Upload.tsx
@@ -84,11 +84,10 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) {
title="Upload File"
/>
-
-
+
{file ? (
-
+
@@ -126,10 +125,9 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) {
) : null}
-