supermemory/apps/web/lib/company-brain-entry.ts
MaheshtheDev db7f5c3f64 fix(web): select the correct Company Brain workspace (#1372)
## What changed

- Make `/brain` reuse the active Company Brain, switch to a single existing Company Brain, show a picker for multiple choices, or create one only when none exists.
- Wait for active-organization restoration before making that decision.
- Make Company Brain onboarding match organizations by the confirmed company domain and never offer unrelated-domain workspaces.
- Create a new Company Brain when no matching workspace exists and show an actionable workspace-limit toast when creation is blocked.
- Improve the organization picker, loading, and error states.

## Why

The previous flows could use or mutate the currently active normal organization, start research against stale Company Brain metadata, or offer unrelated Company Brain workspaces after a quota failure.

## Impact

Normal organizations are no longer silently converted. Research is scoped to the Company Brain for the confirmed domain, and users receive a clear recovery path when they reach their workspace limit.

This keeps the existing `{ domain }` research API contract; no organization-reconfiguration API is required.

## Validation

- Biome checks passed for all five changed web files
- Targeted web TypeScript diagnostics reported no errors for the changed files
- React Doctor against `origin/main`: no issues found
- `git diff --check`

No test files were added.
2026-07-28 06:14:22 +00:00

81 lines
2.3 KiB
TypeScript

import {
getBrainMode,
getBrainWorkspaceDomain,
getCompanyBrainOverride,
hasCompanyBrain,
} from "./billing-utils"
export type BrainEntryOrganization = {
id: string
name: string
slug: string
metadata?: Record<string, unknown> | string | null
}
export type CompanyBrainEntryDecision =
| { action: "use"; organization: BrainEntryOrganization }
| { action: "switch"; organization: BrainEntryOrganization }
| { action: "choose"; organizations: BrainEntryOrganization[] }
| { action: "create" }
export function isCompanyBrainOrganization(
organization: BrainEntryOrganization,
): boolean {
const override = getCompanyBrainOverride(organization.metadata)
if (override !== undefined) return override
return (
hasCompanyBrain(organization.metadata) ||
getBrainMode(organization.metadata) === "team"
)
}
export function getCompanyBrainOrganizations(
organizations: BrainEntryOrganization[],
): BrainEntryOrganization[] {
return organizations.filter(isCompanyBrainOrganization)
}
function normalizeDomain(domain: string): string {
return domain
.trim()
.toLowerCase()
.replace(/^https?:\/\//, "")
.replace(/^www\./, "")
.replace(/\/.*$/, "")
}
export function resolveCompanyBrainEntry(
activeOrganizationId: string | null | undefined,
organizations: BrainEntryOrganization[],
requestedDomain?: string,
): CompanyBrainEntryDecision {
const normalizedRequestedDomain = requestedDomain
? normalizeDomain(requestedDomain)
: null
const companyBrains = getCompanyBrainOrganizations(organizations).filter(
(organization) => {
if (!normalizedRequestedDomain) return true
const organizationDomain = getBrainWorkspaceDomain(organization.metadata)
return (
organizationDomain !== null &&
normalizeDomain(organizationDomain) === normalizedRequestedDomain
)
},
)
const active = organizations.find(
(organization) => organization.id === activeOrganizationId,
)
if (
active &&
companyBrains.some((organization) => organization.id === active.id)
) {
return { action: "use", organization: active }
}
if (companyBrains.length === 1 && companyBrains[0]) {
return { action: "switch", organization: companyBrains[0] }
}
if (companyBrains.length > 1) {
return { action: "choose", organizations: companyBrains }
}
return { action: "create" }
}