RIFF how-to · 2026-07-06 · design: Config Cascade & Per-Org Branding

Brand Your Flow's Follow-Along Page

Give every phone line its own colors, badge, tagline — and its own caller-friendly progress labels — in one YAML block.

0 · What you get without doing anything

1 · Declare the brand (one block)

Preferred home: your company's org file, so every one of its lines matches automatically.

# orgs/capsule_law.org.yaml
org_id: capsule_law
display_name: "Capsule Law"
brand:
  name: "Capsule Law"           # header title + page <title>
  badge: "CL"                   # the little avatar chip (1–3 chars)
  tagline: "your intake, live"  # subtitle under the name
  colors:
    primary: "#c8a24a"          # accents / glow
    highlight: "#e2c56f"        # highlights / gold
    bg: "#0b0e19"               # page background (optional)
    panel: "#131730"            # card background (optional)
  journey:                      # optional — see §4
    cap_intake__stage_matter: "Your case"
    cap_intake__stage_contact: "How to reach you"

Then point each of the company's flows at it — one line in the flow/pack YAML:

org: capsule_law
Single flow, no org? Put the same block directly in the flow:
config:
  brand: {name: "Riff Cafe", badge: "RC", colors: {primary: "#38bdf8"}}
A flow-level block also overrides its org's brand per key — capsule_law's immigration line can keep the org colors but swap the badge.

2 · Which color does what

tokenpage variablewhat the caller sees
primary--glowaccents, live indicators, links' glow
highlight--goldemphasis text, collected-answer highlights
bg--bgpage background
panel--panelcards / sections

Pick primary/highlight with good contrast against your bg — a contrast lint gate is planned; until then, eyeball it in the preview (§5).

3 · What lights up as the phone reads

The page doesn't just sit there while the agent talks — three surfaces highlight in real time, and all three take your brand's highlight color automatically (no extra config):

surfacewhen it lights uphow it uses highlight
Menu optionsas each option is read aloud, or hinted for tappingthe key glows and fills at low opacity (--menu-highlight)
Karaoke word-sweepword-by-word as the agent speaks its linethe current word gets a soft 16% wash + glow (derived --gold-soft)
Fresh-slot flashthe moment a just-heard answer lands in "heard so far"the row's border, wash, and ring flash in highlight
One deliberate exception: the node-degraded warning pill stays warning-gold on every brand — a system-health signal shouldn't dress in brand colors.

4 · Journey labels — how your progress READS

Without journey:, the page prettifies state ids ("Cap Service Request Stage Problem"). With it, callers see your words:

journey:
  cap_service_request__stage_problem:  "Problem"
  cap_service_request__stage_location: "Address"
  cap_service_request__stage_timing:   "Time"
  cap_service_request__confirm:        "Confirm"
  cap_service_request__done:           "Booked!"
State ids come from your compiled flow: grep "^ cap_" flows/<flow>.yaml, the Line Page, or python -m riff.flow_eval --describe. Unmapped states fall back to prettified ids — map only the steps callers should recognize.

5 · Verify

  1. Lint: python -m riff.flow_eval.linter <flow_id> — the companion-brand-missing warn disappears; a dangling org: is an error.
  2. Tokens: python -c "from riff.branding import brand_for_flow; print(brand_for_flow('<flow_id>'))" — you should see your colors/journey, not the fallback.
  3. Live: place a test call, say "text link", open the SMS'd companion URL — your colors, your badge, your step names. Org-file edits show on the page's next poll (no restart); Python changes still need a server restart.

6 · How it works (30 seconds)

The effective-config cascade (call → state → state group → flow → org → global) resolves your brand: block; brand_for_flow() projects it to public tokens only (no internal ids, no provenance) into /api/companion/state; the single companion.html template maps tokens onto its CSS variables and label lookup. Resolution order: declared → legacy keyword theme → derived palette.

Safety rails. Brand is freely overridable (it's cosmetic); what data the page may SHOW is governed separately by companion_public_slots + the privacy tier, which only tighten down the cascade. You cannot loosen privacy by branding a page.

These brands are LIVE in the flow YAMLs (flow-level config.brand; org files lift shared identity up in PR4). Each mock mirrors the follow-along page: header, that flow's REAL menu options, and the "heard so far" slot echo — the value renders in the brand's highlight color.

DA
David Austin Lawlawyer intake
● Live
MENU — say it or press it
0Set up profile and text follow-along
1Start a new intake
2Existing client or case update
3Appointments
Forms · Billing · Urgent · Message
HEARD SO FAR
Full nameSarah Thompson
DA
David Austin Law — Immigrationimmigration intake
● Live
MENU — say it or press it
1Family petition
2Work visa
3Naturalization
4Asylum or humanitarian
Renewals · Removal defense · Message
HEARD SO FAR
Petition typeFamily petition — spouse
AP
David Austin Plumbing Prosplumbing service
● Live
NO KEYPAD MENU
Conversational scheduling — the journey strip shows: Your problem → Days that work → Days to avoid → Request summary → All set!
HEARD SO FAR
Days that workJuly 12, 14, or 16
DE
David Austin Electricalelectrical intake
● Live
MENU — say it or press it
0New customer / intake
1Existing job or problem
3Schedule appointment
5Emergency service
Pricing · Billing · Speak to someone
HEARD SO FAR
ProblemOutlet sparking in the kitchen
PM
Property Maintenancemaintenance requests
● Live
MENU — say it or press it
1Plumbing
2Appliance
3Heating or cooling
4Electrical
Pests · Locks · Common areas · Message
HEARD SO FAR
IssueDishwasher won't drain — unit 4B
RS
Riff Scheduling Demobook a visit
● Live
NO KEYPAD MENU
Conversational booking demo — offers real slots, no keypad.
HEARD SO FAR
AppointmentFriday at 10:00 AM
SC
Space Channelspace channel
● Live
MENU — say it or press it
1Launches
2Space Channel news
3Space weather
4UFO files
Missions · Network · Radio · Report a bug
HEARD SO FAR
Bug reportDashboard shows stale launch data

The exact block behind the plumbing card, as it appears in flows/austin_plumbing.yaml:

config:
  brand:
    name: "David Austin Plumbing Pros"
    badge: "AP"
    tagline: "plumbing service"
    colors: {primary: "#38bdf8", highlight: "#facc15"}
    journey:
      greeting: ["Welcome", "The call is getting started."]
      triage: ["Your problem", "Tell us what's going on."]
      scheduling_collect_possible_days: "Days that work"
      scheduling_collect_blocked_days: "Days to avoid"
      scheduling_request_summary: ["Request summary", "Reading your visit request back."]
      complete: ["All set!", "Our dispatcher will confirm shortly."]
Why some journeys are short: auto-generated stage ids (cap_outlet_switch__stage_outlet_switch_3…) aren't self-describing, and a WRONG label is worse than the prettified fallback. Label the states you're sure of; extend as you review each vertical's stages. The 4 in-flight vertical packs (locksmith, barbershop, pet shop, café) get their brands when that work lands — their files are active WIP.

8 · Troubleshooting

symptomcause → fix
page shows space-channel / generic lookno declared brand reached the server — check org: spelling (lint catches dangling refs), and that the block is brand: under the org root or flow config:
colors ignored but name/badge appliedcolors must be a mapping with keys from §2 — a stray string is skipped
step labels not appliedjourney keys must match state ids EXACTLY (case, double underscores)
two flows of one company look differentone of them overrides the org brand at flow level — check its config.brand