@vibes.diy/prompts 14.3.82 → 14.3.84

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 CHANGED
@@ -104,16 +104,19 @@ access.js — the buddy grant and the followers audience arm are independent:
104
104
  ### `access.js`
105
105
  ```js
106
106
  const ch = `log:${user.userHandle}`;
107
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
107
108
 
108
109
  if (doc.type === "share") {
109
110
  // app-owned: grant the ONE handle this person picked (plus themselves).
110
111
  if (doc.authorHandle !== user.userHandle) throw { forbidden: "your own log" };
112
+ if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "your own log" }; // an update stays its writer's
111
113
  return { channels: [ch], grant: { users: { [doc.buddyHandle]: [ch], [user.userHandle]: [ch] } } };
112
114
  }
113
115
 
114
116
  if (doc.type === "entry") {
115
117
  // platform-owned: the audience arm; the checkbox above is what arms it.
116
118
  if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
119
+ if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" }; // an update stays its writer's
117
120
  return {
118
121
  channels: [ch],
119
122
  grant: { users: { [user.userHandle]: [ch] } },
@@ -122,6 +125,8 @@ if (doc.type === "entry") {
122
125
  }
123
126
 
124
127
  throw { forbidden: "unknown document type" }; // every access.js ends by denying what it does not know
128
+
129
+ // Summary: each person's workout log is theirs, shared with the buddies they pick and, once they say so, with their followers, and only its writer changes a record. Every record carries its writer's handle in authorHandle, and a share names the buddy in buddyHandle.
125
130
  ```
126
131
 
127
132
  ### Private generated images and published snapshots
@@ -154,6 +159,8 @@ function posterMedia(doc, oldDoc, user) {
154
159
  return { channels: [channel], grant: { users: { [doc.authorHandle]: [channel] } } };
155
160
  }
156
161
  export { posterMedia as "media:posters" };
162
+
163
+ // Summary: each person's generated poster images are theirs alone to see, and every version stays as it was made. A media record carries its writer's handle in authorHandle and names its poster in hostDocId.
157
164
  ```
158
165
 
159
166
  Each writer's versions are private even if another writer guesses the same
@@ -238,6 +245,8 @@ export function notes(doc, oldDoc, user, ctx) {
238
245
  const mine = `user:${user.userHandle}`;
239
246
  return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
240
247
  }
248
+
249
+ // Summary: members of a channel read, post and edit everything in its chat, and each person's notes are theirs alone. A chat record names its channel in channelId.
241
250
  ```
242
251
 
243
252
  Databases without a matching named export fall through to `export default` if one exists. If there is no default export either, the database uses the default app-level permissions (no access function).
@@ -251,6 +260,8 @@ function crewChat(doc, oldDoc, user, ctx) {
251
260
  return { channels: [doc.channelId] };
252
261
  }
253
262
  export { crewChat as "crew-chat" };
263
+
264
+ // Summary: members of a channel read, post and edit everything in its crew chat. A record names its channel in channelId.
254
265
  ```
255
266
 
256
267
  ### Catch-all with `export default`
@@ -270,6 +281,8 @@ export default function (doc, oldDoc, user, ctx) {
270
281
  const mine = `user:${user.userHandle}`;
271
282
  return { channels: [mine], grant: { users: { [user.userHandle]: [mine] } } };
272
283
  }
284
+
285
+ // Summary: members of a channel read, post and edit everything in its chat, and everything else each person writes is theirs alone. A chat record names its channel in channelId.
273
286
  ```
274
287
 
275
288
  This is especially useful when an app has many databases.
@@ -302,7 +315,7 @@ One trap: applying a release's `seed.json` is the **platform** writing, not the
302
315
 
303
316
  The **placement** of `access.js` depends on which turn shape you are in — one-shot whole-app generation vs. incremental scaffold/follow-up edits. Follow the subsection that matches your turn; the other one's ordering is wrong for you and will strand writes.
304
317
 
305
- **Variant-neutral rules (always):** write `access.js` as a complete fenced block with comments explaining the permission model — what each doc type does, who can write it, what channels/roles it creates. **Never put access function code inside an `App.jsx` block** — it will overwrite the React component; the path heading (e.g. `access.js` vs `App.jsx`) is how the system knows which file to write. Whatever the turn shape, `App.jsx` gates its write surfaces on `useVibe(dbName).can` — the same rules this access function enforces.
318
+ **Variant-neutral rules (always):** write `access.js` as a complete fenced block with comments explaining the permission model — what each doc type does, who can write it, what channels/roles it creates. End the file with its `// Summary:` line (see _The `// Summary:` line_ below). **Never put access function code inside an `App.jsx` block** — it will overwrite the React component; the path heading (e.g. `access.js` vs `App.jsx`) is how the system knows which file to write. Whatever the turn shape, `App.jsx` gates its write surfaces on `useVibe(dbName).can` — the same rules this access function enforces.
306
319
 
307
320
  **One-shot generation (the whole app in one full-file block):** emit `access.js` as the **last file of the turn** — after the complete `App.jsx` and any companion feature files. You are NOT emitting incremental `SEARCH`/`REPLACE` edits here — write the finished files in full. The worked examples below show the access-function shapes; ignore any edit-by-edit cadence in them and emit the access function as one complete block.
308
321
 
@@ -330,12 +343,24 @@ export function chat(doc, oldDoc, user, ctx) {
330
343
 
331
344
  throw { forbidden: "unknown document type" };
332
345
  }
346
+
347
+ // Summary: members of a channel read and post its messages, and each author edits their own. A message carries its writer's handle in userHandle and its channel in channelId.
333
348
  ```
334
349
 
335
350
  `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`. A grant/member/role doc must also return `channels` (route it to an owner-readable admin channel like `["admin:grants"]`) — a channel-less result is rejected as "unreadable write". On updates, keep ownership immutable (`if (oldDoc && doc.<authorField> !== oldDoc.<authorField>) throw`, where `<authorField>` is your doc's author field — `authorHandle`/`userHandle`/`senderHandle`) and let the author edit their own (as `chat.message` above shows). Pictures other viewers make for a shared doc are their own records in the media database, so an author-only update keeps every picture flowing.
336
351
 
337
352
  **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.
338
353
 
354
+ ### The `// Summary:` line — the rules in one plain sentence
355
+
356
+ Every `access.js` ends with one plain-language summary line after the function: the file closes with a comment starting `// Summary:`, and nothing follows it (a long one may wrap onto further `//` lines). In one or two sentences in the app's own nouns, it says what people can do — who can see and change what — and what a record needs to be admitted: the field that carries its writer's handle, and the state a new record starts in:
357
+
358
+ ```js partial
359
+ // Summary: each person's recipes are theirs alone; a friend added to a recipe can read it. A recipe carries its writer's handle in authorHandle; orders start with status "new".
360
+ ```
361
+
362
+ Name each field exactly as the function reads it — the line is how the person, and the platform, read the rules at a glance. Whenever this turn changes the function, rewrite the summary in the same block so it describes the rules as they now stand. The seed's records follow the line: each one carries `"@owner"` in the writer field it names and starts in the state it names. Every worked example below ends with its own.
363
+
339
364
  ### Reconciling `seed.json` when you write or change `access.js`
340
365
 
341
366
  An app usually gets its `seed.json` on an earlier turn than its `access.js`. So when you add or change an access function, you are almost always meeting a seed file that was written before any of these rules existed — **and the seed is yours to bring into line, not the other way round.** The access function is the constraint; the seed is a sample of the data. When a sample violates a constraint, change the sample.
@@ -348,6 +373,8 @@ So, in the same turn as the access edit and after it:
348
373
  - **Never add a branch to `access.js` just to admit a seed item.** A branch exists because the app writes that type. If a seed item's type appears nowhere in the app's own code, delete the item — do not widen the permission model to let example data through.
349
374
  - **Keep every explicit `_id` byte-identical.** Seed items that other items reference, and well-known singletons (`config:profile`, `settings:sharing`), carry an explicit `_id` that the app's code matches literally. Renaming one during a reconcile breaks that lookup in `App.jsx` and orphans any document an owner already imported, in a single edit. Keep the `key` slugs stable for the same reason — the platform derives idempotency from them.
350
375
 
376
+ **Write the seed as its writer: the owner.** Applying the import writes every record as the owner, with `user = { userHandle: <the owner's handle>, isOwner: true }`, and stamps nothing on the record. So a field a branch compares to the writer — `authorHandle`, `creatorHandle`, `hostHandle` — carries `"@owner"`, and the platform fills in the handle of whoever applies the seed: `{ "key": "pancakes", "type": "recipe", "title": "Sunday pancakes", "authorHandle": "@owner" }` is the owner's own record in the app you build and in every remix of it. Write the marker as the whole value, exactly `"@owner"`. Where the rules set the state a new record starts in ("orders start as new"), each seeded record starts in that state.
377
+
351
378
  **Order the seed so the function admits it.** Applying the import runs every document through `access.js` as the owner's first write, in file order, so the seed passes its own rules or it does not arrive at all. A document whose branch reads a channel off a parent needs that parent earlier in the file, granting the channel the child will be routed into; a type the function routes on a field carries that field on every item. A task list seeded as `{ type: "task", listId: "main" }` needs a `list` item with `_id: "main"` above it whose branch grants `list:main` — without it every task is refused on the first apply, and the app opens empty with nothing on screen to say why.
352
379
 
353
380
  If the reconcile removes every item, **still emit `seed.json`, containing `{}`**. Omitting the file does NOT delete it — a file your turn does not touch is carried forward from the previous version verbatim, so leaving `seed.json` out preserves exactly the stale, unauthorized seed you were removing. An empty object is a valid seed and replaces it.
@@ -388,6 +415,8 @@ export function wall(doc, oldDoc, user, ctx) {
388
415
 
389
416
  throw { forbidden: "unknown document type" };
390
417
  }
418
+
419
+ // Summary: the owner opens channels everyone can read, and anyone signed in posts to them and edits their own posts. A post carries its writer's handle in authorHandle and its channel in channelId.
391
420
  ```
392
421
 
393
422
  ### Worked example — per-object collaboration with join request
@@ -396,24 +425,47 @@ export function wall(doc, oldDoc, user, ctx) {
396
425
  ```js partial
397
426
  export function board(doc, oldDoc, user, ctx) {
398
427
  if (!user) throw { forbidden: "sign in" };
399
- const channel = `board:${doc.boardId}`;
428
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
400
429
 
401
430
  if (doc.type === "board") {
402
- if (oldDoc && doc.author !== oldDoc.author) throw { forbidden: "creator is fixed" };
403
- return { channels: [channel], grant: { users: { [user.userHandle]: [channel] } } };
431
+ // A board's channel comes from its own _id, so every new board record opens a room of its own.
432
+ const own = `board:${doc._id}`;
433
+ if (!oldDoc) {
434
+ // The person who starts a board is its author, and they are inside it from the first write.
435
+ if (doc.author !== user.userHandle) throw { forbidden: "start your own board" };
436
+ } else {
437
+ if (doc.author !== oldDoc.author) throw { forbidden: "creator is fixed" }; // write-once
438
+ ctx.requireAccess(own); // its members rename and edit the board they are in
439
+ }
440
+ return { channels: [own], grant: { users: { [doc.author]: [own] } } };
404
441
  }
405
442
 
443
+ // Everything else names its board in boardId and stays on that board.
444
+ if (oldDoc && oldDoc.boardId !== doc.boardId) throw { forbidden: "a record stays on its board" };
445
+ const channel = `board:${doc.boardId}`;
446
+
406
447
  if (doc.type === "share") {
407
- ctx.requireAccess(channel);
448
+ ctx.requireAccess(channel); // a member lets a friend in
408
449
  return { channels: [channel], grant: { users: { [doc.invitee]: [channel] } } };
409
450
  }
410
451
 
411
- if (doc.type === "request") return { channels: [channel] };
452
+ if (doc.type === "request") {
453
+ // Anyone signed in asks to join for themselves; members read the ask and answer it with a share.
454
+ if (doc.requester !== user.userHandle || (oldDoc && oldDoc.requester !== user.userHandle)) {
455
+ throw { forbidden: "ask for yourself" };
456
+ }
457
+ return { channels: [channel] };
458
+ }
459
+
460
+ if (doc.type === "item") {
461
+ ctx.requireAccess(channel); // any member adds and edits every item on the board
462
+ return { channels: [channel] };
463
+ }
412
464
 
413
- ctx.requireAccess(channel);
414
- if (oldDoc && oldDoc.boardId !== doc.boardId) throw { forbidden: "item stays on its board" };
415
- return { channels: [channel] };
465
+ throw { forbidden: "unknown document type" };
416
466
  }
467
+
468
+ // Summary: anyone signed in starts their own board, its members let a friend in with a share and add and edit every item on it, and anyone signed in can ask to join. A board carries its creator's handle in author, a request its asker's handle in requester, and every other record names its board in boardId.
417
469
  ```
418
470
 
419
471
  `ctx.requireAccess(channel)` gates on **membership** (a `grant.users`/`grant.roles` grant), not `grant.public` (read-only) — so an open feed 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`. A grant/share/request doc must also return `channels` — a channel-less result is rejected. On updates, keep ownership immutable (`doc.authorHandle !== oldDoc.authorHandle` → throw) and let the author edit their own (as `wall.post` above shows). Pictures other viewers make for a shared doc are their own records in the media database, so an author-only update keeps every picture flowing.
@@ -442,6 +494,8 @@ export function notes(doc, oldDoc, user, ctx) {
442
494
 
443
495
  throw { forbidden: "unknown document type" };
444
496
  }
497
+
498
+ // Summary: each person's notes are theirs alone, private from the first one they write. A note carries its writer's handle in authorHandle.
445
499
  ```
446
500
 
447
501
  Every item a visitor creates lands on their own `user:<handle>` channel, private to them from the first load. Pair it with a small "Only you can see this" cue near their content. This is the shape whenever the app is one person's own collection kept for themselves — the whole space is one private channel, no membership step in the way. The word "personal" routes on what the data is FOR: a personal tracker/journal/notes app is this private per-user shape, while a personal blog or portfolio exists to be READ — that one is the publication shape below (public read + author-owned posts, a roster of one), not a private channel.
@@ -487,6 +541,8 @@ export function pieces(doc, oldDoc, user, ctx) {
487
541
 
488
542
  throw { forbidden: "unknown document type" };
489
543
  }
544
+
545
+ // Summary: anyone signed in proposes pieces everyone can see, and each person's practice is their own — on the leaderboard or shared with one buddy, as they choose. Every record carries its writer's handle in authorHandle, and tracking records and check-ins name their piece in pieceId.
490
546
  ```
491
547
 
492
548
  The leaderboard is just the access model: read the public `track:` channels and sum them; each viewer additionally sees their own tallies and any buddy who granted them in. A practice log whose owner **asked to keep it to themselves** is instead the per-visitor shape shown above: every visitor's pieces and check-ins on their single `user:${user.userHandle}` channel, self-granted and private to them.
@@ -626,6 +682,8 @@ export function announcements(doc, oldDoc, user, ctx) {
626
682
 
627
683
  throw { forbidden: "unknown document type" };
628
684
  }
685
+
686
+ // Summary: the owner opens channels everyone can read and names who posts; posters write in their channels and edit their own posts. A post carries its writer's handle in authorHandle.
629
687
  ```
630
688
 
631
689
  App.jsx — `useVibe().can` gates every write surface (posts AND owner-only management); `access.hasChannel()` reflects display-only membership:
@@ -771,6 +829,8 @@ export function chat(doc, oldDoc, user, ctx) {
771
829
 
772
830
  throw { forbidden: "unknown document type" };
773
831
  }
832
+
833
+ // Summary: the owner opens channels everyone can read, and anyone signed in posts in them and edits their own posts. A post carries its writer's handle in authorHandle.
774
834
  ```
775
835
 
776
836
  App.jsx — `access.hasChannel()` filters which channels are visible (display); `useVibe().can` gates the write surface:
@@ -842,6 +902,8 @@ export default function (doc, oldDoc, user, ctx) {
842
902
 
843
903
  throw { forbidden: "unknown document type" };
844
904
  }
905
+
906
+ // Summary: each person makes their own lists and shares one with a friend, and everyone on a list adds and edits its items. A list carries its writer's handle in author, and items and shares name their list in listId.
845
907
  ```
846
908
 
847
909
  To invite someone who isn't a member yet without knowing their handle in advance, invert the flow with a request doc: a `request` type takes **no** `ctx.requireAccess` (any signed-in user may create one — their handle is `user.userHandle`, unforgeable) and routes to the list channel `ch(doc.listId)`, where current members read it (the requester can't read their own request back — they aren't a member yet — which is fine; they just wait to be granted). A member then writes the `share` above to approve.
@@ -944,6 +1006,8 @@ export function boards(doc, oldDoc, user, ctx) {
944
1006
 
945
1007
  throw { forbidden: "unknown document type" };
946
1008
  }
1009
+
1010
+ // Summary: everyone signed in has their own boards, a board's creator lets a friend in with a member record, and members add and edit every card on it. A board carries its creator's handle in creatorHandle, a card names its board in boardId, and a member record names the friend in userHandle and its writer in addedBy.
947
1011
  ```
948
1012
 
949
1013
  App.jsx — the invite surface is `HandleInput` plus one `member` write, shown to whoever the access fn would accept:
@@ -1116,6 +1180,8 @@ export function chat(doc, oldDoc, user, ctx) {
1116
1180
 
1117
1181
  throw { forbidden: "unknown document type" };
1118
1182
  }
1183
+
1184
+ // Summary: a channel's owner names its members, and members read and post messages and invite others in. A channel carries its owner in ownerHandle, a message its writer in userHandle, and an invite its sender in senderHandle.
1119
1185
  ```
1120
1186
 
1121
1187
  This single access function handles three document types: **channel-meta** — owner creates a channel and grants access to listed members, **message** — only the author can post, must already have channel access, **channel-invite** — any channel member can invite others; deleting the invite revokes the grant.
@@ -1161,6 +1227,9 @@ App.jsx — show only channels the viewer is in (`access.hasChannel`), gate the
1161
1227
  ### `access.js`
1162
1228
  ```js partial
1163
1229
  export function survey(doc, oldDoc, user, ctx) {
1230
+ // A record keeps its type, so a response stays a private response and results update only as results.
1231
+ if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" };
1232
+
1164
1233
  if (doc.type === "survey-response") {
1165
1234
  if (oldDoc) throw { forbidden: "responses are write-once" };
1166
1235
  return { channels: ["inbound-responses"], allowAnonymous: true };
@@ -1179,6 +1248,17 @@ export function survey(doc, oldDoc, user, ctx) {
1179
1248
  };
1180
1249
  }
1181
1250
 
1251
+ if (doc.type === "team-member") {
1252
+ // The owner names each person on the feedback team; the record IS the membership.
1253
+ ctx.requireRole("owner");
1254
+ if (typeof doc.memberHandle !== "string" || doc.memberHandle.length === 0) throw { forbidden: "name a team member" };
1255
+ return {
1256
+ channels: ["admin:grants"],
1257
+ members: { "feedback-team": [doc.memberHandle] },
1258
+ grant: { roles: { owner: ["admin:grants"] } },
1259
+ };
1260
+ }
1261
+
1182
1262
  if (doc.type === "final-results") {
1183
1263
  ctx.requireRole("feedback-team");
1184
1264
  return { channels: [doc._id], grant: { public: [doc._id] } };
@@ -1186,9 +1266,11 @@ export function survey(doc, oldDoc, user, ctx) {
1186
1266
 
1187
1267
  throw { forbidden: "unknown document type" };
1188
1268
  }
1269
+
1270
+ // Summary: anyone, signed in or not, submits responses that stay as written; the owner names each feedback team member with a team-member record, and the team reads the responses and publishes the final results for everyone. A team-member record names its person in memberHandle.
1189
1271
  ```
1190
1272
 
1191
- 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.
1273
+ 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, the **singleton grant doc** pattern (survey-config) wires role-to-channel access in one place, and each **team-member** record the owner writes puts one person in that role through `members` — so the team the config lets in has people in it, and removing the record lets that person go.
1192
1274
 
1193
1275
  App.jsx — anonymous-friendly submit form, owner-only config panel, role-gated results view:
1194
1276
 
@@ -1196,7 +1278,7 @@ App.jsx — anonymous-friendly submit form, owner-only config panel, role-gated
1196
1278
  <<<<<<< SEARCH
1197
1279
  const { database } = useFireproof("notes");
1198
1280
  =======
1199
- const { database, useLiveQuery, access } = useFireproof("survey");
1281
+ const { database, useLiveQuery } = useFireproof("survey");
1200
1282
  const { me, can } = useVibe("survey");
1201
1283
  // Submit form is shown for anyone — allowAnonymous makes this can.create return ok for null user.
1202
1284
  const canSubmit = can.create({ type: "survey-response", question: "q1", answer: "" });
@@ -1221,11 +1303,17 @@ App.jsx — anonymous-friendly submit form, owner-only config panel, role-gated
1221
1303
  </form>
1222
1304
  ) : <Text muted>{canSubmit.reason}</Text>}
1223
1305
 
1224
- {/* owner-only admin to wire up the feedback-team role */}
1225
- {canConfigure.ok && <ConfigPanel />}
1306
+ {/* owner-only admin: wire up the feedback-team role and name who is on the team */}
1307
+ {canConfigure.ok && (
1308
+ <ConfigPanel
1309
+ onAddMember={(handle) =>
1310
+ database.put({ _id: `team-member:${handle}`, type: "team-member", memberHandle: handle })
1311
+ }
1312
+ />
1313
+ )}
1226
1314
 
1227
- {/* results render only for users with the feedback-team role — access.hasChannel filters them in */}
1228
- {results.filter((r) => access.hasChannel(r._id)).map((r) => <ResultCard key={r._id} doc={r} />)}
1315
+ {/* final results are public: everyone, signed in or not, reads every published result */}
1316
+ {results.map((r) => <ResultCard key={r._id} doc={r} />)}
1229
1317
  >>>>>>> REPLACE
1230
1318
  ```
1231
1319
 
@@ -1243,6 +1331,8 @@ export function guestbook(doc, oldDoc, user, ctx) {
1243
1331
  }
1244
1332
  throw { forbidden: "unknown document type" };
1245
1333
  }
1334
+
1335
+ // Summary: anyone, signed in or not, signs the guestbook and everyone reads it; each entry stays as it was signed.
1246
1336
  ```
1247
1337
 
1248
1338
  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.
@@ -0,0 +1,2 @@
1
+ import type { LlmConfig } from "./types.js";
2
+ export declare const blocklyConfig: LlmConfig;
@@ -0,0 +1,59 @@
1
+ export const blocklyConfig = {
2
+ name: "blockly",
3
+ label: "Blockly",
4
+ description: "Block editors built with Blockly, where people snap pieces together instead of typing code. " +
5
+ "Two audiences, one recipe. Teaching code: Scratch-like coding playgrounds for kids and " +
6
+ "beginners, drag-and-drop page builders with a live preview and Code Peek, turtle and robot " +
7
+ "sequences. Business logic edited by the people who own it: no-code rules engines (credit " +
8
+ "scoring, fraud flags, approval thresholds, compliance checks, discount and pricing rules, " +
9
+ "eligibility, routing and triage) whose blocks compile to a JSON rule tree the app evaluates in " +
10
+ "the browser and in backend.js; CRM, ERP and ticket workflow automations (when a record arrives, " +
11
+ "if it matches, tag it, assign it, send it to a queue, schedule a follow-up); low-code form and " +
12
+ "screen builders; device, robot and conveyor step sequences; QA, test-case and integration " +
13
+ "scripts (steps, expectations, mock responses). Covers the one pinned esm.sh ?bundle import, " +
14
+ "custom blocks with connection checks as the type system, JSON toolboxes with shadow defaults, " +
15
+ "custom CodeGenerator output (HTML, JSON, steps) and the standard javascriptGenerator run in a " +
16
+ "sandboxed iframe with step highlighting, workspace JSON saved to Fireproof with autosave, a " +
17
+ "Blockly theme from the app's tokens, locales via setLocale, and the zelos renderer on phones. " +
18
+ "blockly, scratch, blocks, block editor, visual programming, drag and drop code, no-code, low-code, " +
19
+ "rules engine, rule builder, decision rules, business rules, policy builder, workflow builder, " +
20
+ "automation builder, if-this-then-that, approval flow, test builder, puzzle pieces",
21
+ cues: [
22
+ "blockly",
23
+ "scratch",
24
+ "scratch-like",
25
+ "block editor",
26
+ "block coding",
27
+ "blocks editor",
28
+ "visual programming",
29
+ "visual scripting",
30
+ "drag-and-drop blocks",
31
+ "drag and drop blocks",
32
+ "snap together",
33
+ "snap blocks",
34
+ "puzzle pieces",
35
+ "rules builder",
36
+ "rule builder",
37
+ "rules engine",
38
+ "business rules",
39
+ "decision rules",
40
+ "policy builder",
41
+ "approval rules",
42
+ "scoring rules",
43
+ "fraud rules",
44
+ "compliance rules",
45
+ "eligibility rules",
46
+ "routing rules",
47
+ "no-code",
48
+ "low-code",
49
+ "workflow builder",
50
+ "automation builder",
51
+ "if this then that",
52
+ "trigger and action",
53
+ "test builder",
54
+ "test script builder",
55
+ "robot program",
56
+ "program the robot",
57
+ ],
58
+ };
59
+ //# sourceMappingURL=blockly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blockly.js","sourceRoot":"","sources":["../../jsr/llms/blockly.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,WAAW,EACT,8FAA8F;QAC9F,yFAAyF;QACzF,6FAA6F;QAC7F,2FAA2F;QAC3F,4FAA4F;QAC5F,iGAAiG;QACjG,kGAAkG;QAClG,iGAAiG;QACjG,4FAA4F;QAC5F,8FAA8F;QAC9F,gGAAgG;QAChG,gGAAgG;QAChG,8FAA8F;QAC9F,gGAAgG;QAChG,qGAAqG;QACrG,gGAAgG;QAChG,mFAAmF;IACrF,IAAI,EAAE;QACJ,SAAS;QACT,SAAS;QACT,cAAc;QACd,cAAc;QACd,cAAc;QACd,eAAe;QACf,oBAAoB;QACpB,kBAAkB;QAClB,sBAAsB;QACtB,sBAAsB;QACtB,eAAe;QACf,aAAa;QACb,eAAe;QACf,eAAe;QACf,cAAc;QACd,cAAc;QACd,gBAAgB;QAChB,gBAAgB;QAChB,gBAAgB;QAChB,gBAAgB;QAChB,eAAe;QACf,aAAa;QACb,kBAAkB;QAClB,mBAAmB;QACnB,eAAe;QACf,SAAS;QACT,UAAU;QACV,kBAAkB;QAClB,oBAAoB;QACpB,mBAAmB;QACnB,oBAAoB;QACpB,cAAc;QACd,qBAAqB;QACrB,eAAe;QACf,mBAAmB;KACpB;CAMF,CAAC"}