@vibes.diy/prompts 14.1.38 → 14.1.40

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.
@@ -13,7 +13,26 @@ export interface GenerationVariety {
13
13
  readonly archetype: string;
14
14
  readonly flourishes: string[];
15
15
  }
16
- export declare function drawGenerationVariety(rnd: () => number): GenerationVariety;
16
+ export interface VarietyPin {
17
+ readonly archetype: string;
18
+ readonly flourishes: string[];
19
+ }
20
+ export declare const VARIETY_PIN_GRAMMAR = "<archetype>:<flourish>,<flourish>,<flourish>";
21
+ export declare function varietyNames(): {
22
+ archetypes: string[];
23
+ flourishes: string[];
24
+ };
25
+ export type VarietyPinCheck = {
26
+ readonly ok: true;
27
+ readonly pin: VarietyPin;
28
+ } | {
29
+ readonly ok: false;
30
+ readonly message: string;
31
+ };
32
+ export declare function validateVarietyPin(candidate: unknown): VarietyPinCheck;
33
+ export declare function parseVarietyPin(spec: string): VarietyPinCheck;
34
+ export declare function validVarietyPinOrUndefined(candidate: unknown): VarietyPin | undefined;
35
+ export declare function drawGenerationVariety(rnd: () => number, pin?: VarietyPin): GenerationVariety;
17
36
  export declare function renderVarietySection(variety: Partial<GenerationVariety>): string;
18
37
  export declare function renderVarietyContextForPreAlloc(variety: Partial<GenerationVariety>): string;
19
38
  export declare function renderTextureNoteSection(note: string | undefined): string;
@@ -75,11 +75,76 @@ export const FLOURISHES = [
75
75
  },
76
76
  ];
77
77
  export const FLOURISH_DRAW_COUNT = 3;
78
+ export const VARIETY_PIN_GRAMMAR = "<archetype>:<flourish>,<flourish>,<flourish>";
79
+ export function varietyNames() {
80
+ return {
81
+ archetypes: LAYOUT_ARCHETYPES.map((a) => a.id),
82
+ flourishes: FLOURISHES.map((f) => f.id),
83
+ };
84
+ }
85
+ function varietyNamesMessage() {
86
+ const names = varietyNames();
87
+ return (`Grammar: ${VARIETY_PIN_GRAMMAR}\n` +
88
+ `Archetypes: ${names.archetypes.join(", ")}\n` +
89
+ `Flourishes: ${names.flourishes.join(", ")}`);
90
+ }
91
+ export function validateVarietyPin(candidate) {
92
+ const c = candidate;
93
+ const archetype = typeof c?.archetype === "string" ? c.archetype.trim() : "";
94
+ const rawFlourishes = Array.isArray(c?.flourishes) ? c.flourishes : undefined;
95
+ if (archetype.length === 0 || rawFlourishes === undefined) {
96
+ return {
97
+ ok: false,
98
+ message: `A variety pin needs an archetype and ${FLOURISH_DRAW_COUNT} flourishes.\n${varietyNamesMessage()}`,
99
+ };
100
+ }
101
+ const flourishes = rawFlourishes.map((f) => (typeof f === "string" ? f.trim() : ""));
102
+ if (LAYOUT_ARCHETYPES.some((a) => a.id === archetype) === false) {
103
+ return { ok: false, message: `Unknown layout archetype "${archetype}".\n${varietyNamesMessage()}` };
104
+ }
105
+ if (flourishes.length !== FLOURISH_DRAW_COUNT) {
106
+ return {
107
+ ok: false,
108
+ message: `A variety pin names exactly ${FLOURISH_DRAW_COUNT} flourishes; got ${flourishes.length}.\n${varietyNamesMessage()}`,
109
+ };
110
+ }
111
+ if (new Set(flourishes).size !== flourishes.length) {
112
+ return { ok: false, message: `A variety pin's flourishes must be distinct.\n${varietyNamesMessage()}` };
113
+ }
114
+ const unknown = flourishes.filter((f) => FLOURISHES.some((known) => known.id === f) === false);
115
+ if (unknown.length > 0) {
116
+ return { ok: false, message: `Unknown flourish(es) ${unknown.map((u) => `"${u}"`).join(", ")}.\n${varietyNamesMessage()}` };
117
+ }
118
+ return { ok: true, pin: { archetype, flourishes } };
119
+ }
120
+ export function parseVarietyPin(spec) {
121
+ const trimmed = spec.trim();
122
+ const colon = trimmed.indexOf(":");
123
+ if (colon <= 0) {
124
+ return { ok: false, message: `A variety pin is written ${VARIETY_PIN_GRAMMAR}.\n${varietyNamesMessage()}` };
125
+ }
126
+ return validateVarietyPin({
127
+ archetype: trimmed.slice(0, colon),
128
+ flourishes: trimmed
129
+ .slice(colon + 1)
130
+ .split(",")
131
+ .map((f) => f.trim())
132
+ .filter((f) => f.length > 0),
133
+ });
134
+ }
135
+ export function validVarietyPinOrUndefined(candidate) {
136
+ if (candidate === undefined || candidate === null)
137
+ return undefined;
138
+ const checked = validateVarietyPin(candidate);
139
+ return checked.ok ? checked.pin : undefined;
140
+ }
78
141
  function pickIndex(rnd, length) {
79
142
  const raw = Math.floor(rnd() * length);
80
143
  return raw >= length ? length - 1 : raw;
81
144
  }
82
- export function drawGenerationVariety(rnd) {
145
+ export function drawGenerationVariety(rnd, pin) {
146
+ if (pin !== undefined)
147
+ return { archetype: pin.archetype, flourishes: [...pin.flourishes] };
83
148
  const archetype = LAYOUT_ARCHETYPES[pickIndex(rnd, LAYOUT_ARCHETYPES.length)].id;
84
149
  const pool = FLOURISHES.map((f) => f.id);
85
150
  const flourishes = [];
@@ -1 +1 @@
1
- {"version":3,"file":"generation-variety.js","sourceRoot":"","sources":["../jsr/generation-variety.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generation-variety.js","sourceRoot":"","sources":["../jsr/generation-variety.ts"],"names":[],"mappings":"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"}
package/llms/access.md CHANGED
@@ -66,7 +66,9 @@ Keep the two visually distinct (a checkbox with its own explanatory line; a sepa
66
66
 
67
67
  App.jsx — the checkbox is the consent call; the picker is a doc write:
68
68
 
69
- ```jsx
69
+ ```jsx file=App.jsx
70
+ const { can } = useVibe("logs"); // the write gate is always the server's own verdict
71
+
70
72
  // checking is the consent preflight; unchecking is the off switch. Never one verb for both edges.
71
73
  <label>
72
74
  <input
@@ -82,12 +84,16 @@ App.jsx — the checkbox is the consent call; the picker is a doc write:
82
84
  {showFollowers && <ul>{followers.map((f) => <li key={f.handle}>{f.handle}</li>)}</ul>}
83
85
 
84
86
  {/* the app's own list — one share doc per person picked */}
85
- {can.create({ type: "share", authorHandle: me?.userHandle, buddyHandle: "x" }).ok && <AddBuddyForm />}
87
+ {can.create({ type: "share", authorHandle: me?.userHandle, buddyHandle: "x" }).ok && (
88
+ <AddBuddyForm
89
+ onAdd={(buddyHandle) => database.put({ type: "share", authorHandle: me?.userHandle, buddyHandle })}
90
+ />
91
+ )}
86
92
  ```
87
93
 
88
94
  access.js — the buddy grant and the followers audience arm are independent:
89
95
 
90
- ```js
96
+ ```js file=access.js
91
97
  const ch = `log:${user.userHandle}`;
92
98
 
93
99
  if (doc.type === "share") {
@@ -105,6 +111,8 @@ if (doc.type === "entry") {
105
111
  audience: { followersOf: user.userHandle },
106
112
  };
107
113
  }
114
+
115
+ throw { forbidden: "unknown document type" }; // every access.js ends by denying what it does not know
108
116
  ```
109
117
 
110
118
  ### The ImgGen version-append nuance
@@ -195,7 +203,7 @@ Use `oldDoc` (the previous version of the document) to enforce invariants across
195
203
 
196
204
  Some documents belong to the machinery rather than to a person: a scheduled run's state doc, a counter a `scheduled` sweep advances, a notification the backend generates. Gate those on the **origin** with `ctx.requireBackend(...)` — one line, no conditional:
197
205
 
198
- ```js
206
+ ```js file=access.js partial
199
207
  if (doc.type === "runState") {
200
208
  ctx.requireBackend("scheduled");
201
209
  return { channels: ["show"] };
@@ -224,7 +232,7 @@ Worked example — members-only chat writes
224
232
 
225
233
  access.js
226
234
 
227
- ```js
235
+ ```js file=access.js partial
228
236
  export function chat(doc, oldDoc, user, ctx) {
229
237
  if (!user) throw { forbidden: "authentication required" };
230
238
 
@@ -279,7 +287,7 @@ _Each example below closes its type dispatch with the default terminal `throw {
279
287
 
280
288
  access.js
281
289
 
282
- ```js
290
+ ```js file=access.js partial
283
291
  export function wall(doc, oldDoc, user, ctx) {
284
292
  if (!user) throw { forbidden: "sign in" };
285
293
 
@@ -310,7 +318,7 @@ export function wall(doc, oldDoc, user, ctx) {
310
318
 
311
319
  access.js
312
320
 
313
- ```js
321
+ ```js file=access.js partial
314
322
  export function board(doc, oldDoc, user, ctx) {
315
323
  if (!user) throw { forbidden: "sign in" };
316
324
  const channel = `board:${doc.boardId}`;
@@ -343,7 +351,7 @@ export function board(doc, oldDoc, user, ctx) {
343
351
 
344
352
  access.js
345
353
 
346
- ```js
354
+ ```js file=access.js partial
347
355
  export function notes(doc, oldDoc, user, ctx) {
348
356
  if (!user) throw { forbidden: "sign in" };
349
357
  const ch = `user:${user.userHandle}`; // this visitor's own private space
@@ -372,7 +380,7 @@ Every item a visitor creates lands on their own `user:<handle>` channel, private
372
380
 
373
381
  access.js
374
382
 
375
- ```js
383
+ ```js file=access.js partial
376
384
  export function habits(doc, oldDoc, user, ctx) {
377
385
  if (!user) throw { forbidden: "sign in" };
378
386
 
@@ -414,7 +422,7 @@ The leaderboard is just the access model: read the public `track:` channels and
414
422
 
415
423
  The personal blog's post branch is small — public read is the blog's resting state, declared inline on every post result (readers are the point of publishing):
416
424
 
417
- ```js
425
+ ```js file=access.js partial
418
426
  if (doc.type === "post") {
419
427
  if (!oldDoc) {
420
428
  if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
@@ -496,7 +504,7 @@ This example shows the full round-trip — access.js declares channels and grant
496
504
 
497
505
  access.js
498
506
 
499
- ```js
507
+ ```js file=access.js
500
508
  export function announcements(doc, oldDoc, user, ctx) {
501
509
  if (!user) throw { forbidden: "sign in" };
502
510
 
@@ -546,7 +554,7 @@ export function announcements(doc, oldDoc, user, ctx) {
546
554
 
547
555
  App.jsx — `useVibe().can` gates every write surface (posts AND owner-only management); `access.hasChannel()` reflects display-only membership:
548
556
 
549
- ```jsx
557
+ ```jsx file=App.jsx
550
558
  import React from "react";
551
559
  import { useFireproof } from "use-fireproof";
552
560
  import { useViewer, useVibe } from "use-vibes";
@@ -648,7 +656,7 @@ Channels everyone can read, and any signed-in user can post to. The channel doc
648
656
 
649
657
  access.js
650
658
 
651
- ```js
659
+ ```js file=access.js partial
652
660
  export function chat(doc, oldDoc, user, ctx) {
653
661
  if (!user) throw { forbidden: "sign in" };
654
662
 
@@ -718,7 +726,7 @@ Reach for this by default, and certainly whenever the prompt says **invite, join
718
726
 
719
727
  access.js
720
728
 
721
- ```js
729
+ ```js file=access.js partial
722
730
  export default function (doc, oldDoc, user, ctx) {
723
731
  if (!user) throw { forbidden: "sign in" };
724
732
  const ch = (id) => `list:${id}`;
@@ -805,7 +813,7 @@ Reach for this variant whenever the app is built around a *thing* people come ba
805
813
 
806
814
  access.js
807
815
 
808
- ```js
816
+ ```js file=access.js partial
809
817
  export function boards(doc, oldDoc, user, ctx) {
810
818
  if (!user?.userHandle) throw { forbidden: "sign in" };
811
819
  const safeId = (id) => {
@@ -911,7 +919,7 @@ That bounds what the build's own closing line says: the rules and the screen it
911
919
 
912
920
  access.js
913
921
 
914
- ```js
922
+ ```js file=access.js partial
915
923
  export function chat(doc, oldDoc, user, ctx) {
916
924
  if (!user) throw { forbidden: "authentication required" };
917
925
 
@@ -997,7 +1005,7 @@ App.jsx — show only channels the viewer is in (`access.hasChannel`), gate the
997
1005
 
998
1006
  access.js
999
1007
 
1000
- ```js
1008
+ ```js file=access.js partial
1001
1009
  export function survey(doc, oldDoc, user, ctx) {
1002
1010
  if (doc.type === "survey-response") {
1003
1011
  if (oldDoc) throw { forbidden: "responses are write-once" };
@@ -1073,7 +1081,7 @@ When the prompt says **anyone can sign / submit without logging in** (a guestboo
1073
1081
 
1074
1082
  access.js
1075
1083
 
1076
- ```js
1084
+ ```js file=access.js partial
1077
1085
  export function guestbook(doc, oldDoc, user, ctx) {
1078
1086
  if (doc.type === "entry") {
1079
1087
  if (oldDoc) throw { forbidden: "entries are write-once" };
package/llms/backend.md CHANGED
@@ -444,7 +444,7 @@ in `nodeReleases`, and records refresh state in that same database.
444
444
 
445
445
  backend.js
446
446
 
447
- ```js
447
+ ```js file=backend.js
448
448
  export const config = { scheduled: { interval: "15m" } };
449
449
 
450
450
  const SNAPSHOT = [
@@ -486,6 +486,7 @@ async function putIfChanged(ctx, next, db) {
486
486
  }
487
487
 
488
488
  export async function scheduled(event, ctx) {
489
+ ctx.log("debug", "refresh tick", { at: event.scheduledTime }); // proof of life, before any read
489
490
  await ensureSnapshot(ctx);
490
491
 
491
492
  // One request, no retry storm; ctx.fetch enforces 15s/10MB and egress caps.
@@ -540,7 +541,7 @@ export async function scheduled(event, ctx) {
540
541
 
541
542
  App.jsx
542
543
 
543
- ```jsx
544
+ ```jsx file=App.jsx
544
545
  import React from "react";
545
546
  import { useFireproof } from "use-fireproof";
546
547
 
@@ -612,7 +613,7 @@ Runs for requests to the app's `/_api` route. The request path is rooted after
612
613
  `/_api`: a call to `https://slug--owner.host/_api/webhooks/pay` arrives with
613
614
  pathname `/webhooks/pay`. Return a standard `Response`.
614
615
 
615
- ```js
616
+ ```js file=backend.js
616
617
  export async function fetch(request, ctx) {
617
618
  const url = new URL(request.url);
618
619
  if (url.pathname === "/rsvp" && request.method === "POST") {
@@ -628,7 +629,7 @@ export async function fetch(request, ctx) {
628
629
 
629
630
  From `App.jsx`, call it with a relative fetch — no host needed:
630
631
 
631
- ```js
632
+ ```js file=App.jsx partial
632
633
  const res = await fetch("/_api/rsvp", { method: "POST", body: JSON.stringify({ name }) });
633
634
  ```
634
635
 
@@ -640,7 +641,7 @@ owner, declare `config.fetch.unfilteredReads` for the collections it reads, page
640
641
  `ctx.db.query` returns, and keep progress in a state document read by id so a second run continues
641
642
  where the first stopped.
642
643
 
643
- ```js
644
+ ```js file=backend.js
644
645
  export const config = { fetch: { unfilteredReads: ["rsvps"] } };
645
646
 
646
647
  export async function fetch(request, ctx) {
@@ -681,7 +682,7 @@ branch on: a score or a summary is only ever computed from text the handler actu
681
682
 
682
683
  backend.js
683
684
 
684
- ```js
685
+ ```js file=backend.js
685
686
  export async function fetch(request, ctx) {
686
687
  const url = new URL(request.url);
687
688
  if (url.pathname === "/page") {
@@ -720,7 +721,7 @@ export async function fetch(request, ctx) {
720
721
 
721
722
  From `App.jsx`, the page asks the handler for the address it was given:
722
723
 
723
- ```js
724
+ ```js file=App.jsx partial
724
725
  const res = await fetch(`/_api/page?url=${encodeURIComponent(url)}`);
725
726
  const page = await res.json();
726
727
  if (page.ok === false) {
@@ -747,7 +748,7 @@ server owes in response.
747
748
  Runs after any document write to the app's databases commits (user writes and
748
749
  backend writes alike). The event:
749
750
 
750
- ```js
751
+ ```js file=backend.js
751
752
  export async function onChange(event, ctx) {
752
753
  // event: { dbName, docId, doc, oldDoc, seq, deleted }
753
754
  if (event.dbName !== "votes" || event.deleted) return;
@@ -771,11 +772,12 @@ Two rules keep change-reactions sane:
771
772
  Requires a `config` export with a **static string-literal** interval between
772
773
  `"5s"` and `"1h"` (e.g. `"30s"`, `"5m"`, `"1h"` — computed values are rejected):
773
774
 
774
- ```js
775
+ ```js file=backend.js
775
776
  export const config = { scheduled: { interval: "15m" } };
776
777
 
777
778
  export async function scheduled(event, ctx) {
778
779
  // event: { scheduledTime } — ISO timestamp of this tick
780
+ ctx.log("debug", "heartbeat tick", { at: event.scheduledTime }); // proof of life, every tick
779
781
  await ctx.db.put({ kind: "heartbeat", at: event.scheduledTime }, { db: "status" });
780
782
  }
781
783
  ```
@@ -798,7 +800,7 @@ saving on its own, which a stored hour-offset does not.
798
800
 
799
801
  App.jsx — when the owner saves the schedule:
800
802
 
801
- ```jsx
803
+ ```jsx file=App.jsx partial
802
804
  await merge({
803
805
  hour: 12, // the hour they picked, in their own day
804
806
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, // e.g. "America/Detroit"
@@ -807,7 +809,7 @@ await merge({
807
809
 
808
810
  backend.js — the tick compares in that zone:
809
811
 
810
- ```js
812
+ ```js file=backend.js partial
811
813
  const hourLocal = Number(
812
814
  new Intl.DateTimeFormat("en-US", {
813
815
  timeZone: settings.timeZone ?? "UTC",
@@ -873,7 +875,7 @@ repeats" above: the interval is only how often the handler wakes up.
873
875
 
874
876
  App.jsx — write the zone once, when they arrive:
875
877
 
876
- ```jsx
878
+ ```jsx file=App.jsx
877
879
  useEffect(() => {
878
880
  if (!me?.userHandle) return;
879
881
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -884,7 +886,7 @@ useEffect(() => {
884
886
 
885
887
  backend.js — evaluate each person in their own zone:
886
888
 
887
- ```js
889
+ ```js file=backend.js
888
890
  export const config = { scheduled: { interval: "15m" } };
889
891
  const WEEKDAYS = new Set(["Mon", "Tue", "Wed", "Thu", "Fri"]);
890
892
 
@@ -940,7 +942,7 @@ is the only place an owner can tell. For a job that acts twice a month that mean
940
942
  weeks of silence that reads exactly like a dead alarm. So log once per tick,
941
943
  before any early return:
942
944
 
943
- ```js
945
+ ```js file=backend.js partial
944
946
  export async function scheduled(event, ctx) {
945
947
  const due = eventsDueNow(event.scheduledTime);
946
948
  ctx.log("debug", "tick", { due: due.length }); // proof of life; no reads, no spend
@@ -1022,7 +1024,7 @@ genuinely produced something is a real state change and belongs in that doc —
1022
1024
  unlike a per-tick `checkedAt`, which is the churn the zero-churn rule above is
1023
1025
  about.
1024
1026
 
1025
- ```js
1027
+ ```js file=backend.js
1026
1028
  export const config = { scheduled: { interval: "15m" } };
1027
1029
 
1028
1030
  const SHOW_DB = "show"; // the one database App.jsx live-queries
@@ -1032,6 +1034,7 @@ const SHOW_DB = "show"; // the one database App.jsx live-queries
1032
1034
  let lastFinishedDay;
1033
1035
 
1034
1036
  export async function scheduled(event, ctx) {
1037
+ ctx.log("debug", "digest tick", { at: event.scheduledTime }); // proof of life, before any return
1035
1038
  const settings = await ctx.db.get("schedule", { db: SHOW_DB });
1036
1039
  const zone = settings?.timeZone ?? "UTC"; // captured in the owner's browser
1037
1040
  const at = new Date(event.scheduledTime);
@@ -1097,7 +1100,13 @@ export async function scheduled(event, ctx) {
1097
1100
  // The day stays claimed: the owner sees what went wrong and is not billed twice for it.
1098
1101
  ctx.log("error", "[digest] draft failed after the day was claimed", { message: err.message });
1099
1102
  await ctx.db.put(
1100
- { _id: "run:status", type: "runStatus", state: "failed", message: "today's draft didn't finish" },
1103
+ {
1104
+ _id: "run:status",
1105
+ type: "runStatus",
1106
+ state: "failed",
1107
+ checkedAt: event.scheduledTime, // a second failure must still read as a new one
1108
+ message: "today's draft didn't finish",
1109
+ },
1101
1110
  { db: SHOW_DB }
1102
1111
  );
1103
1112
  }
@@ -1111,7 +1120,7 @@ including the owner's own — while still no-opping under admin mode, so a hand
1111
1120
  repair runs the function and keeps the routing. `access.js` is where that is
1112
1121
  said:
1113
1122
 
1114
- ```js
1123
+ ```js file=access.js
1115
1124
  // access.js
1116
1125
  export default function (doc, oldDoc, user, ctx) {
1117
1126
  if (doc.type === "runState" || doc.type === "runStatus") {
@@ -1123,11 +1132,24 @@ export default function (doc, oldDoc, user, ctx) {
1123
1132
  }
1124
1133
  ```
1125
1134
 
1126
- App.jsx reads the same database, and renders that status beside the drafts, so a
1127
- failed run and a good one never look alike:
1135
+ App.jsx writes the `schedule` document that tick reads — the hour the owner
1136
+ picked and, beside it, the zone their own browser reported — and reads the same
1137
+ database back, rendering the status beside the drafts so a failed run and a good
1138
+ one never look alike:
1139
+
1140
+ ```jsx file=App.jsx
1141
+ const { useLiveQuery, database } = useFireproof("show");
1142
+
1143
+ async function saveSchedule(hour) {
1144
+ // The hour is theirs and so is the zone: capture it here, store it beside the hour.
1145
+ await database.put({
1146
+ _id: "schedule",
1147
+ type: "schedule",
1148
+ hour,
1149
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1150
+ });
1151
+ }
1128
1152
 
1129
- ```jsx
1130
- const { useLiveQuery } = useFireproof("show");
1131
1153
  const { docs: statusDocs } = useLiveQuery("type", { key: "runStatus" });
1132
1154
  const run = statusDocs.find((doc) => doc._id === "run:status");
1133
1155
  // …
@@ -1147,7 +1169,7 @@ mirrors each note into a per-author `activity` entry (acting as that author —
1147
1169
  same privilege, just automated), and a `scheduled` sweep maintains one `digest`
1148
1170
  doc that `access.js` restricts to the **owner**, so no user can forge it.
1149
1171
 
1150
- ```js
1172
+ ```js file=backend.js
1151
1173
  export const config = { scheduled: { interval: "15m" } };
1152
1174
 
1153
1175
  export async function onChange(event, ctx) {
@@ -1159,6 +1181,7 @@ export async function onChange(event, ctx) {
1159
1181
  }
1160
1182
 
1161
1183
  export async function scheduled(event, ctx) {
1184
+ ctx.log("debug", "digest sweep", { at: event.scheduledTime }); // proof of life, before any return
1162
1185
  // Acts as the app owner — access.js allows `digest` docs for the owner only,
1163
1186
  // making this document genuinely server-controlled.
1164
1187
  //
package/llms/bluesky.md CHANGED
@@ -135,7 +135,7 @@ person can act on — "no data" is not.
135
135
 
136
136
  ## Complete backend.js
137
137
 
138
- ```js
138
+ ```js file=backend.js app=bluesky-browser
139
139
  const XRPC = "https://public.api.bsky.app/xrpc";
140
140
  const DB = "bluesky";
141
141
 
@@ -253,7 +253,7 @@ export async function fetch(request, ctx) {
253
253
  A profile card, its follow graph, and the account's posts — one handle in, no
254
254
  account of the visitor's involved.
255
255
 
256
- ```jsx
256
+ ```jsx file=App.jsx app=bluesky-browser
257
257
  import React, { useState } from "react";
258
258
 
259
259
  export default function App() {
package/llms/calendar.md CHANGED
@@ -110,7 +110,7 @@ button is simply there once the user has subscribable content.
110
110
  there means the Subscribe link never renders, even though the mint effect and
111
111
  the feed itself work.
112
112
 
113
- ```jsx
113
+ ```jsx file=App.jsx app=calendar-feed
114
114
  // access.js needs a `caltoken` branch routing the doc to the owner's PRIVATE
115
115
  // channel (like notes) — the token is the secret.
116
116
  const { docs: calTokens } = useLiveQuery(byTypeUser, { key: ["caltoken", me.userHandle] });
@@ -153,7 +153,7 @@ the button tooltip.
153
153
  "2026-07-31", time: "18:00:00", endtime: "", title, venue, url } }` — adapt
154
154
  the doc shape, db name, and timezone to the app.
155
155
 
156
- ```js
156
+ ```js file=backend.js app=calendar-feed
157
157
  export const config = { scheduled: { interval: "1m" } };
158
158
 
159
159
  const TZ = "America/Los_Angeles"; // the app's local timezone
package/llms/callai.md CHANGED
@@ -62,7 +62,7 @@ Send an image by passing the prompt as an array of content parts: an `image_url`
62
62
 
63
63
  The prompt is the content of one user message, so a plain string means exactly one text part and the array is that same message's parts. Read the picture the user picked with a `FileReader` and hand over the data URL it produces:
64
64
 
65
- ```javascript
65
+ ```javascript file=App.jsx
66
66
  import { callAI } from "call-ai";
67
67
 
68
68
  function readAsDataUrl(file) {
@@ -31,7 +31,7 @@ a public profile) is an ordinary `ctx.fetch` and needs no connection.
31
31
 
32
32
  ### 1. Declare it — `backend.js`
33
33
 
34
- ```js
34
+ ```js file=backend.js app=instagram-connection
35
35
  export const config = {
36
36
  connections: [
37
37
  {
@@ -70,7 +70,7 @@ for the rest.
70
70
 
71
71
  ### 2. Ask for it — `App.jsx`
72
72
 
73
- ```jsx
73
+ ```jsx file=App.jsx app=instagram-connection
74
74
  import { useConnection } from "use-vibes";
75
75
 
76
76
  function ConnectInstagram() {
@@ -112,7 +112,7 @@ the platform's Settings page, not in your app.
112
112
 
113
113
  ### 3. Read it — `backend.js`
114
114
 
115
- ```js
115
+ ```js file=backend.js app=instagram-connection
116
116
  import { instagram, connectionState } from "@vibes.diy/social";
117
117
 
118
118
  export async function fetch(request, ctx) {
@@ -24,7 +24,7 @@ never automatically from an effect, a timer, or straight out of an async `callAI
24
24
  resolution. Gate it behind an explicit "Create my ___" button the user presses
25
25
  when they're ready.
26
26
 
27
- ```jsx
27
+ ```jsx file=App.jsx
28
28
  import React from "react";
29
29
  import { createVibe } from "use-vibes";
30
30
 
@@ -63,7 +63,7 @@ Let the user edit each slide inline and present full-screen.`;
63
63
 
64
64
  ## Full pattern — interview, then hand off
65
65
 
66
- ```jsx
66
+ ```jsx file=App.jsx
67
67
  import React from "react";
68
68
  import { callAI } from "call-ai";
69
69
  import { createVibe } from "use-vibes";
package/llms/d3.md CHANGED
@@ -138,7 +138,7 @@ Structure your component in this order:
138
138
 
139
139
  ### 1. Simple Bar Chart
140
140
 
141
- ```js
141
+ ```js file=App.jsx
142
142
  import * as d3 from "d3";
143
143
 
144
144
  // 1. Design tokens and dimensions
package/llms/fireproof.md CHANGED
@@ -29,7 +29,7 @@ This complete app shows Fireproof's core: `useFireproof` gives you hooks, `useDo
29
29
 
30
30
  App.jsx
31
31
 
32
- ```jsx
32
+ ```jsx file=App.jsx
33
33
  import React from "react";
34
34
  import { useFireproof } from "use-fireproof";
35
35
 
@@ -112,7 +112,7 @@ When an app ships with default content — starter slides, template rows, exampl
112
112
 
113
113
  The trap is the live-query empty state. `useLiveQuery` renders empty before data arrives, so `docs.length === 0` is briefly true on **every** fresh load — and a `useRef`/`useState` "already seeded" flag only guards the current session, never the next reload, a second device, or another collaborator. With **auto-generated** `_id`s, each of those re-seeds the full set and duplicate copies pile up without bound. Deterministic `_id`s collapse every re-seed back onto the same N documents.
114
114
 
115
- ```jsx
115
+ ```jsx file=App.jsx
116
116
  import React from "react";
117
117
  import { useFireproof } from "use-fireproof";
118
118
  import { useVibe } from "use-vibes";
@@ -442,7 +442,7 @@ Building an image uploader with `_files`:
442
442
 
443
443
  App.jsx
444
444
 
445
- ```jsx
445
+ ```jsx file=App.jsx
446
446
  import React from "react";
447
447
  import { useFireproof } from "use-fireproof";
448
448
 
@@ -576,7 +576,7 @@ App.jsx
576
576
 
577
577
  Code listing for todo tracker App.jsx. Note the code ordering: hooks, then handlers, then classNames right before JSX.
578
578
 
579
- ```js
579
+ ```js file=App.jsx
580
580
  import React from "react";
581
581
  import { useFireproof } from "use-fireproof";
582
582
 
package/llms/image-gen.md CHANGED
@@ -10,7 +10,7 @@ Start with a minimal image generation component:
10
10
 
11
11
  App.jsx
12
12
 
13
- ```jsx
13
+ ```jsx file=App.jsx
14
14
  import React from "react";
15
15
  import { useFireproof } from "use-fireproof";
16
16
  import { ImgGen } from "use-vibes";
package/llms/p5.md CHANGED
@@ -22,7 +22,7 @@ the instance so remounts do not leak a second draw loop.
22
22
 
23
23
  App.jsx
24
24
 
25
- ```jsx
25
+ ```jsx file=App.jsx
26
26
  import React from "react";
27
27
  import p5 from "https://esm.sh/p5@1.11.13";
28
28
 
package/llms/spotify.md CHANGED
@@ -158,7 +158,7 @@ the app looks broken rather than misconfigured.
158
158
 
159
159
  ## Complete backend.js
160
160
 
161
- ```js
161
+ ```js file=backend.js app=spotify-browser
162
162
  const TOKEN_URL = "https://accounts.spotify.com/api/token";
163
163
  const API = "https://api.spotify.com/v1";
164
164
 
@@ -301,7 +301,7 @@ function artistRow(a) {
301
301
 
302
302
  ## App.jsx sketch
303
303
 
304
- ```jsx
304
+ ```jsx file=App.jsx app=spotify-browser
305
305
  import React, { useState } from "react";
306
306
 
307
307
  const SETUP_STEPS = [
package/llms/three-js.md CHANGED
@@ -17,7 +17,7 @@ and renderer so remounts don't leak GPU memory.
17
17
 
18
18
  App.jsx
19
19
 
20
- ```jsx
20
+ ```jsx file=App.jsx
21
21
  import React from "react";
22
22
  import * as THREE from "three";
23
23
 
package/llms/use-vibe.md CHANGED
@@ -26,7 +26,7 @@ Never stamp author fields from `me` while the verdict is pending — `me` is nul
26
26
 
27
27
  Gate every write affordance on `can.*`. Hold it while `pending`, and write your own copy when denied. Never branch write permission on `viewer`, `access.hasRole()`/`access.hasChannel()`, or document fields — those drift from what `access.js` actually does. Rendering **other** users (authors, rosters) is `useViewer()`'s `<ViewerTag userHandle={...} />`, not `useVibe`. The current viewer's own pill and the "signed in as" label are system chrome in the Vibes Switch (the logo) — don't build them into the app. Asking an anonymous visitor to sign in is a different thing and it IS yours: `useViewer().requestLogin()`, at the moment their work becomes worth keeping (see use-viewer docs). A resolved denial is one such moment — write the sign-in copy in your app's own words and wire it to that callable, never to a sentence about the logo. (The one exception is inline avatar self-edit: a guarded no-prop `{viewer && <ViewerTag />}` lets any signed-in member change their own photo in place — see use-viewer docs.)
28
28
 
29
- ```jsx
29
+ ```jsx file=App.jsx
30
30
  import { useVibe } from "use-vibes";
31
31
 
32
32
  function PromptBar({ database }) {
@@ -38,9 +38,14 @@ function PromptBar({ database }) {
38
38
  if (!v.ok) return <p className="muted">Join the board to add a tile.</p>;
39
39
  // 3. allowed
40
40
  return (
41
- <form onSubmit={/* … */}>
41
+ <form
42
+ onSubmit={(e) => {
43
+ e.preventDefault();
44
+ database.put({ type: "tile", authorHandle: me?.userHandle, text: e.target.tile.value });
45
+ }}
46
+ >
42
47
  {/* no current-user pill — identity + sign-in live in the Vibes Switch (the logo) */}
43
- <input placeholder="Add a tile…" />
48
+ <input name="tile" placeholder="Add a tile…" />
44
49
  <button type="submit">Post</button>
45
50
  </form>
46
51
  );
@@ -14,7 +14,7 @@ Start with a minimal component that reads the viewer identity. You don't render
14
14
 
15
15
  App.jsx
16
16
 
17
- ```jsx
17
+ ```jsx file=App.jsx
18
18
  import React from "react";
19
19
  import { useFireproof } from "use-fireproof";
20
20
  import { useViewer } from "use-vibes";
package/llms/webxr.md CHANGED
@@ -417,7 +417,7 @@ for (let i = 0; i < 200; i++) {
417
417
 
418
418
  A floating galaxy of 2000 particles with a custom-shader core sphere, navigable in VR. Session data stored in Fireproof.
419
419
 
420
- ```javascript
420
+ ```javascript file=App.jsx
421
421
  import * as BABYLON from "@babylonjs/core";
422
422
  import React, { useEffect, useRef } from "react";
423
423
  import { useFireproof } from "use-fireproof";
package/llms/youtube.md CHANGED
@@ -250,7 +250,7 @@ nothing they can act on, and the four cases above each have a different fix.
250
250
 
251
251
  ## Complete backend.js
252
252
 
253
- ```js
253
+ ```js file=backend.js app=youtube-dashboard
254
254
  export const config = {
255
255
  scheduled: { interval: "1h" },
256
256
  // The visitor path below reads the snapshot docs on the fetch lane, which
@@ -507,7 +507,7 @@ export async function scheduled(event, ctx) {
507
507
 
508
508
  ## App.jsx sketch
509
509
 
510
- ```jsx
510
+ ```jsx file=App.jsx app=youtube-dashboard
511
511
  import React, { useState } from "react";
512
512
  import { useFireproof } from "use-fireproof";
513
513
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.1.38",
3
+ "version": "14.1.40",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -34,9 +34,9 @@
34
34
  "license": "Apache-2.0",
35
35
  "dependencies": {
36
36
  "@adviser/cement": "~0.5.34",
37
- "@vibes.diy/call-ai-v2": "14.1.38",
38
- "@vibes.diy/identity": "14.1.38",
39
- "@vibes.diy/use-vibes-types": "14.1.38",
37
+ "@vibes.diy/call-ai-v2": "14.1.40",
38
+ "@vibes.diy/identity": "14.1.40",
39
+ "@vibes.diy/use-vibes-types": "14.1.40",
40
40
  "arktype": "~2.2.3",
41
41
  "json-schema-faker": "~0.6.3"
42
42
  },