People
The brand's own people as records — name, role, contact details, bio, portrait — that a site's contact and team sections bind to.
A site says who works here more often than any other fact, and it says it in three or four places: a contact card under a call to action, a team directory, the person who leads a service. Typed by hand, every one of those is a copy of the same facts that goes stale on its own.
A person is Braaand's record for that: a brand-scoped row (ppl_…) with a name, a role, a department, an email, a phone number, a bio, a portrait (a brand asset) and free tags. A site section that shows someone binds to the record, and every field is written from it — the name, the role, the email as a mailto: link, the phone as a tel: link, the portrait. Edit the record and every bound section on every site follows.
Where people come from
The library already knows who is in a photo: a portrait tagged with metadata.person carries a first name, a last name and often a title, an email and a phone (the same identity the pipeline's find_person and the For each node read). Add from photos on the People page — sync_people over MCP, braaand people sync on the CLI — seeds one record per name from those photos, with the best portrait and whatever details the photo carries. It is idempotent: a second run fills blank fields on an existing record and never overwrites a value someone set. The rest is a form: /brands/{brandId}/people.
A person's address
Every person has a slug, the last part of their page's address (/profil/asa-oberg). It is assigned when the record is created (the name, lower-cased, with å ä ö folded, and -2, -3 for namesakes) and then stored: fixing a typo in a name, or a namesake leaving, never moves a live page. To move it on purpose, change the URL field at the bottom of the person's form, send slug to update_person / PATCH …/people/{personId}, or pass --slug on the CLI. A slug someone else holds is refused (409 slug_taken).
When someone leaves
Archive is the everyday removal. The person is taken off every site: a team list closes the gap and gets one shorter, a contact card goes back to its placeholders so someone picks the next contact, and their page is gone. The record stays under Archived on the People page and can be restored; their address stays reserved, so nobody else inherits it. Restoring does not put them back anywhere on its own, since where they appear is a choice.
Erase is for a data erasure request and cannot be undone. The record is deleted, the person's words and portrait are cleared from every site page, and the values the change history held are scrubbed. It needs a brand admin who is signed in (or a user API key); a brand token can never erase. It sits behind the Archived view on the People page, erase_person over MCP and braaand people erase --confirm <id> on the CLI.
Private fields
Any of a person's role, department, email, phone, bio, intro, links, portrait and tags can be marked private with the lock beside the field's label (privateFields on the record). A private field never leaves braaand: it is not written into a site section, not in an export or a site package, and not in what a connected site reads. Marking a field private re-fills every section bound to that person without it. Reads made with a brand token (bt_…) never include private fields; a signed-in user and a user API key see the full record.
Photos follow the record
A portrait in the library can carry a person's details too (metadata.person), and that is what ads read: a For each node making one creative per colleague, a template with the name and title bound into it. Once a photo is linked to a person (metadata.person.personId), the record is the truth and the photo follows it: change the title under People and every linked photo says the new title, so the next ad does too. Add from photos links every photo that carries a person's name, and picking a portrait links that photo. On a linked photo the person fields are read only in the library, with a link to the record. Private fields are never written onto a photo, and the photo's bio is the short card line, not the long page bio.
Binding
Every section that shows a person declares a person group — which of its slots are the name, the role, the email, the phone, the bio, the portrait. A contact card has one group; a team section has one per item. In the site editor, a section with people carries a People block in its rail: pick a person for a card, or fill a team list from everyone, a department or a tag. Bound fields are written from the record; typing into one unbinds that group, because the words you type beat the record.
Two things follow from the binding being a fill. The rendered markup stays plain values, so the HTML twin, the export and the site package need nothing new. And the generator's copy pass never writes a bound field — a person's name, email and phone are facts, and a model is not asked to invent them. When a site is filled, any team list nobody has bound takes the brand's people automatically, the way a navbar takes the brand's mark; a lone contact card is a person's choice and stays a pick.
For agents
| Tool | What it does |
|---|---|
list_people / get_person |
The brand's people, sorted; one record in full. |
create_person / update_person |
The record. An update re-fills every bound section. privateFields lists what never leaves braaand; status archives or restores. |
archive_person |
The everyday removal: the person leaves every site, the record stays. |
erase_person |
Permanent, for a data erasure request. Brand admin, and the id repeated as confirmPersonId. |
sync_people |
Seed records from the library's person-tagged photos. |
bind_site_people |
Point a section's person group (contact) or a team list (people) at one or several people. |
REST: GET/POST /api/brands/{brandId}/people, GET/PATCH/DELETE …/people/{personId}, POST …/people/sync. CLI: braaand people list | get | create | update | delete | sync. A ppl_… id resolves like every other id (/r/ppl_…, resolve_id).