From 33e927417f25bbc9f68f3e24b6b8484ef2c13068 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:08:16 +0000 Subject: [PATCH 1/3] feat(web): search channels in proactivity exceptions picker (#1377) Workspaces with hundreds of Slack channels made the plain select unusable, so the channel exception picker is now a Popover + Command combobox that filters as you type. The exceptions block is also reworked into one list card with an inline add row, matching the max-w-3xl measure used by the workspace prompt pane. Fixes ENG-1138 --- .../settings/company-brain-proactivity.tsx | 239 ++++++++++-------- 1 file changed, 134 insertions(+), 105 deletions(-) diff --git a/apps/web/components/settings/company-brain-proactivity.tsx b/apps/web/components/settings/company-brain-proactivity.tsx index 4e17eaf4..5b98c1ff 100644 --- a/apps/web/components/settings/company-brain-proactivity.tsx +++ b/apps/web/components/settings/company-brain-proactivity.tsx @@ -2,15 +2,18 @@ import { useQuery } from "@tanstack/react-query" import { cn } from "@lib/utils" -import { Check, Loader2, Lock, X } from "lucide-react" +import { Check, Loader2, Lock, Plus, X } from "lucide-react" +import { useState } from "react" import { useAuth } from "@lib/auth-context" import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@ui/components/select" + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@ui/components/command" +import { Popover, PopoverContent, PopoverTrigger } from "@ui/components/popover" import { type BrainChannelProactivity, type BrainProactivityDefault, @@ -27,7 +30,6 @@ const BACKEND = type Channel = { id: string; name: string; isPrivate: boolean } const HOME_CHANNEL_NAME = "company-brain" -const ADD_PLACEHOLDER = "__add__" const MODES: { id: BrainProactivityDefault @@ -50,16 +52,12 @@ const fieldLabel = cn( dmSans125ClassName(), "text-[11px] font-medium uppercase tracking-[0.06em] text-[#5B6675]", ) -const controlClass = cn( - dmSans125ClassName(), - "h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] text-[#FAFAFA] outline-none disabled:opacity-50", -) const selectContentClass = cn( dmSans125ClassName(), "rounded-[10px] border-white/[0.08] bg-[#1B1F24] text-[#FAFAFA] shadow-[0px_8px_24px_rgba(0,0,0,0.5)]", ) -const selectItemClass = - "cursor-pointer rounded-[8px] text-[13px] text-[#FAFAFA] hover:bg-white/10 hover:text-white data-[highlighted]:bg-white/10 data-[highlighted]:text-white focus:bg-white/10 focus:text-white" +const commandItemClass = + "cursor-pointer rounded-[8px] text-[13px] text-[#FAFAFA] data-[selected=true]:bg-white/10 data-[selected=true]:text-white" export default function CompanyBrainProactivity() { const isCompanyBrain = useHasCompanyBrain() @@ -68,6 +66,7 @@ export default function CompanyBrainProactivity() { const settingsQuery = useBrainSettings(isCompanyBrain) const update = useUpdateBrainSettings() + const [pickerOpen, setPickerOpen] = useState(false) const slackStatusQuery = useQuery({ queryKey: ["brain", "slack-status", org?.id], @@ -129,7 +128,7 @@ export default function CompanyBrainProactivity() { } return ( -
+
{settingsQuery.isLoading ? (
@@ -183,100 +182,130 @@ export default function CompanyBrainProactivity() {
Channel exceptions - {Object.entries(overrides).map(([channelId, value]) => ( -
- +
+ {Object.entries(overrides).map(([channelId, value]) => ( +
+ + #{channelName(channelId)} + +
+
+ {(["proactive", "quiet"] as const).map((option) => ( + + ))} +
+ +
+
+ ))} +
+ {addable.length > 0 ? ( + + + + + + + + + + No channels found. + + + {addable.map((ch) => ( + { + setOverride( + ch.id, + activeMode === "all_channels" + ? "quiet" + : "proactive", + ) + setPickerOpen(false) + }} + > + {ch.isPrivate ? "đź”’ " : "# "} + {ch.name} + + ))} + + + + + + ) : channelsQuery.isLoading || + slackStatusQuery.isLoading ? null : ( +

- #{channelName(channelId)} - -

-
- {(["proactive", "quiet"] as const).map((option) => ( - - ))} -
- -
-
- ))} - {addable.length > 0 ? ( - - ) : channelsQuery.isLoading || slackStatusQuery.isLoading ? null : ( -

- {slackStatusQuery.data?.connected === false - ? "Connect Slack to set per-channel exceptions." - : "Invite Company Brain to a Slack channel to list it here."} -

- )} + {slackStatusQuery.data?.connected === false + ? "Connect Slack to set per-channel exceptions." + : "Invite Company Brain to a Slack channel to list it here."} +

+ )} +
{!isAdmin ? ( From a051ba0e28bd63ad37fe05968d6a258d5ddf5c1c Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:27:36 +0000 Subject: [PATCH 2/3] feat(web): give Configure sections real routes under /configure (#1378) Configure sections were local useState, so they could not be linked or bookmarked and always opened on the default section. Each section is now a route (/configure, /configure/models, /configure/workspace-prompt, /configure/proactivity, /configure/automations), mirroring the existing /integrations/[card] pattern. The shell renders from the segment layout so switching sections does not remount the app, and legacy /?view=configure links forward to /configure. Fixes ENG-1140 --- .../app/(app)/configure/[section]/page.tsx | 17 +++++++++ apps/web/app/(app)/configure/layout.tsx | 15 ++++++++ apps/web/app/(app)/configure/page.tsx | 4 ++ apps/web/components/configure-view.tsx | 33 +++++++++-------- apps/web/lib/configure-routes.ts | 37 +++++++++++++++++++ apps/web/lib/view-mode-context.tsx | 30 +++++++++++---- 6 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 apps/web/app/(app)/configure/[section]/page.tsx create mode 100644 apps/web/app/(app)/configure/layout.tsx create mode 100644 apps/web/app/(app)/configure/page.tsx create mode 100644 apps/web/lib/configure-routes.ts diff --git a/apps/web/app/(app)/configure/[section]/page.tsx b/apps/web/app/(app)/configure/[section]/page.tsx new file mode 100644 index 00000000..5acd2b3a --- /dev/null +++ b/apps/web/app/(app)/configure/[section]/page.tsx @@ -0,0 +1,17 @@ +import { notFound, redirect } from "next/navigation" +import { + DEFAULT_CONFIGURE_SECTION, + isConfigureSection, +} from "@/lib/configure-routes" + +export default async function ConfigureSectionPage({ + params, +}: { + params: Promise<{ section: string }> +}) { + const { section } = await params + // Default section is canonical at /configure. + if (section === DEFAULT_CONFIGURE_SECTION) redirect("/configure") + if (!isConfigureSection(section)) notFound() + return null +} diff --git a/apps/web/app/(app)/configure/layout.tsx b/apps/web/app/(app)/configure/layout.tsx new file mode 100644 index 00000000..340bd4c1 --- /dev/null +++ b/apps/web/app/(app)/configure/layout.tsx @@ -0,0 +1,15 @@ +import { AppExperience } from "@/components/app-experience" + +// Shell lives here so section nav doesn't remount the app. +export default function ConfigureLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + <> + + {children} + + ) +} diff --git a/apps/web/app/(app)/configure/page.tsx b/apps/web/app/(app)/configure/page.tsx new file mode 100644 index 00000000..fe7bd566 --- /dev/null +++ b/apps/web/app/(app)/configure/page.tsx @@ -0,0 +1,4 @@ +// Shell renders in layout.tsx. +export default function ConfigurePage() { + return null +} diff --git a/apps/web/components/configure-view.tsx b/apps/web/components/configure-view.tsx index 21206920..98be696b 100644 --- a/apps/web/components/configure-view.tsx +++ b/apps/web/components/configure-view.tsx @@ -2,7 +2,8 @@ import { cn } from "@lib/utils" import { Blocks, CalendarClock, Cpu, ScrollText } from "lucide-react" -import { useState } from "react" +import Link from "next/link" +import { usePathname } from "next/navigation" import CompanyBrainConnections from "@/components/settings/company-brain-connections" import CompanyBrainModels from "@/components/settings/company-brain-models" import CompanyBrainProactivity from "@/components/settings/company-brain-proactivity" @@ -11,15 +12,14 @@ import { ProactivenessIcon } from "@/components/settings/proactiveness-icon" import { WorkspacePrompt } from "@/components/settings/workspace-prompt" import { ErrorBoundary } from "@/components/error-boundary" import { useAuth } from "@lib/auth-context" +import { + type ConfigureSection, + configureSectionToPath, + DEFAULT_CONFIGURE_SECTION, + pathToConfigureSection, +} from "@/lib/configure-routes" import { dmSans125ClassName } from "@/lib/fonts" -type ConfigureSection = - | "company-brain" - | "models" - | "workspace-prompt" - | "proactivity" - | "automations" - const SECTIONS: { id: ConfigureSection label: string @@ -27,7 +27,7 @@ const SECTIONS: { icon: React.ComponentType<{ className?: string }> }[] = [ { - id: "company-brain", + id: "tools", label: "Integrations", description: "Connect the tools your brain works with. Your account covers your own actions and reads; workspace accounts are a shared fallback.", @@ -65,8 +65,10 @@ const SECTIONS: { export function ConfigureView() { const { org } = useAuth() - const [activeSection, setActiveSection] = - useState("company-brain") + const pathname = usePathname() + // Reachable via ?view=configure too, where the path carries no section. + const activeSection = + pathToConfigureSection(pathname) ?? DEFAULT_CONFIGURE_SECTION const active = SECTIONS.find((section) => section.id === activeSection) if (!active) return null @@ -90,11 +92,10 @@ export function ConfigureView() { const isActive = section.id === activeSection const Icon = section.icon return ( - + ) })} @@ -135,7 +136,7 @@ export function ConfigureView() {

} > - {activeSection === "company-brain" ? ( + {activeSection === "tools" ? ( ) : activeSection === "models" ? ( diff --git a/apps/web/lib/configure-routes.ts b/apps/web/lib/configure-routes.ts new file mode 100644 index 00000000..cdef17dd --- /dev/null +++ b/apps/web/lib/configure-routes.ts @@ -0,0 +1,37 @@ +// Sections under the /configure route. +// "tools" is the "Integrations" section, slugged to avoid clashing with /integrations. +export const CONFIGURE_SECTIONS = [ + "tools", + "models", + "workspace-prompt", + "proactivity", + "automations", +] as const + +export type ConfigureSection = (typeof CONFIGURE_SECTIONS)[number] + +export const DEFAULT_CONFIGURE_SECTION: ConfigureSection = "tools" + +export function isConfigureSection(slug: string): slug is ConfigureSection { + return (CONFIGURE_SECTIONS as readonly string[]).includes(slug) +} + +export function configureSectionToPath(section: ConfigureSection): string { + return section === DEFAULT_CONFIGURE_SECTION + ? "/configure" + : `/configure/${section}` +} + +export function pathToConfigureSection( + pathname: string, +): ConfigureSection | null { + const trimmed = pathname.replace(/\/$/, "") + if (trimmed === "/configure") return DEFAULT_CONFIGURE_SECTION + const slug = trimmed.match(/^\/configure\/([^/]+)$/)?.[1] + if (slug && isConfigureSection(slug)) return slug + return null +} + +export function isConfigurePath(pathname: string): boolean { + return pathToConfigureSection(pathname) !== null +} diff --git a/apps/web/lib/view-mode-context.tsx b/apps/web/lib/view-mode-context.tsx index d26fc731..0bc5f218 100644 --- a/apps/web/lib/view-mode-context.tsx +++ b/apps/web/lib/view-mode-context.tsx @@ -8,6 +8,7 @@ import { isIntegrationView, pathToIntegrationView, } from "@/lib/integration-routes" +import { isConfigurePath } from "@/lib/configure-routes" import { analytics } from "@/lib/analytics" import { useCallback, useEffect } from "react" @@ -33,8 +34,11 @@ export function useViewMode() { const router = useRouter() const [paramView, setParamView] = useQueryState("view", viewParam) - // On /integrations[/card] the path is the source of truth; elsewhere the ?view param is. - const pathView = pathToIntegrationView(pathname) + // On /integrations[/card] and /configure[/section] the path is the source of truth; + // elsewhere the ?view param is. + const pathView: ViewMode | null = + pathToIntegrationView(pathname) ?? + (isConfigurePath(pathname) ? "configure" : null) const viewMode: ViewMode = pathView ?? paramView const setViewMode = useCallback( @@ -44,8 +48,12 @@ export function useViewMode() { router.push(integrationViewToPath(mode)) return } - // Leaving (or already off) the integrations route for a non-integration view. - if (pathToIntegrationView(pathname)) { + if (mode === "configure") { + router.push("/configure") + return + } + // Leaving (or already off) a path-owned route for a param-owned view. + if (pathToIntegrationView(pathname) || isConfigurePath(pathname)) { router.push(mode === "dashboard" ? "/" : `/?view=${mode}`) return } @@ -57,8 +65,8 @@ export function useViewMode() { return { viewMode, setViewMode, isInitialized: true } } -// Forwards legacy /?view=integrations (and sub-views) to the canonical /integrations route, -// preserving any other query params. Call once near the app root. +// Forwards legacy /?view=integrations (and sub-views) and /?view=configure to their +// canonical routes, preserving any other query params. Call once near the app root. export function useLegacyViewRedirect() { const pathname = usePathname() const router = useRouter() @@ -67,10 +75,16 @@ export function useLegacyViewRedirect() { useEffect(() => { if (pathname !== "/") return const view = searchParams.get("view") - if (!view || !isIntegrationView(view)) return + if (!view) return + const target = isIntegrationView(view) + ? integrationViewToPath(view) + : view === "configure" + ? "/configure" + : null + if (!target) return const params = new URLSearchParams(searchParams.toString()) params.delete("view") const qs = params.toString() - router.replace(integrationViewToPath(view) + (qs ? `?${qs}` : "")) + router.replace(target + (qs ? `?${qs}` : "")) }, [pathname, searchParams, router]) } From 95a34602b7cf0317bfdbced0eb475bc55cdf5d26 Mon Sep 17 00:00:00 2001 From: Prasanna721 <106952318+Prasanna721@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:28:22 +0000 Subject: [PATCH 3/3] register SpaceState durable object (#1376) Registers `SpaceState` before the stateless MCP starts routing requests through it. The class is intentionally unused here, so this changes no request behavior. Rollout: 1. Apply this exact commit once with `wrangler deploy`, not `wrangler versions upload`. 2. Rerun the Workers check and merge this parent PR. 3. Land #1120, which adds the active-space methods and request routing. Verified with the MCP widget build, isolated class typecheck, and Wrangler deploy/version dry runs. --- apps/mcp/src/index.ts | 1 + apps/mcp/src/server/space-state.ts | 3 +++ apps/mcp/wrangler.jsonc | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 apps/mcp/src/server/space-state.ts diff --git a/apps/mcp/src/index.ts b/apps/mcp/src/index.ts index 45731982..ed7f6ec4 100644 --- a/apps/mcp/src/index.ts +++ b/apps/mcp/src/index.ts @@ -197,5 +197,6 @@ app.all("/mcp/*", handleMcpRequest) // Export the Durable Object class for Cloudflare Workers export { SupermemoryMCP } +export { SpaceState } from "./server/space-state" export default app diff --git a/apps/mcp/src/server/space-state.ts b/apps/mcp/src/server/space-state.ts new file mode 100644 index 00000000..91bc7ba4 --- /dev/null +++ b/apps/mcp/src/server/space-state.ts @@ -0,0 +1,3 @@ +import { DurableObject } from "cloudflare:workers" + +export class SpaceState extends DurableObject {} diff --git a/apps/mcp/wrangler.jsonc b/apps/mcp/wrangler.jsonc index 1c2f3a19..9b4dff99 100644 --- a/apps/mcp/wrangler.jsonc +++ b/apps/mcp/wrangler.jsonc @@ -25,6 +25,10 @@ { "name": "MCP_SERVER", "class_name": "SupermemoryMCP" + }, + { + "name": "SPACE_STATE", + "class_name": "SpaceState" } ] }, @@ -33,6 +37,10 @@ { "tag": "v1", "new_sqlite_classes": ["SupermemoryMCP"] + }, + { + "tag": "v2", + "new_sqlite_classes": ["SpaceState"] } ],