@vibes.diy/prompts 2.5.0 → 2.5.1
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 +30 -13
- package/llms/use-viewer.md +2 -2
- package/package.json +3 -3
- package/system-prompt-initial.md +2 -2
- package/system-prompt.md +2 -2
package/llms/fireproof.md
CHANGED
|
@@ -59,14 +59,16 @@ export default function App() {
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The access function lives in a separate file. Even simple apps include one — it's the server-side authority for who can write:
|
|
62
|
+
The access function lives in a separate file. Even simple apps include one — it's the server-side authority for who can write, and it routes each document to a channel so the author can read it back:
|
|
63
63
|
|
|
64
64
|
access.js
|
|
65
65
|
|
|
66
66
|
```js
|
|
67
67
|
export default function (doc, oldDoc, user) {
|
|
68
68
|
if (!user) throw { forbidden: "sign in to save" };
|
|
69
|
-
|
|
69
|
+
// Private to the author: one channel per user holds all of their documents.
|
|
70
|
+
const mine = `user:${user.userHandle}`;
|
|
71
|
+
return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
|
|
70
72
|
}
|
|
71
73
|
```
|
|
72
74
|
|
|
@@ -376,7 +378,7 @@ export function announcements(doc, oldDoc, user, ctx) {
|
|
|
376
378
|
return { channels: [doc.channel] };
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
|
|
381
|
+
throw { forbidden: "unknown document type" };
|
|
380
382
|
}
|
|
381
383
|
```
|
|
382
384
|
|
|
@@ -474,7 +476,7 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
474
476
|
return { channels: [doc.channel] };
|
|
475
477
|
}
|
|
476
478
|
|
|
477
|
-
|
|
479
|
+
throw { forbidden: "unknown document type" };
|
|
478
480
|
}
|
|
479
481
|
```
|
|
480
482
|
|
|
@@ -523,7 +525,7 @@ Access functions live in `/access.js`, a separate file in the vibe's filesystem
|
|
|
523
525
|
|
|
524
526
|
### AccessDescriptor return type
|
|
525
527
|
|
|
526
|
-
All fields are optional
|
|
528
|
+
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" }`.
|
|
527
529
|
|
|
528
530
|
```ts
|
|
529
531
|
type AccessDescriptor = {
|
|
@@ -553,6 +555,15 @@ type AccessDescriptor = {
|
|
|
553
555
|
|
|
554
556
|
**Access functions are server-enforced policy code.** Checks should be deterministic over `(doc, oldDoc, user, ctx)` and deny with `throw { forbidden: "reason" }` when violated.
|
|
555
557
|
|
|
558
|
+
### Choosing channels — keep the count low
|
|
559
|
+
|
|
560
|
+
A channel is a _reusable_ unit of read access: grant a user into a channel once and they can read every document routed there. Reach for the smallest number of channels the sharing actually requires.
|
|
561
|
+
|
|
562
|
+
- **A reusable group reads many docs** (a team, a board, a project): route to one channel the _collaboration_ owns — `return { channels: [doc.channelId] }` — and grant membership once via a meta or invite doc. Many documents share the one channel.
|
|
563
|
+
- **Only the author reads it** (private notes, a user's own uploads): route to one channel the _user_ owns. `const mine = \`user:${user.userHandle}\`; return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };` — all of that user's private documents live in this single channel.
|
|
564
|
+
- **A document goes to a one-off set with no reusable group:** route to several channels at once — `return { channels: [\`user:${aHandle}\`, \`user:${bHandle}\`] }`. Mint a per-document channel (`channels: [doc._id]`) only when each document genuinely has its own disjoint audience.
|
|
565
|
+
- **Refusing a write:** `throw { forbidden: "reason" }`. Every document you store is routed to at least one channel so it can be read back.
|
|
566
|
+
|
|
556
567
|
### Example: Workspace chat with channels
|
|
557
568
|
|
|
558
569
|
access.js
|
|
@@ -587,7 +598,7 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
587
598
|
};
|
|
588
599
|
}
|
|
589
600
|
|
|
590
|
-
|
|
601
|
+
throw { forbidden: "unknown document type" };
|
|
591
602
|
}
|
|
592
603
|
```
|
|
593
604
|
|
|
@@ -620,8 +631,7 @@ export function survey(doc, oldDoc, user, ctx) {
|
|
|
620
631
|
return { channels: [doc._id], grant: { public: [doc._id] } };
|
|
621
632
|
}
|
|
622
633
|
|
|
623
|
-
|
|
624
|
-
return {};
|
|
634
|
+
throw { forbidden: "unknown document type" };
|
|
625
635
|
}
|
|
626
636
|
```
|
|
627
637
|
|
|
@@ -642,7 +652,9 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
642
652
|
|
|
643
653
|
export function notes(doc, oldDoc, user, ctx) {
|
|
644
654
|
if (!user) throw { forbidden: "authentication required" };
|
|
645
|
-
|
|
655
|
+
// Private to the author — one channel per user.
|
|
656
|
+
const mine = `user:${user.userHandle}`;
|
|
657
|
+
return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
|
|
646
658
|
}
|
|
647
659
|
```
|
|
648
660
|
|
|
@@ -674,10 +686,11 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
674
686
|
return { channels: [doc.channelId] };
|
|
675
687
|
}
|
|
676
688
|
|
|
677
|
-
// Everything else:
|
|
689
|
+
// Everything else: authenticated users get a private per-user channel
|
|
678
690
|
export default function (doc, oldDoc, user, ctx) {
|
|
679
691
|
if (!user) throw { forbidden: "authentication required" };
|
|
680
|
-
|
|
692
|
+
const mine = `user:${user.userHandle}`;
|
|
693
|
+
return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
|
|
681
694
|
}
|
|
682
695
|
```
|
|
683
696
|
|
|
@@ -908,7 +921,9 @@ access.js
|
|
|
908
921
|
```js
|
|
909
922
|
export function imageUploads(doc, oldDoc, user) {
|
|
910
923
|
if (!user) throw { forbidden: "sign in to upload" };
|
|
911
|
-
|
|
924
|
+
// Each uploader reads their own images — one private channel per user.
|
|
925
|
+
const mine = `user:${user.userHandle}`;
|
|
926
|
+
return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
|
|
912
927
|
}
|
|
913
928
|
```
|
|
914
929
|
|
|
@@ -1044,6 +1059,8 @@ export function todoList(doc, oldDoc, user) {
|
|
|
1044
1059
|
if (doc.type === "todo" && doc.createdBy !== user.userHandle) {
|
|
1045
1060
|
throw { forbidden: "only the author can edit" };
|
|
1046
1061
|
}
|
|
1047
|
-
|
|
1062
|
+
// Private to the author — one channel per user.
|
|
1063
|
+
const mine = `user:${user.userHandle}`;
|
|
1064
|
+
return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
|
|
1048
1065
|
}
|
|
1049
1066
|
```
|
package/llms/use-viewer.md
CHANGED
|
@@ -34,7 +34,7 @@ export default function App() {
|
|
|
34
34
|
|
|
35
35
|
## What you get
|
|
36
36
|
|
|
37
|
-
- `viewer` — `{ userHandle, displayName
|
|
37
|
+
- `viewer` — `{ userHandle, displayName? }` or `null` for anonymous visitors. Avatars are not on the payload — render them with `<ViewerTag userHandle={...} />`, which resolves the avatar from the handle. Don't build avatar URLs yourself.
|
|
38
38
|
- `isViewerPending` — `true` while the platform is still resolving the viewer identity (e.g. on first render before the parent shell has pushed the identity update). **Gate any auth-dependent UI on `!isViewerPending`** to avoid flashing the wrong state. Once it becomes `false`, `viewer` is either populated or definitively `null`.
|
|
39
39
|
- `isOwner` — `true` when the viewer owns this vibe. Use it for management UI (settings, role grants, moderation).
|
|
40
40
|
- `can(action, dbName?)` — `true`/`false` for `"read"`, `"write"`, `"delete"`. Checks app-level ACLs. In most apps `viewer` and `access.hasRole()`/`access.hasChannel()` are the right gates instead.
|
|
@@ -147,7 +147,7 @@ export default function App() {
|
|
|
147
147
|
Key points:
|
|
148
148
|
|
|
149
149
|
- **Stamp `authorHandle` at write time** — persist the author's handle on the doc. Render with `<ViewerTag userHandle={authorHandle} />` which resolves display name and avatar automatically.
|
|
150
|
-
-
|
|
150
|
+
- **Avatars are stable** — ViewerTag resolves the avatar from the handle; if the author changes their avatar, the URL stays the same and the bytes update. ViewerTag handles this for you.
|
|
151
151
|
- **One source of identity** — persist `authorHandle` on the doc. ViewerTag does the rest.
|
|
152
152
|
|
|
153
153
|
## Notes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -30,8 +30,8 @@
|
|
|
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/use-vibes-types": "^2.5.
|
|
33
|
+
"@vibes.diy/call-ai-v2": "^2.5.1",
|
|
34
|
+
"@vibes.diy/use-vibes-types": "^2.5.1",
|
|
35
35
|
"arktype": "~2.2.0",
|
|
36
36
|
"json-schema-faker": "~0.6.1"
|
|
37
37
|
},
|
package/system-prompt-initial.md
CHANGED
|
@@ -14,7 +14,7 @@ You are an AI assistant tasked with creating React components. You should create
|
|
|
14
14
|
- Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
|
|
15
15
|
- Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
|
|
16
16
|
- For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
|
|
17
|
-
- Access control is decided by the runtime, not by your code. `useViewer()` from `"use-vibes"` gives you `const { viewer, isOwner, isViewerPending, ViewerTag } = useViewer();`. `viewer` is `{ userHandle, displayName
|
|
17
|
+
- Access control is decided by the runtime, not by your code. `useViewer()` from `"use-vibes"` gives you `const { viewer, isOwner, isViewerPending, ViewerTag } = useViewer();`. `viewer` is `{ userHandle, displayName? } | null` (null for anonymous). **Gate write surfaces on `viewer`** — show forms only when signed in, render a read-only fallback otherwise. For apps with an access function (`access.js`), gate further with `access.hasRole()` or `access.hasChannel()` from `useFireproof()` — never re-derive permissions from document fields client-side. Use `isOwner` for management UI (settings, moderation). Render avatars with `<ViewerTag userHandle={authorHandle} />`. This applies to every app — never skip useViewer because the app "sounds single-user"; the runtime decides sharing, not the prompt. See use-viewer docs.
|
|
18
18
|
- Don't try to generate png or base64 data, use placeholder image APIs instead, like https://picsum.photos/400 where 400 is the square size
|
|
19
19
|
- Never use emojis in the UI. Use inline SVG icons instead — simple, single-color, stroke-based SVGs (24x24 viewBox, strokeWidth 2, strokeLinecap round, strokeLinejoin round). Build icons directly in JSX, do not import icon libraries.
|
|
20
20
|
- List data items on the main page of your app so users don't have to hunt for them
|
|
@@ -66,7 +66,7 @@ Target ~40–60 lines. The shell should look like a real app with empty sections
|
|
|
66
66
|
> ctx.requireAccess(doc.channelId)
|
|
67
67
|
> return { channels: [doc.channelId] }
|
|
68
68
|
> }
|
|
69
|
-
>
|
|
69
|
+
> throw { forbidden: "unknown document type" }
|
|
70
70
|
> }
|
|
71
71
|
> ```
|
|
72
72
|
|
package/system-prompt.md
CHANGED
|
@@ -14,7 +14,7 @@ You are an AI assistant tasked with creating React components. You should create
|
|
|
14
14
|
- Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
|
|
15
15
|
- Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
|
|
16
16
|
- For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
|
|
17
|
-
- Access control is decided by the runtime, not by your code. `useViewer()` from `"use-vibes"` gives you `const { viewer, isOwner, isViewerPending, ViewerTag } = useViewer();`. `viewer` is `{ userHandle, displayName
|
|
17
|
+
- Access control is decided by the runtime, not by your code. `useViewer()` from `"use-vibes"` gives you `const { viewer, isOwner, isViewerPending, ViewerTag } = useViewer();`. `viewer` is `{ userHandle, displayName? } | null` (null for anonymous). **Gate write surfaces on `viewer`** — show forms only when signed in, render a read-only fallback otherwise. For apps with an access function (`access.js`), gate further with `access.hasRole()` or `access.hasChannel()` from `useFireproof()` — never re-derive permissions from document fields client-side. Use `isOwner` for management UI (settings, moderation). Render avatars with `<ViewerTag userHandle={authorHandle} />`. This applies to every app — never skip useViewer because the app "sounds single-user"; the runtime decides sharing, not the prompt. See use-viewer docs.
|
|
18
18
|
- Don't try to generate png or base64 data, use placeholder image APIs instead, like https://picsum.photos/400 where 400 is the square size
|
|
19
19
|
- Never use emojis in the UI. Use inline SVG icons instead — simple, single-color, stroke-based SVGs (24x24 viewBox, strokeWidth 2, strokeLinecap round, strokeLinejoin round). Build icons directly in JSX, do not import icon libraries.
|
|
20
20
|
- Consider and potentially reuse/extend code from previous responses if relevant
|
|
@@ -274,7 +274,7 @@ When the app uses channel-based read isolation or per-document write validation,
|
|
|
274
274
|
> ctx.requireAccess(doc.channelId);
|
|
275
275
|
> return { channels: [doc.channelId] };
|
|
276
276
|
> }
|
|
277
|
-
>
|
|
277
|
+
> throw { forbidden: "unknown document type" };
|
|
278
278
|
> }
|
|
279
279
|
> ```
|
|
280
280
|
|