@vibes.diy/prompts 14.3.120 → 14.3.122
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/llms/richtext.js +2 -1
- package/llms/richtext.js.map +1 -1
- package/llms/richtext.md +53 -0
- package/package.json +5 -5
- package/system-prompt-initial-instant.md +1 -0
- package/system-prompt-initial-oneshot.md +181 -65
- package/system-prompt-initial.md +181 -65
- package/system-prompt.md +10 -9
- package/themes/looks/pouf/brief.md +5 -5
- package/themes/looks/pouf/catalog.generated.d.ts +1 -1
- package/themes/looks/pouf/catalog.generated.js +5 -5
- package/themes/looks/pouf/catalog.generated.js.map +1 -1
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/llms/richtext.js
CHANGED
|
@@ -6,7 +6,8 @@ export const richtextConfig = {
|
|
|
6
6
|
"edits with the last editor named, meeting notes with action items, a recipe book with real pages, " +
|
|
7
7
|
"a journal with one entry per day. Covers every Tiptap package pinned to one version by its esm.sh " +
|
|
8
8
|
"URL (one ProseMirror, no second React), an editor module with createEditor, load, flush, run and " +
|
|
9
|
-
"destroy, a saved note as its JSON plus plain text and a derived title,
|
|
9
|
+
"destroy, a saved note as its JSON plus plain text and a derived title, a title field beside the " +
|
|
10
|
+
"page saved with the body (after its own pause and on close, so drafts keep their titles), saving after typing pauses " +
|
|
10
11
|
"with flush on blur and switch, loading a document without a save echo (setContent with emitUpdate " +
|
|
11
12
|
"false), destroy in the effect's cleanup, the page styled from the host with arbitrary variants, " +
|
|
12
13
|
"toolbar commands and active states, and images as _files. " +
|
package/llms/richtext.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"richtext.js","sourceRoot":"","sources":["../../jsr/llms/richtext.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,cAAc,GAAc;IACvC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EACT,oGAAoG;QACpG,gGAAgG;QAChG,oGAAoG;QACpG,oGAAoG;QACpG,mGAAmG;QACnG,
|
|
1
|
+
{"version":3,"file":"richtext.js","sourceRoot":"","sources":["../../jsr/llms/richtext.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,cAAc,GAAc;IACvC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EACT,oGAAoG;QACpG,gGAAgG;QAChG,oGAAoG;QACpG,oGAAoG;QACpG,mGAAmG;QACnG,kGAAkG;QAClG,uHAAuH;QACvH,oGAAoG;QACpG,kGAAkG;QAClG,4DAA4D;QAC5D,sGAAsG;QACtG,2FAA2F;IAC7F,IAAI,EAAE;QACJ,UAAU;QACV,WAAW;QACX,aAAa;QACb,aAAa;QACb,YAAY;QACZ,SAAS;QACT,OAAO;QACP,MAAM;QACN,iBAAiB;QACjB,YAAY;QACZ,WAAW;QACX,WAAW;QACX,aAAa;QACb,SAAS;QACT,aAAa;QACb,eAAe;QACf,iBAAiB;QACjB,eAAe;QACf,gBAAgB;QAChB,oBAAoB;QACpB,iBAAiB;QACjB,oBAAoB;QACpB,cAAc;QACd,QAAQ;QACR,aAAa;QACb,OAAO;QACP,SAAS;QACT,cAAc;QACd,WAAW;KACZ;CACF,CAAC"}
|
package/llms/richtext.md
CHANGED
|
@@ -357,6 +357,55 @@ nothing twice. The save callback reads the open note through a ref rather
|
|
|
357
357
|
than a closure, so the editor is created once and switching notes costs
|
|
358
358
|
nothing.
|
|
359
359
|
|
|
360
|
+
## A title field saves with the body
|
|
361
|
+
|
|
362
|
+
**A title field beside the editor saves through the same write as the body:
|
|
363
|
+
one `write` helper merges every change into the open document and puts it,
|
|
364
|
+
one put at a time, so the title saves after its own pause and closing the
|
|
365
|
+
document waits for the page and the title to land together.** A blog with
|
|
366
|
+
drafts, a journal with named entries or a recipe book with a name field
|
|
367
|
+
keeps the title in an `Input` above the page, and the writer expects both to
|
|
368
|
+
be there when they step back to the list. Publishing then changes `status`
|
|
369
|
+
and nothing else, so a private draft keeps every word its writer typed,
|
|
370
|
+
title included.
|
|
371
|
+
|
|
372
|
+
```jsx
|
|
373
|
+
const [title, setTitle] = useState(post.title);
|
|
374
|
+
const docRef = useRef(post); // the open document as last written
|
|
375
|
+
const queue = useRef(Promise.resolve()); // puts to one _id run in turn
|
|
376
|
+
|
|
377
|
+
// The page's onChange and the title both write here: the change merges into
|
|
378
|
+
// the open document at once, and its put waits for the one before it.
|
|
379
|
+
function write(changes) {
|
|
380
|
+
docRef.current = { ...docRef.current, ...changes, updatedAt: Date.now() };
|
|
381
|
+
const next = docRef.current;
|
|
382
|
+
queue.current = queue.current.then(() => database.put(next));
|
|
383
|
+
return queue.current;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// The title waits for its own pause, like the page.
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (title === docRef.current.title) return;
|
|
389
|
+
const timer = setTimeout(() => write({ title }), 600);
|
|
390
|
+
return () => clearTimeout(timer);
|
|
391
|
+
}, [title]);
|
|
392
|
+
|
|
393
|
+
// Back to the list: flush the page, add the title, and wait for both.
|
|
394
|
+
async function close() {
|
|
395
|
+
surfaceRef.current?.flush();
|
|
396
|
+
if (title !== docRef.current.title) write({ title });
|
|
397
|
+
await queue.current;
|
|
398
|
+
onClose();
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
The page's `onChange` calls the same helper, `write({ body: json, text })`.
|
|
403
|
+
Because each change merges into `docRef` before its put is queued, the last
|
|
404
|
+
put carries the page and the title together, and puts to one `_id` never
|
|
405
|
+
overlap (overlapping puts collide on the document's sequence, as in the
|
|
406
|
+
saving section above). The `Input` stays `onChange={setTitle}`; the effect
|
|
407
|
+
does the saving.
|
|
408
|
+
|
|
360
409
|
## Loading a document lands without a save
|
|
361
410
|
|
|
362
411
|
**A document arriving from the database is loaded with `setContent` and
|
|
@@ -604,6 +653,10 @@ warning.
|
|
|
604
653
|
derived. `titleOf(json)` on every save.
|
|
605
654
|
- **Typing is lost when switching notes** — the debounce had not fired.
|
|
606
655
|
`flush()` before changing the open note.
|
|
656
|
+
- **Every draft comes back as "Untitled draft"** — the title field lives in
|
|
657
|
+
React state and only Publish writes it. Save the title after its own
|
|
658
|
+
pause and in the close step, through the same queued `write` as the page,
|
|
659
|
+
and let the close step wait for it.
|
|
607
660
|
- **Two editors after a hot reload** — the effect's cleanup did not destroy
|
|
608
661
|
the editor.
|
|
609
662
|
- **The page has no styles** — Tiptap ships none, and a multi-file app
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.122",
|
|
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.122",
|
|
43
|
+
"@vibes.diy/call-ai-v2": "14.3.122",
|
|
44
|
+
"@vibes.diy/identity": "14.3.122",
|
|
45
|
+
"@vibes.diy/use-vibes-types": "14.3.122",
|
|
46
46
|
"arktype": "~2.2.6",
|
|
47
47
|
"json-schema-faker": "~0.6.3"
|
|
48
48
|
},
|
|
@@ -97,6 +97,7 @@ export default function App() {
|
|
|
97
97
|
- **The named interaction works end to end**: the person's sentence names it, and the app builds that and only that, with realistic data. Sharing, invitations, roles, scheduled jobs, emails and outside services come in a later pass, and so does the line about who can see the data, which sits beside the share control that pass adds; the title, subtitle and hero say what the app is for.
|
|
98
98
|
- **Seed content** is realistic and specific to the app's world, with a `key` per item and the exact fields the app reads.
|
|
99
99
|
- **Layout comes from the components** (`Stack`, `Row`, `Grid`, `Card`) and their `tone`, `size` and `variant` props; plain spacing like `p-6` sits on your own wrapping elements only. The look brief below says how the page is composed.
|
|
100
|
+
- **A bar fixed to the bottom of the screen — a tab bar, a nav, a save bar — rides above the platform's controls**, so every tab and button on it stays tappable: `BottomNav` sits there by itself, and a bar of your own puts `bottom: 'var(--vibes-controls-inset, 0px)'` in its `style` instead of using `bottom-0`.
|
|
100
101
|
- **Tapping a person opens their profile**: `<ViewerTag userHandle={m.userHandle} onClick={() => setProfile(m.userHandle)} />` binds the handle the tag shows, and a hero header is `<ViewerTag userHandle={profile} size={120} avatarOnly />` — the tag itself is the button, drawn natively. Opening a profile follows nobody.
|
|
101
102
|
|
|
102
103
|
# Fireproof on vibes.diy — the short version
|