@vibes.diy/prompts 14.3.124 → 14.3.126
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 +11 -8
- package/llms/calendar.md +3 -2
- package/llms/fireproof.md +11 -6
- package/llms/motion.md +2 -2
- package/llms/physics.md +4 -3
- package/llms/pouf-blog.md +2 -2
- package/llms/pouf-calendar.md +2 -3
- package/llms/pouf-chat.md +7 -5
- package/llms/pouf-contact.md +5 -4
- package/llms/pouf-course.md +1 -1
- package/llms/pouf-crm.md +5 -4
- package/llms/pouf-dashboard.md +11 -6
- package/llms/pouf-editorial.md +3 -3
- package/llms/pouf-event.md +2 -2
- package/llms/pouf-feed.md +6 -4
- package/llms/pouf-inbox.md +9 -5
- package/llms/pouf-inventory.md +5 -5
- package/llms/pouf-kanban.md +3 -2
- package/llms/pouf-settings.md +2 -2
- package/llms/pouf-support.md +11 -7
- package/llms/pouf-todo.md +3 -2
- package/llms/strudel.md +3 -2
- package/llms/use-vibe.md +3 -2
- package/llms/use-viewer.md +3 -2
- package/llms/worlds.md +3 -2
- package/package.json +5 -5
- package/system-prompt-initial-oneshot.md +9 -6
- package/system-prompt-initial.md +9 -6
- package/system-prompt.md +1 -0
package/llms/access.md
CHANGED
|
@@ -802,14 +802,15 @@ export default function App() {
|
|
|
802
802
|
|
|
803
803
|
async function submitPost() {
|
|
804
804
|
if (!draft.trim() || !viewer) return;
|
|
805
|
-
|
|
805
|
+
setDraft("");
|
|
806
|
+
const { ok } = await database.put({
|
|
806
807
|
type: "post",
|
|
807
808
|
channel,
|
|
808
809
|
body: draft.trim(),
|
|
809
810
|
authorHandle: viewer.userHandle,
|
|
810
811
|
createdAt: Date.now(),
|
|
811
812
|
});
|
|
812
|
-
setDraft(
|
|
813
|
+
if (!ok) setDraft((now) => now || draft);
|
|
813
814
|
}
|
|
814
815
|
|
|
815
816
|
return (
|
|
@@ -1139,10 +1140,11 @@ function BoardWorkspace() {
|
|
|
1139
1140
|
const startBoard = async (event) => {
|
|
1140
1141
|
event.preventDefault();
|
|
1141
1142
|
if (!ready || !newBoard.name || !can.create(newBoard).ok) return;
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1143
|
+
setName(""); // ready for the next name at once
|
|
1144
|
+
setDraftId("board-" + crypto.randomUUID()); // and the next board gets an id of its own
|
|
1145
|
+
const { ok } = await database.put(newBoard);
|
|
1146
|
+
if (ok) setPickedBoardId(newBoard._id);
|
|
1147
|
+
else setName((now) => now || newBoard.name);
|
|
1146
1148
|
};
|
|
1147
1149
|
const board =
|
|
1148
1150
|
boards.find((board) => board._id === pickedBoardId) ??
|
|
@@ -1334,8 +1336,9 @@ function Shop() {
|
|
|
1334
1336
|
const addFlavor = async (event) => {
|
|
1335
1337
|
event.preventDefault();
|
|
1336
1338
|
if (!ready || !flavor.name || !can.create(flavor).ok) return;
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
+
setName(""); // ready for the next flavor at once
|
|
1340
|
+
const { ok } = await database.put(flavor); // the exact value the control checked
|
|
1341
|
+
if (!ok) setName((now) => now || flavor.name);
|
|
1339
1342
|
};
|
|
1340
1343
|
const order = async (f) => {
|
|
1341
1344
|
const next = orderFor(f);
|
package/llms/calendar.md
CHANGED
|
@@ -70,9 +70,10 @@ export default function App() {
|
|
|
70
70
|
};
|
|
71
71
|
const save = async () => {
|
|
72
72
|
if (!words.trim() || !day) return;
|
|
73
|
-
|
|
74
|
-
setWords("");
|
|
73
|
+
setWords(""); // ready for the next session at once
|
|
75
74
|
setDay(today);
|
|
75
|
+
const { ok } = await database.put({ type: "session", piece: words.trim(), day, createdAt: Date.now() });
|
|
76
|
+
if (!ok) setWords((now) => now || words);
|
|
76
77
|
};
|
|
77
78
|
return (
|
|
78
79
|
<Stack gap={3}>
|
package/llms/fireproof.md
CHANGED
|
@@ -62,9 +62,11 @@ function AddItem() {
|
|
|
62
62
|
const [text, setText] = useState("");
|
|
63
63
|
const [note, setNote] = useState("");
|
|
64
64
|
async function add() {
|
|
65
|
-
const
|
|
65
|
+
const typed = text;
|
|
66
|
+
setText(""); // the field is ready for the next item the moment they press Add
|
|
67
|
+
const { ok, synced } = await database.put({ type: "item", text: typed });
|
|
66
68
|
setNote(synced.code === "draft-read-only" ? synced.message : "");
|
|
67
|
-
if (ok) setText(
|
|
69
|
+
if (!ok) setText((now) => now || typed); // hand their words back while the field is still empty
|
|
68
70
|
}
|
|
69
71
|
return (
|
|
70
72
|
<Stack gap={2}>
|
|
@@ -78,6 +80,8 @@ function AddItem() {
|
|
|
78
80
|
}
|
|
79
81
|
```
|
|
80
82
|
|
|
83
|
+
Clear the field as the person submits, before the put: capture what they typed, empty the field, then write the captured value. The field is ready for their next entry the moment they press Add, and anything they type while the write settles stays theirs for the next save. When the result comes back `ok: false`, hand the words back with an updater that keeps newer typing, `setText((now) => now || typed)`. `useDocument`'s `submit()` follows the same shape on its own: it hands back a fresh draft as the person submits, saves the submitted one, and restores it when that save does not land while the form is still untouched.
|
|
84
|
+
|
|
81
85
|
### Basic Example
|
|
82
86
|
|
|
83
87
|
When the composer is on screen, wire these visible actions in the same build, using the app's own record names. One composer has Add and Ask actions. Add extracts and normalizes facts, saves each record immediately, and shows saved records with edit actions; one item and a pasted batch share this action. Each saved row has Change by sentence: selecting it changes Add to Apply correction, which saves onto that same record id. Ask sends the typed question with the relevant saved records and displays a read-only answer beside the composer. Edit opens the selected row’s populated fields. One parent-owned Undo restores the latest successful creation batch or the previous document from either a sentence correction or a field edit.
|
|
@@ -255,7 +259,7 @@ export default function App() {
|
|
|
255
259
|
|
|
256
260
|
### Editing Documents
|
|
257
261
|
|
|
258
|
-
Address an existing record by its `_id`. `useDocument(task)` above holds the selected record's editable fields: `merge(updates)` changes the draft, `save()` writes it, and `reset()` restores its initial state. `submit(event)` combines prevent-default, save and
|
|
262
|
+
Address an existing record by its `_id`. `useDocument(task)` above holds the selected record's editable fields: `merge(updates)` changes the draft, `save()` writes it, and `reset()` restores its initial state. `submit(event)` combines prevent-default, reset and save: the form resets as the person submits, the submitted draft is saved, and edits made while the save settles stay in the form. Choose the operation that matches the editor's lifecycle. For correction by sentence, pass the current editable fields and the person's correction to the same extraction schema, normalize the result and immediately update the existing document in that submit action with its trusted `_id` and identity fields intact. The callai skill's Text to records example includes that path and pasted lists.
|
|
259
263
|
|
|
260
264
|
## Required starting records
|
|
261
265
|
|
|
@@ -793,7 +797,7 @@ import { Button, Card, ErrorNote, Field, Figure, Grid, Heading, Input, Stack, Te
|
|
|
793
797
|
export default function App() {
|
|
794
798
|
const { useDocument, useLiveQuery } = useFireproof("imageUploads");
|
|
795
799
|
|
|
796
|
-
const { doc, merge,
|
|
800
|
+
const { doc, merge, submit } = useDocument({
|
|
797
801
|
_files: {},
|
|
798
802
|
caption: "",
|
|
799
803
|
type: "upload",
|
|
@@ -814,8 +818,9 @@ export default function App() {
|
|
|
814
818
|
setSaveError("");
|
|
815
819
|
try {
|
|
816
820
|
// The save uploads the photo first, so it can reject; show why.
|
|
817
|
-
|
|
818
|
-
|
|
821
|
+
// submit() hands back a fresh form at once and saves the submitted one; a failed upload
|
|
822
|
+
// puts the photo back in the form while it is still untouched.
|
|
823
|
+
await submit();
|
|
819
824
|
} catch (err) {
|
|
820
825
|
setSaveError(err.message);
|
|
821
826
|
}
|
package/llms/motion.md
CHANGED
|
@@ -481,9 +481,9 @@ export default function App() {
|
|
|
481
481
|
|
|
482
482
|
async function send() {
|
|
483
483
|
if (!canPost) return;
|
|
484
|
-
|
|
485
|
-
setTo("");
|
|
484
|
+
setTo(""); // ready for the next kudo at once
|
|
486
485
|
setText("");
|
|
486
|
+
await database.put({ ...draft, _id: crypto.randomUUID(), createdAt: Date.now() });
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
return (
|
package/llms/physics.md
CHANGED
|
@@ -333,9 +333,10 @@ export default function App() {
|
|
|
333
333
|
async function keep() {
|
|
334
334
|
if (!canMake) return;
|
|
335
335
|
const doc = { _id: crypto.randomUUID(), type: "level", name: name || "My run", pieces, seed: 7, creatorHandle: me.userHandle, updatedAt: Date.now() };
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
setActiveId(doc._id);
|
|
336
|
+
setName(""); // ready for the next name at once
|
|
337
|
+
const { ok } = await database.put(doc);
|
|
338
|
+
if (ok) setActiveId(doc._id);
|
|
339
|
+
else setName((now) => now || name);
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
return (
|
package/llms/pouf-blog.md
CHANGED
|
@@ -107,11 +107,11 @@ export default function App() {
|
|
|
107
107
|
if (Object.keys(next).length > 0) return;
|
|
108
108
|
setPublishing(true);
|
|
109
109
|
try {
|
|
110
|
-
|
|
111
|
-
// Cleared only once the write lands; the filter resets so the new post is in view.
|
|
110
|
+
// The form is ready at once; the filter resets so the new post is in view.
|
|
112
111
|
setDraft(EMPTY_DRAFT);
|
|
113
112
|
setWriting(false);
|
|
114
113
|
pick("all");
|
|
114
|
+
await database.put({ type: "post", title: draft.title.trim(), category: draft.category, body: draft.body.trim(), authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
115
115
|
toast.success("Post published");
|
|
116
116
|
} finally {
|
|
117
117
|
setPublishing(false);
|
package/llms/pouf-calendar.md
CHANGED
|
@@ -144,6 +144,8 @@ export default function App() {
|
|
|
144
144
|
if (Object.keys(next).length > 0) return;
|
|
145
145
|
setSaving(true);
|
|
146
146
|
try {
|
|
147
|
+
setDraft(EMPTY_DRAFT);
|
|
148
|
+
setAdding(false);
|
|
147
149
|
await database.put({
|
|
148
150
|
type: "event",
|
|
149
151
|
date: selected,
|
|
@@ -153,9 +155,6 @@ export default function App() {
|
|
|
153
155
|
authorHandle: me?.userHandle,
|
|
154
156
|
createdAt: Date.now(),
|
|
155
157
|
});
|
|
156
|
-
// Cleared only once the write lands, so a failed save keeps what was typed.
|
|
157
|
-
setDraft(EMPTY_DRAFT);
|
|
158
|
-
setAdding(false);
|
|
159
158
|
toast.success("Event added");
|
|
160
159
|
} finally {
|
|
161
160
|
setSaving(false);
|
package/llms/pouf-chat.md
CHANGED
|
@@ -41,9 +41,10 @@ function Thread({ room, onBack }) {
|
|
|
41
41
|
e.preventDefault();
|
|
42
42
|
const text = draft.trim();
|
|
43
43
|
if (text === "") return;
|
|
44
|
-
|
|
45
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
44
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
46
45
|
setDraft("");
|
|
46
|
+
const { ok } = await database.put({ type: "message", roomId: room._id, text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
47
|
+
if (!ok) setDraft((now) => now || text);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
return (
|
|
@@ -128,9 +129,10 @@ export default function App() {
|
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
130
131
|
setError(undefined);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
setActiveId(id);
|
|
132
|
+
setName(""); // ready for the next room name at once
|
|
133
|
+
const { id, ok } = await database.put({ type: "room", name: text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
134
|
+
if (ok) setActiveId(id);
|
|
135
|
+
else setName((now) => now || text);
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
function latest(roomId) {
|
package/llms/pouf-contact.md
CHANGED
|
@@ -63,7 +63,9 @@ export default function App() {
|
|
|
63
63
|
}
|
|
64
64
|
setSending(true);
|
|
65
65
|
try {
|
|
66
|
-
|
|
66
|
+
// The form is fresh for whoever writes next; the thank-you waits for the receipt.
|
|
67
|
+
setForm(BLANK);
|
|
68
|
+
const { ok } = await database.put({
|
|
67
69
|
...draft,
|
|
68
70
|
name: form.name.trim(),
|
|
69
71
|
email: form.email.trim(),
|
|
@@ -71,9 +73,8 @@ export default function App() {
|
|
|
71
73
|
message: form.message.trim(),
|
|
72
74
|
createdAt: Date.now(),
|
|
73
75
|
});
|
|
74
|
-
setSent({ name: form.name.trim(), email: form.email.trim() });
|
|
75
|
-
|
|
76
|
-
setForm(BLANK);
|
|
76
|
+
if (ok) setSent({ name: form.name.trim(), email: form.email.trim() });
|
|
77
|
+
else setForm((now) => (now === BLANK ? form : now));
|
|
77
78
|
} finally {
|
|
78
79
|
setSending(false);
|
|
79
80
|
}
|
package/llms/pouf-course.md
CHANGED
|
@@ -161,9 +161,9 @@ export default function App() {
|
|
|
161
161
|
};
|
|
162
162
|
setErrors(found);
|
|
163
163
|
if (found.title || found.minutes) return;
|
|
164
|
-
await database.put({ type: "lesson", title: form.title.trim(), kind: form.kind, minutes, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
165
164
|
setForm({ title: "", kind: "Lesson", minutes: "10" });
|
|
166
165
|
setAdding(false);
|
|
166
|
+
await database.put({ type: "lesson", title: form.title.trim(), kind: form.kind, minutes, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
167
167
|
toast.success("Lesson added");
|
|
168
168
|
}
|
|
169
169
|
|
package/llms/pouf-crm.md
CHANGED
|
@@ -88,9 +88,10 @@ function CustomerView({ customer, notes, database, can, me, onBack }) {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
setError(undefined);
|
|
91
|
-
|
|
92
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
91
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
93
92
|
setDraft("");
|
|
93
|
+
const { ok } = await database.put({ type: "note", customerId: customer._id, text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
94
|
+
if (!ok) setDraft((now) => now || text);
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
return (
|
|
@@ -201,6 +202,8 @@ export default function App() {
|
|
|
201
202
|
};
|
|
202
203
|
setErrors(found);
|
|
203
204
|
if (found.name || found.value) return;
|
|
205
|
+
setForm({ name: "", company: "", email: "", value: "", stage: "lead" });
|
|
206
|
+
setAdding(false);
|
|
204
207
|
await database.put({
|
|
205
208
|
type: "customer",
|
|
206
209
|
name: form.name.trim(),
|
|
@@ -211,8 +214,6 @@ export default function App() {
|
|
|
211
214
|
authorHandle: me?.userHandle,
|
|
212
215
|
createdAt: Date.now(),
|
|
213
216
|
});
|
|
214
|
-
setForm({ name: "", company: "", email: "", value: "", stage: "lead" });
|
|
215
|
-
setAdding(false);
|
|
216
217
|
toast.success("Customer added");
|
|
217
218
|
}
|
|
218
219
|
|
package/llms/pouf-dashboard.md
CHANGED
|
@@ -113,13 +113,18 @@ export default function App() {
|
|
|
113
113
|
setErrors(next);
|
|
114
114
|
if (Object.keys(next).length > 0) return;
|
|
115
115
|
setSaving(true);
|
|
116
|
+
// The fields are ready for the next sale at once; a save that does not land hands them back.
|
|
117
|
+
setItem("");
|
|
118
|
+
setAmount("");
|
|
119
|
+
setState("paid");
|
|
116
120
|
try {
|
|
117
|
-
await database.put({ type: "sale", item: item.trim(), amount: value, state, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const { ok } = await database.put({ type: "sale", item: item.trim(), amount: value, state, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
122
|
+
if (ok) toast.success("Sale recorded");
|
|
123
|
+
else {
|
|
124
|
+
setItem((now) => now || item);
|
|
125
|
+
setAmount((now) => now || amount);
|
|
126
|
+
setState((now) => (now === "paid" ? state : now));
|
|
127
|
+
}
|
|
123
128
|
} finally {
|
|
124
129
|
setSaving(false);
|
|
125
130
|
}
|
package/llms/pouf-editorial.md
CHANGED
|
@@ -141,6 +141,9 @@ export default function App() {
|
|
|
141
141
|
const found = { title: form.title.trim() === "" ? "Give the story a working title." : undefined };
|
|
142
142
|
setErrors(found);
|
|
143
143
|
if (found.title) return;
|
|
144
|
+
setForm({ title: "", section: "", due: "" });
|
|
145
|
+
setPitching(false);
|
|
146
|
+
setStage("draft");
|
|
144
147
|
await database.put({
|
|
145
148
|
type: "story",
|
|
146
149
|
title: form.title.trim(),
|
|
@@ -150,9 +153,6 @@ export default function App() {
|
|
|
150
153
|
authorHandle: me?.userHandle,
|
|
151
154
|
createdAt: Date.now(),
|
|
152
155
|
});
|
|
153
|
-
setForm({ title: "", section: "", due: "" });
|
|
154
|
-
setPitching(false);
|
|
155
|
-
setStage("draft");
|
|
156
156
|
toast.success("Story pitched");
|
|
157
157
|
}
|
|
158
158
|
|
package/llms/pouf-event.md
CHANGED
|
@@ -97,6 +97,8 @@ export default function App() {
|
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
setNameError(undefined);
|
|
100
|
+
setForm({ name: "", group: "", ticket: "General" });
|
|
101
|
+
setAdding(false);
|
|
100
102
|
await database.put({
|
|
101
103
|
type: "guest",
|
|
102
104
|
name: form.name.trim(),
|
|
@@ -106,8 +108,6 @@ export default function App() {
|
|
|
106
108
|
authorHandle: me?.userHandle,
|
|
107
109
|
createdAt: Date.now(),
|
|
108
110
|
});
|
|
109
|
-
setForm({ name: "", group: "", ticket: "General" });
|
|
110
|
-
setAdding(false);
|
|
111
111
|
toast.success("Guest added");
|
|
112
112
|
}
|
|
113
113
|
|
package/llms/pouf-feed.md
CHANGED
|
@@ -42,8 +42,9 @@ function Post({ post, likes, comments, database, can, me }) {
|
|
|
42
42
|
e.preventDefault();
|
|
43
43
|
const text = reply.trim();
|
|
44
44
|
if (text === "") return;
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
setReply(""); // ready for the next reply at once
|
|
46
|
+
const { ok } = await database.put({ type: "comment", postId: post._id, text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
47
|
+
if (!ok) setReply((now) => now || text);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
async function remove() {
|
|
@@ -162,9 +163,10 @@ export default function App() {
|
|
|
162
163
|
return;
|
|
163
164
|
}
|
|
164
165
|
setError(undefined);
|
|
165
|
-
|
|
166
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
166
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
167
167
|
setDraft("");
|
|
168
|
+
const { ok } = await database.put({ type: "post", text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
169
|
+
if (!ok) setDraft((now) => now || text);
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
return (
|
package/llms/pouf-inbox.md
CHANGED
|
@@ -54,9 +54,13 @@ function Reader({ note, replies, database, can, me, onBack }) {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
setError(undefined);
|
|
57
|
-
|
|
58
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
57
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
59
58
|
setDraft("");
|
|
59
|
+
const { ok } = await database.put({ type: "reply", noteId: note._id, text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
60
|
+
if (!ok) {
|
|
61
|
+
setDraft((now) => now || text);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
60
64
|
toast.success("Reply sent");
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -194,6 +198,9 @@ export default function App() {
|
|
|
194
198
|
};
|
|
195
199
|
setErrors(found);
|
|
196
200
|
if (found.subject || found.body) return;
|
|
201
|
+
setSubject("");
|
|
202
|
+
setBody("");
|
|
203
|
+
setComposing(false);
|
|
197
204
|
await database.put({
|
|
198
205
|
type: "note",
|
|
199
206
|
subject: subject.trim(),
|
|
@@ -203,9 +210,6 @@ export default function App() {
|
|
|
203
210
|
authorHandle: me?.userHandle,
|
|
204
211
|
createdAt: Date.now(),
|
|
205
212
|
});
|
|
206
|
-
setSubject("");
|
|
207
|
-
setBody("");
|
|
208
|
-
setComposing(false);
|
|
209
213
|
toast.success("Note sent");
|
|
210
214
|
}
|
|
211
215
|
|
package/llms/pouf-inventory.md
CHANGED
|
@@ -183,6 +183,11 @@ export default function App() {
|
|
|
183
183
|
};
|
|
184
184
|
setErrors(found);
|
|
185
185
|
if (Object.values(found).some(Boolean)) return;
|
|
186
|
+
setForm(EMPTY_FORM);
|
|
187
|
+
setAdding(false);
|
|
188
|
+
// Clear the search and filter so the new item is on screen, then open it.
|
|
189
|
+
setQuery("");
|
|
190
|
+
setFilter("all");
|
|
186
191
|
const { id } = await database.put({
|
|
187
192
|
type: "item",
|
|
188
193
|
name: form.name.trim(),
|
|
@@ -193,11 +198,6 @@ export default function App() {
|
|
|
193
198
|
authorHandle: me?.userHandle,
|
|
194
199
|
createdAt: Date.now(),
|
|
195
200
|
});
|
|
196
|
-
setForm(EMPTY_FORM);
|
|
197
|
-
setAdding(false);
|
|
198
|
-
// Clear the search and filter so the new item is on screen, then open it.
|
|
199
|
-
setQuery("");
|
|
200
|
-
setFilter("all");
|
|
201
201
|
setSelectedId(id);
|
|
202
202
|
toast.success("Item added");
|
|
203
203
|
}
|
package/llms/pouf-kanban.md
CHANGED
|
@@ -64,9 +64,10 @@ export default function App() {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
setError(undefined);
|
|
67
|
-
|
|
68
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
67
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
69
68
|
setDraft("");
|
|
69
|
+
const { ok } = await database.put({ type: "card", title, column: "todo", authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
70
|
+
if (!ok) setDraft((now) => now || title);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
function moveCard(card, column) {
|
package/llms/pouf-settings.md
CHANGED
|
@@ -63,9 +63,9 @@ function SettingsForm({ saved, database, can, me }) {
|
|
|
63
63
|
setSaving(true);
|
|
64
64
|
try {
|
|
65
65
|
const written = { ...saved, ...draft, ...form, name: form.name.trim(), updatedAt: Date.now() };
|
|
66
|
-
|
|
67
|
-
// Match the form to what was stored (trimmed), so it reads clean after saving.
|
|
66
|
+
// Match the form to what is stored (trimmed), so it reads clean the moment they save.
|
|
68
67
|
setForm(pick(written));
|
|
68
|
+
await database.put(written);
|
|
69
69
|
toast.success("Settings saved");
|
|
70
70
|
} finally {
|
|
71
71
|
setSaving(false);
|
package/llms/pouf-support.md
CHANGED
|
@@ -76,12 +76,16 @@ function TicketView({ ticket, replies, database, can, me, onBack }) {
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
setError(undefined);
|
|
79
|
-
|
|
79
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
80
|
+
setDraft("");
|
|
81
|
+
const { ok } = await database.put({ type: "reply", ticketId: ticket._id, text, authorHandle: me?.userHandle, createdAt: Date.now() });
|
|
82
|
+
if (!ok) {
|
|
83
|
+
setDraft((now) => now || text);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
80
86
|
// The asker replying puts the ball back with the desk; anyone else replying waits on the asker.
|
|
81
87
|
const next = me?.userHandle && me.userHandle === ticket.authorHandle ? "open" : "waiting";
|
|
82
88
|
if (canSetStatus && ticket.status !== next) await database.put({ ...ticket, status: next });
|
|
83
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
84
|
-
setDraft("");
|
|
85
89
|
toast.success("Reply sent");
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -203,6 +207,10 @@ export default function App() {
|
|
|
203
207
|
};
|
|
204
208
|
setErrors(found);
|
|
205
209
|
if (found.subject || found.body) return;
|
|
210
|
+
setSubject("");
|
|
211
|
+
setBody("");
|
|
212
|
+
setUrgent(false);
|
|
213
|
+
setLogging(false);
|
|
206
214
|
await database.put({
|
|
207
215
|
type: "ticket",
|
|
208
216
|
subject: subject.trim(),
|
|
@@ -212,10 +220,6 @@ export default function App() {
|
|
|
212
220
|
authorHandle: me?.userHandle,
|
|
213
221
|
createdAt: Date.now(),
|
|
214
222
|
});
|
|
215
|
-
setSubject("");
|
|
216
|
-
setBody("");
|
|
217
|
-
setUrgent(false);
|
|
218
|
-
setLogging(false);
|
|
219
223
|
toast.success("Ticket logged");
|
|
220
224
|
}
|
|
221
225
|
|
package/llms/pouf-todo.md
CHANGED
|
@@ -61,9 +61,10 @@ export default function App() {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
setError(undefined);
|
|
64
|
-
|
|
65
|
-
// Cleared only once the write lands, so a local failure keeps the typed words.
|
|
64
|
+
// The field is ready for the next one at once; a save that does not land hands the words back.
|
|
66
65
|
setDraft("");
|
|
66
|
+
const { ok } = await database.put({ type: "task", text, done: false, createdAt: Date.now(), authorHandle: me?.userHandle });
|
|
67
|
+
if (!ok) setDraft((now) => now || text);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function setDone(task, checked) {
|
package/llms/strudel.md
CHANGED
|
@@ -268,8 +268,9 @@ export default function App() {
|
|
|
268
268
|
const verdict = can.create(draft);
|
|
269
269
|
|
|
270
270
|
async function keep() {
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
setName(""); // ready for the next name at once
|
|
272
|
+
const { ok } = await database.put({ ...draft, createdAt: Date.now() });
|
|
273
|
+
if (!ok) setName((now) => now || name);
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
return (
|
package/llms/use-vibe.md
CHANGED
|
@@ -62,8 +62,9 @@ function PromptBar({ database }) {
|
|
|
62
62
|
<form
|
|
63
63
|
onSubmit={async (e) => {
|
|
64
64
|
e.preventDefault();
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
setText(""); // ready for the next tile at once
|
|
66
|
+
const { ok } = await database.put({ type: "tile", authorHandle: me?.userHandle, text });
|
|
67
|
+
if (!ok) setText((now) => now || text);
|
|
67
68
|
}}
|
|
68
69
|
>
|
|
69
70
|
{/* no current-user pill — identity + sign-in live in the Vibes Switch (the logo) */}
|
package/llms/use-viewer.md
CHANGED
|
@@ -200,12 +200,13 @@ Also add the `post` handler before `if (isViewerPending)`:
|
|
|
200
200
|
=======
|
|
201
201
|
async function post() {
|
|
202
202
|
if (!body.trim()) return;
|
|
203
|
-
|
|
203
|
+
setBody(""); // ready for the next post at once
|
|
204
|
+
const { ok } = await database.put({
|
|
204
205
|
body: body.trim(),
|
|
205
206
|
createdAt: Date.now(),
|
|
206
207
|
authorHandle: me?.userHandle,
|
|
207
208
|
});
|
|
208
|
-
setBody(
|
|
209
|
+
if (!ok) setBody((now) => now || body);
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
if (isViewerPending) return null;
|
package/llms/worlds.md
CHANGED
|
@@ -451,8 +451,9 @@ export default function App() {
|
|
|
451
451
|
async function keep() {
|
|
452
452
|
const world = worldRef.current;
|
|
453
453
|
if (!world || !canKeep) return;
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
setName(""); // ready for the next name at once
|
|
455
|
+
const { ok } = await database.put({ ...draft, _id: crypto.randomUUID(), tick: world.tick, createdAt: Date.now() });
|
|
456
|
+
if (!ok) setName((now) => now || name);
|
|
456
457
|
}
|
|
457
458
|
|
|
458
459
|
const dial = (key, label, max) => (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.126",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"exports": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@adviser/cement": "~0.5.34",
|
|
42
|
-
"@vibes.diy/api-types": "14.3.
|
|
43
|
-
"@vibes.diy/call-ai-v2": "14.3.
|
|
44
|
-
"@vibes.diy/identity": "14.3.
|
|
45
|
-
"@vibes.diy/use-vibes-types": "14.3.
|
|
42
|
+
"@vibes.diy/api-types": "14.3.126",
|
|
43
|
+
"@vibes.diy/call-ai-v2": "14.3.126",
|
|
44
|
+
"@vibes.diy/identity": "14.3.126",
|
|
45
|
+
"@vibes.diy/use-vibes-types": "14.3.126",
|
|
46
46
|
"arktype": "~2.2.6",
|
|
47
47
|
"json-schema-faker": "~0.6.3"
|
|
48
48
|
},
|
|
@@ -37,6 +37,7 @@ You are an AI assistant tasked with creating React components. You should create
|
|
|
37
37
|
- 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) pass `loading={isLoading}` to the trigger `Button`, which shows the package's own spinner and blocks a second click, (2) 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); }`
|
|
38
38
|
- 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.
|
|
39
39
|
- 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. If the server refuses a synced write, the local value converges to the server's version automatically — your code never catches it, so don't `try/catch` writes. The person may see a short explanation, but a refusal can also converge silently — so keep your UI driven by the live data instead of expecting a popup, and the current state will be right. Transport and offline failures are handled by the built-in queue, so don't hand-roll retry either. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
|
|
40
|
+
- A form that adds one record after another empties its field as the person submits, before the write: capture what they typed, clear the field, then save the captured value — `const typed = name; setName(""); const { ok } = await database.put({ type: "habit", name: typed }); if (!ok) setName((now) => now || typed);`. The field is ready for the next entry the moment they press Add, and words they type while the save settles stay theirs.
|
|
40
41
|
- For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
|
|
41
42
|
- 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)`); the verdict has three states — while it is `pending` (or `ready` is false) hold the surface in a quiet checking state, and on a resolved denial write the sign-in or join copy in your app's own words, letting `reason` inform the sentence you write. `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 is system chrome in the Vibes Switch (the panel the logo opens) — don't add a header pill for the current user. But ASKING an anonymous visitor to sign in is the app's job: call `useViewer().requestLogin()` from your own button, gated on `!isViewerPending`, at the moment the visitor has made something worth keeping — never a sentence telling them to find the logo, and never a wall in front of an app that already works for them. 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.
|
|
42
43
|
- 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
|
|
@@ -158,10 +159,11 @@ function Studio() {
|
|
|
158
159
|
const startLoom = async (event) => {
|
|
159
160
|
event.preventDefault();
|
|
160
161
|
if (!ready || !newLoom.name || !can.create(newLoom).ok) return;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
setLoomName(""); // ready for the next name at once
|
|
163
|
+
setLoomId("loom-" + crypto.randomUUID()); // and the next loom gets an id of its own
|
|
164
|
+
const { ok } = await database.put(newLoom); // the exact value the control checked
|
|
165
|
+
if (ok) setPickedPlace(newLoom._id);
|
|
166
|
+
else setLoomName((now) => now || newLoom.name);
|
|
165
167
|
};
|
|
166
168
|
// Hooks stay above this hold. Identity resolves before sign-in copy or controls.
|
|
167
169
|
if (!ready) return <p role="status">Checking studio access…</p>;
|
|
@@ -197,8 +199,9 @@ function PlaceContents({ place }) {
|
|
|
197
199
|
const add = async (event) => {
|
|
198
200
|
event.preventDefault();
|
|
199
201
|
if (!ready || !draft.title || !can.create(draft).ok) return;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
setTitle(""); // ready for the next weaving at once
|
|
203
|
+
const { ok } = await database.put(draft); // the exact value the control checked
|
|
204
|
+
if (!ok) setTitle((now) => now || draft.title);
|
|
202
205
|
};
|
|
203
206
|
return <section>
|
|
204
207
|
{place.loom ? <h1>{place.loom.name}</h1> : <StudioName studioHandle={place.key.studioHandle} />}
|
package/system-prompt-initial.md
CHANGED
|
@@ -37,6 +37,7 @@ You are an AI assistant tasked with creating React components. You should create
|
|
|
37
37
|
- 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) pass `loading={isLoading}` to the trigger `Button`, which shows the package's own spinner and blocks a second click, (2) 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); }`
|
|
38
38
|
- 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.
|
|
39
39
|
- 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. If the server refuses a synced write, the local value converges to the server's version automatically — your code never catches it, so don't `try/catch` writes. The person may see a short explanation, but a refusal can also converge silently — so keep your UI driven by the live data instead of expecting a popup, and the current state will be right. Transport and offline failures are handled by the built-in queue, so don't hand-roll retry either. Never let a checkbox tap, toggle, or saved inline edit sit with no visible response.
|
|
40
|
+
- A form that adds one record after another empties its field as the person submits, before the write: capture what they typed, clear the field, then save the captured value — `const typed = name; setName(""); const { ok } = await database.put({ type: "habit", name: typed }); if (!ok) setName((now) => now || typed);`. The field is ready for the next entry the moment they press Add, and words they type while the save settles stay theirs.
|
|
40
41
|
- For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
|
|
41
42
|
- 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)`); the verdict has three states — while it is `pending` (or `ready` is false) hold the surface in a quiet checking state, and on a resolved denial write the sign-in or join copy in your app's own words, letting `reason` inform the sentence you write. `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 is system chrome in the Vibes Switch (the panel the logo opens) — don't add a header pill for the current user. But ASKING an anonymous visitor to sign in is the app's job: call `useViewer().requestLogin()` from your own button, gated on `!isViewerPending`, at the moment the visitor has made something worth keeping — never a sentence telling them to find the logo, and never a wall in front of an app that already works for them. 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.
|
|
42
43
|
- 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
|
|
@@ -162,10 +163,11 @@ function Studio() {
|
|
|
162
163
|
const startLoom = async (event) => {
|
|
163
164
|
event.preventDefault();
|
|
164
165
|
if (!ready || !newLoom.name || !can.create(newLoom).ok) return;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
setLoomName(""); // ready for the next name at once
|
|
167
|
+
setLoomId("loom-" + crypto.randomUUID()); // and the next loom gets an id of its own
|
|
168
|
+
const { ok } = await database.put(newLoom); // the exact value the control checked
|
|
169
|
+
if (ok) setPickedPlace(newLoom._id);
|
|
170
|
+
else setLoomName((now) => now || newLoom.name);
|
|
169
171
|
};
|
|
170
172
|
// Hooks stay above this hold. Identity resolves before sign-in copy or controls.
|
|
171
173
|
if (!ready) return <p role="status">Checking studio access…</p>;
|
|
@@ -201,8 +203,9 @@ function PlaceContents({ place }) {
|
|
|
201
203
|
const add = async (event) => {
|
|
202
204
|
event.preventDefault();
|
|
203
205
|
if (!ready || !draft.title || !can.create(draft).ok) return;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
setTitle(""); // ready for the next weaving at once
|
|
207
|
+
const { ok } = await database.put(draft); // the exact value the control checked
|
|
208
|
+
if (!ok) setTitle((now) => now || draft.title);
|
|
206
209
|
};
|
|
207
210
|
return <section>
|
|
208
211
|
{place.loom ? <h1>{place.loom.name}</h1> : <StudioName studioHandle={place.key.studioHandle} />}
|
package/system-prompt.md
CHANGED
|
@@ -37,6 +37,7 @@ You are an AI assistant tasked with creating React components. You should create
|
|
|
37
37
|
- 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) pass `loading={isLoading}` to the trigger `Button`, which shows the package's own spinner and blocks a second click, (2) 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); }`
|
|
38
38
|
- 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.
|
|
39
39
|
- 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, so make the UI react the moment the person acts: flip the visible value immediately and let `useLiveQuery` reconcile. The server has the final word — a refused write converges to the server's version on its own, and the platform tells the person, in `access.js`'s own `forbidden("…")` words when it gives some — so write plainly, keep the UI reactive to the store (`useLiveQuery`/`useDocument`) so the converged state shows the truth, and gate write surfaces up front with `useVibe(dbName).can`. A resolved `put` means saved on this device; for feedback beside a control, follow its receipt, and every `useLiveQuery` row carries `doc._synced.status`: `const { synced } = await database.put({ ...task, done: !task.done }); synced.on((s) => setNote(s.status === "refused" && s.message ? s.message : ""));`. The built-in queue retries transport and offline failures for you. Every checkbox tap, toggle, and saved inline edit shows a visible response.
|
|
40
|
+
- A form that adds one record after another empties its field as the person submits, before the write: capture what they typed, clear the field, then save the captured value — `const typed = name; setName(""); const { ok } = await database.put({ type: "habit", name: typed }); if (!ok) setName((now) => now || typed);`. The field is ready for the next entry the moment they press Add, and words they type while the save settles stay theirs.
|
|
40
41
|
- For file uploads use drag and drop and store using the `doc._files` API; for AI image generation use `<ImgGen prompt="..." />`
|
|
41
42
|
- 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)`); the verdict has three states — while it is `pending` (or `ready` is false) hold the surface in a quiet checking state, and on a resolved denial write the sign-in or join copy in your app's own words, letting `reason` inform the sentence you write. `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 is system chrome in the Vibes Switch (the panel the logo opens) — don't add a header pill for the current user. But ASKING an anonymous visitor to sign in is the app's job: call `useViewer().requestLogin()` from your own button, gated on `!isViewerPending`, at the moment the visitor has made something worth keeping — never a sentence telling them to find the logo, and never a wall in front of an app that already works for them. 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. `can.*` is a hint: the server checks each change against the saved record, so a write `can.*` allowed can still be refused — it converges on its own and the platform says so. See use-vibe docs.
|
|
42
43
|
- 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
|