@vibes.diy/prompts 14.1.31 → 14.1.32

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
@@ -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`: either the prompt names sharing, privacy, teams, members, roles, approval or who-can-see-what, or the platform is running the second pass every app gets — a first build drafts the records with their keys already in place (`creatorHandle` on the object, the parent id on each child, `authorHandle` on every record) and leaves the rules and the invite screen to this pass. 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.
3
+ This doc is in view because the app is getting its `access.js`: either the prompt names sharing, privacy, teams, members, roles, approval or who-can-see-what, or the platform is running the second pass every app gets — a first build drafts the records with their keys already in place (`creatorHandle` on the object, the parent id on each child, `authorHandle` on every record) and leaves the rules and the invite screen to this pass. 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 the build itself — see _A person the ask names is let in by the build_ below — so the files that land carry their handle.
4
4
 
5
5
  **On a second pass over an app that already exists, this is a whole-app upgrade, applied in full.** Read `App.jsx` for the object type the app is about, 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` to match it — the invite screen, the `can` gates, the object id on every child — keeping everything the app already does.
6
6
 
@@ -825,7 +825,10 @@ export function boards(doc, oldDoc, user, ctx) {
825
825
 
826
826
  if (doc.type === "member") {
827
827
  // The board's admin invites a person the picker resolved; the doc IS the grant.
828
- if (oldDoc) throw { forbidden: "membership grants are fixed" };
828
+ // Fixed once written — an identical re-put (a cold replica replaying a first-render
829
+ // write) is harmless and admitted; any change to who, where or by whom is refused.
830
+ if (oldDoc && (doc.userHandle !== oldDoc.userHandle || doc.boardId !== oldDoc.boardId || doc.addedBy !== oldDoc.addedBy))
831
+ throw { forbidden: "membership grants are fixed" };
829
832
  if (doc.addedBy !== user.userHandle) throw { forbidden: "addedBy must be you" };
830
833
  if (doc.boardId !== myDefault) ctx.requireAccess(ch(doc.boardId) + "/admin");
831
834
  return { channels: [ch(doc.boardId)], grant: { users: { [doc.userHandle]: [ch(doc.boardId)] } } };
@@ -851,6 +854,34 @@ const addCard = (text) => database.put({ type: "card", boardId: pickedBoardId ||
851
854
 
852
855
  Where the app's own maintainer needs to move records through the gate — a CLI migration re-homing cards — `!user.isOwner` reads as a bypass written beside a check (`if (!user.isOwner) ctx.requireAccess(chan)`), never as the arm that decides who may act.
853
856
 
857
+ #### A person the ask names is let in by the build
858
+
859
+ When the instruction names somebody by their handle — "let my friend priya-k see and add to my chores list", "an expedition notebook for me and theo" — **the build writes that person's membership itself, so the files that land carry their handle.** The invite surface above is where the maker adds the *next* person; the person the ask already named is in from the first render, with nothing left for the maker to type.
860
+
861
+ **A handle is the thing the person typed as one — a platform handle, the same token `HandleInput` would resolve.** A plain first name ("share it with Priya") is not a handle: the build has no way to know which account, if any, that name means, and a grant written for a guessed handle either reaches a stranger who happens to own it or nobody. For a name that is not a handle, ship the invite screen as usual and let the narration say the maker can add that person by handle — an honest "still to be added" rather than a membership written for the wrong person.
862
+
863
+ The shape: the handles the ask named live in one constant at the top of `App.jsx`, keyed by the asker's own handle (the app context names it), and a first-render effect writes one `member` doc per name onto the asker's own default object — a fixed `_id`, read back first so a mount after the first one finds the record and writes nothing, gated on `can.create` so it waits for the rules to admit it, and run as the asker (the access function above accepts `addedBy === user.userHandle` on their own default board with no admin grant to bootstrap). The `member` rule admits an identical re-put on purpose: a cold replica can miss a record it holds (`ready` is hydration, not sync), and a replay that changes nothing must land quietly rather than refuse — and toast — a membership that is already fine. The effect keys on `ready` and the handle, not on `can`, so it runs once per identity rather than once per render. Anyone else who opens the app finds nothing in the constant under their handle and writes nothing.
864
+
865
+ ```jsx
866
+ // The people the ask named, by the handle that asked. Read at first render; the invite box adds the rest.
867
+ const sharedWith = { theo: ["priya-k"] };
868
+
869
+ const { me, can, ready } = useVibe("boards");
870
+ useEffect(() => {
871
+ if (!ready || !me?.userHandle) return;
872
+ const boardId = `default-${me.userHandle}`;
873
+ for (const userHandle of sharedWith[me.userHandle] ?? []) {
874
+ const member = { _id: `member:${boardId}:${userHandle}`, type: "member", boardId, userHandle, addedBy: me.userHandle };
875
+ // Look before writing so a second mount finds it and moves on; an identical replay is admitted anyway.
876
+ database.get(member._id).catch(() => {
877
+ if (can.create(member).ok) return database.put(member);
878
+ });
879
+ }
880
+ }, [ready, me?.userHandle]);
881
+ ```
882
+
883
+ Two things follow for the reply the build's narration writes. The record the app writes is what lets the person in, so the sentence is _"priya-k is named on your chores list"_ — a fact about the files — and never a promise about what they can see right now. And when the ask names a person the app already lets in, the constant is where to look: their handle there is the evidence, and a handle that is absent means the membership is still to be written.
884
+
854
885
  ## More worked round-trip examples
855
886
 
856
887
  ### Example: Workspace chat with channels
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.1.31",
3
+ "version": "14.1.32",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -34,9 +34,9 @@
34
34
  "license": "Apache-2.0",
35
35
  "dependencies": {
36
36
  "@adviser/cement": "~0.5.34",
37
- "@vibes.diy/call-ai-v2": "14.1.31",
38
- "@vibes.diy/identity": "14.1.31",
39
- "@vibes.diy/use-vibes-types": "14.1.31",
37
+ "@vibes.diy/call-ai-v2": "14.1.32",
38
+ "@vibes.diy/identity": "14.1.32",
39
+ "@vibes.diy/use-vibes-types": "14.1.32",
40
40
  "arktype": "~2.2.3",
41
41
  "json-schema-faker": "~0.6.3"
42
42
  },
package/system-prompt.md CHANGED
@@ -91,7 +91,7 @@ The sandbox serves raw ES modules, so `App.jsx` can import local `.js`/`.jsx` fi
91
91
  - **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`.
92
92
  - **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).
93
93
 
94
- **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 the instruction is the platform's second pass over an app drafted without one, that is a whole-app upgrade applied in full: read `App.jsx` for the records it already writes, emit the complete `access.js` first, then the `App.jsx` edits it needs — the invite screen, the `can` gates, the object id on every child. 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.
94
+ **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 the instruction is the platform's second pass over an app drafted without one, that is a whole-app upgrade applied in full: read `App.jsx` for the records it already writes, emit the complete `access.js` first, then the `App.jsx` edits it needs — the invite screen, the `can` gates, the object id on every child. 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 writes their membership itself — a `sharedWith` constant keyed by the asker's handle and one `member` doc per name written on first render onto the asker's own object, as the access skill doc's _A person the ask names is let in by the build_ shows — so the files that land carry their handle and the invite screen is left for the next person.
95
95
 
96
96
  **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.
97
97