@vibes.diy/prompts 14.3.120 → 14.3.121
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 +206 -108
- package/llms/board-games.md +1 -1
- package/llms/connections.md +1 -1
- package/llms/fireproof.md +66 -84
- package/package.json +5 -5
- package/system-prompt-initial-oneshot.md +180 -64
- package/system-prompt-initial.md +180 -64
- package/system-prompt.md +9 -8
package/llms/fireproof.md
CHANGED
|
@@ -259,110 +259,91 @@ Address an existing record by its `_id`. `useDocument(task)` above holds the sel
|
|
|
259
259
|
|
|
260
260
|
## Required starting records
|
|
261
261
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
the
|
|
266
|
-
`seed.json`
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
explicit import. In that file, give each item a stable `key`; its derived identity
|
|
270
|
-
is `seed:<key>`. References to another item use that derived identity. An `_id`
|
|
271
|
-
that repeats the item's own derived identity is equivalent to leaving it out.
|
|
272
|
-
Explicit singleton identities such as `config:board` remain available.
|
|
273
|
-
|
|
274
|
-
Four rules make it hold:
|
|
262
|
+
A person's own action — a tap, a submit, a pick — is what starts a write; nothing in the app writes on load or on mount. Starting state comes in three kinds, and none of them is written by the app's own code:
|
|
263
|
+
|
|
264
|
+
- **A person's own starting state needs no record.** Their records live on the channel named by their handle (see `access.md`), so the app opens on their tree with nothing to create first, and the view renders its defaults — a name, a setting, an empty list's invitation — from constants until the person's own action saves one.
|
|
265
|
+
- **App-wide named containers or participants the request requires** — the pottery studio and its named apprentice, the three projects in a studio, the rooms in a planner — are written by the agent building the app, with `put_doc` as the owner, once the build whose rules admit them is saved, then read back. That save is the owner's own action. The app's opening view shows them when they are there and an inviting add flow until they are.
|
|
266
|
+
- **Example content stays the optional `seed.json` import** — believable entries the screenshot renders and the owner adds with one ask. A name present only in `seed.json` describes an offered import, not an existing record or grant. In that file, give each item a stable `key`; its derived identity is `seed:<key>`. References to another item use that derived identity. An `_id` that repeats the item's own derived identity is equivalent to leaving it out. Explicit singleton identities such as `config:board` remain available. The file also populates the temporary, view-only preview while saving is being prepared.
|
|
267
|
+
|
|
268
|
+
Four rules make the required records hold:
|
|
275
269
|
|
|
276
270
|
- **Each required record gets a fixed `_id` you choose** (`"project:atlas"`), so
|
|
277
|
-
a second
|
|
278
|
-
- **
|
|
279
|
-
the
|
|
280
|
-
|
|
281
|
-
- **
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
one copy. Reading the query first and then deciding whether to write is the shape
|
|
291
|
-
that races — the read is empty in both tabs before either write lands.
|
|
271
|
+
a second save of it lands on the same document instead of minting another.
|
|
272
|
+
- **Gate their creation on a role in `access.js`** — `ctx.requireRole("owner")`,
|
|
273
|
+
or a role the owner hands out with a role record — when the records describe the
|
|
274
|
+
app itself rather than a visitor's own work.
|
|
275
|
+
- **List parents and membership or role grants before the records that depend on
|
|
276
|
+
them**, so a dependent document never lands against a container or a grant that
|
|
277
|
+
does not exist yet.
|
|
278
|
+
- **Leave the app's render paths write-free.** A write in an effect that fires when
|
|
279
|
+
a query reads empty runs on every fresh load, on every device and for every
|
|
280
|
+
visitor, because the live query renders empty before data arrives.
|
|
281
|
+
|
|
282
|
+
Fixed ids are also what makes a repeated save safe: two devices saving the same
|
|
283
|
+
record write the same document, and last-writer-wins converges on one copy.
|
|
292
284
|
|
|
293
285
|
### Match sample records to the owner's opening query
|
|
294
286
|
|
|
295
|
-
The initial build's `<app-owner>` context supplies the app owner's handle.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
287
|
+
The initial build's `<app-owner>` context supplies the app owner's handle. A
|
|
288
|
+
sample record in the owner's own tree names that tree with `"@owner"` as the whole
|
|
289
|
+
field value, which the platform fills in for whoever applies the seed. The opening
|
|
290
|
+
view reads the signed-in person's own tree by their handle, so every sample card
|
|
291
|
+
carries `"ownerHandle": "@owner"`:
|
|
299
292
|
|
|
300
293
|
```jsx
|
|
301
|
-
const
|
|
302
|
-
const { docs: cards } = useLiveQuery("boardId", { key: boardId });
|
|
294
|
+
const { docs: cards } = useLiveQuery("ownerHandle", { key: me?.userHandle });
|
|
303
295
|
```
|
|
304
296
|
|
|
305
297
|
```json
|
|
306
298
|
{
|
|
307
299
|
"boards": [
|
|
308
|
-
{ "key": "tent", "type": "card", "
|
|
309
|
-
{ "key": "stove", "type": "card", "
|
|
310
|
-
{ "key": "lantern", "type": "card", "
|
|
311
|
-
{ "key": "cooler", "type": "card", "
|
|
300
|
+
{ "key": "tent", "type": "card", "ownerHandle": "@owner", "authorHandle": "@owner", "text": "Blue tent, sleeps four" },
|
|
301
|
+
{ "key": "stove", "type": "card", "ownerHandle": "@owner", "authorHandle": "@owner", "text": "Two-burner camp stove" },
|
|
302
|
+
{ "key": "lantern", "type": "card", "ownerHandle": "@owner", "authorHandle": "@owner", "text": "Two rechargeable lanterns" },
|
|
303
|
+
{ "key": "cooler", "type": "card", "ownerHandle": "@owner", "authorHandle": "@owner", "text": "Wheeled cooler" }
|
|
312
304
|
]
|
|
313
305
|
}
|
|
314
306
|
```
|
|
315
307
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
308
|
+
An id that only contains the handle (`settings-mira`) is not a whole value, so
|
|
309
|
+
write the supplied owner handle into it literally; keep the app's query and real
|
|
310
|
+
writes driven by the current viewer and selected object. The database name,
|
|
311
|
+
record type, parent references and query filters agree across the sample file and
|
|
312
|
+
opening view. Later access rules preserve those parent references. The platform
|
|
313
|
+
labels the temporary preview as sample data; those records reach persistent
|
|
314
|
+
storage only through the person's explicit import.
|
|
322
315
|
|
|
323
316
|
### Seeding starter data — give each seed a deterministic `_id`
|
|
324
317
|
|
|
325
|
-
When an app ships with default content — starter slides, template rows, example cards —
|
|
318
|
+
When an app ships with default content — starter slides, template rows, example cards — the content is a set of `seed.json` items, each with a **deterministic identity** (a stable `key`, or `_id: "seed:intro"`). This makes adding it **idempotent**: a second add overwrites the same documents instead of creating fresh copies.
|
|
326
319
|
|
|
327
|
-
The
|
|
320
|
+
The app itself writes no copy. `useLiveQuery` renders empty before data arrives, so `docs.length === 0` is briefly true on **every** fresh load, and a "seed when empty" effect re-runs on every reload, every device and for every visitor. The empty view instead invites the person's own first action, and that action writes the record it checked:
|
|
328
321
|
|
|
329
322
|
### `App.jsx`
|
|
330
|
-
```jsx
|
|
331
|
-
|
|
332
|
-
import { useFireproof } from "use-fireproof";
|
|
333
|
-
import { useVibe } from "use-vibes";
|
|
334
|
-
|
|
335
|
-
// Stable _id per seed, and no Date.now()/random fields — keep the payload
|
|
336
|
-
// constant so a redundant seed is a true no-op, not a churning rewrite.
|
|
337
|
-
const SEED = [
|
|
338
|
-
{ _id: "seed:intro", type: "slide", title: "Welcome", position: 1000 },
|
|
339
|
-
{ _id: "seed:problem", type: "slide", title: "The problem", position: 2000 },
|
|
340
|
-
{ _id: "seed:ask", type: "slide", title: "The ask", position: 3000 },
|
|
341
|
-
];
|
|
342
|
-
|
|
343
|
-
export default function App() {
|
|
323
|
+
```jsx partial
|
|
324
|
+
function Deck() {
|
|
344
325
|
const { useLiveQuery, database } = useFireproof("deck");
|
|
345
326
|
const { ready, can } = useVibe("deck");
|
|
346
327
|
const { docs: slides } = useLiveQuery("position");
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return <
|
|
328
|
+
// The person's own tap writes the first slide: the control checks this exact record.
|
|
329
|
+
const first = { type: "slide", title: "Welcome", position: 1000 };
|
|
330
|
+
const allowed = ready && can.create(first).ok;
|
|
331
|
+
|
|
332
|
+
if (slides.length === 0) {
|
|
333
|
+
return (
|
|
334
|
+
<Stack gap={2}>
|
|
335
|
+
<Text>Your deck is ready for its first slide.</Text>
|
|
336
|
+
<Button type="button" disabled={!allowed} onClick={() => allowed && database.put(first)}>
|
|
337
|
+
Add the first slide
|
|
338
|
+
</Button>
|
|
339
|
+
</Stack>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return <Slides slides={slides} />;
|
|
362
343
|
}
|
|
363
344
|
```
|
|
364
345
|
|
|
365
|
-
Don't stamp `Date.now()` or other changing values into seed documents — that makes each re-
|
|
346
|
+
Don't stamp `Date.now()` or other changing values into seed documents — that makes each re-add rewrite the doc with new content, churning revisions even though the `_id` is stable. Resource-like docs — channels, profiles, **seeds** — get deterministic `_id`s; event/content docs let `_id` auto-generate.
|
|
366
347
|
|
|
367
348
|
### Updating Documents in Event Handlers
|
|
368
349
|
|
|
@@ -454,7 +435,7 @@ A drag that touches many documents (painting pixels, drawing on a shared board)
|
|
|
454
435
|
|
|
455
436
|
Keep each cell its own document with a deterministic `_id` (`pixel-<x>-<y>`). Never fold the canvas into one document every visitor saves: the server keeps whichever save arrives last, so one visitor's save erases everyone else's strokes. An unsaved draft drops out for peers about 12 seconds after it stops updating.
|
|
456
437
|
|
|
457
|
-
A draft reaches only the peers already subscribed to its channel, and a channel's grant comes from a saved document.
|
|
438
|
+
A draft reaches only the peers already subscribed to its channel, and a channel's grant comes from a saved document. The painter's own pointer-down saves a fixed-id room record on that channel before the first stroke is drawn, so the stroke is seen live; nothing writes on load or on mount.
|
|
458
439
|
|
|
459
440
|
### `App.jsx`
|
|
460
441
|
```jsx
|
|
@@ -485,12 +466,6 @@ function Canvas({ me }) {
|
|
|
485
466
|
const cellsRef = useRef([]);
|
|
486
467
|
const canvasRef = useRef(null);
|
|
487
468
|
|
|
488
|
-
useEffect(() => {
|
|
489
|
-
// The room record grants the channel, so peers subscribe before the first stroke.
|
|
490
|
-
const room = { _id: "canvas:main", type: "canvas" };
|
|
491
|
-
if (ready && can.create(room).ok) database.put(room);
|
|
492
|
-
}, [ready, can, database]);
|
|
493
|
-
|
|
494
469
|
useEffect(() => {
|
|
495
470
|
const ctx = canvasRef.current.getContext("2d");
|
|
496
471
|
ctx.fillStyle = "#ffffff";
|
|
@@ -504,6 +479,13 @@ function Canvas({ me }) {
|
|
|
504
479
|
for (const [x, y] of stroke.cells) fill(x, y, stroke.color);
|
|
505
480
|
}, [pixels, strokes, stroke, me]);
|
|
506
481
|
|
|
482
|
+
function startStroke(e) {
|
|
483
|
+
// The painter's own pointer-down saves the room record that grants the channel; the
|
|
484
|
+
// fixed id makes every later save the same document.
|
|
485
|
+
const room = { _id: "canvas:main", type: "canvas" };
|
|
486
|
+
if (ready && can.create(room).ok) database.put(room);
|
|
487
|
+
extendStroke(e);
|
|
488
|
+
}
|
|
507
489
|
function extendStroke(e) {
|
|
508
490
|
if (!canPaint) return;
|
|
509
491
|
const r = canvasRef.current.getBoundingClientRect();
|
|
@@ -532,7 +514,7 @@ function Canvas({ me }) {
|
|
|
532
514
|
className="w-full max-w-lg aspect-square touch-none"
|
|
533
515
|
onPointerDown={(e) => {
|
|
534
516
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
535
|
-
|
|
517
|
+
startStroke(e);
|
|
536
518
|
}}
|
|
537
519
|
onPointerMove={(e) => e.buttons && extendStroke(e)}
|
|
538
520
|
onPointerUp={endStroke}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.121",
|
|
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.
|
|
43
|
-
"@vibes.diy/call-ai-v2": "14.3.
|
|
44
|
-
"@vibes.diy/identity": "14.3.
|
|
45
|
-
"@vibes.diy/use-vibes-types": "14.3.
|
|
42
|
+
"@vibes.diy/api-types": "14.3.121",
|
|
43
|
+
"@vibes.diy/call-ai-v2": "14.3.121",
|
|
44
|
+
"@vibes.diy/identity": "14.3.121",
|
|
45
|
+
"@vibes.diy/use-vibes-types": "14.3.121",
|
|
46
46
|
"arktype": "~2.2.6",
|
|
47
47
|
"json-schema-faker": "~0.6.3"
|
|
48
48
|
},
|