Brand
A config-only modifier that carries a circle's visual identity — wordmark, six-token palette, logo, fonts, and footer copy — and stamps it onto the surfaces the platform serves: login chrome, the SPA shell, and canvas theme tokens.
Working with it
Selecting a Brand reveals its settings in the properties panel; it has no dedicated full-screen workbench.
How it appears
The same element type rendered as a definition, a circle instance, and a live workspace card.
When to use / not
When to use
- Giving a circle one app-wide visual identity — attach at the circle root and every surface beneath it inherits the wordmark, palette, and fonts.
- Overriding the look of just one app or SPA — attach the brand to that host and the nearest-cascade lets it win for that subtree only.
- Branding the anonymous login surface — brand is readable by audience as well as collaborator, so the sign-in page carries your identity before a user is known.
When not to use
- Setting a single accent color via meta.appearance.color — that legacy path is paint-cache-only; attach a brand modifier for the source-of-truth surface.
- Building the actual UI — brand only themes a host's chrome; use spa, ssr, or view to author the application itself.
- Gating who may read or invoke an element — that is auth-policy or api-token; brand is presentational config with no middleware phase.
Topology
Attaches to another element as a modifier, shaping that element's behaviour rather than running on its own.
Properties
wordmarkstring- Brand wordmark text (e.g., 'RIDANGO')
accent_markstring- Optional decorative accent next to the wordmark (e.g., '»')
app_titlestring- Default app title shown beside the wordmark (e.g., 'Month-end review')
status_labelstring- Live-pill label (e.g., 'LIVE', 'STAGING', 'DEMO')
paletteobject- Six semantic color tokens that drive every UI surface
font_displaystring- Display font stack (e.g., 'Archivo, Helvetica Neue, ...')
font_bodystring- Body font stack (e.g., 'Inter, Helvetica Neue, ...')
logo_svgstring- Inline SVG markup for the brand logo (preferred)
logo_urlstring- Hosted logo URL (alternative to logo_svg)
favicon_urlstring- Hosted browser favicon URL for branded login and app shell surfaces
taglinestring- Footer tagline (e.g., 'Twelve methods. Your voice. Every line, every month.')
build_onstring- Footer attribution line
nav_itemsarray- Nav structure for SPA chrome (overrides app default if present)
Capabilities
Defined for this element
- Storage
Operations
- attachPOST
- deleteDELETE
- detachPOST
- disablePOST
- enablePOST
- evaluatePOST
- getGET
- get_attached_modifiersGET
- intentionGET
- list_attachmentsGET
- readme_updatePOST
- schemaGET
- updatePATCH
Ports
Inputs
- wordmarkconfig
- paletteconfig
- taglineconfig
Errors / when it fails
- Brand must specify at least one of: wordmark, logo_svg, logo_url
- Fails unless:
wordmark != null or logo_svg != null or logo_url != null
Validation rules
- Brand has no palette — surfaces will fall back to platform defaults
Brand (brand)
Category: modifiers | Form: | Symbol: Br
Per-circle visual identity for chrome, login, and content surfaces
Defines a circle’s visual identity (wordmark, palette, logo, fonts, footer copy). Cascade strategy: nearest — the closest brand attached on the resolution chain (target → parent → … → circle root) wins. No middleware phase — brand is read on demand by the platform serve handlers (login HTML injection, SPA brand resolver endpoint, canvas @theme tokens) and as runtime config by the consumer SPA. Apply at the circle level for an “app-wide brand”; attach to a specific app or SPA to override for that surface only. The modifier is the same shape consumed by
apps/ridango-fi/src/lib/brand/config.ts— server-side spec mirrors the client-side BrandConfig type so the SPA hydrates cleanly. Common mistake: editingmeta.appearance.colorinstead of attaching a brand modifier — that legacy single-color path is paint- cache-only; the brand modifier is the source-of-truth surface.
Guide
Per-circle visual identity — wordmark, palette, logo, fonts, and footer copy for chrome, login, and content surfaces.
What It Does
brand is a config-only modifier that defines a circle’s visual identity: header wordmark and title, a six-token semantic color palette, display/body font stacks, an inline-SVG or hosted logo, favicon, and footer copy. Attach it to a host element and the platform renders that host’s surfaces with the brand’s appearance.
It applies to any host (applies_to: ["*"]) and resolves with a nearest cascade: the platform walks the resolution chain (target → parent → … → circle root) and the closest attached brand wins. Apply at the circle level for an app-wide brand; attach to a specific app or SPA to override that surface only.
There is no middleware phase. Brand is declarative config (handler_type: ConfigOnly, sync) read on demand by the platform’s serve handlers — login HTML injection, the SPA brand resolver endpoint, and canvas @theme tokens — and as runtime config by the consumer SPA. The spec mirrors the client-side BrandConfig type so an SPA hydrates the API response cleanly. Because it is presentational and designed to appear on the anonymous login surface, brand is readable by both collaborator and audience relationships.
Element Definition
- Type:
brand(modifier,modifier_type: config) - Element symbol:
Br· Icon: palette (#FF6050) - Cascade:
nearest(closest attached brand on the chain wins) - Applies to:
*(any host element) - Storage: database, document mode, cache enabled
- Execution: sync, config-only (no middleware hooks)
- Visibility:
collaborator,audience
Properties
All properties are optional unless a validation rule requires one (see below).
Header chrome
| Property | Type | Notes |
|---|---|---|
wordmark | string | Brand wordmark text (e.g. RIDANGO). maxLength 32 |
accent_mark | string | Optional decorative accent next to the wordmark (e.g. »). maxLength 8 |
app_title | string | Default app title shown beside the wordmark. maxLength 64 |
status_label | string | Live-pill label. Default LIVE. maxLength 16 |
Palette (palette, object — six semantic tokens that drive every UI surface; each token matches ^#[0-9a-fA-F]{6}$)
| Token | Role |
|---|---|
accent_500 | Primary action / accent |
accent_400 | Lighter accent (hover, focus) |
accent_600 | Darker accent (pressed) |
surface_inverse | Inverse surface (header background) |
text_inverse | Inverse text (header text) |
body | Body background |
Typography
| Property | Type | Notes |
|---|---|---|
font_display | string | Display font stack |
font_body | string | Body font stack |
Logo / mark
| Property | Type | Notes |
|---|---|---|
logo_svg | string | Inline SVG markup for the brand logo (preferred) |
logo_url | string (uri) | Hosted logo URL (alternative to logo_svg) |
favicon_url | string (uri) | Hosted favicon for branded login and app shell |
Footer
| Property | Type | Notes |
|---|---|---|
tagline | string | Footer tagline. maxLength 200 |
build_on | string | Footer attribution line. Default Built on Triform. maxLength 64 |
Navigation (nav_items, array of objects — overrides app default if present)
Each item: kind (link | group), label, href, match_prefixes (array of string), indicator, muted (boolean).
Validation
- Rule
at_least_wordmark_or_logo: a brand must specify at least one ofwordmark,logo_svg, orlogo_url. - Warning: if
paletteis missing, surfaces fall back to platform defaults.
Activities
Config events emitted on attach/detach:
| Pattern | Category | Title |
|---|---|---|
brand.attached | configuration | Brand attached to ${target_slug} |
brand.detached | configuration | Brand detached from ${target_slug} |
Operations
Brand is config-only. ops.yaml declares no custom operations (operations: {}) — only the universal modifier lifecycle ops (get / update / attach / detach) apply, which the platform inherits automatically.
The brand spec is read at serve/boot time via the platform brand resolver:
GET /api/{circle}/_brand
SPA and canvas clients deserialize this response directly into their BrandConfig.
Quick Start
Create a brand modifier with a wordmark, accent color, and tagline, then attach it to a circle so it brands every surface beneath:
// brand spec (mirrors BrandConfig)
{
"wordmark": "RIDANGO",
"accent_mark": "»",
"app_title": "Month-end review",
"status_label": "LIVE",
"palette": {
"accent_500": "#FF6050",
"accent_400": "#FF8070",
"accent_600": "#D94030",
"surface_inverse": "#111418",
"text_inverse": "#FFFFFF",
"body": "#F7F7F8"
},
"tagline": "Twelve methods. Your voice. Every line, every month.",
"build_on": "Built on Triform"
}
Attach it at the circle root for an app-wide brand, or to a specific app/SPA to override just that surface. The consumer SPA then reads it back:
GET /api/{circle}/_brand
Common Mistakes
- Editing
meta.appearance.colorinstead of attaching a brand modifier. That legacy single-color path is paint-cache-only; the brand modifier is the source-of-truth surface. - Defining neither a wordmark nor a logo. Validation requires at least one of
wordmark,logo_svg, orlogo_url. - Omitting the palette. Allowed, but surfaces fall back to platform defaults (validation warning) — set all six tokens for a fully branded UI.
- Malformed color tokens. Each palette token must be a 6-digit hex string matching
^#[0-9a-fA-F]{6}$. - Expecting brand to override globally from a leaf. Cascade is
nearest, not pervasive — a brand attached deeper on the chain only affects that subtree; attach at the circle root for app-wide reach.
Error Recovery
| Error | Recovery guidance | Next actions |
|---|---|---|
validation | Brand spec uses semantic palette names (accent_500, accent_400, …). Six tokens cover the surface; logo and fonts are optional. |
Properties
| Property | Type | Default | Description |
|---|---|---|---|
wordmark | string | — | Brand wordmark text (e.g., ‘RIDANGO’) |
accent_mark | string | — | Optional decorative accent next to the wordmark (e.g., ‘»’) |
app_title | string | — | Default app title shown beside the wordmark (e.g., ‘Month-end review’) |
status_label | string | "LIVE" | Live-pill label (e.g., ‘LIVE’, ‘STAGING’, ‘DEMO’) |
palette | object | — | Six semantic color tokens that drive every UI surface |
font_display | string | — | Display font stack (e.g., ‘Archivo, Helvetica Neue, …’) |
font_body | string | — | Body font stack (e.g., ‘Inter, Helvetica Neue, …’) |
logo_svg | string | — | Inline SVG markup for the brand logo (preferred) |
logo_url | string | — | Hosted logo URL (alternative to logo_svg) |
favicon_url | string | — | Hosted browser favicon URL for branded login and app shell surfaces |
tagline | string | — | Footer tagline (e.g., ‘Twelve methods. Your voice. Every line, every month.’) |
build_on | string | "Built on Triform" | Footer attribution line |
nav_items | array | — | Nav structure for SPA chrome (overrides app default if present) |
Operations
attach
Post /ops/attach | Auth: Read
Attach this modifier to a target element
Attaches this modifier to a target element. The target_id must be a UUID of an existing element that supports this modifier type (check applies_to in definition.yaml). Priority controls evaluation order when multiple modifiers of the same type are attached — lower priority runs first. The attachment is stored in element_modifiers table. Cascade resolution runs at bond-time to merge this modifier into the target’s resolved config. Common mistake: attaching to an incompatible element type — check topology rules first.
delete
Delete /ops/delete | Auth: Admin
Delete element (soft delete)
Soft delete — sets state to ‘deleted’ but retains the record. Cannot delete elements that have children (has_no_bond precondition) or active runs. Requires admin auth and confirmation.
detach
Post /ops/detach | Auth: Read
Detach this modifier from a target element
Removes this modifier from a target element. Requires the target_id. Pervasive modifiers (audit, policy) can only be detached at the level they were originally attached — inherited pervasive modifiers cannot be detached by child elements. After detach, cascade resolution re-runs to remove this modifier’s effect from the resolved config.
disable
Post /ops/disable | Auth: Admin
Disable element (hides and prevents use)
Idempotent — safe to call on already-disabled elements. Optionally pass a reason string. Disabled elements cannot be invoked or executed. Inverse of enable.
enable
Post /ops/enable | Auth: Admin
Enable element (makes usable and visible)
Idempotent — safe to call on already-enabled elements. Transitions element to ready/enabled state. Cannot enable deleted elements. Inverse of disable.
evaluate
Post /ops/evaluate | Auth: Read
Evaluate modifier against current context
Evaluates this modifier against a context and optional target_id. Returns applies (bool), result (modifier-specific), and message. For modifiers without custom evaluation logic, returns a default pass result. Auth-policy returns allowed/denied. This is the explicit evaluation endpoint — during normal request flow, modifiers are evaluated automatically by the cascade resolver as middleware.
get
Get /ops/get | Auth: Read
Get element details
Element is already resolved by the routing layer — this returns the cached element, not a fresh DB query. Use the path /api/{circle}/{slug} to address elements.
get_attached_modifiers
Get /ops/attached/{target_id} | Auth: Read
Get all modifiers attached to a target element
Lists all modifiers attached to a specific target element, including modifier_id, type, subcategory, and priority. Useful for debugging cascade resolution or understanding which policies apply to an element before invoking it.
intention
Get /ops/intention | Auth: Read
Get element intention with full inheritance chain
Returns three levels: direct (this element’s intention), inherited (from category and root), and resolved (final merged intention). Useful for understanding an element’s purpose in context of its hierarchy.
list_attachments
Get /ops/targets | Auth: Read
List all elements this modifier is attached to
Returns all target elements where this modifier is currently applied. Shows target_id, target_type, priority, and cascade_policy.
readme_update
Post /ops/readme_update | Auth: Write
Update element README.md content
Creates or overwrites README.md in the element’s git repo. Commits to the draft branch. Content must be provided as a markdown string.
schema
Get /ops/schema | Auth: Read
Get element input/output schema (MCP tools/list compatible)
Returns type-level port schemas from the TypeRegistry — not instance-specific overrides. Includes direction (input/output), required flag, and JSON schema per port. Useful for understanding what data an element accepts and produces.
update
Patch /ops/update | Auth: Write
Update element
Partial update — send only the fields you want to change.
spec,name, andintentionare all independently optional.specMUST be a JSON object when present; deep-merged into the existing spec by default. Empty{"spec":{}}preserves existing spec content but still records a new version (no-op for content, not for version state). To clear/replace the entire spec wholesale send{"spec":{...},"deep":false}. List-typed spec fields use replace semantics (the patch list replaces the existing list, no array merging). Coordinates Git + DB writes. Slug cannot be changed after creation.
Lifecycle / runtime
Defined for this element
Execution model: sync
Observability
Defined for this element
Events
- brand.attached
- brand.detached
Pricing / cost
Platform default
Operation costs
- create: free
- update: free
- delete: free
- get: free
- list: free
- invoke: 10000 micro-AU
- tool_use: free
Set it up
- Wordmarkstring
- Brand wordmark text (e.g., 'RIDANGO')
- Taglinestring
- Footer tagline (one short line)
- Accent Colorstring
- Primary action / accent (hex, e.g. '#FF6050')
- App Titlestring
- Default title beside the wordmark
- Logo URLstring
- Hosted logo image (alternative to inline SVG)