Data
Two kinds of live data, both kept out of the blueprint: records (things people add, from the Collections engine) and events (things that happened, from the Events engine).
Collections (ready) #
A collection is the shape of a list of things, in blueprint.collections. Products, sign-ups, tasks, bookings, scores and notes are all collections with different fields. No new database table is ever needed. Code: convex/lib/collections.ts, convex/records.ts. Code that writes records must also write text: recordText(data) (convex/lib/search.ts) so search finds them; after upgrading a deployment, run npx convex run kits/search/search:_backfill once.
{ "id": "orders", "name": "Orders", "fields": [
{ "id": "item", "label": "Item", "type": "text", "required": true },
{ "id": "qty", "label": "How many", "type": "number" },
{ "id": "status", "label": "Status", "type": "choice", "options": ["New", "Packed", "Sent"] }
] }
- 1 to 40 fields. Ids: lowercase, digits and dashes.
- Changing a collection's shape is a blueprint change, saved and undoable. Records already saved keep their values.
- Removing a kit removes its collection shapes but keeps the records, so adding it back brings them back.
Field types #
Every value is checked and cleaned on the server before it's saved.
| Type | Stored as | Rule |
|---|---|---|
text | string | up to 500 characters |
longtext | string | up to 8,000 characters |
number | number | any finite number; 1,200 is read as 1200 |
money | number | like number; the currency comes with the Ledger engine |
date | string | 2026-10-20 (a time may follow) |
choice | string | one of options (at least two) |
checkbox | boolean | |
email | string | looks like an email, up to 200 |
phone | string | up to 500 |
url | string | must start with https:// |
Planned: files and images, a person, a link to another record, a place, rich text.
Showing and collecting records #
| Block | Does |
|---|---|
records.form | Adds a record. fields picks which to ask; defaults (a JSON string) sets values on every new one, like {"status":"New"}. |
records.list | Lists records, newest first. With groupBy naming a choice field, it's a board with one column per option. |
records.count | How many records there are. |
Record views #
More ways to show and write any collection: a journal, a gratitude log, a habit or workout tracker, a reading list, a team check-in. Described in convex/lib/records/views.ts, drawn by src/kits/records.
| Block | Does |
|---|---|
records.composer | A question, the chip field's options to tap (a mood), and a button that opens the full-screen writing sheet |
records.timeline | Records under month or day headers, with search (words and #tags) and Write |
records.calendar | A month coloured by each day's chip; tap a day to read or write for it |
records.streak | Days in a row, the best streak, the total, this week as dots, an optional goal, a nudge until today has one |
records.memories | "On this day": a week, a month, six months and each year ago; hidden when there are none |
records.prompt | A daily prompt (the same all day), "another one", and Write, which saves the prompt on the record |
records.stats | Insights over the last N days: days written, records, words, best streak, a bar per chip option |
Props name which field plays which part, so they fit any collection: title (text), body (longtext), chip (a choice field, with icons and tones per option), date (the record's day; without one, the day it was written), tags (#tags; ones in the body count too), and field (where the prompt is saved). "none" or "" turns a part off, and a field the collection lacks is simply not drawn. Every word is a prop (question, note, write, placeholder, saved with {streak}, label, shuffle, empty, heading, nudge, done).
Tapping a record opens a reader with Share, Edit and Delete. The writing sheet is a native page sheet: pick a day (never the future), the prompt, the chip options, a title, the body with a word count, #tags and any other fields; Cancel asks before throwing work away.
Server reads in kits.records.views: days, onDays, search and one. They follow the same rule as records.list (hidden behind every block means hidden on the server), so visibleTo: ["owner"] on a page keeps a journal private. Each read looks at the newest 2,000 records; writing goes through records.add, update and remove.
Who can do what #
| Action | Needs |
|---|---|
| See records | view |
| Add a record, change or delete your own | add |
| Change or delete anyone's | edit |
Each record knows who added it. records.list returns mine and canChange for every row, so a screen shows edit buttons only where they work. The server checks again on every write.
Limits today #
- A list returns up to 200 records by default, 500 at most.
records.countcounts up to 10,000.- Planned: filters, sorting, search, paging and per-collection permissions.
Events #
The Events engine counts what happens in a space, per day and per person (convex/tally.ts, convex/events.ts). It's on once the space has the Analytics or Leaderboards kit.
- Counted for you:
view(page views, also per page),post(messages and posts),record(records added),join(people who joined),play(votes, spins and moves). - Your own events:
events.track({ name })from a block or screen, with a short lowercase name liketap-buyorfinished-level. Anyone who can see the space may send them. - Totals:
events.summary({ days })gives daily totals per name for the last 1 to 90 days (managers only); theanalyticsblock draws them as charts. - Rankings:
events.leaders({ name, days })ranks people by how often they did something;ledger.leaderboardwitheventshows it. - Counts are kept as running totals, so reading them never scans every row.
Planned: the AI reads totals (never raw personal data) to suggest improvements; mini-apps send events through the bridge; sums of values, not only counts. Analytics belong to each space's owner; our own product analytics are separate and never mixed in.