@vibes.diy/prompts 2.5.13 → 2.5.14
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/fireproof.md +37 -11
- package/package.json +4 -4
- package/system-prompt-initial.md +8 -6
- package/system-prompt.md +9 -7
package/llms/fireproof.md
CHANGED
|
@@ -510,9 +510,9 @@ export default function App() {
|
|
|
510
510
|
|
|
511
511
|
The pattern: `useVibe().can` gates every write surface — including owner-only management, which the access function enforces via `if (!user.isOwner) throw` so `can.create({ type: "roleGrant", … })` is false for non-owners. `access.hasChannel()` reflects display-only membership, and `isOwner` is a display hint only, never the write gate. The access function is the server-side authority — `useVibe().can` is how the UI reflects its decisions for writes.
|
|
512
512
|
|
|
513
|
-
### Example: Channel board with
|
|
513
|
+
### Example: Channel board with open channels (any member posts)
|
|
514
514
|
|
|
515
|
-
|
|
515
|
+
Channels everyone can read, and any signed-in user can post to. The channel doc is `grant.public` (read for everyone) and the post rule checks only the author — **no `ctx.requireAccess`**, because public is read-only and would block every non-owner. (For a members-only board where the owner appoints who can post, grant a poster role and `requireAccess` it, as in the announcements example above.) The UI uses `access.hasChannel()` to filter which channels to display, and `useVibe().can` to gate writes.
|
|
516
516
|
|
|
517
517
|
access.js
|
|
518
518
|
|
|
@@ -522,18 +522,18 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
522
522
|
|
|
523
523
|
if (doc.type === "channel") {
|
|
524
524
|
if (!user.isOwner) throw { forbidden: "owner only" };
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
users: { [user.userHandle]: [doc._id] },
|
|
529
|
-
public: [doc._id],
|
|
530
|
-
},
|
|
531
|
-
};
|
|
525
|
+
// Open channel: public READ for everyone. No write-membership grant is
|
|
526
|
+
// needed — any signed-in user may post (see the post rule below).
|
|
527
|
+
return { channels: [doc._id], grant: { public: [doc._id] } };
|
|
532
528
|
}
|
|
533
529
|
|
|
534
530
|
if (doc.type === "post") {
|
|
535
531
|
if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
536
|
-
|
|
532
|
+
// Any signed-in author may post to this open channel. Do NOT call
|
|
533
|
+
// ctx.requireAccess(doc.channel) here: the channel is grant.public
|
|
534
|
+
// (read-only), which never satisfies requireAccess, so gating on it would
|
|
535
|
+
// block every non-owner from posting. requireAccess is for members-only
|
|
536
|
+
// channels whose writers were granted membership (see announcements above).
|
|
537
537
|
return { channels: [doc.channel] };
|
|
538
538
|
}
|
|
539
539
|
|
|
@@ -549,7 +549,9 @@ App.jsx — `access.hasChannel()` filters which channels are visible (display);
|
|
|
549
549
|
=======
|
|
550
550
|
const { database, useLiveQuery, access } = useFireproof("chat");
|
|
551
551
|
const { docs: channels } = useLiveQuery("type", { key: "channel" });
|
|
552
|
-
|
|
552
|
+
// Build the candidate from the doc you'll write — the access fn checks
|
|
553
|
+
// authorHandle, so a bare { type: "post" } would be denied and hide the form.
|
|
554
|
+
const canPost = useVibe("chat").can.create({ type: "post", channel, authorHandle: viewer?.userHandle });
|
|
553
555
|
>>>>>>> REPLACE
|
|
554
556
|
```
|
|
555
557
|
|
|
@@ -585,6 +587,11 @@ Access functions live in `/access.js`, a separate file in the vibe's filesystem
|
|
|
585
587
|
|
|
586
588
|
**Helpers (`ctx`):** Opaque closures over the materialized grant state. They throw or pass — you cannot enumerate channels, list members, or iterate grants. Both helpers also throw when `user` is null: `ctx.requireAccess(channelId)` throws if user is not in the channel, `ctx.requireRole(roleName)` throws if user is not in the role.
|
|
587
589
|
|
|
590
|
+
**`requireAccess` checks _membership_, not public read — don't gate an open channel's writes on it.** `ctx.requireAccess(channelId)` passes only for a channel the user is a member of: granted directly through `grant.users[handle]`, or through a `grant.roles` role they hold. **`grant.public` does NOT satisfy `requireAccess`** — public is read-only ("anyone through the door can _read_"), it never confers write membership. So a channel that is only `grant.public` and gated on `ctx.requireAccess` can be written by **nobody** but the owner-in-admin-mode — every other write returns `not in channel`, silently hiding the form (`useVibe().can` faithfully reflects this). Choose by intent:
|
|
591
|
+
|
|
592
|
+
- **Open channel — any signed-in user may post** (public board, guestbook, comment wall): do **not** call `ctx.requireAccess`. Route the doc to the channel and check the author — `if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" }; return { channels: [doc.channelId] }`. `grant.public` on the channel doc gives everyone read; the write is open to any author.
|
|
593
|
+
- **Restricted channel — only members may post**: gate the write on `ctx.requireAccess(doc.channelId)` **and** grant writers membership explicitly — `grant.users` (direct) or `grant.roles` + a `members`/role-grant doc. `public` alongside is read-only and is fine for letting non-members read, but it is never what lets a member write.
|
|
594
|
+
|
|
588
595
|
### AccessDescriptor return type
|
|
589
596
|
|
|
590
597
|
All fields are optional, but a stored document must be routed to at least one channel (`channels`) to be readable — a result with no channels is refused at write time. To reject a write outright, `throw { forbidden: "reason" }`.
|
|
@@ -699,6 +706,25 @@ export function survey(doc, oldDoc, user, ctx) {
|
|
|
699
706
|
|
|
700
707
|
Key patterns: `allowAnonymous: true` on survey-response lets unauthenticated visitors submit, `grant.public` on final-results makes them readable by any member without a specific channel grant, and the **singleton grant doc** pattern (survey-config) wires role-to-channel access in one place.
|
|
701
708
|
|
|
709
|
+
### Example: Public guestbook / contact form (anonymous writes)
|
|
710
|
+
|
|
711
|
+
When the prompt says **anyone can sign / submit without logging in** (a guestbook, a contact form, an RSVP), do **not** throw on `!user` — return `allowAnonymous: true` so the write is accepted for anonymous visitors. `useVibe().can.create(...)` then returns `ok` for an anonymous viewer, and the form shows instead of a sign-in wall. Stamp `authorHandle` only when there is a user.
|
|
712
|
+
|
|
713
|
+
access.js
|
|
714
|
+
|
|
715
|
+
```js
|
|
716
|
+
export function guestbook(doc, oldDoc, user, ctx) {
|
|
717
|
+
if (doc.type === "entry") {
|
|
718
|
+
if (oldDoc) throw { forbidden: "entries are write-once" };
|
|
719
|
+
// No `if (!user) throw` — anyone may sign. allowAnonymous opts the write in.
|
|
720
|
+
return { channels: ["public"], grant: { public: ["public"] }, allowAnonymous: true };
|
|
721
|
+
}
|
|
722
|
+
throw { forbidden: "unknown document type" };
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
In `App.jsx`, gate the form on `useVibe("guestbook").can.create({ type: "entry" }).ok` (true for anon here) and stamp `authorHandle: me?.userHandle` only when signed in. Without `allowAnonymous: true` the runtime rejects the null-user write even though the function didn't throw — so the guestbook would silently require login, the exact miss to avoid.
|
|
727
|
+
|
|
702
728
|
### Multiple databases in one file
|
|
703
729
|
|
|
704
730
|
Each named export gates its own database. A single `/access.js` can gate all databases the app uses:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@fireproof/core-types-base": "~0.24.19",
|
|
31
31
|
"@fireproof/core-types-protocols-cloud": "~0.24.19",
|
|
32
32
|
"@fireproof/use-fireproof": "~0.24.19",
|
|
33
|
-
"@vibes.diy/call-ai-v2": "^2.5.
|
|
34
|
-
"@vibes.diy/identity": "^2.5.
|
|
35
|
-
"@vibes.diy/use-vibes-types": "^2.5.
|
|
33
|
+
"@vibes.diy/call-ai-v2": "^2.5.14",
|
|
34
|
+
"@vibes.diy/identity": "^2.5.14",
|
|
35
|
+
"@vibes.diy/use-vibes-types": "^2.5.14",
|
|
36
36
|
"arktype": "~2.2.1",
|
|
37
37
|
"json-schema-faker": "~0.6.2"
|
|
38
38
|
},
|
package/system-prompt-initial.md
CHANGED
|
@@ -51,29 +51,31 @@ Target ~40–60 lines. The shell should look like a real app with empty sections
|
|
|
51
51
|
|
|
52
52
|
**Step 3 — 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` with `access` 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.
|
|
53
53
|
|
|
54
|
-
> Access function — owner manages channels
|
|
54
|
+
> Access function — owner manages channels; any signed-in member posts to these open channels.
|
|
55
55
|
>
|
|
56
56
|
> access.js
|
|
57
57
|
>
|
|
58
58
|
> ```js
|
|
59
|
-
> // Each channel doc grants public
|
|
60
|
-
> //
|
|
61
|
-
> //
|
|
59
|
+
> // Each channel doc grants public READ; only the owner creates channels.
|
|
60
|
+
> // OPEN board: any signed-in user posts — do NOT gate posts on ctx.requireAccess
|
|
61
|
+
> // (grant.public is read-only and never satisfies it, so it would block everyone
|
|
62
|
+
> // but the owner). Members-only channel instead? grant a role + requireAccess it.
|
|
62
63
|
> export function chat(doc, oldDoc, user, ctx) {
|
|
63
64
|
> if (!user) throw { forbidden: "sign in" };
|
|
64
65
|
> if (doc.type === "channel") {
|
|
65
66
|
> if (!user.isOwner) throw { forbidden: "owner only" };
|
|
66
|
-
> return { channels: [doc.
|
|
67
|
+
> return { channels: [doc._id], grant: { public: [doc._id] } };
|
|
67
68
|
> }
|
|
68
69
|
> if (doc.type === "message") {
|
|
69
70
|
> if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
70
|
-
> ctx.requireAccess(doc.channelId);
|
|
71
71
|
> return { channels: [doc.channelId] };
|
|
72
72
|
> }
|
|
73
73
|
> throw { forbidden: "unknown document type" };
|
|
74
74
|
> }
|
|
75
75
|
> ```
|
|
76
76
|
|
|
77
|
+
`ctx.requireAccess(channel)` gates on **membership** (a `grant.users`/`grant.roles` grant), not `grant.public` (read-only) — so an open channel anyone signed-in may post to must not gate writes on it; check the author and route the doc. For writes needing no sign-in ("anyone can sign/submit"), return `allowAnonymous: true` instead of throwing on `!user`.
|
|
78
|
+
|
|
77
79
|
**Never put access function code inside an `App.jsx` block** — it will overwrite the React component. The filename line (`access.js` vs `App.jsx`) is how the system knows which file to write.
|
|
78
80
|
|
|
79
81
|
After the final edit (and `access.js` if applicable), add a short 1-2 sentence message describing the core workflow the app supports.
|
package/system-prompt.md
CHANGED
|
@@ -282,6 +282,8 @@ When the app uses channel-based read isolation or per-document write validation,
|
|
|
282
282
|
> }
|
|
283
283
|
> ```
|
|
284
284
|
|
|
285
|
+
`ctx.requireAccess(channel)` gates on channel **membership** (a `grant.users`/`grant.roles` grant), NOT on `grant.public`, which is read-only — so a channel anyone signed-in should post to must **not** gate writes on `requireAccess` (it would block every non-owner); just check the author and route the doc. Reserve `requireAccess` for members-only channels whose writers you granted membership. For writes that need no sign-in at all ("anyone can sign/submit"), return `allowAnonymous: true` instead of throwing on `!user`. See the fireproof access docs.
|
|
286
|
+
|
|
285
287
|
**Never put access function code inside an `App.jsx` block** — it will overwrite the React component. The filename line (`access.js` vs `App.jsx`) is how the system knows which file to write.
|
|
286
288
|
|
|
287
289
|
## Your starter scaffold
|
|
@@ -396,29 +398,29 @@ Example streamed output for a team board app:
|
|
|
396
398
|
> }
|
|
397
399
|
> ```
|
|
398
400
|
>
|
|
399
|
-
> Access function — owner manages channels
|
|
401
|
+
> Access function — owner manages channels; any signed-in member posts to these open channels.
|
|
400
402
|
>
|
|
401
403
|
> access.js
|
|
402
404
|
>
|
|
403
405
|
> ```js
|
|
404
|
-
> // Each channel doc grants public
|
|
405
|
-
> //
|
|
406
|
-
> //
|
|
406
|
+
> // Each channel doc grants public READ to that channel; only the owner creates channels.
|
|
407
|
+
> // OPEN board: any signed-in user posts. Do NOT gate posts on ctx.requireAccess —
|
|
408
|
+
> // grant.public is read-only and never satisfies it, so it would block every
|
|
409
|
+
> // non-owner. (Members-only channel instead? grant a role + requireAccess it.)
|
|
407
410
|
> export function crewBoard(doc, oldDoc, user, ctx) {
|
|
408
411
|
> if (!user) throw { forbidden: "sign in" };
|
|
409
412
|
>
|
|
410
413
|
> if (doc.type === "channel") {
|
|
411
414
|
> if (!user.isOwner) throw { forbidden: "owner only" };
|
|
412
|
-
> return { channels: [doc.
|
|
415
|
+
> return { channels: [doc._id], grant: { public: [doc._id] } };
|
|
413
416
|
> }
|
|
414
417
|
>
|
|
415
418
|
> if (doc.type === "post") {
|
|
416
419
|
> if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
417
|
-
> ctx.requireAccess(doc.channelId);
|
|
418
420
|
> return { channels: [doc.channelId] };
|
|
419
421
|
> }
|
|
420
422
|
>
|
|
421
|
-
>
|
|
423
|
+
> throw { forbidden: "unknown document type" };
|
|
422
424
|
> }
|
|
423
425
|
> ```
|
|
424
426
|
>
|