@vibes.diy/prompts 14.3.123 → 14.3.124

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
@@ -1383,7 +1383,7 @@ function Shop() {
1383
1383
 
1384
1384
  When the instruction names somebody by their handle — "let my friend priya-k see and add to my seed-swap shelf", "an expedition notebook for me and theo" — **the build's part is the rule that admits a membership and the screen that writes one; the membership itself is a record.** It is the same `member` doc the rule admits and the invite surface above writes, saved for that handle in the asker's own tree — or on the second object the ask names — once the rules are live, by whoever is driving the build acting as the maker, read back, and only then spoken of as in. An app whose first build carried the rules and no invite screen still states this shape in full, in `access.js`'s member branch: the type and fields that branch admits are the record a later save writes, and an edit that adds the screen afterwards writes that same record.
1385
1385
 
1386
- So the record's shape is a contract the files state plainly. The `_id` is fixed by the place and the handle — `` `member:${me.userHandle}:${userHandle}` `` in the person's tree, `` `member:${boardId}:${userHandle}` `` on a board — so a second attempt is the same record rather than a twin, and a read by that id answers "is this person already in?" on its own. The fields are the ones the rule reads: `type`, `userHandle`, `addedBy` set to the writer's own handle, and the object's id when the membership is on a second object. The `member` rule admits an identical re-put, so a replay lands quietly. The tree's channel stays its person's because their own writes grant it, and a second object keeps its creator in the collaboration through its own record, independently of every content and invitation record. One invite handler in `App.jsx` writes exactly that shape, which is what makes the app's own screen and any later save agree on one record.
1386
+ So the record's shape is a contract the files state plainly. The `_id` is fixed by the place and the handle — `` `member:${me.userHandle}:${userHandle}` `` in the person's tree, `` `member:${boardId}:${userHandle}` `` on a board — so a second attempt is the same record rather than a twin, and a read by that id answers "is this person already in?" on its own. The fields are the ones the rule reads: `type`, `userHandle`, `addedBy` set to the writer's own handle, and the object's id when the membership is on a second object. The notes rules above route every record to the writer's own channel and read no tree field, so the record below carries none. When an app's rules instead make every record name its tree in a field before any type branch — `studioHandle`, refused with "a record names its tree" when absent — the member record carries the same `studioHandle` as the tree it joins, in the `can.create` preview that decides whether the add-a-friend control shows and in the record the invite saves; build both with one `memberFor(handle)` so they cannot drift, and a preview that leaves the field out is refused and hides the control for everyone. The `member` rule admits an identical re-put, so a replay lands quietly. The tree's channel stays its person's because their own writes grant it, and a second object keeps its creator in the collaboration through its own record, independently of every content and invitation record. One invite handler in `App.jsx` writes exactly that shape, which is what makes the app's own screen and any later save agree on one record.
1387
1387
 
1388
1388
  ```jsx
1389
1389
  const { HandleInput } = useViewer();
package/llms/richtext.md CHANGED
@@ -406,6 +406,35 @@ overlap (overlapping puts collide on the document's sequence, as in the
406
406
  saving section above). The `Input` stays `onChange={setTitle}`; the effect
407
407
  does the saving.
408
408
 
409
+ ## The first draft stays open through its first save
410
+
411
+ **A draft the empty view opens stays open through its first save: which
412
+ document is open is state, and the draft joins it before its first put,
413
+ never by a reading of the list.** A blog, journal or recipe book whose list
414
+ is empty may open straight onto a blank draft, so the first post is front
415
+ and centre. That draft's first autosave puts it in the list, so a view that
416
+ chose the editor because the list was empty (`editing ?? (mine.length === 0
417
+ ? blank(id) : null)`) flips back to the list mid-sentence. The put shows the
418
+ draft in the list at once, before the save is confirmed, so handing the
419
+ draft back once the put resolves is one render too late: the editor
420
+ unmounts and remounts, and the cursor is gone. Hand the editor the
421
+ open-state setter and call it in `write` before the put: the draft keeps
422
+ its `_id`, so the editor keeps its `key`, and the writer keeps typing until
423
+ they choose Back.
424
+
425
+ ```jsx
426
+ const [editing, setEditing] = useState(null); // the open draft
427
+ const [firstId] = useState(() => crypto.randomUUID()); // the empty view's draft
428
+ const target = editing ?? (mine.length === 0 ? blank(firstId) : null);
429
+
430
+ // In App: the draft's first change makes it the open draft.
431
+ <PostEditor key={target._id} initial={target} onOpen={setEditing} onClose={() => setEditing(null)} />
432
+
433
+ // In PostEditor's write: the draft becomes the open one, then its put is queued.
434
+ onOpen(next);
435
+ queue.current = queue.current.then(() => database.put(next));
436
+ ```
437
+
409
438
  ## Loading a document lands without a save
410
439
 
411
440
  **A document arriving from the database is loaded with `setContent` and
@@ -657,6 +686,10 @@ warning.
657
686
  React state and only Publish writes it. Save the title after its own
658
687
  pause and in the close step, through the same queued `write` as the page,
659
688
  and let the close step wait for it.
689
+ - **The first draft closes itself after a pause** — the empty list was all
690
+ that held the editor open, and the draft's first save filled the list.
691
+ `write` hands the draft to the open-state setter before the put
692
+ (`onOpen={setEditing}`); a callback after the put lands too late.
660
693
  - **Two editors after a hot reload** — the effect's cleanup did not destroy
661
694
  the editor.
662
695
  - **The page has no styles** — Tiptap ships none, and a multi-file app
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.3.123",
3
+ "version": "14.3.124",
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.123",
43
- "@vibes.diy/call-ai-v2": "14.3.123",
44
- "@vibes.diy/identity": "14.3.123",
45
- "@vibes.diy/use-vibes-types": "14.3.123",
42
+ "@vibes.diy/api-types": "14.3.124",
43
+ "@vibes.diy/call-ai-v2": "14.3.124",
44
+ "@vibes.diy/identity": "14.3.124",
45
+ "@vibes.diy/use-vibes-types": "14.3.124",
46
46
  "arktype": "~2.2.6",
47
47
  "json-schema-faker": "~0.6.3"
48
48
  },
@@ -248,8 +248,12 @@ function AddFriend({ place }) {
248
248
  const { me, can, ready } = useVibe("studio");
249
249
  const { HandleInput } = useViewer();
250
250
  const where = place.loom ? place.loom._id : place.key.studioHandle;
251
+ // The member names the place it joins in the field the rules route on, in the preview and the
252
+ // saved record alike: the tree's own studioHandle, or the loom's loomId. Without it the rules
253
+ // refuse the preview, and the card never shows.
251
254
  const memberFor = (handle) => ({
252
- _id: "member:" + where + ":" + handle, type: "member", ...place.key,
255
+ _id: "member:" + where + ":" + handle, type: "member",
256
+ ...(place.loom ? { loomId: place.loom._id } : { studioHandle: place.key.studioHandle }),
253
257
  userHandle: handle, addedBy: me?.userHandle, authorHandle: me?.userHandle,
254
258
  });
255
259
  const invite = async (handle) => {
@@ -399,7 +403,7 @@ The control and its handler check the **same complete candidate** that `database
399
403
  Write this turn's `access.js` against precisely these shapes: the person's own records route to the channel named by their handle and grant it on every write; a friend's write into that tree needs the channel a `member` record granted them; a second or shared object routes to the channel named by its own `_id` and grants its creator that channel and its admin channel; membership grants the place it names. Keep each record's type, author and place fixed across edits. A bobbin's `_id` equals `bobbin-<authorHandle>-<weavingId>`; the client takes the place and the weaving id from the displayed weaving, and the rule holds them fixed on subsequent writes. Nothing writes on load or on mount: the first screen opens on the person's tree because the channel is named by their handle, and every record exists because the person made it.
400
404
 
401
405
  - **Keep the opening view useful.** The person arrives inside their own tree: the first screen is the app at work, named for the app, with a rename that writes the settings record when they want a name of their own. The header carries the app's name once; a separate name line becomes useful after a rename or a second object. A second-object form joins the object list when useful. Show the person's own tree, each tree a membership lets them into, and the whole readable type-query result of second objects, including ones someone else made; `creatorHandle` identifies their makers.
402
- - **Invite with the actual membership candidate.** When the ask names sharing, a group or other people, include `HandleInput` from `useViewer()` and a handler that saves `{ _id: "member:" + place + ":" + handle, type: "member", studioHandle: me.userHandle, userHandle: handle, addedBy: me.userHandle, authorHandle: me.userHandle }` for the person's own tree, or the same record with `loomId` for a loom. Construct that value after the picker resolves the handle, gate it with `can.create`, then persist the same value. The `member` branch admits exactly that shape: in a tree the writer grants their own channel, on a second object its admin grants that object's channel, and the subject, place and writer stay fixed. A resolved `invite:<id>` is stored as the subject just like a handle. Await a real membership write and readback before saying a named person is in. When the app is a person's own collection, this same control is its first-screen share, with the line "Yours alone — add a friend to share it" beside it; otherwise, when sharing was not asked for, offer inviting as a closing option.
406
+ - **Invite with the actual membership candidate.** When the ask names sharing, a group or other people, include `HandleInput` from `useViewer()` and a handler that saves `{ _id: "member:" + place + ":" + handle, type: "member", studioHandle: me.userHandle, userHandle: handle, addedBy: me.userHandle, authorHandle: me.userHandle }` for the person's own tree, or the same record with `loomId` for a loom. Every member record carries the same `studioHandle` as the tree it joins — the field the rules require of every record in a tree — in the `can.create` preview that decides whether the card shows and in the record the invite saves, both built by one `memberFor`. Construct that value after the picker resolves the handle, gate it with `can.create`, then persist the same value. The `member` branch admits exactly that shape: in a tree the writer grants their own channel, on a second object its admin grants that object's channel, and the subject, place and writer stay fixed. A resolved `invite:<id>` is stored as the subject just like a handle. Await a real membership write and readback before saying a named person is in. When the app is a person's own collection, this same control is its first-screen share, with the line "Yours alone — add a friend to share it" beside it; otherwise, when sharing was not asked for, offer inviting as a closing option.
403
407
  - **Persist the sharing choice.** A public/follower toggle writes the field its rules read (`isPublic`, `sharedWithFollowers`), with the full edited candidate checked before saving. Its who-can-see line sits beside that control and describes the channel the rules actually share. Follow `access.md` for follower consent and channel granularity.
404
408
  - **Protect supporting records.** A membership record containing contact details or a capability token belongs only to its intended readers; choose its private channel accordingly while its grant admits the recipient to the place's channel. Keep contact/token-bearing types distinct from ordinary content and out of content queries and Ask inputs.
405
409
  - **Keep examples and the file protocol consistent.** The first build writes the files and privacy rules the surrounding prompt requires. `seed.json` uses these same types, field names and place keys, with `"@owner"` in writer and tree fields (`authorHandle`, `studioHandle`), and leaves the settings record out so the demo opens named for the app. The access rules admit every example record, and the final `// Summary:` accurately describes those rules; private records begin private. Follow the existing attachment/file protocol for uploaded files and their visibility.
@@ -410,7 +414,7 @@ When the user's prompt hands you **concrete example data** — an image of items
410
414
 
411
415
  **Required starting state is saved by an action, never by the app on load.** A person's own action — a tap, a submit, a pick — is what starts a write; nothing writes on load or on mount. A person's own starting state needs no record: their records live on the channel named by their handle, and the view renders its defaults from constants until the person's own action saves one. App-wide named containers or participants the request requires — the requested pottery studio and its named apprentice — are written by the agent building the app with `put_doc` as the owner during the build, once the rules that admit them are live, and read back; that save is the owner's own action. Give each one a stable identity, write parents and their membership or role grants before what depends on them, and let the opening view show them when they are there and an inviting add flow until they are. `access.js` admits exactly those records, and the app's code holds the handlers a person uses to add more. 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. `seed.json` stays the optional example-content import: a name present only there describes an offered import, not an existing record or grant.
412
416
 
413
- **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 and nothing on load — only what a person's own action 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.
417
+ **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 and nothing on load — only what a person's own action 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. That switch waits for the person to finish: an add flow that saves as they go — a draft that autosaves at each pause — stays open through its own first save, because which entry is open is state, set as the person starts the entry and before its first save, never a reading of the list. 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.
414
418
 
415
419
  **The seed dresses the opening screen.** The screenshot and the owner's first look both see one phone screen, so write the seed for that screen: the richest, most representative state of the app lives on the view it opens to, filled with just enough records to cover that one screen at the size the view draws them — a few rich cards, or a handful more compact rows — a lived-in week rather than a wall. Give that view one of each state the app knows how to show, so every feature is visible at first glance: a canoe club's trip board opens on a trip already paddled, one coming up next and one just posted with its put-in note; a sourdough starter log opens on a feed marked done, one due next, and a fresh loaf with its note. Every sample belongs to the owner, and its names, notes and numbers read like a real person's, so the first screen looks like someone has been happily using the app for a while.
416
420
 
@@ -252,8 +252,12 @@ function AddFriend({ place }) {
252
252
  const { me, can, ready } = useVibe("studio");
253
253
  const { HandleInput } = useViewer();
254
254
  const where = place.loom ? place.loom._id : place.key.studioHandle;
255
+ // The member names the place it joins in the field the rules route on, in the preview and the
256
+ // saved record alike: the tree's own studioHandle, or the loom's loomId. Without it the rules
257
+ // refuse the preview, and the card never shows.
255
258
  const memberFor = (handle) => ({
256
- _id: "member:" + where + ":" + handle, type: "member", ...place.key,
259
+ _id: "member:" + where + ":" + handle, type: "member",
260
+ ...(place.loom ? { loomId: place.loom._id } : { studioHandle: place.key.studioHandle }),
257
261
  userHandle: handle, addedBy: me?.userHandle, authorHandle: me?.userHandle,
258
262
  });
259
263
  const invite = async (handle) => {
@@ -403,7 +407,7 @@ The control and its handler check the **same complete candidate** that `database
403
407
  Write this turn's `access.js` against precisely these shapes: the person's own records route to the channel named by their handle and grant it on every write; a friend's write into that tree needs the channel a `member` record granted them; a second or shared object routes to the channel named by its own `_id` and grants its creator that channel and its admin channel; membership grants the place it names. Keep each record's type, author and place fixed across edits. A bobbin's `_id` equals `bobbin-<authorHandle>-<weavingId>`; the client takes the place and the weaving id from the displayed weaving, and the rule holds them fixed on subsequent writes. Nothing writes on load or on mount: the first screen opens on the person's tree because the channel is named by their handle, and every record exists because the person made it.
404
408
 
405
409
  - **Keep the opening view useful.** The person arrives inside their own tree: the first screen is the app at work, named for the app, with a rename that writes the settings record when they want a name of their own. The header carries the app's name once; a separate name line becomes useful after a rename or a second object. A second-object form joins the object list when useful. Show the person's own tree, each tree a membership lets them into, and the whole readable type-query result of second objects, including ones someone else made; `creatorHandle` identifies their makers.
406
- - **Invite with the actual membership candidate.** When the ask names sharing, a group or other people, include `HandleInput` from `useViewer()` and a handler that saves `{ _id: "member:" + place + ":" + handle, type: "member", studioHandle: me.userHandle, userHandle: handle, addedBy: me.userHandle, authorHandle: me.userHandle }` for the person's own tree, or the same record with `loomId` for a loom. Construct that value after the picker resolves the handle, gate it with `can.create`, then persist the same value. The `member` branch admits exactly that shape: in a tree the writer grants their own channel, on a second object its admin grants that object's channel, and the subject, place and writer stay fixed. A resolved `invite:<id>` is stored as the subject just like a handle. Await a real membership write and readback before saying a named person is in. When the app is a person's own collection, this same control is its first-screen share, with the line "Yours alone — add a friend to share it" beside it; otherwise, when sharing was not asked for, offer inviting as a closing option.
410
+ - **Invite with the actual membership candidate.** When the ask names sharing, a group or other people, include `HandleInput` from `useViewer()` and a handler that saves `{ _id: "member:" + place + ":" + handle, type: "member", studioHandle: me.userHandle, userHandle: handle, addedBy: me.userHandle, authorHandle: me.userHandle }` for the person's own tree, or the same record with `loomId` for a loom. Every member record carries the same `studioHandle` as the tree it joins — the field the rules require of every record in a tree — in the `can.create` preview that decides whether the card shows and in the record the invite saves, both built by one `memberFor`. Construct that value after the picker resolves the handle, gate it with `can.create`, then persist the same value. The `member` branch admits exactly that shape: in a tree the writer grants their own channel, on a second object its admin grants that object's channel, and the subject, place and writer stay fixed. A resolved `invite:<id>` is stored as the subject just like a handle. Await a real membership write and readback before saying a named person is in. When the app is a person's own collection, this same control is its first-screen share, with the line "Yours alone — add a friend to share it" beside it; otherwise, when sharing was not asked for, offer inviting as a closing option.
407
411
  - **Persist the sharing choice.** A public/follower toggle writes the field its rules read (`isPublic`, `sharedWithFollowers`), with the full edited candidate checked before saving. Its who-can-see line sits beside that control and describes the channel the rules actually share. Follow `access.md` for follower consent and channel granularity.
408
412
  - **Protect supporting records.** A membership record containing contact details or a capability token belongs only to its intended readers; choose its private channel accordingly while its grant admits the recipient to the place's channel. Keep contact/token-bearing types distinct from ordinary content and out of content queries and Ask inputs.
409
413
  - **Keep examples and the file protocol consistent.** The first build writes the files and privacy rules the surrounding prompt requires. `seed.json` uses these same types, field names and place keys, with `"@owner"` in writer and tree fields (`authorHandle`, `studioHandle`), and leaves the settings record out so the demo opens named for the app. The access rules admit every example record, and the final `// Summary:` accurately describes those rules; private records begin private. Follow the existing attachment/file protocol for uploaded files and their visibility.
@@ -414,7 +418,7 @@ When the user's prompt hands you **concrete example data** — an image of items
414
418
 
415
419
  **Required starting state is saved by an action, never by the app on load.** A person's own action — a tap, a submit, a pick — is what starts a write; nothing writes on load or on mount. A person's own starting state needs no record: their records live on the channel named by their handle, and the view renders its defaults from constants until the person's own action saves one. App-wide named containers or participants the request requires — the requested pottery studio and its named apprentice — are written by the agent building the app with `put_doc` as the owner during the build, once the rules that admit them are live, and read back; that save is the owner's own action. Give each one a stable identity, write parents and their membership or role grants before what depends on them, and let the opening view show them when they are there and an inviting add flow until they are. `access.js` admits exactly those records, and the app's code holds the handlers a person uses to add more. 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. `seed.json` stays the optional example-content import: a name present only there describes an offered import, not an existing record or grant.
416
420
 
417
- **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 and nothing on load — only what a person's own action 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.
421
+ **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 and nothing on load — only what a person's own action 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. That switch waits for the person to finish: an add flow that saves as they go — a draft that autosaves at each pause — stays open through its own first save, because which entry is open is state, set as the person starts the entry and before its first save, never a reading of the list. 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.
418
422
 
419
423
  **The seed dresses the opening screen.** The screenshot and the owner's first look both see one phone screen, so write the seed for that screen: the richest, most representative state of the app lives on the view it opens to, filled with just enough records to cover that one screen at the size the view draws them — a few rich cards, or a handful more compact rows — a lived-in week rather than a wall. Give that view one of each state the app knows how to show, so every feature is visible at first glance: a canoe club's trip board opens on a trip already paddled, one coming up next and one just posted with its put-in note; a sourdough starter log opens on a feed marked done, one due next, and a fresh loaf with its note. Every sample belongs to the owner, and its names, notes and numbers read like a real person's, so the first screen looks like someone has been happily using the app for a while.
420
424
 
package/system-prompt.md CHANGED
@@ -724,7 +724,7 @@ When the user's prompt hands you **concrete example data** — an image of items
724
724
 
725
725
  **Required starting state is saved by an action, never by the app on load.** A person's own action — a tap, a submit, a pick — is what starts a write; nothing writes on load or on mount. A person's own starting state needs no record: their records live on the channel named by their handle, and the view renders its defaults from constants until the person's own action saves one. App-wide named containers or participants the request requires — the requested pottery studio and its named apprentice — are written by the agent building the app with `put_doc` as the owner during the build, once the rules that admit them are live, and read back; that save is the owner's own action. Give each one a stable identity, write parents and their membership or role grants before what depends on them, and let the opening view show them when they are there and an inviting add flow until they are. `access.js` admits exactly those records, and the app's code holds the handlers a person uses to add more. 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. `seed.json` stays the optional example-content import: a name present only there describes an offered import, not an existing record or grant.
726
726
 
727
- **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 and nothing on load — only what a person's own action 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
+ **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 and nothing on load — only what a person's own action 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. That switch waits for the person to finish: an add flow that saves as they go — a draft that autosaves at each pause — stays open through its own first save, because which entry is open is state, set as the person starts the entry and before its first save, never a reading of the list. 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.
728
728
 
729
729
  **The seed dresses the opening screen.** The screenshot and the owner's first look both see one phone screen, so write the seed for that screen: the richest, most representative state of the app lives on the view it opens to, filled with just enough records to cover that one screen at the size the view draws them — a few rich cards, or a handful more compact rows — a lived-in week rather than a wall. Give that view one of each state the app knows how to show, so every feature is visible at first glance: a canoe club's trip board opens on a trip already paddled, one coming up next and one just posted with its put-in note; a sourdough starter log opens on a feed marked done, one due next, and a fresh loaf with its note. Every sample belongs to the owner, and its names, notes and numbers read like a real person's, so the first screen looks like someone has been happily using the app for a while.
730
730