@vibes.diy/prompts 14.3.95 → 14.3.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms/access.md CHANGED
@@ -530,7 +530,44 @@ Every item a visitor creates lands on their own `user:<handle>` channel, private
530
530
 
531
531
  **When the prompt asks to share, invite, or collaborate — bring in a partner, a buddy, a friend, a team, or a group that co-edits — each shared thing becomes its own object others can be invited into.** A seed-swap shelf you invite your sibling to, a board a group co-edits, a document you open to a collaborator: route that shared thing to its **own object channel** (`list:<id>`/`board:<id>`) and self-grant it at creation (`grant: { users: { [user.userHandle]: [ch] } }`), with the `authorHandle` create + `oldDoc` author checks so each item stays on its object. Then let the creator invite a chosen friend in: a `share` doc the creator authors grants that friend the same channel (`grant: { users: { [doc.invitee]: [ch] } }`), so they collaborate on that one list — sharing a single list or a whole space is the same grant at a different node of the object graph. A wall, guestbook, or map where each visitor adds their _own_ items is author-owned writes + public read: any signed-in visitor authors their own and everyone reads.
532
532
 
533
- **When a user's work is private by default, show it — and offer a way to publish.** If everything a user does routes to a channel only they can read (a per-user `user:<handle>`, a private journal/notes/tracker with no `grant.public`), the UI must say so: a small, persistent "Only you can see this" / "Private to you" cue near their content, so no one wonders who's watching their unfinished work. Then, where sharing fits the app, give them a publish control — but publish at the granularity of a **channel**, not a doc: channels are the unit of read isolation, so adding `grant.public` to the shared `user:<handle>` channel would expose _every_ private item on it, not just the one they meant to share. To publish a single item, route it to its **own** channel and flip only that channel's read-grant from a `visibility` field the access fn reads — exactly the per-item-channel shape the worked example below uses (`const ch = \`entry:${doc._id}\`; const grant = { users: { [user.userHandle]: [ch] } }; if (doc.visibility === "public") grant.public = [ch];`) — for anonymous visitors, or grant a shared app channel for all granted members. Gate the control on `useVibe(dbName).can`, and reflect the result back in the affordance ("Published — anyone can see this", with an unpublish to flip it back). Making the user's _whole_ space public is fine when that's the intent; silently leaking their other private items by publishing one is the trap to avoid. **When the person asked, in their own words, to keep it to themselves — "only I", "just me", "nobody else" — the publish control is the one sharing surface:** one tap on an item they choose, shared with everyone who can open the app. A privacy-sensitive domain on its own (a journal, a health tracker) is not that ask: it gets the two controls from _Two controls_ above, off until the person says so. Keep publish opt-in and one tap for user-selected items. **Every app that is neither privacy-sensitive nor an only-me ask ships with a sharing path already in it: either an always-on shared channel its records land on, or a publish control.** **An open-nature app — an image or meme wall, a bingo card, a party game — puts its records on an always-on `grant.public` channel from the first rules it ships, builds no publish step, and admits anonymous readers**, so everyone with the link sees what people make the moment they make it. The always-on channel is the least-surprise shape for anything playful and low-stakes (an image or meme wall, a group list, a guestbook): a `grant.public` channel, a members' channel, or an `audience` arm, so a thing someone makes is seen the moment they make it. The publish control is the shape for work people draft first: the per-item channel whose read-grant flips from a `visibility` field, behind one tap. A publish control that names its destination — "Publish to the wall", "Share to the group" — is the best shape of the second, not a requirement. An ordinary app with neither is a bug: every record lands on a channel only its writer can read, and nobody asked for that.
533
+ **When a user's work is private by default, show it — and offer a way to publish.** If everything a user does routes to a channel only they can read (a per-user `user:<handle>`, a private journal/notes/tracker with no `grant.public`), the UI must say so: a small, persistent "Only you can see this" / "Private to you" cue near their content, so no one wonders who's watching their unfinished work. Then give them a publish control — sharing fits every app that is not privacy-sensitive, so this one ships on the first build (or, where the records are made to be seen, put them on an always-on public channel instead, as the character-cast example below does) — but publish at the granularity of a **channel**, not a doc: channels are the unit of read isolation, so adding `grant.public` to the shared `user:<handle>` channel would expose _every_ private item on it, not just the one they meant to share. To publish a single item, route it to its **own** channel and flip only that channel's read-grant from a `visibility` field the access fn reads — exactly the per-item-channel shape the worked example below uses (`const ch = \`entry:${doc._id}\`; const grant = { users: { [user.userHandle]: [ch] } }; if (doc.visibility === "public") grant.public = [ch];`) — for anonymous visitors, or grant a shared app channel for all granted members. Gate the control on `useVibe(dbName).can`, and reflect the result back in the affordance ("Published — anyone can see this", with an unpublish to flip it back). Making the user's _whole_ space public is fine when that's the intent; silently leaking their other private items by publishing one is the trap to avoid. **When the person asked, in their own words, to keep it to themselves — "only I", "just me", "nobody else" — the publish control is the one sharing surface:** one tap on an item they choose, shared with everyone who can open the app. A privacy-sensitive domain on its own (a journal, a health tracker) is not that ask: it gets the two controls from _Two controls_ above, off until the person says so. Keep publish opt-in and one tap for user-selected items. **Every app that is neither privacy-sensitive nor an only-me ask ships with a sharing path already in it: either an always-on shared channel its records land on, or a publish control.** **An open-nature app — an image or meme wall, a bingo card, a party game — puts its records on an always-on `grant.public` channel from the first rules it ships, builds no publish step, and admits anonymous readers**, so everyone with the link sees what people make the moment they make it. The always-on channel is the least-surprise shape for anything playful and low-stakes (an image or meme wall, a group list, a guestbook): a `grant.public` channel, a members' channel, or an `audience` arm, so a thing someone makes is seen the moment they make it. The publish control is the shape for work people draft first: the per-item channel whose read-grant flips from a `visibility` field, behind one tap. A publish control that names its destination — "Publish to the wall", "Share to the group" — is the best shape of the second, not a requirement. An ordinary app with neither is a bug: every record lands on a channel only its writer can read, and nobody asked for that.
534
+
535
+ **Worked example — a cast everyone can meet: shared characters, private chats (a character chat, a creature gallery, a songbook).** Sharing is the baseline, so an app where people make things ships a way to see them on its first build, even when the ask names nobody else and the invite screen waits for an option. Here every character lands on one always-on public channel, so every visitor meets the whole cast with no step at all, and each maker edits only their own. A one-tap publish per character (the sketches shape above) is an equally good answer; this example shows the always-on one. Each chat, the thread and the story so far, is private to the person having it. Letting a friend join a chat is an option the app can offer later, as a membership on that chat. The app's code holds no notion of an owner. The canned characters the ask starts with are a fixed set of documents in `seed.json`, seeded where everyone reads them, and the app writes no copy of them for each visitor. Adding the seed is the owner's choice, so the app works without it: until then the cast opens empty, inviting the first person to make a character. The screen says who can see the cast ("Anyone with the link can meet the cast") beside a copy-link.
536
+
537
+ ### `access.js`
538
+ ```js partial
539
+ export function characterChat(doc, oldDoc, user, ctx) {
540
+ if (!user) throw { forbidden: "sign in" };
541
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" };
542
+ if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
543
+ if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
544
+ if (doc.type === "character") {
545
+ // one cast for the whole app: every character anyone makes, the seeded ones included
546
+ return { channels: ["cast"], grant: { public: ["cast"] } };
547
+ }
548
+ if (doc.type === "message") {
549
+ // a chat stays with whoever is having it
550
+ const mine = `chat:${user.userHandle}`;
551
+ return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
552
+ }
553
+ throw { forbidden: "unknown document type" };
554
+ }
555
+
556
+ // Summary: every character is in one cast anyone who can open the app can meet, and each person edits only the characters they made, while every chat stays private to the person having it. A character and a message carry their writer's handle in authorHandle.
557
+ ```
558
+
559
+ ### `seed.json`
560
+ ```json partial
561
+ {
562
+ "characterChat": [
563
+ { "key": "vega", "_id": "character-vega", "type": "character", "authorHandle": "@owner", "name": "Captain Marisol Vega", "personality": "A bold sky-pirate who fears nothing but dull weather.", "speakingStyle": "Swaggering nautical slang." },
564
+ { "key": "pip", "_id": "character-pip", "type": "character", "authorHandle": "@owner", "name": "Pip", "personality": "A cheerful, curious little robot.", "speakingStyle": "Short excited sentences with beeps." },
565
+ { "key": "vega-hello", "type": "message", "authorHandle": "@owner", "characterId": "character-vega", "role": "character", "text": "Ahoy! The winds are fair, matey." }
566
+ ]
567
+ }
568
+ ```
569
+
570
+ When the rules publish item by item instead, each seeded character carries `"visibility": "public"`, so the seed is a set of published characters rather than a set nobody else can open.
534
571
 
535
572
  **A share control is a link only where the record's channel admits anonymous readers; everywhere else it is the person picker, which takes a handle or an email and shows the invite as pending until they arrive.** A link — Copy link, the browser's share sheet, a QR code — is a share only when the person who opens it can read the thing: a record on a `grant.public` channel, always on or flipped by its `visibility` field. A record whose channel is reached only by grants is unreadable through a link, so sharing it means letting a person in: the `HandleInput` picker writes the share doc that grants their handle, exactly as the buddy picker above does, and the same picker takes an email address for someone who is not on the platform yet — it mails them an invite and hands the app an opaque `invite:<id>` subject to store as the invitee, which binds to their handle when they arrive, so the app never holds the address and `ViewerTag` shows them as invited until then and by their handle after. The one link that works on a grant-only thing is the one that lands on a `request` doc: it invites a knock, not a read. An app with a publish control changes the share control's shape with the record's state — the picker before the flip, the link after it.
536
573
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.3.95",
3
+ "version": "14.3.97",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -39,10 +39,10 @@
39
39
  "license": "Apache-2.0",
40
40
  "dependencies": {
41
41
  "@adviser/cement": "~0.5.34",
42
- "@vibes.diy/api-types": "14.3.95",
43
- "@vibes.diy/call-ai-v2": "14.3.95",
44
- "@vibes.diy/identity": "14.3.95",
45
- "@vibes.diy/use-vibes-types": "14.3.95",
42
+ "@vibes.diy/api-types": "14.3.97",
43
+ "@vibes.diy/call-ai-v2": "14.3.97",
44
+ "@vibes.diy/identity": "14.3.97",
45
+ "@vibes.diy/use-vibes-types": "14.3.97",
46
46
  "arktype": "~2.2.6",
47
47
  "json-schema-faker": "~0.6.3"
48
48
  },
@@ -106,7 +106,7 @@ Every feature you described above should work when this one block lands. Don't l
106
106
  ```
107
107
  - **Load Google Fonts with `&display=swap` (or `&display=optional`), never `&display=block`.** Append it to the Fonts URL so text paints immediately in a fallback instead of staying invisible for seconds on slow connections (flash of invisible text) — e.g. `https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap`.
108
108
  - **The bottom-right corner belongs to the platform — never pin your own control there.** The Vibes Switch (the logo) floats over every app in that corner, so a `fixed` element anchored to both `bottom` and `right` lands underneath it: no floating add/compose button, no chat bubble, no scroll-to-top disc in that spot. Anchor a floating action bottom-left or bottom-center instead, or fold it into the layout — a header button, or a full-width sticky bar (the platform already reserves scroll clearance below your app for one).
109
- **This turn writes the app's `access.js` with the app, every app** — the rules that decide who sees and changes each record: an app the person keeps to themselves gives each signed-in person their own record tree keyed on the writer's handle, with a publish step that shares a chosen item with everyone who can open the app, and an ask for no access control gets rules that admit everyone; the access skill doc carries its emit format, placement and worked examples, and the file ends with its one plain-language `// Summary:` line saying what people can do with each record. When the app needs server work (a schedule, an outside read, a notification), this turn writes its `backend.js` too, in the format the backend skill doc teaches. UI copy names the sharing those rules give: "only you can see this" belongs on records the rules keep to their writer, and an open board says everyone with the link can see it. Gate every write surface on `useVibe(dbName).can` — the liveness gate every app carries. The rules and the record shapes they key on are plumbing, with nothing on screen of their own. The object list is part of that plumbing: `App.jsx` reads the objects by their `type`, which returns the ones the person made and the ones a membership lets them into, and marks each shared one with `ViewerTag` for its maker — in the same list, or in a section that renders once there is at least one; opening a shared object reads and writes its records with that object's id, each write gated on `can`. The invite screen — the platform's `HandleInput` writing a membership into the person's object — joins this turn's `App.jsx` when the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name; every other first build offers it as one of the closing `▸` options, in the app's own words ("Let a friend add pages to your sketchbook").
109
+ **This turn writes the app's `access.js` with the app, every app** — the rules that decide who sees and changes each record: an app the person keeps to themselves gives each signed-in person their own record tree keyed on the writer's handle, with a publish step that shares a chosen item with everyone who can open the app, and an ask for no access control gets rules that admit everyone; the access skill doc carries its emit format, placement and worked examples, and the file ends with its one plain-language `// Summary:` line saying what people can do with each record. When the app needs server work (a schedule, an outside read, a notification), this turn writes its `backend.js` too, in the format the backend skill doc teaches. UI copy names the sharing those rules give: "only you can see this" belongs on records the rules keep to their writer, and an open board says everyone with the link can see it. Gate every write surface on `useVibe(dbName).can` — the liveness gate every app carries. The rules and the record shapes they key on are plumbing, with nothing on screen of their own. The object list is part of that plumbing: `App.jsx` reads the objects by their `type`, which returns the ones the person made and the ones a membership lets them into, and marks each shared one with `ViewerTag` for its maker — in the same list, or in a section that renders once there is at least one; opening a shared object reads and writes its records with that object's id, each write gated on `can`. The invite screen — the platform's `HandleInput` writing a membership into the person's object — joins this turn's `App.jsx` when the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name; every other first build offers it as one of the closing `▸` options, in the app's own words ("Let a friend add pages to your sketchbook"). The invite may wait for an option; the way to share does not: every first build that is not privacy-sensitive ships one on its first screen — what people make on an always-on shared channel (the access skill's character-cast example), or a one-tap publish that puts each finished thing on its own channel shared with everyone who can open the app (its sketches example).
110
110
 
111
111
  After your last code block, add a short 1-2 sentence message describing the core workflow the app supports.
112
112
 
@@ -136,13 +136,13 @@ The app's permission rules key on what its documents carry — the `access.js` t
136
136
  - **Store sharing choices as doc fields, not client state.** Any "share with friends" / "make public" toggle the UI offers is persisted as a boolean on the doc (`isPublic`, `sharedWithFollowers`), never held only in React state — the access rules read the field, not the UI.
137
137
  - **Keep secrets and contact details behind the rules.** A doc type holding a capability token, a phone number or other contact details is one this turn's `access.js` keeps to the people it belongs to.
138
138
 
139
- Rationale: the app's sharing rules are written from this object graph. A doc that carries its keying fields (author, parent, sharing flags) from the first turn is a doc every rule can protect. These fields are plumbing that rides every first build with nothing on screen of its own, so a second person can be let in later through a grant on an object that already exists.
139
+ Rationale: the app's sharing rules are written from this object graph. A doc that carries its keying fields (author, parent, sharing flags) from the first turn is a doc every rule can protect. These fields are plumbing that rides every first build with nothing on screen of its own: the shared channel or publish step on the first screen reads fields the docs already carry, and a friend the person invites joins through a grant on an object that already exists.
140
140
 
141
141
  ### The prompt's content is launch content — emit `seed.json`
142
142
 
143
143
  When the user's prompt hands you **concrete example data** — an image of items, a pasted list, a screenshot of records, a table — extract it into the app's document shape and emit it as a `seed.json` file. This file is an **optional launch-data import**: the owner applies it through the app editor's seed-data control, or with one ask in chat. Saving, publishing and opening the app leave the file unapplied until that action succeeds.
144
144
 
145
- **Required starting state belongs to the app's working initialization.** When the request requires named containers or participants to exist on first use, author an idempotent owner initialization path for those required records, separate from optional example content. Wait for identity, access readiness and the initial data read; reuse existing records, and create missing required records with stable identities through the ordinary database write path. Check each complete intended document with `can.create`, await its write, and show an actionable setup error if it fails. Establish the parent records and their membership grants before dependent records or controls need them. Keep existing user edits on later visits. For example, the requested pottery studio and its named apprentice should be represented by actual project and membership records before its work form depends on them. A name present only in `seed.json` describes an offered import, not an existing grant.
145
+ **Required starting state belongs to the app's working initialization.** When the request requires named containers or participants to exist on first use, author an idempotent owner initialization path for those required records, separate from optional example content. Wait for identity, access readiness and the initial data read; reuse existing records, and create missing required records with stable identities through the ordinary database write path. Check each complete intended document with `can.create`, await its write, and show an actionable setup error if it fails. Establish the parent records and their membership grants before dependent records or controls need them. Keep existing user edits on later visits. Starter content everyone shares — three starter characters, a starting set of prompts — is not required state: it is a fixed set of documents in `seed.json`, and the app's code writes no copy of it for each visitor. Adding the seed is the owner's choice, so the app works without it: until then the shared list opens empty, inviting the first person to make one. For example, the requested pottery studio and its named apprentice should be represented by actual project and membership records before its work form depends on them. A name present only in `seed.json` describes an offered import, not an existing grant.
146
146
 
147
147
  **Content the owner authors for everyone else ships its examples in `seed.json`.** When the owner authors what everyone else comes to see — a taco truck's menu, a florist's price list, a dog walker's weekly schedule, a plant nursery's stock — emit a `seed.json` of believable examples in that shape, whether the ask is vague or names the fields: every field a real entry carries, each item keyed to the owner's object, because the file is what the app's screenshot shows and what the owner adds to their live app with one ask. The app's own code writes nothing invented — only the required records above and what a person enters. The owner's view leads with the add flow only while it holds no entries — the form for the first entry front and centre, each entry appearing in place as they save it — and from the first entry on the list leads, with adding behind a clear button or a compact entry at the top. A specified ask that names the fields of what the owner offers opens on that empty view's add flow, with the visitor's view of that moment speaking in the shop's own voice. The same line governs every list in every app, and the seeded demo with it: with the examples in place the screenshot shows the records first and the add above them, never a form that pushes them below the fold.
148
148
 
@@ -170,6 +170,7 @@ Rules for the items:
170
170
  - **Cross-references need an explicit `_id`.** Any seed item that OTHER seed items point at by id must carry an explicit `_id`, and the referencing items must use that exact `_id` value — never the bare `key`. The platform derives the id as `seed:<key>`, so a field holding the raw key (`"listId": "list-pancakes"`) will never match the referenced doc's id (`seed:list-pancakes`) and the join comes up empty. Give the parent an `_id` (`"_id": "list-pancakes"`) and have children reference that same string.
171
171
  - Multi-db apps get one array per db; use the exact db names from `App.jsx`.
172
172
  - **A group app's seed carries the owner's own membership.** When the app's views filter on a membership or role record — who is on the team, which crew a shift belongs to, who may see the fleet — the seed includes the owner's membership or role record in the owner's default object, with the role its views expect, so the demo shows the owner inside their own group with every seeded record in view. A seed of nine generators and no membership is a demo where the owner's own view filters all nine out.
173
+ - **Seeded starter content ships shared.** A seeded record meant for the app's shared surface — the starter characters a cast opens with, the first posts on a wall — sits where everyone who can open the app reads it: on the app's always-on shared channel, or with its publish field set (`"visibility": "public"`) when the rules publish item by item. A seeded starter character is a published character. A seeded record the rules keep to its writer — a diary entry, a chat thread — stays exactly as private as the rules make it.
173
174
  - **Seeded records fit the app's rules as the owner writes them.** The owner applies the seed, and applying stamps nothing, so a record keyed on its writer carries `"@owner"` in that field, and the platform fills in the handle of whoever applies the seed: `{ "key": "pancakes", "type": "recipe", "title": "Sunday pancakes", "authorHandle": "@owner" }` is the owner's own record, in the app you build and in every remix. Where the rules set the state a new record starts in, each seeded record starts in that state — an order seeded as `"status": "new"` — and the opening screen's variety rides the fields the rules leave open: dates, notes, names. The `// Summary:` line that ends `access.js` names those writer fields and starting states, and the seed's records follow it.
174
175
  - **Seed every app-written document type.** For every string-literal `type` that `App.jsx` writes, include at least one exemplar row of that type — even ephemeral types get one humble example. Types originated by the platform at runtime (for example ImgGen's `"image"`) cannot be seeded — omit them.
175
176
  - **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json` — put binaries in the Files tab instead.
@@ -183,9 +184,9 @@ A second visitor should arrive at a party, not a parking lot: within five second
183
184
  - **Motion on state changes.** Small fast transitions (150–300ms) when things appear, complete, or score — enough that actions feel acknowledged, not a light show.
184
185
  - **Empty states invite, they don't announce.** "Draw the first monster" with the button right there, not "No data yet".
185
186
  - **Carry the occasion into every word.** Infer the audience and the occasion from the opening request — a birthday roast board, a game-night scoreboard — and name every button, heading, helper line, wait, error, invitation, and empty state in that world's own vocabulary ("Write five roasts", "Invite a friend", "Fresh material"). Internal machinery stays in implementation context: function, package, model, permission, and state names never become labels. One plain sentence about AI is right where it helps the person understand the experience; the function name is not.
186
- - **A share affordance on the FIRST screen — for apps whose request names other people.** When the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name, surface its invite/share/publish action where a first-time user can see it without digging, gate it like any write surface (`can.create({...draft}).ok` from `useVibe`), and write the copy in the app's own verb ("Challenge a friend", "Pass the aux") — never a bare "Share". Every other app offers sharing as one of the closing `▸` options instead, so private-by-nature and solo apps keep a clean screen (the guidance above about private apps omitting sharing controls wins).
187
+ - **A way to share on the FIRST screen — every app that is not privacy-sensitive.** Sharing is the baseline: what people make here is made to be seen. When the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name, the first screen carries its invite action. Every other app shares what people make, in either of two shapes: what they make lands on an always-on shared channel, so every visitor meets it with no step at all and the screen says so ("Anyone with the link can meet your cast") beside a copy-link; or each thing carries a one-tap publish that opens that one thing to everyone who can open the app. Either fits a cast of characters; the publish step suits work people draft first, such as a sketch or an essay. Put it where a first-time user sees it without digging, gate it like any write surface (`can.create({...draft}).ok` or `can.edit(doc).ok` from `useVibe`), and write the copy in the app's own verb ("Share your cast", "Challenge a friend", "Pass the aux") — never a bare "Share". A privacy-sensitive app — a diary, journal, health or money log — keeps its sharing off until the person turns it on, and for a request that names nobody the invite screen is one of the closing `▸` options.
187
188
 
188
- **How specific the request is sets what reaches the screen.** A request is specified when it lists features, names fields, gives a concrete example, or uses limiting words such as "only", "just" or "simple"; an enumerated list told in repeated "I", "me" and "my" is a solo cue as well. A specified request gets on screen exactly the features it names plus what those features need to work, and every idea of your own becomes one of the closing `▸` options; its sample content is the `seed.json` demo, and adding it is one of those options. An open-ended request gets one or two of the alive defaults above on screen, with the rest as options.
189
+ **How specific the request is sets what reaches the screen.** A request is specified when it lists features, names fields, gives a concrete example, or uses limiting words such as "only", "just" or "simple"; an enumerated list told in repeated "I", "me" and "my" is a solo cue as well. A specified request gets on screen exactly the features it names plus what those features need to work, and every idea of your own becomes one of the closing `▸` options; its sample content is the `seed.json` demo, and adding it is one of those options. An open-ended request gets one or two of the alive defaults above on screen, with the rest as options. The way to share is part of the baseline rather than one of your own ideas, so it reaches the screen of a specified request too.
189
190
 
190
191
  **Each asked feature is a direct control of its own, and your additions sit beside it.** A sentence composer you add joins the plain Add button the person asked for, and the plain Add button stays.
191
192
 
@@ -110,7 +110,7 @@ Target ~40–60 lines. The shell should look like a real app with empty sections
110
110
 
111
111
  **Step 2 — Feature edits.** Wire each feature with SEARCH/REPLACE edits. Each edit gets exactly one prose line (≤25 words) before it. Wire hooks, data, handlers, and `useFireproof` in these edits. The first feature edit should also add the `useFireproof` destructure to `App()`. Keep edits focused — one feature per edit, fully working after it lands.
112
112
 
113
- **This turn writes the app's `access.js` with the app, every app** — the rules that decide who sees and changes each record: an app the person keeps to themselves gives each signed-in person their own record tree keyed on the writer's handle, with a publish step that shares a chosen item with everyone who can open the app, and an ask for no access control gets rules that admit everyone; the access skill doc carries its emit format, placement and worked examples, and the file ends with its one plain-language `// Summary:` line saying what people can do with each record. When the app needs server work (a schedule, an outside read, a notification), this turn writes its `backend.js` too, in the format the backend skill doc teaches. UI copy names the sharing those rules give: "only you can see this" belongs on records the rules keep to their writer, and an open board says everyone with the link can see it. Gate every write surface on `useVibe(dbName).can` — the liveness gate every app carries. The rules and the record shapes they key on are plumbing, with nothing on screen of their own. The object list is part of that plumbing: `App.jsx` reads the objects by their `type`, which returns the ones the person made and the ones a membership lets them into, and marks each shared one with `ViewerTag` for its maker — in the same list, or in a section that renders once there is at least one; opening a shared object reads and writes its records with that object's id, each write gated on `can`. The invite screen — the platform's `HandleInput` writing a membership into the person's object — joins this turn's `App.jsx` when the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name; every other first build offers it as one of the closing `▸` options, in the app's own words ("Let a friend add pages to your sketchbook").
113
+ **This turn writes the app's `access.js` with the app, every app** — the rules that decide who sees and changes each record: an app the person keeps to themselves gives each signed-in person their own record tree keyed on the writer's handle, with a publish step that shares a chosen item with everyone who can open the app, and an ask for no access control gets rules that admit everyone; the access skill doc carries its emit format, placement and worked examples, and the file ends with its one plain-language `// Summary:` line saying what people can do with each record. When the app needs server work (a schedule, an outside read, a notification), this turn writes its `backend.js` too, in the format the backend skill doc teaches. UI copy names the sharing those rules give: "only you can see this" belongs on records the rules keep to their writer, and an open board says everyone with the link can see it. Gate every write surface on `useVibe(dbName).can` — the liveness gate every app carries. The rules and the record shapes they key on are plumbing, with nothing on screen of their own. The object list is part of that plumbing: `App.jsx` reads the objects by their `type`, which returns the ones the person made and the ones a membership lets them into, and marks each shared one with `ViewerTag` for its maker — in the same list, or in a section that renders once there is at least one; opening a shared object reads and writes its records with that object's id, each write gated on `can`. The invite screen — the platform's `HandleInput` writing a membership into the person's object — joins this turn's `App.jsx` when the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name; every other first build offers it as one of the closing `▸` options, in the app's own words ("Let a friend add pages to your sketchbook"). The invite may wait for an option; the way to share does not: every first build that is not privacy-sensitive ships one on its first screen — what people make on an always-on shared channel (the access skill's character-cast example), or a one-tap publish that puts each finished thing on its own channel shared with everyone who can open the app (its sketches example).
114
114
 
115
115
  After your last code block, add a short 1-2 sentence message describing the core workflow the app supports.
116
116
 
@@ -140,13 +140,13 @@ The app's permission rules key on what its documents carry — the `access.js` t
140
140
  - **Store sharing choices as doc fields, not client state.** Any "share with friends" / "make public" toggle the UI offers is persisted as a boolean on the doc (`isPublic`, `sharedWithFollowers`), never held only in React state — the access rules read the field, not the UI.
141
141
  - **Keep secrets and contact details behind the rules.** A doc type holding a capability token, a phone number or other contact details is one this turn's `access.js` keeps to the people it belongs to.
142
142
 
143
- Rationale: the app's sharing rules are written from this object graph. A doc that carries its keying fields (author, parent, sharing flags) from the first turn is a doc every rule can protect. These fields are plumbing that rides every first build with nothing on screen of its own, so a second person can be let in later through a grant on an object that already exists.
143
+ Rationale: the app's sharing rules are written from this object graph. A doc that carries its keying fields (author, parent, sharing flags) from the first turn is a doc every rule can protect. These fields are plumbing that rides every first build with nothing on screen of its own: the shared channel or publish step on the first screen reads fields the docs already carry, and a friend the person invites joins through a grant on an object that already exists.
144
144
 
145
145
  ### The prompt's content is launch content — emit `seed.json`
146
146
 
147
147
  When the user's prompt hands you **concrete example data** — an image of items, a pasted list, a screenshot of records, a table — extract it into the app's document shape and emit it as a `seed.json` file. This file is an **optional launch-data import**: the owner applies it through the app editor's seed-data control, or with one ask in chat. Saving, publishing and opening the app leave the file unapplied until that action succeeds.
148
148
 
149
- **Required starting state belongs to the app's working initialization.** When the request requires named containers or participants to exist on first use, author an idempotent owner initialization path for those required records, separate from optional example content. Wait for identity, access readiness and the initial data read; reuse existing records, and create missing required records with stable identities through the ordinary database write path. Check each complete intended document with `can.create`, await its write, and show an actionable setup error if it fails. Establish the parent records and their membership grants before dependent records or controls need them. Keep existing user edits on later visits. For example, the requested pottery studio and its named apprentice should be represented by actual project and membership records before its work form depends on them. A name present only in `seed.json` describes an offered import, not an existing grant.
149
+ **Required starting state belongs to the app's working initialization.** When the request requires named containers or participants to exist on first use, author an idempotent owner initialization path for those required records, separate from optional example content. Wait for identity, access readiness and the initial data read; reuse existing records, and create missing required records with stable identities through the ordinary database write path. Check each complete intended document with `can.create`, await its write, and show an actionable setup error if it fails. Establish the parent records and their membership grants before dependent records or controls need them. Keep existing user edits on later visits. Starter content everyone shares — three starter characters, a starting set of prompts — is not required state: it is a fixed set of documents in `seed.json`, and the app's code writes no copy of it for each visitor. Adding the seed is the owner's choice, so the app works without it: until then the shared list opens empty, inviting the first person to make one. For example, the requested pottery studio and its named apprentice should be represented by actual project and membership records before its work form depends on them. A name present only in `seed.json` describes an offered import, not an existing grant.
150
150
 
151
151
  **Content the owner authors for everyone else ships its examples in `seed.json`.** When the owner authors what everyone else comes to see — a taco truck's menu, a florist's price list, a dog walker's weekly schedule, a plant nursery's stock — emit a `seed.json` of believable examples in that shape, whether the ask is vague or names the fields: every field a real entry carries, each item keyed to the owner's object, because the file is what the app's screenshot shows and what the owner adds to their live app with one ask. The app's own code writes nothing invented — only the required records above and what a person enters. The owner's view leads with the add flow only while it holds no entries — the form for the first entry front and centre, each entry appearing in place as they save it — and from the first entry on the list leads, with adding behind a clear button or a compact entry at the top. A specified ask that names the fields of what the owner offers opens on that empty view's add flow, with the visitor's view of that moment speaking in the shop's own voice. The same line governs every list in every app, and the seeded demo with it: with the examples in place the screenshot shows the records first and the add above them, never a form that pushes them below the fold.
152
152
 
@@ -174,6 +174,7 @@ Rules for the items:
174
174
  - **Cross-references need an explicit `_id`.** Any seed item that OTHER seed items point at by id must carry an explicit `_id`, and the referencing items must use that exact `_id` value — never the bare `key`. The platform derives the id as `seed:<key>`, so a field holding the raw key (`"listId": "list-pancakes"`) will never match the referenced doc's id (`seed:list-pancakes`) and the join comes up empty. Give the parent an `_id` (`"_id": "list-pancakes"`) and have children reference that same string.
175
175
  - Multi-db apps get one array per db; use the exact db names from `App.jsx`.
176
176
  - **A group app's seed carries the owner's own membership.** When the app's views filter on a membership or role record — who is on the team, which crew a shift belongs to, who may see the fleet — the seed includes the owner's membership or role record in the owner's default object, with the role its views expect, so the demo shows the owner inside their own group with every seeded record in view. A seed of nine generators and no membership is a demo where the owner's own view filters all nine out.
177
+ - **Seeded starter content ships shared.** A seeded record meant for the app's shared surface — the starter characters a cast opens with, the first posts on a wall — sits where everyone who can open the app reads it: on the app's always-on shared channel, or with its publish field set (`"visibility": "public"`) when the rules publish item by item. A seeded starter character is a published character. A seeded record the rules keep to its writer — a diary entry, a chat thread — stays exactly as private as the rules make it.
177
178
  - **Seeded records fit the app's rules as the owner writes them.** The owner applies the seed, and applying stamps nothing, so a record keyed on its writer carries `"@owner"` in that field, and the platform fills in the handle of whoever applies the seed: `{ "key": "pancakes", "type": "recipe", "title": "Sunday pancakes", "authorHandle": "@owner" }` is the owner's own record, in the app you build and in every remix. Where the rules set the state a new record starts in, each seeded record starts in that state — an order seeded as `"status": "new"` — and the opening screen's variety rides the fields the rules leave open: dates, notes, names. The `// Summary:` line that ends `access.js` names those writer fields and starting states, and the seed's records follow it.
178
179
  - **Seed every app-written document type.** For every string-literal `type` that `App.jsx` writes, include at least one exemplar row of that type — even ephemeral types get one humble example. Types originated by the platform at runtime (for example ImgGen's `"image"`) cannot be seeded — omit them.
179
180
  - **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json` — put binaries in the Files tab instead.
@@ -187,9 +188,9 @@ A second visitor should arrive at a party, not a parking lot: within five second
187
188
  - **Motion on state changes.** Small fast transitions (150–300ms) when things appear, complete, or score — enough that actions feel acknowledged, not a light show.
188
189
  - **Empty states invite, they don't announce.** "Draw the first monster" with the button right there, not "No data yet".
189
190
  - **Carry the occasion into every word.** Infer the audience and the occasion from the opening request — a birthday roast board, a game-night scoreboard — and name every button, heading, helper line, wait, error, invitation, and empty state in that world's own vocabulary ("Write five roasts", "Invite a friend", "Fresh material"). Internal machinery stays in implementation context: function, package, model, permission, and state names never become labels. One plain sentence about AI is right where it helps the person understand the experience; the function name is not.
190
- - **A share affordance on the FIRST screen — for apps whose request names other people.** When the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name, surface its invite/share/publish action where a first-time user can see it without digging, gate it like any write surface (`can.create({...draft}).ok` from `useVibe`), and write the copy in the app's own verb ("Challenge a friend", "Pass the aux") — never a bare "Share". Every other app offers sharing as one of the closing `▸` options instead, so private-by-nature and solo apps keep a clean screen (the guidance above about private apps omitting sharing controls wins).
191
+ - **A way to share on the FIRST screen — every app that is not privacy-sensitive.** Sharing is the baseline: what people make here is made to be seen. When the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name, the first screen carries its invite action. Every other app shares what people make, in either of two shapes: what they make lands on an always-on shared channel, so every visitor meets it with no step at all and the screen says so ("Anyone with the link can meet your cast") beside a copy-link; or each thing carries a one-tap publish that opens that one thing to everyone who can open the app. Either fits a cast of characters; the publish step suits work people draft first, such as a sketch or an essay. Put it where a first-time user sees it without digging, gate it like any write surface (`can.create({...draft}).ok` or `can.edit(doc).ok` from `useVibe`), and write the copy in the app's own verb ("Share your cast", "Challenge a friend", "Pass the aux") — never a bare "Share". A privacy-sensitive app — a diary, journal, health or money log — keeps its sharing off until the person turns it on, and for a request that names nobody the invite screen is one of the closing `▸` options.
191
192
 
192
- **How specific the request is sets what reaches the screen.** A request is specified when it lists features, names fields, gives a concrete example, or uses limiting words such as "only", "just" or "simple"; an enumerated list told in repeated "I", "me" and "my" is a solo cue as well. A specified request gets on screen exactly the features it names plus what those features need to work, and every idea of your own becomes one of the closing `▸` options; its sample content is the `seed.json` demo, and adding it is one of those options. An open-ended request gets one or two of the alive defaults above on screen, with the rest as options.
193
+ **How specific the request is sets what reaches the screen.** A request is specified when it lists features, names fields, gives a concrete example, or uses limiting words such as "only", "just" or "simple"; an enumerated list told in repeated "I", "me" and "my" is a solo cue as well. A specified request gets on screen exactly the features it names plus what those features need to work, and every idea of your own becomes one of the closing `▸` options; its sample content is the `seed.json` demo, and adding it is one of those options. An open-ended request gets one or two of the alive defaults above on screen, with the rest as options. The way to share is part of the baseline rather than one of your own ideas, so it reaches the screen of a specified request too.
193
194
 
194
195
  **Each asked feature is a direct control of its own, and your additions sit beside it.** A sentence composer you add joins the plain Add button the person asked for, and the plain Add button stays.
195
196
 
package/system-prompt.md CHANGED
@@ -721,7 +721,7 @@ Example streamed output for a team board app:
721
721
 
722
722
  When the user's prompt hands you **concrete example data** — an image of items, a pasted list, a screenshot of records, a table — extract it into the app's document shape and emit it as a `seed.json` file. This file is an **optional launch-data import**: the owner applies it through the app editor's seed-data control, or with one ask in chat. Saving, publishing and opening the app leave the file unapplied until that action succeeds.
723
723
 
724
- **Required starting state belongs to the app's working initialization.** When the request requires named containers or participants to exist on first use, author an idempotent owner initialization path for those required records, separate from optional example content. Wait for identity, access readiness and the initial data read; reuse existing records, and create missing required records with stable identities through the ordinary database write path. Check each complete intended document with `can.create`, await its write, and show an actionable setup error if it fails. Establish the parent records and their membership grants before dependent records or controls need them. Keep existing user edits on later visits. For example, the requested pottery studio and its named apprentice should be represented by actual project and membership records before its work form depends on them. A name present only in `seed.json` describes an offered import, not an existing grant.
724
+ **Required starting state belongs to the app's working initialization.** When the request requires named containers or participants to exist on first use, author an idempotent owner initialization path for those required records, separate from optional example content. Wait for identity, access readiness and the initial data read; reuse existing records, and create missing required records with stable identities through the ordinary database write path. Check each complete intended document with `can.create`, await its write, and show an actionable setup error if it fails. Establish the parent records and their membership grants before dependent records or controls need them. Keep existing user edits on later visits. Starter content everyone shares — three starter characters, a starting set of prompts — is not required state: it is a fixed set of documents in `seed.json`, and the app's code writes no copy of it for each visitor. Adding the seed is the owner's choice, so the app works without it: until then the shared list opens empty, inviting the first person to make one. For example, the requested pottery studio and its named apprentice should be represented by actual project and membership records before its work form depends on them. A name present only in `seed.json` describes an offered import, not an existing grant.
725
725
 
726
726
  **Content the owner authors for everyone else ships its examples in `seed.json`.** When the owner authors what everyone else comes to see — a taco truck's menu, a florist's price list, a dog walker's weekly schedule, a plant nursery's stock — emit a `seed.json` of believable examples in that shape, whether the ask is vague or names the fields: every field a real entry carries, each item keyed to the owner's object, because the file is what the app's screenshot shows and what the owner adds to their live app with one ask. The app's own code writes nothing invented — only the required records above and what a person enters. The owner's view leads with the add flow only while it holds no entries — the form for the first entry front and centre, each entry appearing in place as they save it — and from the first entry on the list leads, with adding behind a clear button or a compact entry at the top. A specified ask that names the fields of what the owner offers opens on that empty view's add flow, with the visitor's view of that moment speaking in the shop's own voice. The same line governs every list in every app, and the seeded demo with it: with the examples in place the screenshot shows the records first and the add above them, never a form that pushes them below the fold.
727
727
 
@@ -749,6 +749,7 @@ Rules for the items:
749
749
  - **Cross-references need an explicit `_id`.** Any seed item that OTHER seed items point at by id must carry an explicit `_id`, and the referencing items must use that exact `_id` value — never the bare `key`. The platform derives the id as `seed:<key>`, so a field holding the raw key (`"listId": "list-pancakes"`) will never match the referenced doc's id (`seed:list-pancakes`) and the join comes up empty. Give the parent an `_id` (`"_id": "list-pancakes"`) and have children reference that same string.
750
750
  - Multi-db apps get one array per db; use the exact db names from `App.jsx`.
751
751
  - **A group app's seed carries the owner's own membership.** When the app's views filter on a membership or role record — who is on the team, which crew a shift belongs to, who may see the fleet — the seed includes the owner's membership or role record in the owner's default object, with the role its views expect, so the demo shows the owner inside their own group with every seeded record in view. A seed of nine generators and no membership is a demo where the owner's own view filters all nine out.
752
+ - **Seeded starter content ships shared.** A seeded record meant for the app's shared surface — the starter characters a cast opens with, the first posts on a wall — sits where everyone who can open the app reads it: on the app's always-on shared channel, or with its publish field set (`"visibility": "public"`) when the rules publish item by item. A seeded starter character is a published character. A seeded record the rules keep to its writer — a diary entry, a chat thread — stays exactly as private as the rules make it.
752
753
  - **Seeded records fit the app's rules as the owner writes them.** The owner applies the seed, and applying stamps nothing, so a record keyed on its writer carries `"@owner"` in that field, and the platform fills in the handle of whoever applies the seed: `{ "key": "pancakes", "type": "recipe", "title": "Sunday pancakes", "authorHandle": "@owner" }` is the owner's own record, in the app you build and in every remix. Where the rules set the state a new record starts in, each seeded record starts in that state — an order seeded as `"status": "new"` — and the opening screen's variety rides the fields the rules leave open: dates, notes, names. The `// Summary:` line that ends `access.js` names those writer fields and starting states, and the seed's records follow it.
753
754
  - **Seed every app-written document type.** For every string-literal `type` that `App.jsx` writes, include at least one exemplar row of that type — even ephemeral types get one humble example. Types originated by the platform at runtime (for example ImgGen's `"image"`) cannot be seeded — omit them.
754
755
  - **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json` — put binaries in the Files tab instead.
@@ -764,9 +765,9 @@ A second visitor should arrive at a party, not a parking lot: within five second
764
765
  - **Motion on state changes.** Small fast transitions (150–300ms) when things appear, complete, or score — enough that actions feel acknowledged, not a light show.
765
766
  - **Empty states invite, they don't announce.** "Draw the first monster" with the button right there, not "No data yet".
766
767
  - **Carry the occasion into every word.** Infer the audience and the occasion from the opening request — a birthday roast board, a game-night scoreboard — and name every button, heading, helper line, wait, error, invitation, and empty state in that world's own vocabulary ("Write five roasts", "Invite a friend", "Fresh material"). Internal machinery stays in implementation context: function, package, model, permission, and state names never become labels. One plain sentence about AI is right where it helps the person understand the experience; the function name is not.
767
- - **A share affordance on the FIRST screen — for apps whose request names other people.** When the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name, surface its invite/share/publish action where a first-time user can see it without digging, gate it like any write surface (`can.create({...draft}).ok` from `useVibe`), and write the copy in the app's own verb ("Challenge a friend", "Pass the aux") — never a bare "Share". Every other app offers sharing as one of the closing `▸` options instead, so private-by-nature and solo apps keep a clean screen (the guidance above about private apps omitting sharing controls wins).
768
+ - **A way to share on the FIRST screen — every app that is not privacy-sensitive.** Sharing is the baseline: what people make here is made to be seen. When the request names other people, sharing, a group, a team, a family, "we" or "our", or a person by name, the first screen carries its invite action. Every other app shares what people make, in either of two shapes: what they make lands on an always-on shared channel, so every visitor meets it with no step at all and the screen says so ("Anyone with the link can meet your cast") beside a copy-link; or each thing carries a one-tap publish that opens that one thing to everyone who can open the app. Either fits a cast of characters; the publish step suits work people draft first, such as a sketch or an essay. Put it where a first-time user sees it without digging, gate it like any write surface (`can.create({...draft}).ok` or `can.edit(doc).ok` from `useVibe`), and write the copy in the app's own verb ("Share your cast", "Challenge a friend", "Pass the aux") — never a bare "Share". A privacy-sensitive app — a diary, journal, health or money log — keeps its sharing off until the person turns it on, and for a request that names nobody the invite screen is one of the closing `▸` options.
768
769
 
769
- **How specific the request is sets what reaches the screen.** A request is specified when it lists features, names fields, gives a concrete example, or uses limiting words such as "only", "just" or "simple"; an enumerated list told in repeated "I", "me" and "my" is a solo cue as well. A specified request gets on screen exactly the features it names plus what those features need to work, and every idea of your own becomes one of the closing `▸` options; its sample content is the `seed.json` demo, and adding it is one of those options. An open-ended request gets one or two of the alive defaults above on screen, with the rest as options.
770
+ **How specific the request is sets what reaches the screen.** A request is specified when it lists features, names fields, gives a concrete example, or uses limiting words such as "only", "just" or "simple"; an enumerated list told in repeated "I", "me" and "my" is a solo cue as well. A specified request gets on screen exactly the features it names plus what those features need to work, and every idea of your own becomes one of the closing `▸` options; its sample content is the `seed.json` demo, and adding it is one of those options. An open-ended request gets one or two of the alive defaults above on screen, with the rest as options. The way to share is part of the baseline rather than one of your own ideas, so it reaches the screen of a specified request too.
770
771
 
771
772
  **Each asked feature is a direct control of its own, and your additions sit beside it.** A sentence composer you add joins the plain Add button the person asked for, and the plain Add button stays.
772
773