@vibes.diy/prompts 8.2.0 → 8.2.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/access.md +8 -0
- package/llms/backend.md +147 -0
- package/package.json +4 -4
- package/system-prompt-initial-oneshot.md +1 -0
- package/system-prompt-initial.md +1 -0
- package/system-prompt.md +1 -0
package/llms/access.md
CHANGED
|
@@ -47,8 +47,16 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
47
47
|
|
|
48
48
|
**A follow-up edit that adds a NEW doc type updates `access.js` FIRST.** The app runs live while your edits stream in, and writes are enforced against the access.js that was in force before this turn until the whole turn completes — so a `db.put` of a doc type the old function rejects fails immediately (`unknown document type`), even though your access.js update lands later in the same reply. Emit the access.js edit adding the new type's branch before the App.jsx edits that write it. And never fire-and-forget background writes of a newly added type: gate seed/auto-write effects on `useVibe(dbName)` — `if (!ready || !can.create(sampleDoc).ok) return;` with `ready`/`can` in the effect deps, checking a representative sample of **each** doc type the effect writes — so a not-yet-allowed write is skipped quietly instead of surfacing rejection errors the user didn't cause.
|
|
49
49
|
|
|
50
|
+
### Ending the function: terminal denial (the default) vs. the discard channel
|
|
51
|
+
|
|
52
|
+
Every access function ends by handling the doc types it knows and **denying everything else**. The **default** ending — closing every worked example below — is the terminal `throw { forbidden: "unknown document type" }`: fail-closed, and loud. If the app later writes a type nobody added a branch for, the write is rejected with a visible error, so type-enumeration drift surfaces instead of silently corrupting the data model. **Reach for the throw unless you have a specific, named reason not to.**
|
|
53
|
+
|
|
54
|
+
The one documented alternative is the **discard channel** — `return { channels: ["discard"], grant: {} }`: accept the unknown-type write into a channel that has **no** grant, so it persists but nothing can ever read it back. This is **deny-by-unreadability**, not deny-by-error — the write silently succeeds and then vanishes, so the user sees neither a failure nor the data. That trade-off makes it the wrong default: a silent swallow hides real bugs the throw would have caught. Use it **only** where a visible failure is worse than a silent one. The canonical case is the **anonymous-local sign-in migration**: when an anonymous visitor signs in, their device-local docs migrate into the cloud db in bulk, and a single stray legacy doc type hitting a terminal throw would fail the *whole* migration and surface error toasts on first sign-in. Routing the unknown remainder to `discard` lets the known docs migrate while the legacy straggler is swallowed instead of breaking the flow. Outside that kind of bulk/legacy-migration path, keep the throw. (Note the shape difference the runtime cares about: `grant: {}` grants no reader, so the write is unreadable — deny. A permissive fallthrough that returns a *readable* channel for unknown types, e.g. `grant: { public: [...] }`, is fail-open and is the exact bug both endings exist to prevent.)
|
|
55
|
+
|
|
50
56
|
## Worked examples — permission design
|
|
51
57
|
|
|
58
|
+
_Each example below closes its type dispatch with the default terminal `throw { forbidden: "unknown document type" }`; substitute the `discard`-channel fallthrough above only for an app on the bulk/legacy-migration path that documents why._
|
|
59
|
+
|
|
52
60
|
### Worked example — open channel wall (author-owned writes)
|
|
53
61
|
|
|
54
62
|
access.js
|
package/llms/backend.md
CHANGED
|
@@ -32,6 +32,10 @@ An app can need more than one — a payments app wants `fetch` (webhook receipt)
|
|
|
32
32
|
**and** `onChange` (email on the new order). If none of these signal words fit
|
|
33
33
|
the request, the app needs no `backend.js` — don't emit one.
|
|
34
34
|
|
|
35
|
+
**Decision rule:** when live external data is the app's substance, use
|
|
36
|
+
`backend.js` + **cache-into-db, never fetch-per-render**; when external data is
|
|
37
|
+
only launch flavor, take a generation-time snapshot into `seed.json` instead.
|
|
38
|
+
|
|
35
39
|
## Output format
|
|
36
40
|
|
|
37
41
|
`backend.js` is a separate file, exactly like `access.js`: one prose line, the
|
|
@@ -143,6 +147,149 @@ const res = await ctx.fetch("https://api.example.com/x", { headers }); // outbou
|
|
|
143
147
|
when the result must be server-authoritative (moderation, digests,
|
|
144
148
|
summaries users shouldn't be able to forge).
|
|
145
149
|
|
|
150
|
+
## Worked external-data pattern — cache into the live database
|
|
151
|
+
|
|
152
|
+
A URL that worked during generation may still be denied by runtime egress.
|
|
153
|
+
Never fetch an external feed from render or a React effect. Poll once per tick,
|
|
154
|
+
normalize a bounded number of rows into the exact database and document shape
|
|
155
|
+
the UI queries, and keep the last successful rows when refresh fails. Seed a
|
|
156
|
+
small honest snapshot before the first request so even a first-run denial has
|
|
157
|
+
something useful to show.
|
|
158
|
+
|
|
159
|
+
This complete example makes one curated GitHub API request every 15 minutes
|
|
160
|
+
(well inside the documented caps), stores at most 20 normalized `release` docs
|
|
161
|
+
in `nodeReleases`, and records refresh state in that same database.
|
|
162
|
+
|
|
163
|
+
backend.js
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
export const config = { scheduled: { interval: "15m" } };
|
|
167
|
+
|
|
168
|
+
const SNAPSHOT = [
|
|
169
|
+
{
|
|
170
|
+
_id: "release:snapshot-v22",
|
|
171
|
+
type: "release",
|
|
172
|
+
title: "Node.js 22 (saved snapshot)",
|
|
173
|
+
url: "https://nodejs.org/en/blog/release/v22.0.0",
|
|
174
|
+
publishedAt: "2024-04-24T00:00:00.000Z",
|
|
175
|
+
source: "saved-snapshot",
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
async function ensureSnapshot(ctx) {
|
|
180
|
+
const docs = await ctx.db.query({ db: "nodeReleases" });
|
|
181
|
+
if (docs.some((doc) => doc.type === "release")) return;
|
|
182
|
+
for (const doc of SNAPSHOT) await ctx.db.put(doc, { db: "nodeReleases" });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function scheduled(event, ctx) {
|
|
186
|
+
await ensureSnapshot(ctx);
|
|
187
|
+
|
|
188
|
+
// One request, no retry storm; ctx.fetch enforces 15s/10MB and egress caps.
|
|
189
|
+
const response = await ctx.fetch("https://api.github.com/repos/nodejs/node/releases?per_page=20", {
|
|
190
|
+
headers: { accept: "application/vnd.github+json" },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (response.status === 403) {
|
|
194
|
+
const denied = await response.clone().json().catch(() => null);
|
|
195
|
+
if (denied?.vibesEgressDenied === true) {
|
|
196
|
+
await ctx.db.put(
|
|
197
|
+
{
|
|
198
|
+
_id: "refresh:status",
|
|
199
|
+
type: "refreshStatus",
|
|
200
|
+
state: "egress-denied",
|
|
201
|
+
message: "showing saved data — live refresh unavailable",
|
|
202
|
+
checkedAt: event.scheduledTime,
|
|
203
|
+
},
|
|
204
|
+
{ db: "nodeReleases" }
|
|
205
|
+
);
|
|
206
|
+
return; // preserve the seeded/last-good release docs
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
await ctx.db.put(
|
|
212
|
+
{
|
|
213
|
+
_id: "refresh:status",
|
|
214
|
+
type: "refreshStatus",
|
|
215
|
+
state: "failed",
|
|
216
|
+
message: "showing saved data — refresh failed",
|
|
217
|
+
checkedAt: event.scheduledTime,
|
|
218
|
+
},
|
|
219
|
+
{ db: "nodeReleases" }
|
|
220
|
+
);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const rows = await response.json();
|
|
225
|
+
for (const row of rows.slice(0, 20)) {
|
|
226
|
+
if (!row?.id || !row?.html_url || !row?.published_at) continue;
|
|
227
|
+
await ctx.db.put(
|
|
228
|
+
{
|
|
229
|
+
_id: `release:github-${row.id}`,
|
|
230
|
+
type: "release",
|
|
231
|
+
title: String(row.name || row.tag_name || "Node.js release"),
|
|
232
|
+
url: String(row.html_url),
|
|
233
|
+
publishedAt: String(row.published_at),
|
|
234
|
+
source: "github-live",
|
|
235
|
+
},
|
|
236
|
+
{ db: "nodeReleases" }
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
await ctx.db.put(
|
|
240
|
+
{
|
|
241
|
+
_id: "refresh:status",
|
|
242
|
+
type: "refreshStatus",
|
|
243
|
+
state: "fresh",
|
|
244
|
+
message: "Live feed refreshed",
|
|
245
|
+
checkedAt: event.scheduledTime,
|
|
246
|
+
},
|
|
247
|
+
{ db: "nodeReleases" }
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
App.jsx
|
|
253
|
+
|
|
254
|
+
```jsx
|
|
255
|
+
import React from "react";
|
|
256
|
+
import { useFireproof } from "use-fireproof";
|
|
257
|
+
|
|
258
|
+
export default function App() {
|
|
259
|
+
const { useLiveQuery } = useFireproof("nodeReleases");
|
|
260
|
+
const { docs: releaseDocs } = useLiveQuery("type", { key: "release" });
|
|
261
|
+
const { docs: statusDocs } = useLiveQuery("type", { key: "refreshStatus" });
|
|
262
|
+
const releases = [...releaseDocs].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt));
|
|
263
|
+
const status = statusDocs.find((doc) => doc._id === "refresh:status");
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<main className="mx-auto max-w-2xl p-6">
|
|
267
|
+
<h1 className="text-3xl font-bold">Node.js releases</h1>
|
|
268
|
+
{status?.state === "egress-denied" && (
|
|
269
|
+
<p role="status" className="my-4 rounded bg-amber-100 p-3 text-amber-950">
|
|
270
|
+
showing saved data — live refresh unavailable
|
|
271
|
+
</p>
|
|
272
|
+
)}
|
|
273
|
+
<ul className="mt-6 space-y-3">
|
|
274
|
+
{releases.map((release) => (
|
|
275
|
+
<li key={release._id} className="rounded border p-4">
|
|
276
|
+
<a className="font-semibold underline" href={release.url} target="_blank" rel="noreferrer">
|
|
277
|
+
{release.title}
|
|
278
|
+
</a>
|
|
279
|
+
<time className="ml-3 text-sm text-slate-500">{release.publishedAt.slice(0, 10)}</time>
|
|
280
|
+
</li>
|
|
281
|
+
))}
|
|
282
|
+
</ul>
|
|
283
|
+
</main>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The `vibesEgressDenied` body is a policy result, not proof that the upstream is
|
|
289
|
+
down. Catch that exact 403 shape and show the saved snapshot with honest copy —
|
|
290
|
+
**"showing saved data — live refresh unavailable"**. Never clear cached rows,
|
|
291
|
+
render a broken screen, or silently label saved data as fresh.
|
|
292
|
+
|
|
146
293
|
## fetch — the app's HTTP endpoint
|
|
147
294
|
|
|
148
295
|
Runs for requests to the app's `/_api` route. The request path is rooted after
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "8.2.
|
|
3
|
+
"version": "8.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"license": "Apache-2.0",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@adviser/cement": "~0.5.34",
|
|
27
|
-
"@vibes.diy/call-ai-v2": "^8.2.
|
|
28
|
-
"@vibes.diy/identity": "^8.2.
|
|
29
|
-
"@vibes.diy/use-vibes-types": "^8.2.
|
|
27
|
+
"@vibes.diy/call-ai-v2": "^8.2.1",
|
|
28
|
+
"@vibes.diy/identity": "^8.2.1",
|
|
29
|
+
"@vibes.diy/use-vibes-types": "^8.2.1",
|
|
30
30
|
"arktype": "~2.2.3",
|
|
31
31
|
"json-schema-faker": "~0.6.2"
|
|
32
32
|
},
|
|
@@ -239,6 +239,7 @@ Rules for the items:
|
|
|
239
239
|
- Every item carries a short immutable **`key`** slug (`"alert"`, `"setname"`), **unique within its database's array**. The key is the item's identity across codegen turns — the platform derives the document id and idempotency from it, so **do NOT invent `_id`s or write any dedupe logic**. Re-emitting an unchanged `seed.json` on a later turn is a no-op; changing an item's content updates that one document.
|
|
240
240
|
- Set `_id` explicitly **only** for a well-known singleton (a `config:setname` settings doc); the `key` is still required.
|
|
241
241
|
- Multi-db apps get one array per db; use the exact db names from `App.jsx`.
|
|
242
|
+
- **Seed every app-written document type.** For every string-literal `type` that `App.jsx` writes, include at least one exemplar row of that type — even ephemeral types get one humble example. Types originated by the platform at runtime (for example ImgGen's `"image"`) cannot be seeded; handle those with `access.js` branches instead.
|
|
242
243
|
- **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json`.
|
|
243
244
|
- **If the app has an `access.js`, every `type` you emit here must have a branch in it** — but don't add an access function _just_ to satisfy this: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written as the **owner** at launch through it, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. Before finishing, if you emitted an `access.js`, confirm it returns a readable descriptor for every distinct `type` present in `seed.json`. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
|
|
244
245
|
|
package/system-prompt-initial.md
CHANGED
|
@@ -243,6 +243,7 @@ Rules for the items:
|
|
|
243
243
|
- Every item carries a short immutable **`key`** slug (`"alert"`, `"setname"`), **unique within its database's array**. The key is the item's identity across codegen turns — the platform derives the document id and idempotency from it, so **do NOT invent `_id`s or write any dedupe logic**. Re-emitting an unchanged `seed.json` on a later turn is a no-op; changing an item's content updates that one document.
|
|
244
244
|
- Set `_id` explicitly **only** for a well-known singleton (a `config:setname` settings doc); the `key` is still required.
|
|
245
245
|
- Multi-db apps get one array per db; use the exact db names from `App.jsx`.
|
|
246
|
+
- **Seed every app-written document type.** For every string-literal `type` that `App.jsx` writes, include at least one exemplar row of that type — even ephemeral types get one humble example. Types originated by the platform at runtime (for example ImgGen's `"image"`) cannot be seeded; handle those with `access.js` branches instead.
|
|
246
247
|
- **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json`.
|
|
247
248
|
- **If the app has an `access.js`, every `type` you emit here must have a branch in it** — but don't add an access function _just_ to satisfy this: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written as the **owner** at launch through it, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. Before finishing, if you emitted an `access.js`, confirm it returns a readable descriptor for every distinct `type` present in `seed.json`. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
|
|
248
249
|
|
package/system-prompt.md
CHANGED
|
@@ -633,6 +633,7 @@ Rules for the items:
|
|
|
633
633
|
- Every item carries a short immutable **`key`** slug (`"alert"`, `"setname"`), **unique within its database's array**. The key is the item's identity across codegen turns — the platform derives the document id and idempotency from it, so **do NOT invent `_id`s or write any dedupe logic**. Re-emitting an unchanged `seed.json` on a later turn is a no-op; changing an item's content updates that one document.
|
|
634
634
|
- Set `_id` explicitly **only** for a well-known singleton (a `config:setname` settings doc); the `key` is still required.
|
|
635
635
|
- Multi-db apps get one array per db; use the exact db names from `App.jsx`.
|
|
636
|
+
- **Seed every app-written document type.** For every string-literal `type` that `App.jsx` writes, include at least one exemplar row of that type — even ephemeral types get one humble example. Types originated by the platform at runtime (for example ImgGen's `"image"`) cannot be seeded; handle those with `access.js` branches instead.
|
|
636
637
|
- **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json`.
|
|
637
638
|
- **If the app has an `access.js`, every `type` you emit here must have a branch in it** — but don't add an access function _just_ to satisfy this: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written as the **owner** at launch through it, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. Before finishing, if you emitted an `access.js`, confirm it returns a readable descriptor for every distinct `type` present in `seed.json`. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
|
|
638
639
|
|