From 99a8adaab5a95eac0a0650da85b3b37a84422800 Mon Sep 17 00:00:00 2001 From: ved015 Date: Tue, 30 Jun 2026 20:01:17 +0530 Subject: [PATCH 01/32] Fix MCP widget dark mode readability Make the widget fall back to dark tokens when the host does not provide an explicit theme, and expand workspace memory count labels from mem/mems to memory/memories. Co-authored-by: Ishaan Gupta --- .../src/widget/components/WorkspaceCard.tsx | 4 +- apps/mcp/src/widget/design/tokens.css | 61 ++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/apps/mcp/src/widget/components/WorkspaceCard.tsx b/apps/mcp/src/widget/components/WorkspaceCard.tsx index cd4122ef..24f39211 100644 --- a/apps/mcp/src/widget/components/WorkspaceCard.tsx +++ b/apps/mcp/src/widget/components/WorkspaceCard.tsx @@ -54,8 +54,8 @@ export function WorkspaceCard({ · - {containerTag.memoryCount} mem - {containerTag.memoryCount === 1 ? "" : "s"} + {containerTag.memoryCount}{" "} + {containerTag.memoryCount === 1 ? "memory" : "memories"} )} diff --git a/apps/mcp/src/widget/design/tokens.css b/apps/mcp/src/widget/design/tokens.css index 54436c36..750cb117 100644 --- a/apps/mcp/src/widget/design/tokens.css +++ b/apps/mcp/src/widget/design/tokens.css @@ -8,7 +8,8 @@ * via `@custom-variant dark (&:is([data-theme="dark"] *))`. */ -:root { +:root, +[data-theme="light"] { color-scheme: light; /* Accent gradient (Supermemory brand) */ @@ -182,7 +183,7 @@ --text-primary: #fafafa; --text-secondary: #a3a3a3; - --text-muted: #525252; + --text-muted: #8a8a8a; --text-inverse: #0a0a0a; --success-muted: rgba(74, 222, 128, 0.2); @@ -220,3 +221,59 @@ --graph-control-bg: #0c1829; --graph-control-border: #1a2333; } + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + color-scheme: dark; + + --bg-primary: #0a0a0a; + --bg-secondary: #171717; + --bg-muted: #262626; + --bg-elevated: #1c1c1c; + --bg-overlay: rgba(10, 10, 10, 0.7); + + --border: #2e2e2e; + --border-muted: #1f1f1f; + + --text-primary: #fafafa; + --text-secondary: #a3a3a3; + --text-muted: #8a8a8a; + --text-inverse: #0a0a0a; + + --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); + + --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); + + --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; + } +} From 75b3aaa618236cad7ee4952a8456519b02c0597b Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:42:30 +0000 Subject: [PATCH 02/32] Fix Biome CSS formatting in tokens.css Consolidate multi-line shadow values to single lines to match Biome formatting rules. Co-Authored-By: Claude Opus 4.5 --- apps/mcp/src/widget/design/tokens.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/mcp/src/widget/design/tokens.css b/apps/mcp/src/widget/design/tokens.css index 750cb117..815f303f 100644 --- a/apps/mcp/src/widget/design/tokens.css +++ b/apps/mcp/src/widget/design/tokens.css @@ -247,11 +247,9 @@ --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); + 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); + 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4); --graph-bg: #0f1419; --graph-doc-fill: #1b1f24; From 42f3aec88583baf8a00c2cfc98b7b615412520c6 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:01:20 +0000 Subject: [PATCH 03/32] feat(web): company brain onboarding redesign + connector entitlement (#1178) - onboarding: unified About step, mode-aware Sources, Slack-focused Flows step, connect feedback (toast + connected state), auto-draft company description from domain - brain-home: Active members stat + invite, real OneDrive icon - useConnectorAccess hook so company_brain unlocks pro-tier connectors across onboarding + integrations - fix company-brain-connections crash on empty connections --- **Session Details** - Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/64a15b29-0848-42d1-af4c-138c5a27136f) - Requested by: Unknown - Address comments on this PR. Add `(aside)` to your comment to have me ignore it. --- apps/web/app/(app)/onboarding/page.tsx | 16 +- .../components/add-document/connections.tsx | 30 +- .../components/brain-home/brain-home-view.tsx | 55 +- .../brain-home/connections-board.tsx | 6 +- apps/web/components/integrations-view.tsx | 24 +- .../onboarding-brain/step-about.tsx | 527 ++++++++++-------- .../onboarding-brain/step-ingest.tsx | 339 +++++------ .../onboarding-brain/step-sources.tsx | 376 +++++++++---- .../components/onboarding-brain/step-team.tsx | 64 +-- apps/web/components/settings/account.tsx | 17 + .../settings/company-brain-connections.tsx | 3 +- apps/web/hooks/use-connector-access.ts | 19 + 12 files changed, 859 insertions(+), 617 deletions(-) create mode 100644 apps/web/hooks/use-connector-access.ts diff --git a/apps/web/app/(app)/onboarding/page.tsx b/apps/web/app/(app)/onboarding/page.tsx index c819d0f8..27bd853d 100644 --- a/apps/web/app/(app)/onboarding/page.tsx +++ b/apps/web/app/(app)/onboarding/page.tsx @@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from "next/navigation" import { toast } from "sonner" import { useAuth } from "@lib/auth-context" import { authClient } from "@lib/auth" +import { SHARED_TEAM_BRAIN_TAG } from "@lib/constants" import { analytics } from "@/lib/analytics" import { BrainShell } from "@/components/onboarding-brain/shell" import { @@ -167,13 +168,17 @@ export default function BrainOnboardingPage() { [router], ) + // Team/company-brain routes everything into the shared Team Brain that + // provisioning creates (sm_org_shared) — not a workspace-name slug space. const containerTag = useMemo( () => - containerTagFromWorkspace( - about.workspaceName || suggestedWorkspaceName, - mode, - ), - [about.workspaceName, suggestedWorkspaceName, mode], + allowTeam && mode === "team" + ? SHARED_TEAM_BRAIN_TAG + : containerTagFromWorkspace( + about.workspaceName || suggestedWorkspaceName, + mode, + ), + [allowTeam, about.workspaceName, suggestedWorkspaceName, mode], ) const isScale = useMemo(() => { @@ -397,7 +402,6 @@ export default function BrainOnboardingPage() { {step === "sources" && ( (null) const [granolaModalOpen, setGranolaModalOpen] = useState(false) @@ -398,7 +398,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { provider: ConnectorProvider syncScope?: GDriveSyncScope }) => { - if (!canAddConnection && !isProUser) { + if (!canAddConnection && !connectorAccess) { throw new Error( "Free plan doesn't include connections. Upgrade to Pro for unlimited connections.", ) @@ -557,7 +557,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { type="button" onClick={() => handleConnect("google-drive")} disabled={ - !isProUser || + !connectorAccess || isConnecting || addConnectionMutation.isPending } @@ -605,14 +605,14 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { ) : provider === "granola" ? ( <> - {!isProUser && ( + {!connectorAccess && ( Pro )} + ) : undefined, + }, { label: "Setup", value: `${setupDone}/3` }, ] return (
{tiles.map((t) => (
-

- {t.label} -

+
+

+ {t.label} +

+ {t.action} +

connectConnector("notion")} /> } + icon={} name="OneDrive" subtitle="Files from Microsoft 365." connected={connectorConnected("onedrive")} diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 9263bbe5..ed04a456 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -4,7 +4,6 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import { useCustomer } from "autumn-js/react" import { cn } from "@lib/utils" import { dmSansClassName, dmSans125ClassName } from "@/lib/fonts" -import { hasActivePlan } from "@lib/queries" import { $fetch } from "@lib/api" import { authClient } from "@lib/auth" import { useAuth } from "@lib/auth-context" @@ -44,6 +43,7 @@ import { Zap, } from "lucide-react" import { formatRelativeTime } from "@/components/settings/sync-utils" +import { useConnectorAccess } from "@/hooks/use-connector-access" import { useConnectionHealth } from "@/hooks/use-connection-health" import { useContainerTags } from "@/hooks/use-container-tags" import { DEFAULT_PROJECT_ID } from "@lib/constants" @@ -2555,8 +2555,11 @@ export function IntegrationsView({ const { allProjects } = useContainerTags() const shortcutsConnect = useShortcutsConnect() const autumn = useCustomer({ queryOptions: { enabled: !publicMode } }) - const hasProProduct = - !publicMode && hasActivePlan(autumn.data?.subscriptions, "api_pro") + // connectorAccess covers pro-tier connectors (incl. company_brain orgs); plugins + // stay on hasProProduct. See useConnectorAccess. + const { hasPro: hasProProduct, connectorAccess } = useConnectorAccess({ + enabled: !publicMode, + }) const isAutumnLoading = !publicMode && autumn.isLoading const [connectingPlugin, setConnectingPlugin] = useState(null) @@ -2601,7 +2604,7 @@ export function IntegrationsView({ return response.data as Connection[] }, staleTime: 30 * 1000, - enabled: !publicMode && hasProProduct, + enabled: !publicMode && connectorAccess, }) const { @@ -2915,7 +2918,7 @@ export function IntegrationsView({ } if (target === "granola") { - if (!hasProProduct) { + if (!connectorAccess) { void setConnectTarget(null) handleUpgrade("api_pro") } else { @@ -2939,6 +2942,7 @@ export function IntegrationsView({ connectTarget, isAutumnLoading, hasProProduct, + connectorAccess, publicMode, redirectToLogin, setConnectTarget, @@ -3397,7 +3401,7 @@ export function IntegrationsView({ case "connector": { const count = connectionsByProvider[item.provider].length const isGranola = item.provider === "granola" - const needsPlanUpgrade = !isAutumnLoading && !hasProProduct + const needsPlanUpgrade = !isAutumnLoading && !connectorAccess if (count > 0) { return (

@@ -3408,7 +3412,7 @@ export function IntegrationsView({ onClick={() => { trackCard(item) if (isGranola) { - if (!hasProProduct) { + if (!connectorAccess) { handleUpgrade("api_pro") return } @@ -3440,7 +3444,7 @@ export function IntegrationsView({ onClick={() => { trackCard(item) if (isGranola) { - if (!hasProProduct) { + if (!connectorAccess) { handleUpgrade("api_pro") return } @@ -4260,9 +4264,9 @@ export function IntegrationsView({ { - setGranolaModalOpen(open && hasProProduct) + setGranolaModalOpen(open && connectorAccess) if (!open) void setConnectTarget(null) }} /> diff --git a/apps/web/components/onboarding-brain/step-about.tsx b/apps/web/components/onboarding-brain/step-about.tsx index 7da4ffd1..eedb2de8 100644 --- a/apps/web/components/onboarding-brain/step-about.tsx +++ b/apps/web/components/onboarding-brain/step-about.tsx @@ -1,7 +1,7 @@ "use client" -import { useEffect, useState } from "react" -import { motion } from "motion/react" +import { useEffect, useRef, useState } from "react" +import { AnimatePresence, motion } from "motion/react" import { Button } from "@ui/components/button" import { Input } from "@ui/components/input" import { Textarea } from "@ui/components/textarea" @@ -14,14 +14,17 @@ import { Mail, Plug, Terminal, - User2, UserPlus, Users2, + Wand2, } from "lucide-react" import { cn } from "@lib/utils" import { dmSans125ClassName } from "@/lib/fonts" import type { BrainMode } from "./types" +const BACKEND = + process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" + export interface AboutValues { name: string about: string @@ -84,36 +87,115 @@ export function StepAbout({ }, [defaultName, suggestedWorkspaceName, domain]) const teamGated = mode === "team" && !allowTeam + const isTeam = mode === "team" && !teamGated + + // Default the workspace name per mode: name-derived for Personal (field hidden), + // email-derived for Team unless the user has typed their own. + const workspaceTouched = useRef(false) + // biome-ignore lint/correctness/useExhaustiveDependencies: derive on mode/name changes only + useEffect(() => { + if (mode === "personal") { + const first = values.name.trim().split(/\s+/)[0] + const derived = first + ? `${first}'s Brain` + : suggestedWorkspaceName || "My brain" + if (values.workspaceName !== derived) { + onChange({ ...values, workspaceName: derived }) + } + } else if (!workspaceTouched.current) { + const derived = suggestedWorkspaceName || "" + if (values.workspaceName !== derived) { + onChange({ ...values, workspaceName: derived }) + } + } + }, [mode, values.name, suggestedWorkspaceName]) + + // Auto-draft the "what does your company do" blurb from the domain. + const valuesRef = useRef(values) + valuesRef.current = values + const aboutTouched = useRef(false) + const summarizedDomain = useRef(null) + const latestDraftDomain = useRef(null) + const [drafting, setDrafting] = useState(false) + const [drafted, setDrafted] = useState(false) + + const draftCompany = async (rawDomain: string) => { + const d = rawDomain.trim().toLowerCase() + if (!d || summarizedDomain.current === d) return + if (aboutTouched.current && valuesRef.current.about.trim()) return + summarizedDomain.current = d + latestDraftDomain.current = d + setDrafting(true) + try { + const res = await fetch(`${BACKEND}/brain/company-summary`, { + method: "POST", + credentials: "include", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ domain: d }), + }) + // Ignore a stale response if the domain changed mid-flight. + if (latestDraftDomain.current !== d) return + if (!res.ok) return + const data = (await res.json()) as { summary?: string | null } + if ( + data.summary && + !aboutTouched.current && + latestDraftDomain.current === d + ) { + onChange({ ...valuesRef.current, about: data.summary }) + setDrafted(true) + } + } catch { + if (latestDraftDomain.current === d) summarizedDomain.current = null + } finally { + if (latestDraftDomain.current === d) setDrafting(false) + } + } + + // Draft once when entering Team with a domain already filled (e.g. from email). + // biome-ignore lint/correctness/useExhaustiveDependencies: run on team-entry only + useEffect(() => { + if (!isTeam) return + const d = (values.workspaceDomain || domain || "").trim() + if (d && !values.about.trim()) draftCompany(d) + }, [isTeam]) + const canContinue = !teamGated && values.name.trim().length > 0 && values.workspaceName.trim().length > 0 return ( -
-
-
+
+
+ + +
-

- Tell us about you -

-

- So your brain sounds like yours, not the docs. -

+
+

+ Tell us about you +

+

+ So your brain sounds like yours, not the docs. +

+
+
-
+
+

Your name

+ {isTeam && ( +
+

Workspace name

+ { + workspaceTouched.current = true + onChange({ ...values, workspaceName: e.target.value }) + }} + placeholder={suggestedWorkspaceName || "Acme"} + className={inputClass} + style={inputBevelStyle} + /> +
+ )} +
+ + {/* Company domain slides in only for Team mode. */} + + {isTeam && ( + +

Company domain

+
+
+ {values.workspaceDomain || domain ? ( + + ) : ( + + )} +
+ + onChange({ ...values, workspaceDomain: e.target.value }) + } + onBlur={(e) => { + const d = e.target.value.trim() + if (d !== values.workspaceDomain) { + onChange({ ...values, workspaceDomain: d }) + } + draftCompany(d) + }} + placeholder="your-team.com" + className={cn(inputClass, "pl-14")} + style={inputBevelStyle} + /> +
+
+ )} +
+ + {!teamGated && (
-

- What are you here for?{" "} - (optional) -

+
+

+ {isTeam ? ( + "What does your company do?" + ) : ( + <> + What are you here for?{" "} + + (optional) + + + )} +

+ {isTeam && + (drafting ? ( + + + Drafting from your site… + + ) : drafted ? ( + + + Drafted from your site · edit anything + + ) : null)} +