@vibes.diy/prompts 9.4.2 → 9.4.3

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/backend.md CHANGED
@@ -182,70 +182,73 @@ async function ensureSnapshot(ctx) {
182
182
  for (const doc of SNAPSHOT) await ctx.db.put(doc, { db: "nodeReleases" });
183
183
  }
184
184
 
185
+ // True only when every tracked field of the desired doc already matches what's
186
+ // stored — extra stored keys (like `_rev`) are ignored. This is what lets a
187
+ // tick that changes nothing write nothing.
188
+ function unchanged(existing, next) {
189
+ if (!existing) return false;
190
+ return Object.keys(next).every((k) => existing[k] === next[k]);
191
+ }
192
+
193
+ async function putIfChanged(ctx, byId, next) {
194
+ if (unchanged(byId.get(next._id), next)) return;
195
+ await ctx.db.put(next, { db: "nodeReleases" });
196
+ }
197
+
185
198
  export async function scheduled(event, ctx) {
186
199
  await ensureSnapshot(ctx);
187
200
 
201
+ // Read current docs once so each write can diff against what's already stored.
202
+ const current = await ctx.db.query({ db: "nodeReleases" });
203
+ const byId = new Map(current.map((doc) => [doc._id, doc]));
204
+
188
205
  // One request, no retry storm; ctx.fetch enforces 15s/10MB and egress caps.
189
206
  const response = await ctx.fetch("https://api.github.com/repos/nodejs/node/releases?per_page=20", {
190
207
  headers: { accept: "application/vnd.github+json" },
191
208
  });
192
209
 
193
210
  if (response.status === 403) {
194
- const denied = await response.clone().json().catch(() => null);
211
+ const denied = await response
212
+ .clone()
213
+ .json()
214
+ .catch(() => null);
195
215
  if (denied?.vibesEgressDenied === true) {
196
- await ctx.db.put(
197
- {
198
- _id: "refresh:status",
199
- type: "refreshStatus",
200
- state: "egress-denied",
201
- message: "showing saved data — live refresh unavailable",
202
- checkedAt: event.scheduledTime,
203
- },
204
- { db: "nodeReleases" }
205
- );
216
+ // A real state change — putIfChanged writes once on entering denial, then
217
+ // nothing while it persists. No per-tick timestamp, so no churn.
218
+ await putIfChanged(ctx, byId, {
219
+ _id: "refresh:status",
220
+ type: "refreshStatus",
221
+ state: "egress-denied",
222
+ message: "showing saved data — live refresh unavailable",
223
+ });
206
224
  return; // preserve the seeded/last-good release docs
207
225
  }
208
226
  }
209
227
 
210
- if (!response.ok) {
211
- await ctx.db.put(
212
- {
213
- _id: "refresh:status",
214
- type: "refreshStatus",
215
- state: "failed",
216
- message: "showing saved data — refresh failed",
217
- checkedAt: event.scheduledTime,
218
- },
219
- { db: "nodeReleases" }
220
- );
221
- return;
222
- }
228
+ if (!response.ok) return; // transient failure: keep last-good rows, write nothing
229
+
230
+ const rows = await response.json().catch(() => null);
231
+ if (!Array.isArray(rows) || rows.length === 0) return; // empty upstream: abort, no writes
223
232
 
224
- const rows = await response.json();
225
233
  for (const row of rows.slice(0, 20)) {
226
234
  if (!row?.id || !row?.html_url || !row?.published_at) continue;
227
- await ctx.db.put(
228
- {
229
- _id: `release:github-${row.id}`,
230
- type: "release",
231
- title: String(row.name || row.tag_name || "Node.js release"),
232
- url: String(row.html_url),
233
- publishedAt: String(row.published_at),
234
- source: "github-live",
235
- },
236
- { db: "nodeReleases" }
237
- );
235
+ // Stable per-item `_id` + diff: a row whose content is identical is skipped.
236
+ await putIfChanged(ctx, byId, {
237
+ _id: `release:github-${row.id}`,
238
+ type: "release",
239
+ title: String(row.name || row.tag_name || "Node.js release"),
240
+ url: String(row.html_url),
241
+ publishedAt: String(row.published_at),
242
+ source: "github-live",
243
+ });
238
244
  }
239
- await ctx.db.put(
240
- {
241
- _id: "refresh:status",
242
- type: "refreshStatus",
243
- state: "fresh",
244
- message: "Live feed refreshed",
245
- checkedAt: event.scheduledTime,
246
- },
247
- { db: "nodeReleases" }
248
- );
245
+
246
+ await putIfChanged(ctx, byId, {
247
+ _id: "refresh:status",
248
+ type: "refreshStatus",
249
+ state: "fresh",
250
+ message: "Live feed refreshed",
251
+ });
249
252
  }
250
253
  ```
251
254
 
@@ -290,6 +293,16 @@ down. Catch that exact 403 shape and show the saved snapshot with honest copy
290
293
  **"showing saved data — live refresh unavailable"**. Never clear cached rows,
291
294
  render a broken screen, or silently label saved data as fresh.
292
295
 
296
+ **Zero-churn discipline for scheduled ticks.** A cron runs forever, so it must
297
+ not write forever. Read the current docs first (`ctx.db.query`), give every item
298
+ a stable `_id`, and `put` **only** the rows whose content actually changed — the
299
+ `putIfChanged` diff above. Never stamp a per-tick `checkedAt`/`updatedAt`/run
300
+ counter onto a doc: it makes every put look new, and each spurious put mints a
301
+ fresh revision and a sync-feed row that re-tickles every replica on every tick.
302
+ A failed or empty upstream fetch aborts with no writes, preserving the last-good
303
+ rows. Only a genuine content change — a new or updated row, a real state
304
+ transition — earns a write.
305
+
293
306
  ## fetch — the app's HTTP endpoint
294
307
 
295
308
  Runs for requests to the app's `/_api` route. The request path is rooted after
package/llms/fireproof.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # Fireproof Database API Guide
2
2
 
3
- Fireproof is a document database with live sync, designed to make browser apps easy. On vibes.diy it runs against Firefly, a cloud-centralized backend: writes are sent to a server that validates them with your `access.js`, persists them, and streams them live to every viewer. Use it in any JavaScript environment with a unified API that works both in React (with hooks) and as a standalone core API.
3
+ Fireproof is a document database with live sync, designed to make browser apps easy. On vibes.diy it runs against Firefly: each app holds a local replica (IndexedDB) that IS the database, so writes succeed locally and instantly and then sync in the background. The Firefly server validates every synced write with your `access.js` on ingest and can still reject it (access denied, conflict); accepted writes stream live to every viewer. Use it in any JavaScript environment with a unified API that works both in React (with hooks) and as a standalone core API.
4
4
 
5
5
  ## Key Features
6
6
 
7
7
  - **Apps run anywhere:** Bundle UI, data, and logic together.
8
- - **Real-Time, cloud-backed:** Writes are validated and persisted server-side, then streamed live to every viewer. `useLiveQuery` keeps the UI in sync as data arrives, so you render empty states rather than loading spinners — but writes can fail (access denied, conflicts, network), so handle write rejections.
8
+ - **Real-Time, local-first:** Writes land in the local replica instantly and sync in the background; the server validates each on ingest and streams the accepted ones live to every viewer. `useLiveQuery` keeps the UI in sync as data arrives, so you render empty states rather than loading spinners — but a synced write can still be rejected on ingest (access denied, conflicts), so handle write rejections.
9
9
  - **Unified API:** TypeScript works with Deno, Bun, Node.js, and the browser.
10
10
  - **React Hooks:** Leverage `useLiveQuery` and `useDocument` for live collaboration. Note: these are NOT top-level exports — they are returned by the `useFireproof()` hook. Always destructure from `const { useLiveQuery, useDocument, database } = useFireproof("dbName")`.
11
11
 
12
12
  **File structure:** A vibe's source is one or more files. `/App.jsx` is the entry point (React component). `/access.js` is optional — include it when the app needs per-document write validation or channel-based read isolation. Both files are pushed together and the server discovers `/access.js` automatically.
13
13
 
14
- Fireproof enforces cryptographic causal consistency and ledger integrity using hash history, providing git-like versioning with lightweight blockchain-style verification. On vibes.diy, the Firefly server is the authority for every write: it stores each document in a per-document append-only sequence, runs your `access.js` to validate and route it, and then syncs it to viewers. Because writes go through the server, they are subject to access rules and can be rejected.
14
+ Fireproof enforces cryptographic causal consistency and ledger integrity using hash history, providing git-like versioning with lightweight blockchain-style verification. On vibes.diy, a write commits to the local replica immediately and syncs to the Firefly server in the background; the server is the authority on acceptance: it stores each document in a per-document append-only sequence, runs your `access.js` to validate and route it on ingest, and then syncs it to viewers. Because every synced write is validated server-side on ingest, it is subject to access rules and can be rejected.
15
15
 
16
16
  ## Installation
17
17
 
@@ -388,21 +388,11 @@ const { useLiveQuery } = useFireproof("todos");
388
388
  const { useLiveQuery } = useFireproof("shared-board", { offlineQueue: false });
389
389
  ```
390
390
 
391
- ## Anonymous local writes (`anonymousLocal`)
391
+ ## Anonymous writes (local-first by default)
392
392
 
393
- For "let a logged-out visitor try it and save a little state before signing in," pass `{ anonymousLocal: true }`. While logged out, `put`/`del`/`useLiveQuery`/`useDocument` run against a local (localStorage) store with the identical API — no auth branching in your code. On first sign-in the local docs migrate into the cloud database, then local storage clears. (This is the signed-**out** read-path opt-in; the offline write queue above is separate and already on for signed-in users.)
393
+ Letting a logged-out visitor try the app and build a little state before signing in needs **no opt-in** it is the universal default on every vibe. While logged out, `put`/`del`/`useLiveQuery`/`useDocument` run against the local replica with the identical API — no auth branching in your code — so favorites, a draft, or a scratch list just work. On first sign-in that local state drains automatically into the user's cloud database and then syncs live; nothing is lost if a drain is interrupted. The sign-in and drain-confirmation toast ("saved to your account") is platform-provided chrome do not build your own "saved"/"synced" notice for it.
394
394
 
395
- ```js
396
- const { useLiveQuery, database } = useFireproof("favorites", {
397
- anonymousLocal: true,
398
- // Optional: reshape/stamp each local doc for its new owner; return falsy to drop it.
399
- // Preserve `_id` (spread `...doc`) — migration retries are only idempotent when
400
- // the id is kept, otherwise a retry after a partial failure creates duplicates.
401
- migrate: (doc, userHandle) => ({ ...doc, owner: userHandle }),
402
- });
403
- ```
404
-
405
- Notes: use it only where anonymous-first state makes sense (favorites, drafts, a scratch list). A returning signed-out visitor is automatically steered to sign in rather than starting a fresh throwaway local session. Migration keeps local data intact if it fails, so nothing is lost.
395
+ The old `{ anonymousLocal: true }` option (and its `migrate` hook) is **deprecated and ignored** — its behavior is now the default, so the runtime warns once and drains any legacy data on its own. Do not emit the flag in new code.
406
396
 
407
397
  ## Reading Resolved Grants and worked access examples
408
398
 
@@ -622,7 +612,7 @@ Other common `oldDoc` patterns: `if (oldDoc === null) { /* create-only logic */
622
612
 
623
613
  ## Architecture: Where's My Data?
624
614
 
625
- Data lives on the Firefly server, which is the source of truth. The browser keeps a local cache for instant reads, but every write is sent to the server, validated by your `access.js`, persisted, and then synced to all users who have read access. A write that fails validation, hits a conflict, or loses the network is rejected — handle those rejections rather than assuming the write always lands.
615
+ Data lives in a local replica (IndexedDB) that the app reads and writes instantly; that replica syncs to the Firefly server in the background. The server is the authority on acceptance — each synced write is validated by your `access.js` on ingest, persisted, and then synced to all users who have read access. A write that fails validation or hits a conflict is rejected on ingest (the server's copy wins) — handle those rejections rather than assuming the write always lands.
626
616
 
627
617
  ## Using Fireproof in JavaScript
628
618
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "9.4.2",
3
+ "version": "9.4.3",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "description": "",
@@ -24,9 +24,9 @@
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
26
  "@adviser/cement": "~0.5.34",
27
- "@vibes.diy/call-ai-v2": "^9.4.2",
28
- "@vibes.diy/identity": "^9.4.2",
29
- "@vibes.diy/use-vibes-types": "^9.4.2",
27
+ "@vibes.diy/call-ai-v2": "^9.4.3",
28
+ "@vibes.diy/identity": "^9.4.3",
29
+ "@vibes.diy/use-vibes-types": "^9.4.3",
30
30
  "arktype": "~2.2.3",
31
31
  "json-schema-faker": "~0.6.2"
32
32
  },
package/style-prompts.js CHANGED
@@ -54,7 +54,7 @@ export const stylePrompts = [
54
54
  `ACCESSIBILITY: Body text ≥0.82rem, section labels ≥0.6rem. Strong contrast (text on primary → var(--on-primary, #ffffff); on secondary → var(--on-secondary, var(--text-primary)); on success → var(--on-success, var(--text-primary)); on accent → var(--on-info, #ffffff)). The on-* names are palette extras — the fallbacks keep colored blocks readable in both light and dark mode. aria-hidden the decorative .hero-text-shadow.`,
55
55
  `RESPONSIVENESS: ≤700px: stat row collapses to 2 columns, form grid to 1 column. ≤500px: nav stacks vertically, stat row to single column.`,
56
56
  `FONTS: Use Google Fonts for Space Grotesk (400,500,600,700) and JetBrains Mono (400,500,700). Use display=swap (so text renders immediately in a fallback and upgrades to the real font when it lands, instead of staying invisible) and add <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> right before the stylesheet so the connection is warm.`,
57
- `LOADING STATES: Every button that triggers an async operation (callAI, fetch, database save) MUST show a loading state. Use a useState boolean \`isLoading\`. While loading: disable the button with \`disabled={isLoading}\`, replace its label with a spinning SVG (a 16x16 circle with 3px stroke, top quarter transparent, CSS animation rotate 0.8s linear infinite), and optionally add a short text like "Loading..." nearby. Pattern: \`setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }\`. The spinner should match the theme: 3px stroke in var(--border) color, no blur, sharp edges. Never leave a button clickable with no feedback during an async call.`,
57
+ `LOADING STATES: Only a genuinely-network async operation (callAI, fetch) shows a loading state. A database save is local-first — it resolves against the local replica instantly, so it gets NO spinner (apply the change optimistically and let useLiveQuery reconcile; roll back only if the write is rejected on sync). A database read is not a network op either — the first render already has the data, so render an inviting empty-state when there's nothing yet, never a loading spinner. For the network calls that do warrant it, use a useState boolean \`isLoading\`. While the call is in flight: disable the button with \`disabled={isLoading}\`, replace its label with a spinning SVG (a 16x16 circle with 3px stroke, top quarter transparent, CSS animation rotate 0.8s linear infinite), and optionally add a short text like "Loading..." nearby. Pattern: \`setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }\`. The spinner should match the theme: 3px stroke in var(--border) color, no blur, sharp edges. Never leave a network-triggering button clickable with no feedback during the call.`,
58
58
  `THEME TOKENS — provided by the platform at runtime. The platform injects these canonical color + structural tokens into the served page, so do NOT restate a :root block — just build the UI on the CSS variables via Tailwind bracket notation (bg-[var(--background)], text-[var(--text-primary)], border-[var(--border)], bg-[var(--primary)]). The block below is the REFERENCE for the token names and values, not something to paste in. They are the swap contract: the user can later swap themes and the app restyles instantly — but only for values routed through these variables. Two hard rules: (1) no literal colors for core surfaces/text/actions/borders — never bg-[#hex]/text-[#hex]/border-[#hex]; a swap can't reach a baked-in literal (a genuinely one-off decorative value may be inlined on the element); (2) don't invent bespoke color tokens — a name a future theme won't define can't be swapped. Plain Tailwind spacing/size utilities (p-4, gap-2, text-lg, rounded-md) are fine — the runtime remaps them onto the structural tokens automatically. Never write a bare --spacing variable — the runtime reserves that name; the spacing token is --vibes-spacing (when editing older code that references var(--spacing), rewrite it to var(--vibes-spacing) or plain spacing utilities). Token names are code-only vocabulary — never render a token name (--background, --primary, …) in the app's visible UI copy, headings, or labels.
59
59
 
60
60
  <style>
@@ -1 +1 @@
1
- {"version":3,"file":"style-prompts.js","sourceRoot":"","sources":["../jsr/style-prompts.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuChC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAkB;IAIzC;QACE,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE;YACN,yXAAyX;YACzX,04BAA04B;YAC14B,2SAA2S;YAC3S,wiBAAwiB;YACxiB,quBAAquB;YACruB,0JAA0J;YAC1J,i0BAAi0B;YACj0B,u/DAAu/D;YACv/D,mcAAmc;YACnc,yaAAya;YACza,2IAA2I;YAC3I,waAAwa;YACxa,uqBAAuqB;YAKvqB;;;EAGJ,kBAAkB;SACX;YACH,oDAAoD;SACrD,CAAC,IAAI,CAAC,MAAM,CAAC;KACf;IACD;QACE,IAAI,EAAE,SAAS;QACf,MAAM,EACJ,+8BAA+8B;KACl9B;IACD;QACE,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,uBAAuB;KAChC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,mBAAmB;KAC5B;IACD;QACE,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,mBAAmB;KAC5B;IACD;QACE,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,kBAAkB;KAC3B;IACD;QACE,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,0BAA0B;KACnC;IACD;QACE,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,kBAAkB;KAC3B;IACD;QACE,IAAI,EAAE,UAAU;QAChB,MAAM,EACJ,orCAAorC;KACvrC;CACF,CAAC;AAGF,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAwB,CAAC;AAG3D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuB,CAAC;AACnD,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;IAC7B,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,IAAI,gCAAgC,CAAC,CAAC;IAC7F,CAAC;IACD,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7B,CAAC;AAGD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE;IACtC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,IAAI,KAAK,CACb,uBAAuB,kBAAkB,iDAAiD,SAAS,gDAAgD,CACpJ,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC,CAAC,EAAE,CAAC"}
1
+ {"version":3,"file":"style-prompts.js","sourceRoot":"","sources":["../jsr/style-prompts.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuChC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAkB;IAIzC;QACE,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE;YACN,yXAAyX;YACzX,04BAA04B;YAC14B,2SAA2S;YAC3S,wiBAAwiB;YACxiB,quBAAquB;YACruB,0JAA0J;YAC1J,i0BAAi0B;YACj0B,u/DAAu/D;YACv/D,mcAAmc;YACnc,yaAAya;YACza,2IAA2I;YAC3I,waAAwa;YACxa,ylCAAylC;YAKzlC;;;EAGJ,kBAAkB;SACX;YACH,oDAAoD;SACrD,CAAC,IAAI,CAAC,MAAM,CAAC;KACf;IACD;QACE,IAAI,EAAE,SAAS;QACf,MAAM,EACJ,+8BAA+8B;KACl9B;IACD;QACE,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,uBAAuB;KAChC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,mBAAmB;KAC5B;IACD;QACE,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,mBAAmB;KAC5B;IACD;QACE,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,kBAAkB;KAC3B;IACD;QACE,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,0BAA0B;KACnC;IACD;QACE,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,kBAAkB;KAC3B;IACD;QACE,IAAI,EAAE,UAAU;QAChB,MAAM,EACJ,orCAAorC;KACvrC;CACF,CAAC;AAGF,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAwB,CAAC;AAG3D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuB,CAAC;AACnD,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;IAC7B,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,IAAI,gCAAgC,CAAC,CAAC;IAC7F,CAAC;IACD,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7B,CAAC;AAGD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE;IACtC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,IAAI,KAAK,CACb,uBAAuB,kBAAkB,iDAAiD,SAAS,gDAAgD,CACpJ,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC,CAAC,EAAE,CAAC"}
@@ -29,8 +29,9 @@ You are an AI assistant tasked with creating React components. You should create
29
29
  - Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used.
30
30
  - Use Fireproof for data persistence
31
31
  - Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
32
- - Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
33
- - Give instant feedback for Fireproof writes too, not just for callAI/fetch. A `database.put` (toggling a checkbox, marking done, inline edits, reorder, like/vote counters) resolves fast but isn't instant, so the UI must react the moment the user acts. Apply the change optimistically — flip the visible value immediately and let `useLiveQuery` reconcile when the write lands. While the write is in flight, show ONE subtle per-item saving cue on that row: disable it, dim it slightly, and add a small inline `Saving…` spinner or text. Do NOT change the value's own glyph to signal saving — no indeterminate/half-checked checkbox, since that reads as a real tri-state value rather than a transient network state. Track pending state in a keyed collection (e.g. a `Set` of saving ids in `useState` — add the id before the write, delete it in `.finally()`), not a single `savingId` or one global flag, so concurrent writes to different rows each keep their own cue and unrelated rows stay interactive. On failure, revert the optimistic value and surface a brief error (inline or toast) with a way to retry — never let the optimistic UI silently lie when a `put` rejects. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
32
+ - Always show loading states during genuinely-network async operations (callAI, fetch): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
33
+ - Database reads are not a network operation never show a loading state or spinner for them. `useLiveQuery` reads a hydrated local replica, so the first render already contains the data; an empty result means the database is genuinely empty. Render an inviting empty-state instead (friendly copy that prompts the first action), never a loading state.
34
+ - Give instant feedback for Fireproof writes too, not just for callAI/fetch. A `database.put` (toggling a checkbox, marking done, inline edits, reorder, like/vote counters) resolves against the local replica instantly — there is no visible in-flight window to spinner over — but the UI must still react the moment the user acts. Apply the change optimistically: flip the visible value immediately and let `useLiveQuery` reconcile. On failure (a rejected write syncing back from the server), revert the optimistic value and surface a brief error (inline or toast) with a way to retry — never let the optimistic UI silently lie when a `put` is rejected. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
34
35
  - For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
35
36
  - Access control is decided by the runtime, not your code. Gate every write surface — forms, submit/edit/delete buttons, any mutating action — on `useVibe(dbName).can`. `const { me, can, ready } = useVibe("comments")` from `"use-vibes"`, passing the Fireproof database name you write to. Show the editor when `can.create(draft).ok` (or `can.edit(doc)` / `can.delete(doc)`); while `ready` is false show a neutral skeleton/disabled state; when denied, render `can.create(draft).reason` as the fallback copy (the sign-in or join prompt). `can.*` asks the server's permission rules — the same rules the server enforces — so NEVER derive write permission from `viewer`, `access.hasRole()`/`access.hasChannel()`, or document fields. `useViewer()` is identity/display only: `const { ViewerTag } = useViewer()` renders **other** people (`<ViewerTag userHandle={...} />` for comment authors, rosters, "added by" labels). The current viewer's own pill and the sign-in button are system chrome in the Vibes Switch (the panel the logo opens) — don't add a header pill or login button for the current user. Owner-only management UI is gated on `can.*` too (the runtime knows the owner). This applies to every app — the runtime decides sharing, not the prompt. Writes can still be rejected server-side even when `can.*` allows, so keep the optimistic-write + rollback handling. See use-vibe docs.
36
37
  - Don't try to generate png or base64 data, use placeholder image APIs instead, like https://picsum.photos/400 where 400 is the square size
@@ -29,8 +29,9 @@ You are an AI assistant tasked with creating React components. You should create
29
29
  - Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used.
30
30
  - Use Fireproof for data persistence
31
31
  - Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
32
- - Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
33
- - Give instant feedback for Fireproof writes too, not just for callAI/fetch. A `database.put` (toggling a checkbox, marking done, inline edits, reorder, like/vote counters) resolves fast but isn't instant, so the UI must react the moment the user acts. Apply the change optimistically — flip the visible value immediately and let `useLiveQuery` reconcile when the write lands. While the write is in flight, show ONE subtle per-item saving cue on that row: disable it, dim it slightly, and add a small inline `Saving…` spinner or text. Do NOT change the value's own glyph to signal saving — no indeterminate/half-checked checkbox, since that reads as a real tri-state value rather than a transient network state. Track pending state in a keyed collection (e.g. a `Set` of saving ids in `useState` — add the id before the write, delete it in `.finally()`), not a single `savingId` or one global flag, so concurrent writes to different rows each keep their own cue and unrelated rows stay interactive. On failure, revert the optimistic value and surface a brief error (inline or toast) with a way to retry — never let the optimistic UI silently lie when a `put` rejects. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
32
+ - Always show loading states during genuinely-network async operations (callAI, fetch): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
33
+ - Database reads are not a network operation never show a loading state or spinner for them. `useLiveQuery` reads a hydrated local replica, so the first render already contains the data; an empty result means the database is genuinely empty. Render an inviting empty-state instead (friendly copy that prompts the first action), never a loading state.
34
+ - Give instant feedback for Fireproof writes too, not just for callAI/fetch. A `database.put` (toggling a checkbox, marking done, inline edits, reorder, like/vote counters) resolves against the local replica instantly — there is no visible in-flight window to spinner over — but the UI must still react the moment the user acts. Apply the change optimistically: flip the visible value immediately and let `useLiveQuery` reconcile. On failure (a rejected write syncing back from the server), revert the optimistic value and surface a brief error (inline or toast) with a way to retry — never let the optimistic UI silently lie when a `put` is rejected. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
34
35
  - For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
35
36
  - Access control is decided by the runtime, not your code. Gate every write surface — forms, submit/edit/delete buttons, any mutating action — on `useVibe(dbName).can`. `const { me, can, ready } = useVibe("comments")` from `"use-vibes"`, passing the Fireproof database name you write to. Show the editor when `can.create(draft).ok` (or `can.edit(doc)` / `can.delete(doc)`); while `ready` is false show a neutral skeleton/disabled state; when denied, render `can.create(draft).reason` as the fallback copy (the sign-in or join prompt). `can.*` asks the server's permission rules — the same rules the server enforces — so NEVER derive write permission from `viewer`, `access.hasRole()`/`access.hasChannel()`, or document fields. `useViewer()` is identity/display only: `const { ViewerTag } = useViewer()` renders **other** people (`<ViewerTag userHandle={...} />` for comment authors, rosters, "added by" labels). The current viewer's own pill and the sign-in button are system chrome in the Vibes Switch (the panel the logo opens) — don't add a header pill or login button for the current user. Owner-only management UI is gated on `can.*` too (the runtime knows the owner). This applies to every app — the runtime decides sharing, not the prompt. Writes can still be rejected server-side even when `can.*` allows, so keep the optimistic-write + rollback handling. See use-vibe docs.
36
37
  - Don't try to generate png or base64 data, use placeholder image APIs instead, like https://picsum.photos/400 where 400 is the square size
package/system-prompt.md CHANGED
@@ -29,8 +29,9 @@ You are an AI assistant tasked with creating React components. You should create
29
29
  - Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used. Never define components (functions that return JSX) inside `App` or any other component — always define them at module scope and pass data as props. Components defined inside other components are recreated on every render, causing React to unmount and remount them, which breaks form focus and input state.
30
30
  - Use Fireproof for data persistence
31
31
  - Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
32
- - Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
33
- - Give instant feedback for Fireproof writes too, not just for callAI/fetch. A `database.put` (toggling a checkbox, marking done, inline edits, reorder, like/vote counters) resolves fast but isn't instant, so the UI must react the moment the user acts. Apply the change optimistically — flip the visible value immediately and let `useLiveQuery` reconcile when the write lands. While the write is in flight, show ONE subtle per-item saving cue on that row: disable it, dim it slightly, and add a small inline `Saving…` spinner or text. Do NOT change the value's own glyph to signal saving — no indeterminate/half-checked checkbox, since that reads as a real tri-state value rather than a transient network state. Track pending state in a keyed collection (e.g. a `Set` of saving ids in `useState` — add the id before the write, delete it in `.finally()`), not a single `savingId` or one global flag, so concurrent writes to different rows each keep their own cue and unrelated rows stay interactive. On failure, revert the optimistic value and surface a brief error (inline or toast) with a way to retry — never let the optimistic UI silently lie when a `put` rejects. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
32
+ - Always show loading states during genuinely-network async operations (callAI, fetch): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
33
+ - Database reads are not a network operation never show a loading state or spinner for them. `useLiveQuery` reads a hydrated local replica, so the first render already contains the data; an empty result means the database is genuinely empty. Render an inviting empty-state instead (friendly copy that prompts the first action), never a loading state.
34
+ - Give instant feedback for Fireproof writes too, not just for callAI/fetch. A `database.put` (toggling a checkbox, marking done, inline edits, reorder, like/vote counters) resolves against the local replica instantly — there is no visible in-flight window to spinner over — but the UI must still react the moment the user acts. Apply the change optimistically: flip the visible value immediately and let `useLiveQuery` reconcile. On failure (a rejected write syncing back from the server), revert the optimistic value and surface a brief error (inline or toast) with a way to retry — never let the optimistic UI silently lie when a `put` is rejected. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
34
35
  - For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
35
36
  - Access control is decided by the runtime, not your code. Gate every write surface — forms, submit/edit/delete buttons, any mutating action — on `useVibe(dbName).can`. `const { me, can, ready } = useVibe("comments")` from `"use-vibes"`, passing the Fireproof database name you write to. Show the editor when `can.create(draft).ok` (or `can.edit(doc)` / `can.delete(doc)`); while `ready` is false show a neutral skeleton/disabled state; when denied, render `can.create(draft).reason` as the fallback copy (the sign-in or join prompt). `can.*` runs the app's own `access.js` — the same function the server enforces — so NEVER derive write permission from `viewer`, `access.hasRole()`/`access.hasChannel()`, or document fields. `useViewer()` is identity/display only: `const { ViewerTag } = useViewer()` renders **other** people (`<ViewerTag userHandle={...} />` for comment authors, rosters, "added by" labels). The current viewer's own pill and the sign-in button are system chrome in the Vibes Switch (the panel the logo opens) — don't add a header pill or login button for the current user. Owner-only management UI is gated on `can.*` too (the access.js encodes the owner rule). This applies to every app — the runtime decides sharing, not the prompt. Writes can still be rejected server-side even when `can.*` allows, so keep the optimistic-write + rollback handling. See use-vibe docs.
36
37
  - Don't try to generate png or base64 data, use placeholder image APIs instead, like https://picsum.photos/400 where 400 is the square size