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