Blocks
A block is data: { id, kind, props?, children?, visibleTo? }. It is filled in and placed, never coded. Every kind is described once in convex/lib/blocks.ts, and that one list feeds the renderer, the checks, HQ and the AI's prompt, so a new block shows up everywhere.
The full, current list with every prop is in the Catalog (generated).
The blocks by purpose #
All are ready; each needs its engine's kit or setup only where noted.
| Purpose | Kinds |
|---|---|
| Layout | stack (column or row), card (framed box), divider, switcher (one child at a time under segments, tabs or pills) |
| Words and media | heading, text, list, image, stat |
| Actions | button (opens a URL or #page-id) |
| Data (Collections) | records.list (list or board), records.form, records.count, split (shared costs); record views records.composer, records.timeline, records.calendar, records.streak, records.memories, records.prompt, records.stats (Data) |
| Talk (Spaces) | inbox (every conversation in one list), stream (a room, channel or feed), direct, ask and queue (both sides of a support desk or applications), drafts |
| People (Spaces, Structure) | members, invite, units (teams, classes, branches as a tree); invites and referrals invites.code, invites.list, invites.redeem and a general qr (any link, text or page) (People) |
| Account (Account) | account.email, account.password, account.sessions, account.delete: the viewer's own account, on any page |
| Notifications (Notifications) | notify.settings (your own controls), notify.inbox (tap opens the exact page), notify.bell (unread count), notify.send (managers send to members) |
| Safety (docs/SAFETY.md) | safety.queue (moderators' review queue), safety.filters (word filter with a try-it box), safety.blocked (my blocked people), safety.report, safety.block, safety.admin (Zvessa's admins) |
| Web (docs/WEB.md) | web.share (the web address with Copy and Share), hq.web (HQ's Web panel: handle, own domains, pages on the web, embeds) |
| Feeds (Spaces) | feed.timeline (list, cards, grid or stories), feed.composer, feed.profile, feed.people, feed.activity, feed.search, feed.saved, feed.sources |
| Reviews (Spaces, docs/REVIEWS.md) | reviews.summary (average, count, a bar per star, aspects, Write a review), reviews.list (sort and filter), reviews.stars (a small ★ 4.6 (128) line that opens the reviews), reviews.write ("How was it?" stars to tap), reviews.top (subjects ranked), reviews.queue (waiting and reported, managers only) |
| Money, levels, badges (Ledger) | ledger.balance, ledger.send, ledger.give, ledger.history, ledger.leaderboard, ledger.levels, ledger.marks, ledger.grants |
| Play (Rounds) | poll, wheel, game (a live game from blueprint.games), game.screen (its big-screen view) |
| Game lobby (Quick start, docs/LOBBY.md) | poster (one game or link card), posters (a shelf in three sizes), showcase (featured carousel), game.live (games you can join now), game.winners, game.record (your own plays and best), lobby.header (logo, line, coins), lobby.row (a chat-list row that opens the games page), game.frame (one game's play screen on a page) |
| Time (Time) | booking (book a time), scheduled (post later) |
| Numbers (Events) | analytics (charts per day, managers only) |
| Live (Rooms, Files, Screens) | room (group call or stage), call (rings people), media.gallery, media.player, screen.pair, screen.remote |
| Starters (Templates) | hero (page header with picture and buttons), tiles / tile (app-style grid), links / link (services or links with price and action), chats (the chat list), calls.recent (call buttons and recent calls), conversation (one chat with its bar; on the talk.screen page it is every opened chat) |
| Photo books (Blueprint) | photobook.book (cover and pages to swipe, read full screen, Edit book for managers), photobook.shelf (covers of the space's books, plus one per #tag album) |
| Custom logic | miniapp (a sandboxed app from /apps, with a config prop); the public library's miniapps.library (browse, try, add, remix), miniapps.mine (publish and update your apps) and miniapps.review (admins), from the App library kit (Mini-apps) |
| AI (AI jobs, AI limits) | ai.setup ("Connect your AI": paste the Claude key), ai.chat (the builder chat; props placeholder, model, steps), ai.model (model size, streaming, finish pushes), ai.notes (what the AI remembers about you), ai.jobs (latest AI work with Stop), ai.allowance (what the viewer has left today, with a bar), hq.aiUsage (managers: today's use by person, space and bots, and whose key pays) |
| Sharing (Templates) | share.save (save pages or the space, with the list of what's taken out), share.library (browse, preview, add or switch), share.mine (your pieces: publish, update, take out), share.review (admins) |
| Search (docs/SEARCH.md) | search (one box across pages, records, messages, posts, people and files; sources, collection / stream / feed, limit; each person only finds what they could open) |
| Bots and automations (AI) | bots.list (bots and automations with an on/off switch), bots.waiting (actions waiting for you), bots.runs (what happened), bots.run (run one by hand), bots.ask (ask a bot), bots.hooks (webhook addresses, shown once) |
| HQ (system) | hq.proposals, hq.pages, hq.library, hq.styles, hq.access, hq.connections, hq.history, hq.blueprint; with the starters, hq.overview (KPIs), hq.starters (switch template) and hq.navigation (tabs or chat list) |
room and call need LiveKit on the server; big media needs R2 (docs/LIVE.md).
Planned: native form fields (date, slider, segmented choice), a calendar, a map, a QR code, a timer and a pay-by-card button.
switcher takes labels (one per child; a missing one uses the child's title), look (segments, tabs or pills) and start. Only the open part is drawn, so the other parts' live data isn't loaded until someone opens them. In Edit mode every part shows, so each can be changed.
Props #
- A prop is
string,number,booleanorstring[]. Richer values (objects) are passed as a JSON string, asrecords.form'sdefaultsdoes, so every prop stays checkable. - Words go in props (
label,empty,submit,done), so every word is a setting. - Looks never go in props. No colours, fonts or sizes; a different look is a style, a page look or a part override (Styles).
stack'sgap(px) is the one layout number a block holds today. - Links:
https://…opens outside the app;#page-idopens a page of the space;chat:<key>opens a conversation,aithe AI chat, plusmailto:andtel:. Images must behttps://.
Hiding a block #
visibleTo: ["coach", "parent"] shows the block only to those roles; managers always see everything. The server leaves hidden blocks out of what it sends, so they never reach the device (People).
How blocks are drawn #
src/components/Renderer.tsx holds one small function per kind in RENDERERS. It draws with React Native, so the same code runs on iOS, Android and the web. Every renderer reads its look from useWorkspace() and the helpers in src/lib/workspace.tsx (surface, bodyText, headingText, gap, color) and never writes a colour, font or size of its own. An unknown kind shows a quiet "Unknown block" line instead of crashing.
Kits keep their renderers in their own folder (src/kits/<kit>/) in a *_RENDERERS table (SOCIAL_RENDERERS, LIVE_RENDERERS, LEDGER_RENDERERS). Renderer.tsx looks a kind up in RENDERERS first, then in the kit tables when the block draws; the tables are not spread in when the file loads, because that made an import cycle that crashed the app on start. Kit block descriptions are spread into BLOCKS. Blocks that show live data (records, posts, votes) read it through Convex queries, which update on every device as soon as anything changes.
Adding a block #
- Describe it in
BLOCKSinconvex/lib/blocks.ts:kind, a one-sentenceaboutthe AI reads, itsprops(type, required, about) and whether it holdschildren. - Draw it: add a renderer to
RENDERERSinsrc/components/Renderer.tsx(or to your kit's own*_RENDERERStable, added to the lookup list inkitRenderer). Put anything bigger than a few lines in its own component file. If it has an icon, use<Icon name="…" />(Device). - Cross-check it if it points at another part of the blueprint (a collection, a stream, an app): add the check to the
walkincheckBlueprint(convex/lib/blueprint.ts), so a broken reference is refused with a readable message. - Regenerate the catalog:
npm run docs.
HQ and the AI pick the block up from the list; nothing else changes.
Good blocks #
- Abstract. Name it by what it is (
stat,records.list), never by one use (streakCounter). If two kits would want slightly different versions, it's one block with a prop. - Cheap. Lists page their data (no more than they show); timers, sensors, media and animation run only while
useLive()is true; images are sized. See Performance. - Native. Use the platform's own controls and icons where they exist (Device).
- Safe. A block can't run code, fetch a URL or read data its viewer isn't allowed to see. The server checks permissions; the block only asks.
- Accessible. Real buttons with labels, images with
alt, text that scales with the style.