@vibes.diy/prompts 14.1.21 → 14.1.22
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 +7 -5
- package/llms/backend.md +27 -0
- package/llms/callai.initial.md +121 -0
- package/llms/callai.js +1 -0
- package/llms/callai.js.map +1 -1
- package/llms/debugging.md +114 -0
- package/llms/fireproof.initial.md +697 -0
- package/llms/fireproof.js +1 -0
- package/llms/fireproof.js.map +1 -1
- package/llms/image-gen.initial.md +155 -0
- package/llms/image-gen.js +1 -0
- package/llms/image-gen.js.map +1 -1
- package/llms/types.d.ts +1 -0
- package/llms/use-vibe.initial.md +58 -0
- package/llms/use-vibe.js +1 -0
- package/llms/use-vibe.js.map +1 -1
- package/llms/use-viewer.initial.md +303 -0
- package/llms/use-viewer.js +1 -0
- package/llms/use-viewer.js.map +1 -1
- package/llms/use-viewer.md +2 -2
- package/package.json +4 -4
- package/prompts.js +4 -3
- package/prompts.js.map +1 -1
- package/system-prompt-initial-oneshot.md +3 -1
- package/system-prompt-initial.md +3 -1
- package/system-prompt.md +1 -1
package/llms/access.md
CHANGED
|
@@ -299,7 +299,9 @@ export function board(doc, oldDoc, user, ctx) {
|
|
|
299
299
|
|
|
300
300
|
**Build the permission model around what a newcomer should be able to do.** When a stranger opens the app, they should immediately be able to do the thing it's _for_ — add their own todos, post a note, drop a pin, join a shared canvas. So the default is: every signed-in visitor is a first-class participant who creates their own objects and edits what they created (`doc.authorHandle === user.userHandle`, checking `oldDoc` on updates), from first load, with no one needing to let them in.
|
|
301
301
|
|
|
302
|
-
**
|
|
302
|
+
**When the person says "no sharing", "no access control" or "keep it single-user", that is the one case where `access.js` is left out altogether:** the app ships with no rules file, and the object id and `authorHandle` its records already carry are what let rules be added later without touching a doc.
|
|
303
|
+
|
|
304
|
+
**When the person asks for private per-user permissions — "each person sees only their own", "private to every user" — every visitor gets their own private space on a single per-user channel.** A todo list, a daily habit tracker, a reading list, a diary, a notes app, a workout log, or a budget where the data is one person's own routes every doc that visitor creates to the one channel keyed on their handle — `user:${user.userHandle}` — self-granted so only they read it, with `authorHandle: user.userHandle` fixed at create and held immutable on update (`oldDoc.authorHandle === user.userHandle`). Their whole collection lives on that single private channel, reachable from first load, each visitor's space entirely their own:
|
|
303
305
|
|
|
304
306
|
access.js
|
|
305
307
|
|
|
@@ -366,9 +368,9 @@ export function habits(doc, oldDoc, user, ctx) {
|
|
|
366
368
|
}
|
|
367
369
|
```
|
|
368
370
|
|
|
369
|
-
The leaderboard is just the access model: read the public `track:` channels and sum them; each viewer additionally sees their own streaks and any buddy who granted them in. A
|
|
371
|
+
The leaderboard is just the access model: read the public `track:` channels and sum them; each viewer additionally sees their own streaks and any buddy who granted them in. A habit tracker whose owner **asked to keep it to themselves** is instead the per-visitor shape shown above: every visitor's habits and check-ins on their single `user:${user.userHandle}` channel, self-granted and private to them.
|
|
370
372
|
|
|
371
|
-
**"
|
|
373
|
+
**Per-object collaboration is the resting shape of almost every app** (the second worked example above), and certainly of anything that says "invite", "join", "people can join", "collaborate", "share with", "together", "with my partner/team", or names a board/canvas/room/whiteboard a group co-edits — each shared thing is ONE object its members reach directly; it needs no owner. Use the per-object recipe: a channel per object (`board:<id>`/`list:<id>`); the creator self-grants at creation (`grant: { users: { [user.userHandle]: [ch] } }`); child docs gate on `ctx.requireAccess(ch)` so any member edits any child in it (not just their own); a member-authored `share` doc grants a peer the same channel; a `request` doc — which takes **no** `requireAccess` — lets a not-yet-member ask to join. Keep the _object's own_ creator field write-once (`if (oldDoc && doc.author !== oldDoc.author) throw`) and a child's object-id immutable. **Two traps to avoid:** don't build it as an open public feed where each person only owns their own items (that abandons the shared membership), and don't gate it behind a single writer (members self-serve via share/request). The app owner is not special in the data model: any signed-in person creates their own object and invites a friend into it, and the rule keys on `user.userHandle` or the object's creator, never on `user.isOwner`.
|
|
372
374
|
|
|
373
375
|
**Ownership is just the object graph** — whoever authored or created a doc owns it (`doc.authorHandle === user.userHandle`, checked against `oldDoc` on updates). There's no broadcaster shape to reach for by default, and **owner-only publishing is a dead end** — never gate the content itself on `requireRole("owner")`. **A blog, magazine, or publication is public read + author-owned posts, with the owner controlling the _author roster_:** the owner approves authors with a grant doc (`if (doc.type === "author") { ctx.requireRole("owner"); return { channels: ["blog:authors"], grant: { users: { [doc.authorHandle]: ["blog:authors"] }, roles: { owner: ["blog:authors"] } } }; }` — the **one** place `requireRole("owner")` belongs, gating who may author, never the posts); a post then gates on `ctx.requireAccess("blog:authors")` (membership) and is author-owned (`doc.authorHandle === user.userHandle` + the `oldDoc` check), so once approved each author's post is _their own_ object — only they edit it, and they moderate the comments on it (a comment is allowed if it's your comment **or** you own the post: `doc.authorHandle === user.userHandle || doc.postAuthorHandle === user.userHandle`). A personal blog is just this with a roster of one. Always gate write UI on `useVibe(dbName).can`.
|
|
374
376
|
|
|
@@ -674,7 +676,7 @@ Channel `_id` is the channel identifier everywhere. The access function uses `do
|
|
|
674
676
|
|
|
675
677
|
### Example: Per-object sharing (collaborate on your own objects, no admin)
|
|
676
678
|
|
|
677
|
-
Reach for this whenever the prompt says **invite, join, collaborate, share with, together, with my partner/team** — a shared shopping list you invite a partner to, a whiteboard people can join, a trip a group plans together. A list app where every signed-in user makes their own lists, sees only their own, and can invite anyone to collaborate on a specific list — peer to peer, with no app admin in the loop. The pattern: **a channel per object** (`list:<id>`); the creator grants themselves that channel at creation; child docs (items) gate on `ctx.requireAccess` of the list's channel, so **any member edits any item**; any current member shares the list by granting another user the same channel. Membership is direct `grant.users`, so each viewer's access scales with their own memberships. Nothing a person writes reaches anyone else until they chose it: a per-object channel starts with its creator as the only reader, and every other reader arrives through a grant that creator wrote.
|
|
679
|
+
Reach for this by default, and certainly whenever the prompt says **invite, join, collaborate, share with, together, with my partner/team** — a shared shopping list you invite a partner to, a whiteboard people can join, a trip a group plans together, a tracker one person starts and later shares with a coach. A list app where every signed-in user makes their own lists, sees only their own, and can invite anyone to collaborate on a specific list — peer to peer, with no app admin in the loop. The pattern: **a channel per object** (`list:<id>`); the creator grants themselves that channel at creation; child docs (items) gate on `ctx.requireAccess` of the list's channel, so **any member edits any item**; any current member shares the list by granting another user the same channel. Membership is direct `grant.users`, so each viewer's access scales with their own memberships. Nothing a person writes reaches anyone else until they chose it: a per-object channel starts with its creator as the only reader, and every other reader arrives through a grant that creator wrote.
|
|
678
680
|
|
|
679
681
|
access.js
|
|
680
682
|
|
|
@@ -757,7 +759,7 @@ App.jsx — `access.hasChannel()` shows only the lists the viewer belongs to; `u
|
|
|
757
759
|
|
|
758
760
|
#### The same shape with a default object per person, and a member doc that names the friend
|
|
759
761
|
|
|
760
|
-
Reach for this variant whenever the app is
|
|
762
|
+
Reach for this variant whenever the app is built around a *thing* people come back to — a board, a trip, a game night's scoreboard, a hiking group's trail list, a habit tracker one person keeps and a coach is invited into next month. Everything above still holds; three additions make it feel finished from the first load:
|
|
761
763
|
|
|
762
764
|
- **Every signed-in person already has one object, with no doc to create.** Their default channel is derived from their own handle — `` `board:default-${user.userHandle}` `` — so a brand-new visitor opens the app, types, and their first item lands. Each write to it re-grants that person their own channel, because there is no object doc carrying the grant. **The child doc stores that board id rather than having each writer derive one**: the create writes `` boardId: pickedBoardId || `default-${me.userHandle}` `` and the rule holds it fixed from then on, so a card stays on the board its creator put it on however many people edit it later.
|
|
763
765
|
- **Membership is a `member` doc, and picking the friend is `HandleInput`** (`useViewer()`, see use-viewer.md — the platform's people-picker, so the handle on the doc is one the platform resolved). The doc grants the named person the object's channel: `` grant: { users: { [doc.userHandle]: [chan] } } ``.
|
package/llms/backend.md
CHANGED
|
@@ -601,6 +601,33 @@ From `App.jsx`, call it with a relative fetch — no host needed:
|
|
|
601
601
|
const res = await fetch("/_api/rsvp", { method: "POST", body: JSON.stringify({ name }) });
|
|
602
602
|
```
|
|
603
603
|
|
|
604
|
+
### Bulk transformations run as a route
|
|
605
|
+
|
|
606
|
+
A change that touches many stored records is written once, as a named `/_api` route, and run
|
|
607
|
+
once — not looped from outside the app. Gate the route inside the handler on the caller being the
|
|
608
|
+
owner, declare `config.fetch.unfilteredReads` for the collections it reads, page with the cursor
|
|
609
|
+
`ctx.db.query` returns, and keep progress in a state document read by id so a second run continues
|
|
610
|
+
where the first stopped.
|
|
611
|
+
|
|
612
|
+
```js
|
|
613
|
+
export const config = { fetch: { unfilteredReads: ["rsvps"] } };
|
|
614
|
+
|
|
615
|
+
export async function fetch(request, ctx) {
|
|
616
|
+
const url = new URL(request.url);
|
|
617
|
+
if (url.pathname !== "/admin/normalize") return new Response("not found", { status: 404 });
|
|
618
|
+
if (ctx.user?.userHandle !== ctx.ownerHandle) return new Response("forbidden", { status: 403 });
|
|
619
|
+
const page = await ctx.db.query("kind", { key: "rsvp", limit: 100, db: "rsvps" });
|
|
620
|
+
for (const doc of page) {
|
|
621
|
+
if (typeof doc.name === "string" && doc.name !== doc.name.trim()) {
|
|
622
|
+
await ctx.db.put({ ...doc, name: doc.name.trim() }, { db: "rsvps" });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return new Response(JSON.stringify({ ok: true, more: page.next ?? null }), {
|
|
626
|
+
headers: { "content-type": "application/json" },
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
604
631
|
### Reading a page the person names — the `/_api` proxy
|
|
605
632
|
|
|
606
633
|
A page the person names is read in `backend.js` and handed to the app through `/_api`. A browser
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# CallAI Helper Function
|
|
2
|
+
|
|
3
|
+
The `callAI` function asks an AI model and returns a string. With a `schema` the string is structured JSON you `JSON.parse()`; without one it is ordinary text — a chat reply, a caption, a paragraph — used as is. Reach for a schema when the app needs fields, and leave it out when it needs prose.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { callAI } from "call-ai";
|
|
9
|
+
|
|
10
|
+
const response = await callAI("Give me a todo list for learning React", {
|
|
11
|
+
schema: {
|
|
12
|
+
properties: {
|
|
13
|
+
todos: {
|
|
14
|
+
type: "array",
|
|
15
|
+
description: "List of actionable todo items",
|
|
16
|
+
items: { type: "string" },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const todoData = JSON.parse(response);
|
|
22
|
+
console.log(todoData.todos);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Items with properties
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const response = await callAI(
|
|
29
|
+
"Generate 4 items with label, status, priority (low, medium, high, critical), and notes. Return as structured JSON with these fields.",
|
|
30
|
+
{
|
|
31
|
+
schema: {
|
|
32
|
+
properties: {
|
|
33
|
+
items: {
|
|
34
|
+
type: "array",
|
|
35
|
+
items: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
label: { type: "string", description: "Short name for the item" },
|
|
39
|
+
status: { type: "string", description: "Current status (active, done, blocked)" },
|
|
40
|
+
priority: { type: "string", description: "Priority level (low, medium, high, critical)" },
|
|
41
|
+
notes: { type: "string", description: "Additional context or details" },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
const demoData = JSON.parse(response);
|
|
50
|
+
console.log(demoData.items); // Array of item objects
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Schema Tips
|
|
54
|
+
|
|
55
|
+
- Flat schemas perform better across all models. Avoid nesting deeper than 2 levels.
|
|
56
|
+
- Use simple property names like `name`, `type`, `items`, `price`.
|
|
57
|
+
- Keep array items simple (strings or flat objects).
|
|
58
|
+
|
|
59
|
+
## Reading an Image
|
|
60
|
+
|
|
61
|
+
Send an image by passing the prompt as an array of content parts: an `image_url` part carrying the picture beside a `text` part asking the question.
|
|
62
|
+
|
|
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
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
import { callAI } from "call-ai";
|
|
67
|
+
|
|
68
|
+
function readAsDataUrl(file) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const reader = new FileReader();
|
|
71
|
+
reader.onload = () => resolve(reader.result);
|
|
72
|
+
reader.onerror = () => reject(reader.error);
|
|
73
|
+
reader.readAsDataURL(file);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function readFlyer(file) {
|
|
78
|
+
const dataUrl = await readAsDataUrl(file);
|
|
79
|
+
const response = await callAI(
|
|
80
|
+
[
|
|
81
|
+
{ type: "text", text: "Read this event flyer and pull out the event details." },
|
|
82
|
+
{ type: "image_url", image_url: { url: dataUrl } },
|
|
83
|
+
],
|
|
84
|
+
{
|
|
85
|
+
schema: {
|
|
86
|
+
properties: {
|
|
87
|
+
title: { type: "string", description: "Name of the event" },
|
|
88
|
+
date: { type: "string", description: "Date as printed on the flyer" },
|
|
89
|
+
time: { type: "string" },
|
|
90
|
+
venue: { type: "string" },
|
|
91
|
+
description: { type: "string" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
return JSON.parse(response);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A string prompt stays the common path for text-only work and behaves exactly as it always has. A model that reads text alone answers an image request with an error naming that model, so an app that asks about a picture hears when the picture went unread — surface that message the way you surface any other `callAI` error.
|
|
101
|
+
|
|
102
|
+
## Error Handling
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
import { callAI } from "call-ai";
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await callAI("Generate some content", {
|
|
109
|
+
schema: {
|
|
110
|
+
properties: {
|
|
111
|
+
result: { type: "string" },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
console.log(JSON.parse(response));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("API error:", error.message);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Let the request finish. Reading an image takes several seconds, so a slow answer is still an answer: show a waiting state ("reading your photo…") and keep awaiting the call. The `catch` above is what tells the app a request actually failed.
|
package/llms/callai.js
CHANGED
package/llms/callai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callai.js","sourceRoot":"","sources":["../../jsr/llms/callai.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAc;IACrC,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,kDAAkD;IAC/D,YAAY,EAAE,SAAS;IACvB,UAAU,EAAE,QAAQ;CACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"callai.js","sourceRoot":"","sources":["../../jsr/llms/callai.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAc;IACrC,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,kDAAkD;IAC/D,cAAc,EAAE,IAAI;IACpB,YAAY,EAAE,SAAS;IACvB,UAAU,EAAE,QAAQ;CACrB,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# debugging — working out what a deployed app is actually doing
|
|
2
|
+
|
|
3
|
+
This guide is for you, the agent, when something in a live app does not match
|
|
4
|
+
what the code says it should do. It is the method, not a feature the app author
|
|
5
|
+
types.
|
|
6
|
+
|
|
7
|
+
## The loop
|
|
8
|
+
|
|
9
|
+
1. **Reproduce in words.** Say what the person did, what they expected and what
|
|
10
|
+
they saw. A reproduction you can state is a reproduction you can check.
|
|
11
|
+
2. **Read the release state.** Which version is serving, whether newer work is
|
|
12
|
+
waiting behind it, whether the version that is live carries the record rules
|
|
13
|
+
and the server file you think it does. A change that was never released is
|
|
14
|
+
the most common explanation for "I fixed that already".
|
|
15
|
+
3. **Read the logs.** What the deployed app has been recording about itself,
|
|
16
|
+
newest first. A line the app wrote beats an expectation about the code.
|
|
17
|
+
4. **Read the records.** The data settles which of two stories is true. Read it
|
|
18
|
+
as the person whose app it is, so you see what their app shows them.
|
|
19
|
+
5. **One hypothesis.** Name the single thing you now believe is wrong, in one
|
|
20
|
+
sentence.
|
|
21
|
+
6. **The smallest change that tests it.** One record, one line, one handler.
|
|
22
|
+
7. **Verify from a read.** Read the thing back after the change. A claim rests
|
|
23
|
+
on what a read returned, never on the fact that a call succeeded.
|
|
24
|
+
|
|
25
|
+
## Instrumentation the platform already has
|
|
26
|
+
|
|
27
|
+
`ctx.log` writes a line from server code, and `vibe.log` writes one from the
|
|
28
|
+
page. Both land in the app's own log stream, stamped with the version that
|
|
29
|
+
produced them, so a deploy boundary is visible in the stream.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
export async function scheduled(ctx) {
|
|
33
|
+
ctx.log("info", "tick start", { at: new Date().toISOString() });
|
|
34
|
+
const page = await ctx.db.query("kind", { key: "rsvp", limit: 100, db: "rsvps" });
|
|
35
|
+
ctx.log("info", "tick read", { count: page.length });
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
A probe that only paints its result on screen is unreadable from outside the
|
|
40
|
+
app, so a self-checking probe echoes each case on one line:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
console.log(`[RSVP] ${ok ? "PASS" : "FAIL"} duplicate-guard ${JSON.stringify({ id, count })}`);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Start the run with a marker — `console.log("===== START =====")` — so a later
|
|
47
|
+
read knows which lines belong to this attempt.
|
|
48
|
+
|
|
49
|
+
## Bulk work is a route, not a loop
|
|
50
|
+
|
|
51
|
+
Changing a handful of records one at a time is fine. Past that, write the change
|
|
52
|
+
once, as a named route in the app's own server file, and have the person run it.
|
|
53
|
+
A route runs beside the data, keeps its own progress, and can be read and
|
|
54
|
+
re-read before it is trusted.
|
|
55
|
+
|
|
56
|
+
Gate the route inside the handler on the caller being the owner, declare
|
|
57
|
+
`config.fetch.unfilteredReads` for the collections it reads, page with a cursor,
|
|
58
|
+
and remember progress in a state document read by id so a second run continues
|
|
59
|
+
rather than starting over. An empty page can still carry `next`, so loop on the
|
|
60
|
+
cursor.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
export const config = { fetch: { unfilteredReads: ["rsvps"] } };
|
|
64
|
+
|
|
65
|
+
export async function fetch(request, ctx) {
|
|
66
|
+
const url = new URL(request.url);
|
|
67
|
+
if (url.pathname !== "/admin/normalize") return new Response("not found", { status: 404 });
|
|
68
|
+
if (ctx.user?.userHandle !== ctx.ownerHandle) return new Response("forbidden", { status: 403 });
|
|
69
|
+
|
|
70
|
+
const state = (await ctx.db.get("0-normalize-state", { db: "rsvps" })) ?? { _id: "0-normalize-state", after: null };
|
|
71
|
+
let after = state.after;
|
|
72
|
+
let changed = 0;
|
|
73
|
+
for (let round = 0; round < 20; round++) {
|
|
74
|
+
const page = await ctx.db.query("kind", { key: "rsvp", limit: 100, ...(after ? { after } : {}), db: "rsvps" });
|
|
75
|
+
for (const doc of page) {
|
|
76
|
+
if (typeof doc.name === "string" && doc.name !== doc.name.trim()) {
|
|
77
|
+
await ctx.db.put({ ...doc, name: doc.name.trim() }, { db: "rsvps" });
|
|
78
|
+
changed++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!page.next) { after = null; break; }
|
|
82
|
+
after = page.next;
|
|
83
|
+
}
|
|
84
|
+
await ctx.db.put({ ...state, after, at: Date.now() }, { db: "rsvps" });
|
|
85
|
+
ctx.log("info", "normalize round", { changed, after });
|
|
86
|
+
return new Response(JSON.stringify({ ok: true, changed, more: after !== null }), {
|
|
87
|
+
headers: { "content-type": "application/json" },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Once the route is published, run it with `call_own_api`: it calls the app's own
|
|
93
|
+
routes as the person whose app this is, so the handler sees the owner and the
|
|
94
|
+
answer comes back in the conversation. Read the release state first when unsure
|
|
95
|
+
which routes the live version carries, and read the records back afterwards to
|
|
96
|
+
confirm what the run did.
|
|
97
|
+
|
|
98
|
+
The person can run the same route from their own machine with their own
|
|
99
|
+
credential:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
curl -sS -X POST \
|
|
103
|
+
-H "Authorization: Bearer $VIBES_TOKEN" \
|
|
104
|
+
https://vibes.diy/vibe/<owner>/<slug>/_api/admin/normalize
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## A route is live code
|
|
108
|
+
|
|
109
|
+
`backend.js` and `access.js` always run the app's live release, so a route has
|
|
110
|
+
to be published before anyone can call it, and publishing it puts it in front of
|
|
111
|
+
everyone the app serves. Try a route whose effect you are unsure of under a
|
|
112
|
+
second address first, confirm what it did by reading the records back, then ship
|
|
113
|
+
it where the real data is. `call_own_api` reaches the live release too, so what
|
|
114
|
+
it runs is what everyone else is being served.
|