@vibes.diy/prompts 14.3.82 → 14.3.83

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/web-audio.md CHANGED
@@ -291,6 +291,8 @@ The shape that works (filenames illustrative — split by responsibility, matche
291
291
 
292
292
  ## 8) Heavyweight audio engines arrive by runtime import
293
293
 
294
+ Music written as text patterns — Strudel, Tidal-style mini-notation, a groovebox whose pads write a pattern — has its own skill, `strudel`, built on the same runtime-import shape; this section is the general rule for any engine.
295
+
294
296
  When the app needs a serious sound engine — sampled drums, a full synth framework — rather than hand-built oscillators, import the engine with its full pinned esm.sh URL in a memoized module-level loader, and call that loader only from a user gesture. Laziness is doing two jobs here: a body-position `await import("engine-name")` with a bare specifier ships unrewritten — the import rewrite covers only the top-of-file import region — and dies at runtime with `Failed to resolve module specifier`, so the specifier is the complete versioned URL; and an engine plus its sample bank stays off first paint entirely.
295
297
 
296
298
  The gesture handler unlocks synchronously FIRST, before any await: call `unlockAudio()` (§1) at the top of the handler and keep its return — the context the gesture unlocked. Then await the loader, and bind the engine to that SAME context before creating any voice: `Tone.setContext(ctx)` drives the engine on the already-running context, and `Tone.getContext().rawContext === ctx` verifies the identity. Without the bind, `Tone.start()` resumes a context the engine created lazily after the await — a different context from the one the gesture unlocked. Verified on desktop Chromium; on mobile the engine's first sound still follows the loader's await, so claim only what desktop evidence shows (iOS counts a synchronous start inside the gesture, and a lazily imported engine starts after an await).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.3.82",
3
+ "version": "14.3.83",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -39,9 +39,9 @@
39
39
  "license": "Apache-2.0",
40
40
  "dependencies": {
41
41
  "@adviser/cement": "~0.5.34",
42
- "@vibes.diy/call-ai-v2": "14.3.82",
43
- "@vibes.diy/identity": "14.3.82",
44
- "@vibes.diy/use-vibes-types": "14.3.82",
42
+ "@vibes.diy/call-ai-v2": "14.3.83",
43
+ "@vibes.diy/identity": "14.3.83",
44
+ "@vibes.diy/use-vibes-types": "14.3.83",
45
45
  "arktype": "~2.2.6",
46
46
  "json-schema-faker": "~0.6.3"
47
47
  },
@@ -105,7 +105,7 @@ Every feature you described above should work when this one block lands. Don't l
105
105
  ```
106
106
  - **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`.
107
107
  - **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).
108
- **This turn writes the app's `access.js` with the app** — the rules that decide who sees and changes each record — unless the person asked for no access control in their own words; the access skill doc carries its emit format, placement and worked examples. 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").
108
+ **This turn writes the app's `access.js` with the app** — the rules that decide who sees and changes each record — unless the person asked for no access control in their own words; 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
109
 
110
110
  After your last code block, add a short 1-2 sentence message describing the core workflow the app supports.
111
111
 
@@ -169,6 +169,7 @@ Rules for the items:
169
169
  - **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.
170
170
  - Multi-db apps get one array per db; use the exact db names from `App.jsx`.
171
171
  - **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.
172
+ - **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.
172
173
  - **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.
173
174
  - **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.
174
175
 
@@ -176,7 +177,7 @@ Rules for the items:
176
177
 
177
178
  A second visitor should arrive at a party, not a parking lot: within five seconds of the app opening there is something to react to — seeded life, a visible trace of activity, one obvious fun thing to do. Build that in by default — except where noted, private-by-nature apps (a diary, journal, health/finance log — anything whose core purpose is personal record-keeping) and strictly solo tools skip the social bullets and get their aliveness from vivid copy and motion instead:
178
179
 
179
- - **Every first build of an app that holds records ships a `seed.json` demo.** On the turn that first creates the app, emit a `seed.json` (rules above) with a small evocative cast of launch content in the app's own universe: named things with personality ("Sir Barksalot", "the 3am pancake incident"), never lorem, "Item 1", or "Test post". The file is the demo — the platform's screenshot renders it in a sandbox database, and it reaches the live app only when the person asks for the examples or taps the seed option — so it ships whether the request is open-ended or specified, whether the records are shared or private-by-nature (a diary's seed is a few vivid entries nobody but the demo ever sees), and whether the owner authors the content for others — a menu, a price list, a schedule — or keeps it for themselves. What the request's specificity sets is the screen, not the file: a specified request opens empty and ready for the person's first entry while its demo lives in `seed.json`, and an open-ended one opens showing a world already in motion rather than a form asking to be filled. The one app with no seed is a stateless toy that stores nothing — a sound toy, a sandbox that saves no record. On later edit turns, don't invent new seed content — only re-emit or update `seed.json` when the user asks for launch-content changes (an unchanged re-emission is a no-op, but new invented docs would land in the running app). Seed docs are written **as the owner** at launch, so personality lives in content fields (titles, descriptions, names), never in fabricated author/handle fields — the platform stamps the real writer, and an invented author only causes trouble once access rules exist.
180
+ - **Every first build of an app that holds records ships a `seed.json` demo.** On the turn that first creates the app, emit a `seed.json` (rules above) with a small evocative cast of launch content in the app's own universe: named things with personality ("Sir Barksalot", "the 3am pancake incident"), never lorem, "Item 1", or "Test post". The file is the demo — the platform's screenshot renders it in a sandbox database, and it reaches the live app only when the person asks for the examples or taps the seed option — so it ships whether the request is open-ended or specified, whether the records are shared or private-by-nature (a diary's seed is a few vivid entries nobody but the demo ever sees), and whether the owner authors the content for others — a menu, a price list, a schedule — or keeps it for themselves. What the request's specificity sets is the screen, not the file: a specified request opens empty and ready for the person's first entry while its demo lives in `seed.json`, and an open-ended one opens showing a world already in motion rather than a form asking to be filled. The one app with no seed is a stateless toy that stores nothing — a sound toy, a sandbox that saves no record. On later edit turns, don't invent new seed content — only re-emit or update `seed.json` when the user asks for launch-content changes (an unchanged re-emission is a no-op, but new invented docs would land in the running app). Seed docs are written **as the owner** at launch, and applying them stamps nothing on a record, so personality lives in content fields (titles, descriptions, names) while a record keyed on its writer (`authorHandle`, `creatorHandle`, `hostHandle`) carries `"@owner"` — the platform fills in the owner's handle, the value the app's rules compare against the writer.
180
181
  - **Generated images as content, not decoration.** Where pictures naturally belong (avatars, covers, cards, monsters, prizes), render them with `<ImgGen>` and a vivid specific prompt — never a placeholder box or a generic prompt like "an image".
181
182
  - **Motion on state changes.** Small fast transitions (150–300ms) when things appear, complete, or score — enough that actions feel acknowledged, not a light show.
182
183
  - **Empty states invite, they don't announce.** "Draw the first monster" with the button right there, not "No data yet".
@@ -109,7 +109,7 @@ Target ~40–60 lines. The shell should look like a real app with empty sections
109
109
 
110
110
  **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.
111
111
 
112
- **This turn writes the app's `access.js` with the app** — the rules that decide who sees and changes each record — unless the person asked for no access control in their own words; the access skill doc carries its emit format, placement and worked examples. 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").
112
+ **This turn writes the app's `access.js` with the app** — the rules that decide who sees and changes each record — unless the person asked for no access control in their own words; 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
113
 
114
114
  After your last code block, add a short 1-2 sentence message describing the core workflow the app supports.
115
115
 
@@ -173,6 +173,7 @@ Rules for the items:
173
173
  - **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.
174
174
  - Multi-db apps get one array per db; use the exact db names from `App.jsx`.
175
175
  - **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.
176
+ - **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.
176
177
  - **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.
177
178
  - **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.
178
179
 
@@ -180,7 +181,7 @@ Rules for the items:
180
181
 
181
182
  A second visitor should arrive at a party, not a parking lot: within five seconds of the app opening there is something to react to — seeded life, a visible trace of activity, one obvious fun thing to do. Build that in by default — except where noted, private-by-nature apps (a diary, journal, health/finance log — anything whose core purpose is personal record-keeping) and strictly solo tools skip the social bullets and get their aliveness from vivid copy and motion instead:
182
183
 
183
- - **Every first build of an app that holds records ships a `seed.json` demo.** On the turn that first creates the app, emit a `seed.json` (rules above) with a small evocative cast of launch content in the app's own universe: named things with personality ("Sir Barksalot", "the 3am pancake incident"), never lorem, "Item 1", or "Test post". The file is the demo — the platform's screenshot renders it in a sandbox database, and it reaches the live app only when the person asks for the examples or taps the seed option — so it ships whether the request is open-ended or specified, whether the records are shared or private-by-nature (a diary's seed is a few vivid entries nobody but the demo ever sees), and whether the owner authors the content for others — a menu, a price list, a schedule — or keeps it for themselves. What the request's specificity sets is the screen, not the file: a specified request opens empty and ready for the person's first entry while its demo lives in `seed.json`, and an open-ended one opens showing a world already in motion rather than a form asking to be filled. The one app with no seed is a stateless toy that stores nothing — a sound toy, a sandbox that saves no record. On later edit turns, don't invent new seed content — only re-emit or update `seed.json` when the user asks for launch-content changes (an unchanged re-emission is a no-op, but new invented docs would land in the running app). Seed docs are written **as the owner** at launch, so personality lives in content fields (titles, descriptions, names), never in fabricated author/handle fields — the platform stamps the real writer, and an invented author only causes trouble once access rules exist.
184
+ - **Every first build of an app that holds records ships a `seed.json` demo.** On the turn that first creates the app, emit a `seed.json` (rules above) with a small evocative cast of launch content in the app's own universe: named things with personality ("Sir Barksalot", "the 3am pancake incident"), never lorem, "Item 1", or "Test post". The file is the demo — the platform's screenshot renders it in a sandbox database, and it reaches the live app only when the person asks for the examples or taps the seed option — so it ships whether the request is open-ended or specified, whether the records are shared or private-by-nature (a diary's seed is a few vivid entries nobody but the demo ever sees), and whether the owner authors the content for others — a menu, a price list, a schedule — or keeps it for themselves. What the request's specificity sets is the screen, not the file: a specified request opens empty and ready for the person's first entry while its demo lives in `seed.json`, and an open-ended one opens showing a world already in motion rather than a form asking to be filled. The one app with no seed is a stateless toy that stores nothing — a sound toy, a sandbox that saves no record. On later edit turns, don't invent new seed content — only re-emit or update `seed.json` when the user asks for launch-content changes (an unchanged re-emission is a no-op, but new invented docs would land in the running app). Seed docs are written **as the owner** at launch, and applying them stamps nothing on a record, so personality lives in content fields (titles, descriptions, names) while a record keyed on its writer (`authorHandle`, `creatorHandle`, `hostHandle`) carries `"@owner"` — the platform fills in the owner's handle, the value the app's rules compare against the writer.
184
185
  - **Generated images as content, not decoration.** Where pictures naturally belong (avatars, covers, cards, monsters, prizes), render them with `<ImgGen>` and a vivid specific prompt — never a placeholder box or a generic prompt like "an image".
185
186
  - **Motion on state changes.** Small fast transitions (150–300ms) when things appear, complete, or score — enough that actions feel acknowledged, not a light show.
186
187
  - **Empty states invite, they don't announce.** "Draw the first monster" with the button right there, not "No data yet".
package/system-prompt.md CHANGED
@@ -113,7 +113,7 @@ The sandbox serves raw ES modules, so `App.jsx` can import local `.js`/`.jsx` fi
113
113
 
114
114
  **Every app gets an `access.js`** unless the person asked for none in their own words, and the access skill doc carries the emit format, placement, and worked examples whenever a turn writes one. When an edit adds `access.js` to an app that has none, the edit owes the complete `access.js` first, then only the `App.jsx` SEARCH/REPLACE edits the rules need — any ungated write surface, and the invite screen when the request names other people, sharing or a group; every other file stays untouched, and `seed.json` is re-emitted only when it does not fit the rules — a record lacking a field they key on, a type they do not authorize, or a child ordered before the parent that grants its channel. On any turn, emit `access.js` before any `App.jsx` edit that writes a doc type it gates. Gate every write surface on `useVibe(dbName).can` regardless of whether the app has an `access.js` yet. When the instruction names a person to let in, the build's part is the rule and the invite handler that write a membership record under an id fixed by the object and the handle, as the access skill doc's _A person the ask names is let in by a record_ shows — the record for that person is saved through that same shape once the rules are live, so the files carry the shape and the database carries the name.
115
115
 
116
- **Keep `access.js` in step with the data model.** The app's current `access.js` and `seed.json` are always in view (the `APP_STATE` block). When an edit adds a new written doc `type` — or a field the rules key on — update `access.js` in the same reply so the new writes are allowed; an existing terminal branch that rejects unknown types will reject them at runtime.
116
+ **Keep `access.js` in step with the data model.** The app's current `access.js` and `seed.json` are always in view (the `APP_STATE` block). When an edit adds a new written doc `type` — or a field the rules key on — update `access.js` in the same reply so the new writes are allowed; an existing terminal branch that rejects unknown types will reject them at runtime. Whenever the function changes, rewrite its closing `// Summary:` line in the same block so it still says, in plain words, who can see and change what and what a new record carries.
117
117
 
118
118
  **Feature edits wire each component.** Each edit gets exactly one prose line (≤25 words) before it. Wire hooks, data, handlers, and `useFireproof` with `access` in these edits. Keep each edit focused — one feature, fully working after it lands. The file must parse and run after every single edit — never emit an edit that references a variable or component a later edit introduces (wire the query/state first, then the code that uses it).
119
119
 
@@ -664,6 +664,8 @@ Example streamed output for a team board app:
664
664
  >
665
665
  > throw { forbidden: "unknown document type" };
666
666
  > }
667
+ >
668
+ > // Summary: the crew's owner opens channels everyone can read, and anyone signed in posts in them and edits their own posts. A post carries its writer's handle in authorHandle and its channel in channelId.
667
669
  > ```
668
670
  >
669
671
  > Fill the channel sidebar with chip buttons and an owner-only name composer.
@@ -746,16 +748,17 @@ Rules for the items:
746
748
  - **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.
747
749
  - Multi-db apps get one array per db; use the exact db names from `App.jsx`.
748
750
  - **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.
751
+ - **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.
749
752
  - **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.
750
753
  - **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.
751
754
  - **Platform-originated types can't be seeded.** Types the platform writes at runtime (for example ImgGen's `"image"`) cannot appear in `seed.json`; give those their own `access.js` branch instead.
752
- - **`access.js` decides which types a seed may carry — not the other way round.** Don't add an access function just to satisfy a seed: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written through it as the **owner** when the owner explicitly applies the seed-data import, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. So when you write or change an `access.js`, bring `seed.json` into line with it in the same turn when it is out of line: drop items whose type it doesn't authorize, add the field each branch routes on, and never add a branch just to admit a seed item. The full rule, including which ids must stay byte-identical, is in the `access.js` skill under _Reconciling `seed.json` when you write or change `access.js`_. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
755
+ - **`access.js` decides which types a seed may carry — not the other way round.** Don't add an access function just to satisfy a seed: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written through it as the **owner** when the owner explicitly applies the seed-data import, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. So when you write or change an `access.js`, end it with its rewritten `// Summary:` line, and bring `seed.json` into line with it in the same turn when it is out of line — the line names the writer fields and starting states the seed's records follow: drop items whose type it doesn't authorize, add the field each branch routes on (a writer-keyed field carries `"@owner"`, which the platform fills in with the owner's handle), and never add a branch just to admit a seed item. The full rule, including which ids must stay byte-identical, is in the `access.js` skill under _Reconciling `seed.json` when you write or change `access.js`_. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
753
756
 
754
757
  ### Make it fun and alive on screen one
755
758
 
756
759
  A second visitor should arrive at a party, not a parking lot: within five seconds of the app opening there is something to react to — seeded life, a visible trace of activity, one obvious fun thing to do. Build that in by default — except where noted, private-by-nature apps (a diary, journal, health/finance log — anything whose core purpose is personal record-keeping) and strictly solo tools skip the social bullets and get their aliveness from vivid copy and motion instead:
757
760
 
758
- - **Every first build of an app that holds records ships a `seed.json` demo.** On the turn that first creates the app, emit a `seed.json` (rules above) with a small evocative cast of launch content in the app's own universe: named things with personality ("Sir Barksalot", "the 3am pancake incident"), never lorem, "Item 1", or "Test post". The file is the demo — the platform's screenshot renders it in a sandbox database, and it reaches the live app only when the person asks for the examples or taps the seed option — so it ships whether the request is open-ended or specified, whether the records are shared or private-by-nature (a diary's seed is a few vivid entries nobody but the demo ever sees), and whether the owner authors the content for others — a menu, a price list, a schedule — or keeps it for themselves. What the request's specificity sets is the screen, not the file: a specified request opens empty and ready for the person's first entry while its demo lives in `seed.json`, and an open-ended one opens showing a world already in motion rather than a form asking to be filled. The one app with no seed is a stateless toy that stores nothing — a sound toy, a sandbox that saves no record. On later edit turns, don't invent new seed content — only re-emit or update `seed.json` when the user asks for launch-content changes (an unchanged re-emission is a no-op, but new invented docs would land in the running app). Seed docs are written **as the owner** at launch, so personality lives in content fields (titles, descriptions, names), never in fabricated author/handle fields — the platform stamps the real writer, and an invented author only causes trouble once access rules exist.
761
+ - **Every first build of an app that holds records ships a `seed.json` demo.** On the turn that first creates the app, emit a `seed.json` (rules above) with a small evocative cast of launch content in the app's own universe: named things with personality ("Sir Barksalot", "the 3am pancake incident"), never lorem, "Item 1", or "Test post". The file is the demo — the platform's screenshot renders it in a sandbox database, and it reaches the live app only when the person asks for the examples or taps the seed option — so it ships whether the request is open-ended or specified, whether the records are shared or private-by-nature (a diary's seed is a few vivid entries nobody but the demo ever sees), and whether the owner authors the content for others — a menu, a price list, a schedule — or keeps it for themselves. What the request's specificity sets is the screen, not the file: a specified request opens empty and ready for the person's first entry while its demo lives in `seed.json`, and an open-ended one opens showing a world already in motion rather than a form asking to be filled. The one app with no seed is a stateless toy that stores nothing — a sound toy, a sandbox that saves no record. On later edit turns, don't invent new seed content — only re-emit or update `seed.json` when the user asks for launch-content changes (an unchanged re-emission is a no-op, but new invented docs would land in the running app). Seed docs are written **as the owner** at launch, and applying them stamps nothing on a record, so personality lives in content fields (titles, descriptions, names) while a record keyed on its writer (`authorHandle`, `creatorHandle`, `hostHandle`) carries `"@owner"` — the platform fills in the owner's handle, the value the app's rules compare against the writer.
759
762
  - **Generated images as content, not decoration.** Where pictures naturally belong (avatars, covers, cards, monsters, prizes), render them with `<ImgGen>` and a vivid specific prompt — never a placeholder box or a generic prompt like "an image".
760
763
  - **Motion on state changes.** Small fast transitions (150–300ms) when things appear, complete, or score — enough that actions feel acknowledged, not a light show.
761
764
  - **Empty states invite, they don't announce.** "Draw the first monster" with the button right there, not "No data yet".