feat(brand-context): add org-scoped brand context with MCP prompt#3021
feat(brand-context): add org-scoped brand context with MCP prompt#3021viktormarinho merged 30 commits intomainfrom
Conversation
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsSuggested: Minor ( React with an emoji to override the release type:
Current version:
|
There was a problem hiding this comment.
2 issues found across 22 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-brand-context.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-brand-context.tsx:124">
P1: The form state is only initialized once, so later `brandContext` updates are ignored and inputs can become stale.
(Based on your team's feedback about syncing draft input state when async server values change.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/migrations/064-brand-context.ts">
<violation number="1" location="apps/mesh/migrations/064-brand-context.ts:6">
P1: Add a foreign key from `brand_context.organization_id` to `organization.id` with cascade delete to preserve org-scoped referential integrity.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
4 issues found across 11 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/tools/organization/brand-context-update.ts">
<violation number="1" location="apps/mesh/src/tools/organization/brand-context-update.ts:82">
P1: `BRAND_CONTEXT_UPDATE` is missing organization ownership validation before updating by `id`, enabling cross-org modification when an external id is provided.</violation>
<violation number="2" location="apps/mesh/src/tools/organization/brand-context-update.ts:139">
P1: `BRAND_CONTEXT_DELETE` deletes by raw `id` without checking the active organization, which allows cross-org deletion.</violation>
</file>
<file name="apps/mesh/src/tools/organization/brand-context-get.ts">
<violation number="1" location="apps/mesh/src/tools/organization/brand-context-get.ts:75">
P1: `BRAND_CONTEXT_GET` is missing organization scoping and can return brand contexts from other organizations by ID.</violation>
</file>
<file name="apps/mesh/src/storage/brand-context.ts">
<violation number="1" location="apps/mesh/src/storage/brand-context.ts:55">
P1: Brand context get/update/delete are not tenant-scoped, allowing cross-organization access by ID.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/tools/index.ts">
<violation number="1" location="apps/mesh/src/tools/index.ts:270">
P2: Prompt names can collide when multiple brand contexts share the same name, causing one prompt to overwrite the other. Include a unique suffix (e.g., brand.id) in the prompt name to keep all brand prompts accessible.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-brand-context.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-brand-context.tsx:571">
P2: Avoid auto-saving image URL inputs on every keystroke; keep a local draft and persist on blur so partial URLs don’t trigger repeated mutations.
(Based on your team's feedback about saving URL inputs only on blur.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-brand-context.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-brand-context.tsx:301">
P2: Header previews use `brand` props instead of local `colors`/`fonts` state, so the collapsed card can show stale values after edits until a refetch finishes.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment |
|
@cubic-dev-ai review |
@viktormarinho I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 22 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-brand-context.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-brand-context.tsx:283">
P2: Clear `timerRef.current` after the debounced save runs; otherwise props-to-state sync is permanently disabled after the first autosave.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Add a brand context feature that lets organizations define their company profile (name, domain, overview, colors, fonts, logos) which is exposed as an MCP prompt (/company-context) for AI clients. - Migration 064: brand_context table with organization_id PK - BrandContextStorage with get/upsert/delete - BRAND_CONTEXT_GET and BRAND_CONTEXT_UPDATE tools - Dynamic company-context MCP prompt in managementMCP() - Settings UI at /$org/settings/brand-context with structured inputs - Color picker, font rows, always-editable form Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s on save
- Add .references("organization.id").onDelete("cascade") to migration
- Pass images through in UI mutation to prevent silent data loss
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change brand_context from 1:1 (org_id as PK) to N:1 (UUID id as PK, org_id as indexed FK). This lets orgs manage multiple brand profiles. - Migration 064: id column as PK, organization_id as FK with index - Storage: list(orgId), get(id), create(), update(id), delete(id) - Tools: BRAND_CONTEXT_LIST, GET, CREATE, UPDATE, DELETE - MCP prompt: concatenates all brands for the org - UI: list of brand cards with Add/Delete, per-card Save Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of a single /company-context prompt, register /brand-<slug> for each brand (e.g. /brand-acme-corp). The slug is derived from the brand name. Each prompt contains only that brand's context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use unwrapToolResult to properly extract structuredContent from MCP tool response. Remove staleTime so invalidation triggers immediate refetch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…chema Migration 065 handles DBs that already ran the old 064 (organization_id as PK, no id column). Detects whether the id column exists and only alters the table if needed — fresh DBs with the new 064 are a no-op. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove manual Save button. Each field change schedules an auto-save after 1 second of inactivity. Shows "Saving..." indicator in the card header while persisting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uto-save - Logo/Favicon/OG Image fields now show an image preview above the URL input when a valid URL is set - Fix color picker onChange that was using setColors instead of updateColors (skipping auto-save) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brand cards start collapsed showing logo thumbnail, name, domain, color swatches, and font names at a glance. Click to expand into the full editable form. Delete button stays visible in both states. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a metadata text column to brand_context for storing extra Firecrawl data (typography, components, spacing, layout, tone, etc.) that can be normalized into dedicated columns later. - Migration 066 adds column to existing DBs, 064 includes it for fresh - Threaded through storage, tools, schema, and UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Collapse migrations 064+065+066 into one migration with the final schema (id PK, organization_id FK with index, metadata column). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GET/UPDATE/DELETE now verify brand belongs to caller's organization before operating, preventing cross-org access via guessed UUIDs - UPDATE handler uses `!== undefined` pattern for logo/favicon/ogImage so explicit null (clear field) reaches the storage layer - Migration uses sql`now()` instead of string literal "now()" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BrandCard useState initializers only run on mount — after query refetch the local state could be stale. Track brand.updatedAt via ref and re-sync all fields when it changes, skipping if a debounced save is pending to avoid clobbering in-flight edits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… live preview - Storage get/update/delete now require organizationId parameter, enforcing tenant isolation at the data layer (defense-in-depth) - Prompt names include brand ID suffix to prevent collision when multiple brands share the same name - ImageField saves on blur instead of every keystroke to avoid spamming mutations with partial URLs - Collapsed card header uses local state (name, domain, logo, colors, fonts) instead of brand props for instant feedback after edits Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b147c68 to
7915cf3
Compare
Use the reviewed card-based design with hover-to-edit pencil buttons,
save/cancel inline controls, auto-extract banner, checkerboard logo
previews, color palette circles, and font "Aa" previews.
Adapts for multi-brand backend: uses BRAND_CONTEXT_LIST to get first
brand, CREATE on first save, UPDATE with id for subsequent saves.
MCP prompt handles both {label,value}[] and Record<k,v> color formats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
List all brands as expandable entries. Collapsed state shows logo thumbnail (checkerboard bg), name, domain, color swatches, and font names. Clicking expands to the full reviewed section cards (Overview, Logos, Fonts, Colors) with hover-to-edit. Add Brand button at top, per-brand delete on hover. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-brand-context.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-brand-context.tsx:662">
P2: Avoid nesting an interactive delete control inside the header <button>. This is invalid HTML and can cause keyboard/assistive interactions to fail. Move the delete action outside the button or make the header a non-button container with proper keyboard handling.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| return ( | ||
| <div className="rounded-2xl border border-border/60 bg-background"> | ||
| {/* Collapsed header — always visible */} | ||
| <button |
There was a problem hiding this comment.
P2: Avoid nesting an interactive delete control inside the header . This is invalid HTML and can cause keyboard/assistive interactions to fail. Move the delete action outside the button or make the header a non-button container with proper keyboard handling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/views/settings/org-brand-context.tsx, line 662:
<comment>Avoid nesting an interactive delete control inside the header <button>. This is invalid HTML and can cause keyboard/assistive interactions to fail. Move the delete action outside the button or make the header a non-button container with proper keyboard handling.</comment>
<file context>
@@ -599,6 +603,183 @@ function ColorsSection({
+ return (
+ <div className="rounded-2xl border border-border/60 bg-background">
+ {/* Collapsed header — always visible */}
+ <button
+ type="button"
+ className="flex w-full items-center gap-3 p-5"
</file context>
Use Array.isArray check on unwrapped result to prevent "brands.map is not a function" when the response shape is unexpected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ID suffix (e.g. brand-deco-6da413dd) was showing in the MCP client UI as "Brand Deco 6da413dd". Drop it — use just the slug (brand-deco). Falls back to brand ID only when name is empty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tool that scrapes a website via Firecrawl's branding endpoint and
saves the extracted brand context (colors, fonts, logos, OG image,
favicon, typography, and metadata).
- Add FIRECRAWL_API_KEY to Settings
- Map Firecrawl BrandingProfile to our schema (colors as {label,value}[],
fonts as {name,role}[], rich tokens in metadata)
- Derive company name from OG metadata, overview from meta description
- Creates new brand or updates existing (via optional brandId param)
- Wire UI Extract button to call the tool with loading state
- Update colors Zod schema to accept both array and object formats
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 9 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-brand-context.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-brand-context.tsx:149">
P2: The Enter key handler still fires while `isExtracting` is true, so users can trigger multiple extract calls even though the button is disabled. Add an `!isExtracting` guard to the key handler to match the button’s disabled state.</violation>
</file>
<file name="apps/mesh/src/tools/organization/brand-context-extract.ts">
<violation number="1" location="apps/mesh/src/tools/organization/brand-context-extract.ts:179">
P2: Treat empty derived titles as missing so the brand name falls back to the domain. Using `??` here allows an empty string to be saved as the name.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
The previous logic used ogSiteName which often contains the full tagline. Now picks the shortest segment after splitting the page title on separators (|, –, —), which is typically the brand name (e.g. "Visual CMS for Your Storefront | Deco" → "Deco"). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/tools/organization/brand-context-extract.ts">
<violation number="1" location="apps/mesh/src/tools/organization/brand-context-extract.ts:182">
P2: The new title split regex no longer handles the common "-" separator, so titles like "Acme - Home" won’t be split and the extracted name becomes the full title. Re-include hyphen in the separator set to avoid this regression.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| // in the title (e.g. "Visual CMS for Your Storefront | Deco" → "Deco"), | ||
| // then ogSiteName, then the domain as last resort. | ||
| const titleParts = (metadata.title as string) | ||
| ?.split(/[|–—]/) |
There was a problem hiding this comment.
P2: The new title split regex no longer handles the common "-" separator, so titles like "Acme - Home" won’t be split and the extracted name becomes the full title. Re-include hyphen in the separator set to avoid this regression.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/tools/organization/brand-context-extract.ts, line 182:
<comment>The new title split regex no longer handles the common "-" separator, so titles like "Acme - Home" won’t be split and the extracted name becomes the full title. Re-include hyphen in the separator set to avoid this regression.</comment>
<file context>
@@ -175,14 +175,18 @@ export const BRAND_CONTEXT_EXTRACT = defineTool({
+ // in the title (e.g. "Visual CMS for Your Storefront | Deco" → "Deco"),
+ // then ogSiteName, then the domain as last resort.
+ const titleParts = (metadata.title as string)
+ ?.split(/[|–—]/)
+ .map((s) => s.trim())
+ .filter(Boolean);
</file context>
| ?.split(/[|–—]/) | |
| ?.split(/[|\-–—]/) |
Replace hard delete with archive (sets archived_at). Archived brands are filtered from the default list and don't generate MCP prompts. - Add archived_at nullable timestamptz column to migration 064 - Storage list() filters out archived by default, accepts includeArchived option - BRAND_CONTEXT_DELETE now sets archived_at instead of deleting the row - UI shows "Brand archived" toast Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/tools/organization/brand-context-update.ts">
<violation number="1" location="apps/mesh/src/tools/organization/brand-context-update.ts:143">
P2: `BRAND_CONTEXT_DELETE` is marked as non-destructive even though it archives records; this misclassifies the operation for clients that rely on tool safety hints.</violation>
<violation number="2" location="apps/mesh/src/tools/organization/brand-context-update.ts:174">
P2: Archiving always overwrites `archivedAt`, so repeated delete/archive calls lose the original archival timestamp.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Fetch all brands (including archived) and split into active/archived. Active brands render normally. A subtle "N archived" toggle at the bottom reveals archived brands at reduced opacity. Archived entries show an unarchive action instead of archive. - LIST tool accepts includeArchived param - archivedAt added to schema, serialized in all tool responses - UPDATE tool supports archivedAt field for unarchive (set to null) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add is_default boolean to brand_context. When a brand is set as default, a get_brand_context built-in tool is available in every agent session. The agent calls it when brand info is relevant — no system prompt bloat. - Migration 064: add is_default column (boolean, default false) - Storage: getDefault(orgId), setDefault(id, orgId) clears other defaults - Built-in tool: get_brand_context returns default brand data (name, domain, overview, colors, fonts, logos) - Registered in decopilot built-in tools index alongside agent_search etc - UI: star icon on brand cards — filled star for default, click to set - Default brand card has a subtle primary border Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 9 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/storage/brand-context.ts">
<violation number="1" location="apps/mesh/src/storage/brand-context.ts:182">
P1: `setDefault` clears and sets defaults in separate non-atomic writes, which can leave zero or multiple defaults under failures/concurrency.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
1. BRAND_CONTEXT_EXTRACT reads firecrawlApiKey from ctx instead of getSettings() — follows CLAUDE.md rule that tools never access env vars directly 2. Prompt registration deduplicates names — if two brands slugify to the same name, appends ID suffix to prevent crash 3. UI sends null (not undefined) when clearing logo/favicon/ogImage/ fonts/colors so the update handler correctly clears the field Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove staleTime on brand list query so invalidation triggers immediate refetch - Use refetchType: "all" on invalidation - Unwrap tool result in extract mutation to properly detect errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The agent already has access to brand data via BRAND_CONTEXT_LIST from the management MCP. The built-in tool was redundant and added confusion (two tools for the same thing). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AutoExtractBanner Enter key handler now checks !isExtracting to prevent duplicate extractions while one is in flight - BRAND_CONTEXT_DELETE (archive) correctly marked as destructiveHint - setDefault uses a transaction to atomically clear+set, preventing zero or multiple defaults under concurrent writes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/storage/brand-context.ts">
<violation number="1" location="apps/mesh/src/storage/brand-context.ts:181">
P1: `setDefault` can clear the current default and still throw when the target id is invalid, leaving the org with no default. Validate affected rows (and throw) inside the transaction so it rolls back.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| await this.db.transaction().execute(async (trx) => { | ||
| // Clear all defaults for this org | ||
| await trx | ||
| .updateTable("brand_context") | ||
| .set({ is_default: false }) | ||
| .where("organization_id", "=", organizationId) | ||
| .where("is_default", "=", true) | ||
| .execute(); | ||
|
|
||
| // Set the new default | ||
| await trx | ||
| .updateTable("brand_context") | ||
| .set({ is_default: true, updated_at: new Date().toISOString() }) | ||
| .where("id", "=", id) | ||
| .where("organization_id", "=", organizationId) | ||
| .execute(); | ||
| }); | ||
|
|
||
| const result = await this.get(id, organizationId); | ||
| if (!result) throw new Error("Brand context not found"); | ||
| return result; |
There was a problem hiding this comment.
P1: setDefault can clear the current default and still throw when the target id is invalid, leaving the org with no default. Validate affected rows (and throw) inside the transaction so it rolls back.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/storage/brand-context.ts, line 181:
<comment>`setDefault` can clear the current default and still throw when the target id is invalid, leaving the org with no default. Validate affected rows (and throw) inside the transaction so it rolls back.</comment>
<file context>
@@ -178,16 +178,27 @@ export class BrandContextStorage implements BrandContextStoragePort {
- .where("organization_id", "=", organizationId)
- .where("is_default", "=", true)
- .execute();
+ await this.db.transaction().execute(async (trx) => {
+ // Clear all defaults for this org
+ await trx
</file context>
| await this.db.transaction().execute(async (trx) => { | |
| // Clear all defaults for this org | |
| await trx | |
| .updateTable("brand_context") | |
| .set({ is_default: false }) | |
| .where("organization_id", "=", organizationId) | |
| .where("is_default", "=", true) | |
| .execute(); | |
| // Set the new default | |
| await trx | |
| .updateTable("brand_context") | |
| .set({ is_default: true, updated_at: new Date().toISOString() }) | |
| .where("id", "=", id) | |
| .where("organization_id", "=", organizationId) | |
| .execute(); | |
| }); | |
| const result = await this.get(id, organizationId); | |
| if (!result) throw new Error("Brand context not found"); | |
| return result; | |
| const result = await this.db.transaction().execute(async (trx) => { | |
| // Clear all defaults for this org | |
| await trx | |
| .updateTable("brand_context") | |
| .set({ is_default: false }) | |
| .where("organization_id", "=", organizationId) | |
| .where("is_default", "=", true) | |
| .execute(); | |
| // Set the new default | |
| const setResult = await trx | |
| .updateTable("brand_context") | |
| .set({ is_default: true, updated_at: new Date().toISOString() }) | |
| .where("id", "=", id) | |
| .where("organization_id", "=", organizationId) | |
| .executeTakeFirst(); | |
| if (Number(setResult.numUpdatedRows ?? 0) === 0) { | |
| throw new Error("Brand context not found"); | |
| } | |
| const record = await trx | |
| .selectFrom("brand_context") | |
| .selectAll() | |
| .where("id", "=", id) | |
| .where("organization_id", "=", organizationId) | |
| .executeTakeFirst(); | |
| if (!record) throw new Error("Brand context not found"); | |
| return toEntity(record); | |
| }); | |
| return result; |
- UPDATE handler now passes isDefault:false to storage when explicitly set, allowing programmatic removal of default status - BRAND_CONTEXT_DELETE (archive) clears is_default alongside setting archivedAt, preventing stale defaults after unarchive Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
What is this contribution about?
Adds a Brand Context feature — an org-scoped company profile (name, domain, overview, colors, fonts, logos) that gets exposed as an MCP prompt (
/company-context) so any connected AI client knows who the company is.What's included:
brand_contexttable withorganization_idas PKBrandContextStorageclass withget/upsert/delete, JSON parsing for fonts/colors/imagesBRAND_CONTEXT_GETandBRAND_CONTEXT_UPDATEviadefineTool()company-contextprompt registered inmanagementMCP()— formats brand data into a structured markdown message/$org/settings/brand-contextwith always-editable form, structured font rows (family/weight/style), color key-value pairs with native color picker, logo URL inputsHow to Test
bun run dev(migration 064 will auto-apply)/company-context— verify the brand data appears as a formatted promptBRAND_CONTEXT_GET/BRAND_CONTEXT_UPDATEwork via the management MCPMigration Notes
064-brand-contextcreates thebrand_contexttable — runs automatically on startupReview Checklist
bun run check+bun testpass)Summary by cubic
Adds an org-scoped Brand Context with per-brand MCP prompts (
/brand-<slug>) and a multi-brand settings page (create/edit/preview/extract/archive). Removes theget_brand_contextbuilt-in tool in favor of the management MCP tools.New Features
BrandContextStoragewith list/get/create/update/delete/getDefault/setDefault; all ops requireorganizationId.listfilters archived by default (opt-inincludeArchived). AddsisDefault.BRAND_CONTEXT_LIST/GET/CREATE/UPDATE/DELETE/EXTRACT; all verify org ownership.LISTsupportsincludeArchivedand returnsarchivedAt;UPDATEcan unarchive viaarchivedAt: nulland set default;DELETEarchives. Extractor maps fonts/colors/logos, derives name from the shortest title segment, stores rich tokens inmetadata, and reads the Firecrawl API key from settings./brand-acme), falling back to ID only when the name is empty; archived brands are skipped. Slug collisions are de-duplicated by appending an ID suffix. Prompt accepts colors as{label,value}[]orRecord<string,string>, plus legacy font shapes./$org/settings/brand-contextwith collapsible brand cards (preview, hover-to-edit sections with Save/Cancel), Add Brand, per-brand Archive/Unarchive with an archived toggle, a star to set the default brand (highlighted border when default), and an Auto-extract banner wired toBRAND_CONTEXT_EXTRACT. Guards against non-array tool responses.Bug Fixes
staleTime, invalidates withrefetchType: "all"), and unwraps tool results to reliably detect and report errors.BRAND_CONTEXT_DELETEas destructive, and make default-setting transactional to avoid race conditions.isDefault: falseinBRAND_CONTEXT_UPDATE, and clear default on archive inBRAND_CONTEXT_DELETE.cn()for conditional class names.Written for commit 4e9aa8d. Summary will update on new commits.