@vibes.diy/prompts 14.3.79 → 14.3.81
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 +120 -85
- package/llms/backend.js +2 -2
- package/llms/backend.js.map +1 -1
- package/llms/backend.md +51 -43
- package/llms/bluesky.md +16 -14
- package/llms/board-games.d.ts +2 -0
- package/llms/board-games.js +29 -0
- package/llms/board-games.js.map +1 -0
- package/llms/board-games.md +477 -0
- package/llms/calendar.js +19 -2
- package/llms/calendar.js.map +1 -1
- package/llms/calendar.md +119 -1
- package/llms/callai.md +91 -20
- package/llms/connections.md +5 -4
- package/llms/create-vibe.md +5 -4
- package/llms/fireproof.md +261 -109
- package/llms/image-gen.md +44 -27
- package/llms/index.d.ts +2 -1
- package/llms/index.js +3 -0
- package/llms/index.js.map +1 -1
- package/llms/p5.md +9 -11
- package/llms/spotify.md +5 -2
- package/llms/three-js.md +163 -166
- package/llms/use-vibe.md +30 -8
- package/llms/use-viewer.md +61 -45
- package/llms/voxel.md +32 -37
- package/llms/youtube.md +5 -2
- package/package.json +4 -4
- package/prompts.d.ts +4 -2
- package/prompts.js +47 -24
- package/prompts.js.map +1 -1
- package/system-prompt-initial-oneshot.md +15 -12
- package/system-prompt-initial.md +14 -11
- package/system-prompt.md +36 -54
- package/themes/colorsets.js +18 -2
- package/themes/colorsets.js.map +1 -1
- package/themes/looks/pouf/brief.md +65 -47
- package/themes/looks/pouf/catalog.generated.d.ts +1 -1
- package/themes/looks/pouf/catalog.generated.js +103 -10
- package/themes/looks/pouf/catalog.generated.js.map +1 -1
- package/themes/looks/pouf/variety.js +2 -2
- package/themes/looks/pouf/variety.js.map +1 -1
package/llms/calendar.md
CHANGED
|
@@ -1,4 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Dates and calendars — the app knows today; subscriptions as a live .ics feed
|
|
2
|
+
|
|
3
|
+
## Dates — the app knows today
|
|
4
|
+
|
|
5
|
+
An app that keeps a date — a due date, a deadline, a run of daily check-ins, a
|
|
6
|
+
service schedule, a reminder, a timeline — is working against a clock that is
|
|
7
|
+
always on. `new Date()` is today, in the person's own time zone, at the moment
|
|
8
|
+
they type. The build context states today's date too (the `<today>` line), so a
|
|
9
|
+
record you write and a sample you author both start from a known day.
|
|
10
|
+
|
|
11
|
+
**Relative words resolve against now, save at once, and show the resolved date.**
|
|
12
|
+
"Tomorrow", "Friday", "next week", "the 15th" each name exactly one day from
|
|
13
|
+
where the person stands; resolve it with `new Date()` when the record is saved,
|
|
14
|
+
save the ISO day, and show that day beside the record so one tap adjusts it.
|
|
15
|
+
The saved card is the confirmation and the date on it is the question answered.
|
|
16
|
+
A sentence-intake schema call reads dates the same way: the person's local day
|
|
17
|
+
goes into its prompt (`Today: ${keyOf(new Date())}`, with `keyOf` below), as the
|
|
18
|
+
intake samples in the fireproof and callai docs do with their own `Today:`. Days
|
|
19
|
+
are kept as local `YYYY-MM-DD` strings (`keyOf`), so a date stays the day the
|
|
20
|
+
person meant across time zones.
|
|
21
|
+
|
|
22
|
+
### `App.jsx`
|
|
23
|
+
```jsx app=practice-log
|
|
24
|
+
import React, { useState } from "react";
|
|
25
|
+
import { useFireproof } from "use-fireproof";
|
|
26
|
+
import { Button, Field, Input, Row, Stack, Text } from "@vibes.diy/pouf";
|
|
27
|
+
|
|
28
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
29
|
+
const keyOf = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
30
|
+
const DAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
31
|
+
|
|
32
|
+
const daysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
|
|
33
|
+
// "the 15th" → the next month, this one included, that has a 15th and has not passed it.
|
|
34
|
+
function nextDayOfMonth(n, now) {
|
|
35
|
+
if (n < 1 || n > 31) return null;
|
|
36
|
+
for (let i = 0; i < 12; i += 1) {
|
|
37
|
+
const d = new Date(now.getFullYear(), now.getMonth() + i, 1);
|
|
38
|
+
if (n > daysInMonth(d.getFullYear(), d.getMonth())) continue;
|
|
39
|
+
d.setDate(n);
|
|
40
|
+
if (d >= now || keyOf(d) === keyOf(now)) return d;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// "tomorrow", "friday", "next week", "the 15th" → the one day they mean from today.
|
|
46
|
+
function resolveDay(words, now = new Date()) {
|
|
47
|
+
const text = words.toLowerCase();
|
|
48
|
+
const day = new Date(now);
|
|
49
|
+
const weekday = DAYS.findIndex((name) => text.includes(name));
|
|
50
|
+
if (text.includes("tomorrow")) day.setDate(day.getDate() + 1);
|
|
51
|
+
else if (text.includes("next week")) day.setDate(day.getDate() + 7);
|
|
52
|
+
else if (weekday >= 0) day.setDate(day.getDate() + ((weekday - day.getDay() + 7) % 7 || 7));
|
|
53
|
+
else {
|
|
54
|
+
const nth = text.match(/\bthe (\d{1,2})(?:st|nd|rd|th)?\b/);
|
|
55
|
+
if (nth) return nextDayOfMonth(Number(nth[1]), now) && keyOf(nextDayOfMonth(Number(nth[1]), now));
|
|
56
|
+
}
|
|
57
|
+
return keyOf(day);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function App() {
|
|
61
|
+
const { database, useLiveQuery } = useFireproof("practice-log");
|
|
62
|
+
const { docs: sessions } = useLiveQuery("type", { key: "session" });
|
|
63
|
+
const [words, setWords] = useState("");
|
|
64
|
+
const [day, setDay] = useState(keyOf(new Date()));
|
|
65
|
+
const today = keyOf(new Date());
|
|
66
|
+
const plan = (text) => {
|
|
67
|
+
setWords(text);
|
|
68
|
+
// A day the words name lands in the date field; "the 31st" in a short month leaves it for the person to pick.
|
|
69
|
+
setDay(resolveDay(text) ?? "");
|
|
70
|
+
};
|
|
71
|
+
const save = async () => {
|
|
72
|
+
if (!words.trim() || !day) return;
|
|
73
|
+
await database.put({ type: "session", piece: words.trim(), day, createdAt: Date.now() });
|
|
74
|
+
setWords("");
|
|
75
|
+
setDay(today);
|
|
76
|
+
};
|
|
77
|
+
return (
|
|
78
|
+
<Stack gap={3}>
|
|
79
|
+
<Field label="Plan a practice">{(id, d) => <Input id={id} describedBy={d} value={words} onChange={plan} placeholder="Scales on friday" />}</Field>
|
|
80
|
+
<Row gap={2}>
|
|
81
|
+
<Field label="On">{(id, d) => <Input id={id} describedBy={d} type="date" value={day} onChange={setDay} />}</Field>
|
|
82
|
+
<Button onClick={save}>Save</Button>
|
|
83
|
+
</Row>
|
|
84
|
+
{sessions.map((s) => (
|
|
85
|
+
<Row key={s._id} gap={2}>
|
|
86
|
+
<Text>{s.piece}</Text>
|
|
87
|
+
<Text size="sm" muted>{s.day === today ? "today" : s.day}</Text>
|
|
88
|
+
</Row>
|
|
89
|
+
))}
|
|
90
|
+
</Stack>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Sample records are dated around the day they are written.** A demo opens
|
|
96
|
+
alive when its examples sit where the app's own views look: recent activity in
|
|
97
|
+
the last few days, an unbroken run of daily entries reaching today, upcoming
|
|
98
|
+
items this week and next, a month grid with this month's entries on it. Write
|
|
99
|
+
each seed date from the `<today>` line as an offset — yesterday, three days ago,
|
|
100
|
+
this coming Saturday — so a reader of the seed can see the run reaching the day
|
|
101
|
+
it was written. The example below was written on a `<today>` of `2026-10-06`:
|
|
102
|
+
`day` runs three days ago, two days ago, yesterday, today, and the plan lands
|
|
103
|
+
on the coming Saturday (`2026-10-10`); on another day, the same offsets give
|
|
104
|
+
other dates.
|
|
105
|
+
|
|
106
|
+
### `seed.json`
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"practice-log": [
|
|
110
|
+
{ "key": "scales-1", "type": "session", "piece": "G major scales", "day": "2026-10-03" },
|
|
111
|
+
{ "key": "etude-1", "type": "session", "piece": "Kreutzer etude 2", "day": "2026-10-04" },
|
|
112
|
+
{ "key": "scales-2", "type": "session", "piece": "G major scales, faster", "day": "2026-10-05" },
|
|
113
|
+
{ "key": "sonata", "type": "session", "piece": "Sonata first movement", "day": "2026-10-06" },
|
|
114
|
+
{ "key": "recital", "type": "session", "piece": "Run the recital program", "day": "2026-10-10" }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Calendar subscriptions — a live .ics feed of each user's items
|
|
2
120
|
|
|
3
121
|
When an app has per-user dated items — favorited events, RSVPs, picks,
|
|
4
122
|
bookings, shifts — offer a **calendar subscription**: a `webcal://` link that
|
package/llms/callai.md
CHANGED
|
@@ -28,6 +28,7 @@ import React, { useState } from "react";
|
|
|
28
28
|
import { callAI } from "call-ai";
|
|
29
29
|
import { useFireproof } from "use-fireproof";
|
|
30
30
|
import { useVibe } from "use-vibes";
|
|
31
|
+
import { Button, Field, Heading, Input, Row, RowCard, Stack, Text, Textarea } from "@vibes.diy/pouf";
|
|
31
32
|
|
|
32
33
|
const taskSchema = {
|
|
33
34
|
type: "object",
|
|
@@ -75,19 +76,23 @@ function TaskCard({ task, database, can, onCorrect, onSaved, disabled }) {
|
|
|
75
76
|
const { useDocument } = useFireproof(database);
|
|
76
77
|
const { doc, merge, save } = useDocument(task);
|
|
77
78
|
const [editing, setEditing] = useState(false);
|
|
78
|
-
return <
|
|
79
|
-
<
|
|
79
|
+
return <RowCard><Stack gap={2}>
|
|
80
|
+
<Text>{task.title}</Text><Text muted>{task.details}</Text><Text muted>{task.dueDate}</Text>
|
|
80
81
|
{can.edit(task).ok && <>
|
|
81
|
-
<
|
|
82
|
-
|
|
82
|
+
<Row gap={2} wrap>
|
|
83
|
+
<Button type="button" size="sm" variant="quiet" disabled={disabled} onClick={() => onCorrect(task)}>Change by sentence</Button>
|
|
84
|
+
<Button type="button" size="sm" variant="quiet" disabled={disabled} onClick={() => setEditing(true)}>Edit details</Button>
|
|
85
|
+
</Row>
|
|
83
86
|
{editing && <form onSubmit={async (event) => { event.preventDefault(); if (disabled) return; await save(); onSaved(task); setEditing(false); }}>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
<Stack gap={3}>
|
|
88
|
+
<Field label="Task">{(id, describedBy) => <Input id={id} describedBy={describedBy} disabled={disabled} value={doc.title} onChange={(title) => merge({ title })} />}</Field>
|
|
89
|
+
<Field label="Details">{(id, describedBy) => <Textarea id={id} describedBy={describedBy} disabled={disabled} value={doc.details} onChange={(details) => merge({ details })} />}</Field>
|
|
90
|
+
<Field label="Due date">{(id, describedBy) => <Input id={id} describedBy={describedBy} disabled={disabled} type="date" value={doc.dueDate} onChange={(dueDate) => merge({ dueDate })} />}</Field>
|
|
91
|
+
<Row><Button type="submit" size="sm" variant="quiet" disabled={disabled}>Save details</Button></Row>
|
|
92
|
+
</Stack>
|
|
88
93
|
</form>}
|
|
89
94
|
</>}
|
|
90
|
-
</
|
|
95
|
+
</Stack></RowCard>;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
export default function App() {
|
|
@@ -163,21 +168,27 @@ export default function App() {
|
|
|
163
168
|
setMessage(error.message);
|
|
164
169
|
} finally { setBusy(false); }
|
|
165
170
|
}
|
|
166
|
-
return <main>
|
|
167
|
-
<
|
|
171
|
+
return <main className="min-h-screen bg-background text-foreground font-sans p-6"><Stack gap={5}>
|
|
172
|
+
<Heading level={1}>Tasks in your own words</Heading>
|
|
168
173
|
<form onSubmit={submitSentence}>
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
<Stack gap={3}>
|
|
175
|
+
<Field label="Describe tasks, a correction, or ask a question">{(id, describedBy) => <Textarea id={id} describedBy={describedBy} value={sentence} disabled={busy || pending !== undefined} onChange={setSentence} />}</Field>
|
|
176
|
+
{current !== undefined && <Row gap={2} align="center" wrap><Text muted>Changing: {current.title}</Text><Button type="button" size="sm" variant="quiet" disabled={busy || pending !== undefined} onClick={() => { setCurrent(undefined); setSentence(""); }}>Add a new task</Button></Row>}
|
|
177
|
+
<Row gap={2} wrap>
|
|
178
|
+
<Button type="submit" disabled={!ready || busy || sentence.trim().length === 0}>{busy ? "Adding your changes…" : pending !== undefined ? "Retry remaining tasks" : current === undefined ? "Add tasks" : "Apply correction"}</Button>
|
|
179
|
+
<Button type="button" variant="quiet" disabled={!ready || busy || pending !== undefined || sentence.trim().length === 0} onClick={askRecords}>Ask about tasks</Button>
|
|
180
|
+
</Row>
|
|
181
|
+
</Stack>
|
|
173
182
|
</form>
|
|
174
|
-
<
|
|
183
|
+
<Text>{answer}</Text>
|
|
175
184
|
<p role="status">{message}</p>
|
|
176
|
-
{writeRejection && <p role="status">{writeRejection.message}
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
{writeRejection && <Row gap={2} align="center" wrap><p role="status">{writeRejection.message}</p><Button type="button" size="sm" variant="quiet" onClick={clearWriteRejection}>Dismiss</Button></Row>}
|
|
186
|
+
<Row gap={2} wrap>
|
|
187
|
+
{lastWrite !== undefined && <Button type="button" size="sm" variant="quiet" disabled={busy} onClick={undoLast}>Undo</Button>}
|
|
188
|
+
{pending !== undefined && !busy && <Button type="button" size="sm" variant="quiet" tone="down" onClick={() => { setPending(undefined); setSentence(""); setCurrent(undefined); }}>Discard unsaved tasks</Button>}
|
|
189
|
+
</Row>
|
|
179
190
|
{docs.map((task) => <TaskCard key={task._id} task={task} database={database} can={can} disabled={busy || pending !== undefined} onSaved={(previous) => setLastWrite({ created: [], replaced: previous })} onCorrect={(doc) => { if (busy || pending !== undefined) return; setCurrent(doc); setSentence(""); }} />)}
|
|
180
|
-
</main>;
|
|
191
|
+
</Stack></main>;
|
|
181
192
|
}
|
|
182
193
|
```
|
|
183
194
|
|
|
@@ -246,6 +257,66 @@ An image that stays too large even after shrinking is answered at once with an e
|
|
|
246
257
|
|
|
247
258
|
Inside `backend.js`, `ctx.callAI` takes a string prompt.
|
|
248
259
|
|
|
260
|
+
## Choosing a model
|
|
261
|
+
|
|
262
|
+
`listModels({ kind: "image" })` returns the image models this app can use, the app's own choice first; `listModels({ kind: "text" })` returns the text models `callAI` can answer with, in the same order. Import it beside `callAI`: `import { callAI, listModels } from "call-ai"`. Each entry is `{ id, name, description, kind, source, isDefault }`:
|
|
263
|
+
|
|
264
|
+
- `id` — what you hand back: `callAI(prompt, { schema, model: id })`, or `<ImgGen model={id} />` for a picture.
|
|
265
|
+
- `name` and `description` — the words to show a person who is choosing.
|
|
266
|
+
- `isDefault` — `true` on the one model the app uses when it names none. Start a picker there.
|
|
267
|
+
- `source` — where the entry comes from: `"account-setting"` (the signed-in person's own pick for their account), `"app-setting"` (the pick this app's maker made), `"owner-account-setting"` (the maker's pick for all their apps, when this app has none of its own), `"platform-default"` (the platform's pick for this app), or `"catalog"` (the platform's curated models).
|
|
268
|
+
|
|
269
|
+
`callAI(prompt, { schema, model })` answers with the model you name from that list, and leaving `model` out keeps the app's default. Pass an `id` the list returned: any other name comes back as an error message you can show beside the picker. When the app's maker saved their own provider key in settings, that key covers the model they chose there; other models run on the person's credits. Let a person choose with a `Select` built from the list:
|
|
270
|
+
|
|
271
|
+
### `App.jsx`
|
|
272
|
+
```jsx
|
|
273
|
+
import React, { useEffect, useState } from "react";
|
|
274
|
+
import { callAI, listModels } from "call-ai";
|
|
275
|
+
import { Button, Stack, Text } from "@vibes.diy/pouf";
|
|
276
|
+
import { Select } from "@vibes.diy/pouf/interactive";
|
|
277
|
+
|
|
278
|
+
export default function App() {
|
|
279
|
+
const [models, setModels] = useState([]);
|
|
280
|
+
const [model, setModel] = useState("");
|
|
281
|
+
const [haiku, setHaiku] = useState("");
|
|
282
|
+
const [busy, setBusy] = useState(false);
|
|
283
|
+
const [message, setMessage] = useState("");
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
listModels({ kind: "text" })
|
|
286
|
+
.then((list) => {
|
|
287
|
+
setModels(list);
|
|
288
|
+
setModel(list.find((m) => m.isDefault)?.id ?? list[0]?.id ?? "");
|
|
289
|
+
})
|
|
290
|
+
.catch((error) => setMessage(error.message));
|
|
291
|
+
}, []);
|
|
292
|
+
async function writeHaiku() {
|
|
293
|
+
setBusy(true);
|
|
294
|
+
setMessage("");
|
|
295
|
+
try {
|
|
296
|
+
const response = await callAI("Write a haiku about the first snow of the year.", {
|
|
297
|
+
model,
|
|
298
|
+
schema: { properties: { haiku: { type: "string" } }, required: ["haiku"] },
|
|
299
|
+
});
|
|
300
|
+
setHaiku(JSON.parse(response).haiku);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
setMessage(error.message);
|
|
303
|
+
} finally {
|
|
304
|
+
setBusy(false);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return (
|
|
308
|
+
<Stack>
|
|
309
|
+
<Select label="Model" value={model} onChange={setModel} options={models.map((m) => ({ value: m.id, label: m.name }))} />
|
|
310
|
+
<Button onClick={writeHaiku} loading={busy} disabled={model === ""}>Write a haiku</Button>
|
|
311
|
+
<Text>{haiku}</Text>
|
|
312
|
+
<Text>{message}</Text>
|
|
313
|
+
</Stack>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
For pictures, the image-gen skill's "Choosing a Model" section shows one `<ImgGen>` per image model, side by side.
|
|
319
|
+
|
|
249
320
|
## Error Handling
|
|
250
321
|
|
|
251
322
|
```javascript
|
package/llms/connections.md
CHANGED
|
@@ -75,18 +75,19 @@ for the rest.
|
|
|
75
75
|
### `App.jsx`
|
|
76
76
|
```jsx app=instagram-connection
|
|
77
77
|
import { useConnection } from "use-vibes";
|
|
78
|
+
import { Button, Text } from "@vibes.diy/pouf";
|
|
78
79
|
|
|
79
80
|
function ConnectInstagram() {
|
|
80
81
|
const { state, consented, available, accountLabel, link, refresh } = useConnection("instagram");
|
|
81
82
|
if (state === "pending") return null; // still resolving — never flash a Connect button
|
|
82
83
|
// A connection that already works keeps working, whatever `available` says.
|
|
83
84
|
if (consented && (state === "live" || state === "expiring"))
|
|
84
|
-
return <
|
|
85
|
+
return <Text>Connected as {accountLabel}</Text>;
|
|
85
86
|
// Everything below asks the visitor to start or redo a connection, which is
|
|
86
87
|
// the only thing `available` can stop.
|
|
87
|
-
if (!available) return <
|
|
88
|
-
if (state === "dead") return <
|
|
89
|
-
return <button onClick={link}>Connect Instagram</
|
|
88
|
+
if (!available) return <Text muted>Connecting Instagram is not available right now.</Text>;
|
|
89
|
+
if (state === "dead") return <Text muted>Your Instagram connection expired. Reconnect it in Settings.</Text>;
|
|
90
|
+
return <Button type="button" onClick={link}>Connect Instagram</Button>;
|
|
90
91
|
}
|
|
91
92
|
```
|
|
92
93
|
|
package/llms/create-vibe.md
CHANGED
|
@@ -28,7 +28,7 @@ when they're ready.
|
|
|
28
28
|
```jsx
|
|
29
29
|
import React from "react";
|
|
30
30
|
import { createVibe } from "use-vibes";
|
|
31
|
-
import { Button } from "@vibes.diy/
|
|
31
|
+
import { Button } from "@vibes.diy/pouf";
|
|
32
32
|
|
|
33
33
|
export default function App() {
|
|
34
34
|
// `ready` + `spec` come from the interview (see the full example below).
|
|
@@ -69,7 +69,7 @@ Let the user edit each slide inline and present full-screen.`;
|
|
|
69
69
|
import React from "react";
|
|
70
70
|
import { callAI } from "call-ai";
|
|
71
71
|
import { createVibe } from "use-vibes";
|
|
72
|
-
import { Button } from "@vibes.diy/
|
|
72
|
+
import { Button } from "@vibes.diy/pouf";
|
|
73
73
|
|
|
74
74
|
export default function App() {
|
|
75
75
|
const [answers, setAnswers] = React.useState({});
|
|
@@ -95,7 +95,7 @@ export default function App() {
|
|
|
95
95
|
<main id="app">
|
|
96
96
|
{/* ...interview... */}
|
|
97
97
|
{!spec ? (
|
|
98
|
-
<Button size="lg"
|
|
98
|
+
<Button size="lg" loading={building} onClick={finish}>
|
|
99
99
|
{building ? "Preparing…" : "I'm ready"}
|
|
100
100
|
</Button>
|
|
101
101
|
) : (
|
|
@@ -131,6 +131,7 @@ blocked), offer a fallback link so the hand-off still works:
|
|
|
131
131
|
|
|
132
132
|
```jsx
|
|
133
133
|
import { createVibe, buildCreateVibeUrl } from "use-vibes";
|
|
134
|
+
import { Button } from "@vibes.diy/pouf";
|
|
134
135
|
|
|
135
136
|
function HandoffButton({ spec }) {
|
|
136
137
|
const [blocked, setBlocked] = React.useState(false);
|
|
@@ -139,7 +140,7 @@ function HandoffButton({ spec }) {
|
|
|
139
140
|
Open the builder
|
|
140
141
|
</a>
|
|
141
142
|
) : (
|
|
142
|
-
<button onClick={() => setBlocked(!createVibe(spec))}>Create my pitch deck</
|
|
143
|
+
<Button type="button" onClick={() => setBlocked(!createVibe(spec))}>Create my pitch deck</Button>
|
|
143
144
|
);
|
|
144
145
|
}
|
|
145
146
|
```
|