@vibes.diy/prompts 14.3.85 → 14.3.87

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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const accessConfig = {
2
2
  name: "access",
3
3
  label: "access-js",
4
- description: "server-side access.js file — the record rules every app ships with: who reads and writes each record, the creator granted at creation, invites into an object, membership/roles, approval-to-join, moderation, private per-user data, DMs. Name it on every first build; the one app that leaves it out is the one whose person asked in their own words for a single-user app or no sharing",
4
+ description: "server-side access.js file — the record rules every app ships with: who reads and writes each record, the creator granted at creation, invites into an object, membership/roles, approval-to-join, moderation, private per-user data, DMs. Name it on every first build, an app the person keeps to themselves included: its rules give each person their own record tree and a publish step",
5
5
  cues: [
6
6
  "private",
7
7
  "privately",
package/llms/access.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Access Control (`access.js`) — permission design
2
2
 
3
- This doc is in view because the app is getting its `access.js`: a new app's first build writes its rules with its screens, and a later edit adds or changes them. Either way the records carry their keys (`creatorHandle` on the object, the parent id on each child, `authorHandle` on every record), and this build writes the rules that key on them. The rules and those keys are plumbing with nothing on screen of their own; the invite screen joins the build when the ask names other people, sharing, a group or a person by name, and is otherwise offered as an option after the build. Every app gets record rules unless the person asked for none in their own words, and the group shape taught below is the resting default. A person the ask names is let in by a record — see _A person the ask names is let in by a record_ below — so the build states the membership record's shape and the invite handler that writes it.
3
+ This doc is in view because the app is getting its `access.js`: a new app's first build writes its rules with its screens, and a later edit adds or changes them. Either way the records carry their keys (`creatorHandle` on the object, the parent id on each child, `authorHandle` on every record), and this build writes the rules that key on them. The rules and those keys are plumbing with nothing on screen of their own; the invite screen joins the build when the ask names other people, sharing, a group or a person by name, and is otherwise offered as an option after the build. Every app gets record rules, and the group shape taught below is the resting default. A person the ask names is let in by a record — see _A person the ask names is let in by a record_ below — so the build states the membership record's shape and the invite handler that writes it.
4
4
 
5
5
  **When an edit adds the rules to an app that already exists, the edit owes only what the rules need.** Read `App.jsx` for the object type, the field that names its creator and the parent id its children carry; name the channels from those records; write the complete `access.js`; then change `App.jsx` only where the rules require it — stable creator membership, typed content queries, any write surface not yet gated on `can`, and the invite screen when the ask names other people, sharing or a group — with SEARCH/REPLACE edits. A file the rules do not touch is not emitted, and stays as it is.
6
6
 
@@ -235,6 +235,7 @@ Each named export gates its own database. A single `/access.js` can gate all dat
235
235
  ```js
236
236
  export function chat(doc, oldDoc, user, ctx) {
237
237
  if (!user) throw { forbidden: "authentication required" };
238
+ if (oldDoc && oldDoc.channelId !== doc.channelId) throw { forbidden: "a record stays in its channel" };
238
239
  ctx.requireAccess(doc.channelId);
239
240
  return { channels: [doc.channelId] };
240
241
  }
@@ -256,6 +257,7 @@ Databases without a matching named export fall through to `export default` if on
256
257
  ```js
257
258
  function crewChat(doc, oldDoc, user, ctx) {
258
259
  if (!user) throw { forbidden: "authentication required" };
260
+ if (oldDoc && oldDoc.channelId !== doc.channelId) throw { forbidden: "a record stays in its channel" };
259
261
  ctx.requireAccess(doc.channelId);
260
262
  return { channels: [doc.channelId] };
261
263
  }
@@ -271,6 +273,7 @@ Use `export default` to gate every database without writing a named export for e
271
273
  ```js
272
274
  export function chat(doc, oldDoc, user, ctx) {
273
275
  if (!user) throw { forbidden: "authentication required" };
276
+ if (oldDoc && oldDoc.channelId !== doc.channelId) throw { forbidden: "a record stays in its channel" };
274
277
  ctx.requireAccess(doc.channelId);
275
278
  return { channels: [doc.channelId] };
276
279
  }
@@ -291,6 +294,8 @@ This is especially useful when an app has many databases.
291
294
 
292
295
  Use `oldDoc` (the previous version of the document) to enforce invariants across updates: `if (oldDoc === null) { /* create-only logic */ }` for new documents, and `if (oldDoc && doc.version <= oldDoc.version) { throw { forbidden: "version must increase" } }` for monotonic versions.
293
296
 
297
+ An access function that dispatches on `doc.type` keeps each record's type: right after the sign-in check, `if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" };`. The branch that runs is chosen by the record being written, so this one line is what keeps an edit from rewriting someone else's record as another kind and skipping the checks its own kind applies. Deletes pass it: the delete gate hands the function the stored record, type and all, marked `_deleted`. The worked examples here that dispatch on more than one type all enforce it.
298
+
294
299
  ### Documents only the app's own backend writes
295
300
 
296
301
  Some documents belong to the machinery rather than to a person: a scheduled run's state doc, a counter a `scheduled` sweep advances, a notification the backend generates. Gate those on the **origin** with `ctx.requireBackend(...)` — one line, no conditional:
@@ -395,6 +400,7 @@ _Each example below closes its type dispatch with the default terminal `throw {
395
400
  ```js partial
396
401
  export function wall(doc, oldDoc, user, ctx) {
397
402
  if (!user) throw { forbidden: "sign in" };
403
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
398
404
 
399
405
  if (doc.type === "channel") {
400
406
  ctx.requireRole("owner");
@@ -472,7 +478,29 @@ export function board(doc, oldDoc, user, ctx) {
472
478
 
473
479
  **Build the permission model around what a newcomer should be able to do.** When a stranger opens the app, they should immediately be able to do the thing it's _for_ — add their own entries, post a note, drop a pin, join a shared canvas. So the default is: every signed-in visitor is a first-class participant who creates their own objects and edits what they created (`doc.authorHandle === user.userHandle`, checking `oldDoc` on updates), from first load, with no one needing to let them in.
474
480
 
475
- **When the person says "no sharing", "no access control" or "keep it single-user", that is the one case where `access.js` is left out altogether:** the app ships with no rules file, and the object id and `authorHandle` its records already carry are what let rules be added later without touching a doc.
481
+ **Every app ships `access.js`. When the person says "just for me", "no sharing" or "keep it single-user", each signed-in person gets their own record tree, and a publish step shares a chosen item with everyone who can open the app.** The rules key on the writer's own handle, so they read the same for the person who made the app and for anyone they invite later. The app's nature is sensitive, and who can open the app is its sharing setting, not the rules: a published item is world-readable in `access.js` and reaches whoever can open the app — its members while it stays private, everyone once the owner opens it up. Publish copy says that plainly ("Published — everyone who can open this app sees it"):
482
+
483
+ ### `access.js`
484
+ ```js partial
485
+ export function sketches(doc, oldDoc, user, ctx) {
486
+ if (!user) throw { forbidden: "sign in" };
487
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
488
+ if (doc.type === "sketch") {
489
+ if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
490
+ if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
491
+ const mine = `user:${user.userHandle}`; // this person's own tree
492
+ const ch = `sketch:${doc._id}`; // the item's own channel
493
+ const grant = { users: { [user.userHandle]: [mine, ch] } };
494
+ if (doc.visibility === "public") grant.public = [ch]; // published: everyone who can open the app
495
+ return { channels: [mine, ch], grant };
496
+ }
497
+ throw { forbidden: "unknown document type" };
498
+ }
499
+
500
+ // Summary: each person's sketches are their own, and a sketch they publish is open to everyone who can open the app. A sketch carries its writer's handle in authorHandle and starts unpublished.
501
+ ```
502
+
503
+ **An ask for "no access control" or "anyone can edit" ships `access.js` too, with rules that admit everyone:** every signed-in visitor writes the shared records and edits their own, and `allowAnonymous: true` opens the same writes to guests when the ask needs no sign-in. When an ask says both, keeping it to themselves decides.
476
504
 
477
505
  **When the person asks for private per-user permissions — "each person sees only their own", "private to every user" — every visitor gets their own private space on a single per-user channel.** A sleep log, a houseplant watering schedule, a savings jar, a notes app or a workout log where the data is one person's own routes every doc that visitor creates to the one channel keyed on their handle — `user:${user.userHandle}` — self-granted so only they read it, with `authorHandle: user.userHandle` fixed at create and held immutable on update (`oldDoc.authorHandle === user.userHandle`). Their whole collection lives on that single private channel, reachable from first load, each visitor's space entirely their own:
478
506
 
@@ -502,7 +530,7 @@ Every item a visitor creates lands on their own `user:<handle>` channel, private
502
530
 
503
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.
504
532
 
505
- **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. **If the person asked, in their own words, to keep it to themselves — "only I", "just me", "nobody else" — do not add publish/share controls.** 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: for only-me apps, omit the publish UI entirely; for share-capable apps, make publish 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, 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.
506
534
 
507
535
  **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.
508
536
 
@@ -512,6 +540,7 @@ Every item a visitor creates lands on their own `user:<handle>` channel, private
512
540
  ```js partial
513
541
  export function pieces(doc, oldDoc, user, ctx) {
514
542
  if (!user) throw { forbidden: "sign in" };
543
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
515
544
 
516
545
  // A piece: a public object anyone proposes; everyone can read and adopt it.
517
546
  if (doc.type === "piece") {
@@ -641,6 +670,7 @@ This example shows the full round-trip — access.js declares channels and grant
641
670
  ```js
642
671
  export function announcements(doc, oldDoc, user, ctx) {
643
672
  if (!user) throw { forbidden: "sign in" };
673
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
644
674
 
645
675
  if (doc.type === "channel") {
646
676
  ctx.requireRole("owner");
@@ -802,6 +832,7 @@ Channels everyone can read, and any signed-in user can post to. The channel doc
802
832
  ```js partial
803
833
  export function chat(doc, oldDoc, user, ctx) {
804
834
  if (!user) throw { forbidden: "sign in" };
835
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
805
836
 
806
837
  if (doc.type === "channel") {
807
838
  ctx.requireRole("owner");
@@ -871,6 +902,7 @@ Reach for this by default, and certainly whenever the prompt says **invite, join
871
902
  ```js partial
872
903
  export default function (doc, oldDoc, user, ctx) {
873
904
  if (!user) throw { forbidden: "sign in" };
905
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
874
906
  const ch = (id) => `list:${id}`;
875
907
 
876
908
  if (doc.type === "list") {
@@ -896,6 +928,9 @@ export default function (doc, oldDoc, user, ctx) {
896
928
  // Any current member invites a peer by handle — grants them the list channel.
897
929
  // Route the share doc to the list channel itself (every member already holds
898
930
  // it), so members see who was added without a second channel to grant.
931
+ // listId is immutable too, so only a member of the list a share is already on
932
+ // changes it — a member of list X never rewrites list Y's share as one on X.
933
+ if (oldDoc && oldDoc.listId !== doc.listId) throw { forbidden: "a share stays on its list" };
899
934
  ctx.requireAccess(ch(doc.listId));
900
935
  return { channels: [ch(doc.listId)], grant: { users: { [doc.invitee]: [ch(doc.listId)] } } };
901
936
  }
@@ -960,6 +995,7 @@ Reach for this variant whenever the app is built around a *thing* people come ba
960
995
  ```js partial
961
996
  export function boards(doc, oldDoc, user, ctx) {
962
997
  if (!user?.userHandle) throw { forbidden: "sign in" };
998
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
963
999
  const safeId = (id) => {
964
1000
  if (typeof id !== "string" || !/^[A-Za-z0-9_-]+$/.test(id)) throw { forbidden: "bad id" };
965
1001
  return id;
@@ -1143,6 +1179,7 @@ That bounds what the build's own closing line says: the rules and the screen it
1143
1179
  ```js partial
1144
1180
  export function chat(doc, oldDoc, user, ctx) {
1145
1181
  if (!user) throw { forbidden: "authentication required" };
1182
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
1146
1183
 
1147
1184
  if (doc.type === "channel-meta") {
1148
1185
  if (doc.ownerHandle !== user.userHandle) throw { forbidden: "not owner" };
@@ -387,4 +387,4 @@ async function addMember(handle) {
387
387
  {canInvite && <HandleInput onChange={addMember} placeholder="Add a friend…" />}
388
388
  ```
389
389
 
390
- Members render with `<ViewerTag userHandle={m.userHandle} />`, the same way any other stored handle does. An app the person asked to keep to themselves — "just for me", "no sharing", "no access control" — needs none of this; everywhere else the picker belongs.
390
+ Members render with `<ViewerTag userHandle={m.userHandle} />`, the same way any other stored handle does. An app the person asked to keep to themselves — "just for me", "no sharing" — builds no picker: each person keeps their own record tree and shares a chosen item with its publish step, and the records stay shaped so a picker can join later. Everywhere else the picker belongs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.3.85",
3
+ "version": "14.3.87",
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.85",
43
- "@vibes.diy/call-ai-v2": "14.3.85",
44
- "@vibes.diy/identity": "14.3.85",
45
- "@vibes.diy/use-vibes-types": "14.3.85",
42
+ "@vibes.diy/api-types": "14.3.87",
43
+ "@vibes.diy/call-ai-v2": "14.3.87",
44
+ "@vibes.diy/identity": "14.3.87",
45
+ "@vibes.diy/use-vibes-types": "14.3.87",
46
46
  "arktype": "~2.2.6",
47
47
  "json-schema-faker": "~0.6.3"
48
48
  },
@@ -54,7 +54,7 @@ A value the person already has in mind stays a direct control when setting or ad
54
54
  - For an app with the callai skill, let an example chip put an example sentence into the same intake box. Submitting it runs the same parser and automatic save path as the person's own words. Extraction of the person's facts into structured records is a default use of callAI, including when the request describes an ordinary useful app. Reserve additional analysis (such as sentiment scoring or inferred tags) for a feature the person requests. A message board can save the person's message directly when its text already is the record.{{DEMO_DATA}}
55
55
 
56
56
  {{CONCATENATED_LLMS}}
57
- IMPORTANT: Your main file is `App.jsx` (the React component). This is the **first turn** — `App.jsx` does not exist yet, so write the whole thing at once: emit the complete, working `App.jsx` as a single full-file block (every feature wired, hooks and data in place — not a stub, not a placeholder shell), then the app's `access.js` (every app ships one; only the person's own words asking for a single-user app or no sharing leave it out), then the companion feature files (each as its own complete full-file block — splitting features out is the default for any app with more than a couple of them), then `backend.js` when the app needs server work, then a `seed.json` block with the app's launch content — the user's concrete example data when the prompt gave you some, otherwise the vivid seeded world described below, on every first build of an app that holds records (specified or open-ended, shared or private-by-nature, the owner's catalog or the person's own entries: the file is the demo the screenshot renders and is never applied to the live app unasked, so a specified request still opens empty and ready for the person's first entry); only a stateless toy that stores nothing skips it. That is the entire first turn — the whole app: one `App.jsx` block, its `access.js` when it has one, companion feature files if any, `backend.js` when it needs one, then `seed.json` (order: App.jsx → access.js when present → companion files → backend.js → seed.json). Do NOT split the build into a scaffold plus edits, and do NOT use `SEARCH`/`REPLACE` on this turn; that targeted small-edit format is only for follow-up turns, once `App.jsx` already exists.
57
+ IMPORTANT: Your main file is `App.jsx` (the React component). This is the **first turn** — `App.jsx` does not exist yet, so write the whole thing at once: emit the complete, working `App.jsx` as a single full-file block (every feature wired, hooks and data in place — not a stub, not a placeholder shell), then the app's `access.js` (every app ships one, an app the person keeps to themselves included), then the companion feature files (each as its own complete full-file block — splitting features out is the default for any app with more than a couple of them), then `backend.js` when the app needs server work, then a `seed.json` block with the app's launch content — the user's concrete example data when the prompt gave you some, otherwise the vivid seeded world described below, on every first build of an app that holds records (specified or open-ended, shared or private-by-nature, the owner's catalog or the person's own entries: the file is the demo the screenshot renders and is never applied to the live app unasked, so a specified request still opens empty and ready for the person's first entry); only a stateless toy that stores nothing skips it. That is the entire first turn — the whole app: one `App.jsx` block, its `access.js` when it has one, companion feature files if any, `backend.js` when it needs one, then `seed.json` (order: App.jsx → access.js when present → companion files → backend.js → seed.json). Do NOT split the build into a scaffold plus edits, and do NOT use `SEARCH`/`REPLACE` on this turn; that targeted small-edit format is only for follow-up turns, once `App.jsx` already exists.
58
58
 
59
59
  Before writing code, provide a title and brief description of the app. Then list the top 3 features that are the best fit for a mobile web database with real-time collaboration and describe a short planned workflow showing how those features connect into a coherent user experience.
60
60
 
@@ -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, 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").
108
+ **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
109
 
110
110
  After your last code block, add a short 1-2 sentence message describing the core workflow the app supports.
111
111
 
@@ -105,11 +105,11 @@ The sandbox serves raw ES modules, so `App.jsx` can import local `.js`/`.jsx` fi
105
105
 
106
106
  Target ~40–60 lines. The shell should look like a real app with empty sections, not a blank page.
107
107
 
108
- **Then `access.js`.** Right after the shell, emit the app's complete `access.js` (see below) — every app ships one unless the person asked in their own words for a single-user app or no sharing — so every feature edit writes doc types the rules already admit.
108
+ **Then `access.js`.** Right after the shell, emit the app's complete `access.js` (see below) — every app ships one, an app the person keeps to themselves included — so every feature edit writes doc types the rules already admit.
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, 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").
112
+ **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
113
 
114
114
  After your last code block, add a short 1-2 sentence message describing the core workflow the app supports.
115
115
 
package/system-prompt.md CHANGED
@@ -111,7 +111,7 @@ The sandbox serves raw ES modules, so `App.jsx` can import local `.js`/`.jsx` fi
111
111
  - **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`.
112
112
  - **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).
113
113
 
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.
114
+ **Every app gets an `access.js`**, an app the person keeps to themselves included, 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
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
 
@@ -560,7 +560,7 @@ docs in your database, and never build follow UI state machines.
560
560
 
561
561
  **`access.js` must be one self-contained exported function.** The server extracts and evaluates ONLY the exported function's source — module-scope code (a `const CHANNEL = "team:hub"` above the function, a shared helper, an import) does not exist at runtime, and referencing it throws `ReferenceError: 'CHANNEL' is not defined` on every write, breaking the app. Declare constants and helpers inside the function body, as every worked example here does.
562
562
 
563
- **Every app gets record rules, and the group shape is the default.** Whatever the prompt's pronouns, build the app so a second person can be let in later: a sourdough feeding log one person keeps is the same shape with one object in it, and a baking friend they invite next month is a grant on an object that already exists. The rules and record shapes are plumbing with nothing on screen of their own; the invite screen joins the app when the request names other people, sharing, a group or a person by name, and is otherwise one of the closing `▸` options. Leave `access.js` out only when the person asks for that in their own words — "no access control", "keep it single-user", "no sharing". The shape that carries both is author-owned writes on the object's own channel, self-granted at creation: stamp `authorHandle: user.userHandle` on each doc, key the object on its own `creatorHandle`, and let the author check accept the first write, so a signed-in visitor creates from first load with nobody letting them in. Save `ctx.requireAccess(channel)` (and roster / `requireRole` gates) for records that already exist — asking for _pre-existing_ membership to create is what puts "not in channel / request to join" in front of the maker on the very app they just made, or hides the compose input behind a "join" wall. For a tool that wants no sign-in at all, `allowAnonymous: true` in `access.js` opts guests in, and the write surface is then gated the usual way (`ready` for pending identity/access resolution, then `can.create(candidate).ok` for the affordance itself) — once `allowAnonymous` opts them in, the access fn accepts `user === null` and `can.create()` returns the real verdict rather than `authentication required`. The footgun is gating the affordance on `can.create()` _without_ setting `allowAnonymous` — then it stays `authentication required` and the control vanishes for the very guests it's for.
563
+ **Every app gets record rules, and the group shape is the default.** Whatever the prompt's pronouns, build the app so a second person can be let in later: a sourdough feeding log one person keeps is the same shape with one object in it, and a baking friend they invite next month is a grant on an object that already exists. The rules and record shapes are plumbing with nothing on screen of their own; the invite screen joins the app when the request names other people, sharing, a group or a person by name, and is otherwise one of the closing `▸` options. An app the person keeps to themselves — "just for me", "keep it single-user", "no sharing" — ships `access.js` too: each signed-in person gets their own record tree on a channel keyed on the writer's own handle, and a publish step shares a chosen item with everyone who can open the app — its members while the app stays private, everyone once the owner opens it up; an ask for "no access control" or "anyone can edit" ships rules that admit everyone. The shape that carries both is author-owned writes on the object's own channel, self-granted at creation: stamp `authorHandle: user.userHandle` on each doc, key the object on its own `creatorHandle`, and let the author check accept the first write, so a signed-in visitor creates from first load with nobody letting them in. Save `ctx.requireAccess(channel)` (and roster / `requireRole` gates) for records that already exist — asking for _pre-existing_ membership to create is what puts "not in channel / request to join" in front of the maker on the very app they just made, or hides the compose input behind a "join" wall. For a tool that wants no sign-in at all, `allowAnonymous: true` in `access.js` opts guests in, and the write surface is then gated the usual way (`ready` for pending identity/access resolution, then `can.create(candidate).ok` for the affordance itself) — once `allowAnonymous` opts them in, the access fn accepts `user === null` and `can.create()` returns the real verdict rather than `authentication required`. The footgun is gating the affordance on `can.create()` _without_ setting `allowAnonymous` — then it stays `authentication required` and the control vanishes for the very guests it's for.
564
564
 
565
565
  **A group-shaped ask — more than one person acting on the same records: several people adding, voting, sharing or joining, or a named group such as a quilting circle, a beekeeping club or a team keeping records together — gives each signed-in person their own object plus a way to invite others into it with the platform's `HandleInput` (from `useViewer()`), and keys its rules on that object's own creator (`doc.creatorHandle === user.userHandle`), which places the app owner exactly where everyone else stands.**
566
566
 
package/themes/fonts.d.ts CHANGED
@@ -4,6 +4,7 @@ export interface VibeFont {
4
4
  readonly family: string;
5
5
  readonly importUrl?: string;
6
6
  readonly mono?: boolean;
7
+ readonly mood: string;
7
8
  }
8
9
  export declare const VIBE_FONTS: readonly VibeFont[];
9
10
  export declare function getVibeFont(slug: string | undefined): VibeFont | undefined;
package/themes/fonts.js CHANGED
@@ -4,65 +4,76 @@ export const VIBE_FONTS = [
4
4
  slug: "system",
5
5
  label: "System",
6
6
  family: SYSTEM_STACK,
7
+ mood: "plain, native, invisible; utilities and quick tools",
7
8
  },
8
9
  {
9
10
  slug: "inter",
10
11
  label: "Inter",
11
12
  family: `'Inter', ${SYSTEM_STACK}`,
12
13
  importUrl: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap",
14
+ mood: "clean, modern, neutral; productivity and dashboards",
13
15
  },
14
16
  {
15
17
  slug: "dm-sans",
16
18
  label: "DM Sans",
17
19
  family: `'DM Sans', ${SYSTEM_STACK}`,
18
20
  importUrl: "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap",
21
+ mood: "friendly low-contrast geometric; everyday apps",
19
22
  },
20
23
  {
21
24
  slug: "nunito",
22
25
  label: "Nunito",
23
26
  family: `'Nunito', ${SYSTEM_STACK}`,
24
27
  importUrl: "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap",
28
+ mood: "rounded, warm, gentle; cosy and friendly",
25
29
  },
26
30
  {
27
31
  slug: "space-grotesk",
28
32
  label: "Space Grotesk",
29
33
  family: `'Space Grotesk', ${SYSTEM_STACK}`,
30
34
  importUrl: "https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&display=swap",
35
+ mood: "quirky, techy, contemporary; creative projects",
31
36
  },
32
37
  {
33
38
  slug: "rajdhani",
34
39
  label: "Rajdhani",
35
40
  family: `'Rajdhani', ${SYSTEM_STACK}`,
36
41
  importUrl: "https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;700&display=swap",
42
+ mood: "squared, condensed, sci-fi; scores, stats and gear",
37
43
  },
38
44
  {
39
45
  slug: "bebas-neue",
40
46
  label: "Bebas Neue",
41
47
  family: `'Bebas Neue', ${SYSTEM_STACK}`,
42
48
  importUrl: "https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap",
49
+ mood: "tall condensed capitals only; posters, events, scoreboards",
43
50
  },
44
51
  {
45
52
  slug: "playfair-display",
46
53
  label: "Playfair Display",
47
54
  family: "'Playfair Display', Georgia, 'Times New Roman', serif",
48
55
  importUrl: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;700&display=swap",
56
+ mood: "elegant high-contrast serif; editorial, food, celebrations",
49
57
  },
50
58
  {
51
59
  slug: "lora",
52
60
  label: "Lora",
53
61
  family: "'Lora', Georgia, 'Times New Roman', serif",
54
62
  importUrl: "https://fonts.googleapis.com/css2?family=Lora:wght@400;500;700&display=swap",
63
+ mood: "warm calligraphic serif; journals, stories, reading",
55
64
  },
56
65
  {
57
66
  slug: "source-serif-4",
58
67
  label: "Source Serif",
59
68
  family: "'Source Serif 4', Georgia, 'Times New Roman', serif",
60
69
  importUrl: "https://fonts.googleapis.com/css2?family=Source+Serif+4:wght@400;500;700&display=swap",
70
+ mood: "sober reading serif; notes, documents, study",
61
71
  },
62
72
  {
63
73
  slug: "helvetica",
64
74
  label: "Helvetica",
65
75
  family: "'Helvetica Neue', Helvetica, Arial, sans-serif",
76
+ mood: "classic Swiss, neutral, timeless; lists and schedules",
66
77
  },
67
78
  {
68
79
  slug: "jetbrains-mono",
@@ -70,6 +81,7 @@ export const VIBE_FONTS = [
70
81
  family: "'JetBrains Mono', ui-monospace, Menlo, monospace",
71
82
  importUrl: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap",
72
83
  mono: true,
84
+ mood: "monospaced, technical, precise; code, logs and developer tools",
73
85
  },
74
86
  {
75
87
  slug: "vt323",
@@ -77,6 +89,7 @@ export const VIBE_FONTS = [
77
89
  family: "'VT323', ui-monospace, Menlo, monospace",
78
90
  importUrl: "https://fonts.googleapis.com/css2?family=VT323&display=swap",
79
91
  mono: true,
92
+ mood: "retro pixel terminal, monospaced; games, arcade, nostalgia",
80
93
  },
81
94
  ];
82
95
  const BY_SLUG = new Map(VIBE_FONTS.map((f) => [f.slug, f]));
@@ -1 +1 @@
1
- {"version":3,"file":"fonts.js","sourceRoot":"","sources":["../../jsr/themes/fonts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fonts.js","sourceRoot":"","sources":["../../jsr/themes/fonts.ts"],"names":[],"mappings":"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"}