@vibes.diy/prompts 14.1.37 → 14.1.38

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.
Files changed (2) hide show
  1. package/llms/backend.md +81 -0
  2. package/package.json +4 -4
package/llms/backend.md CHANGED
@@ -842,6 +842,87 @@ And **only say a thing you computed.** "Tonight", "this morning", "in an hour"
842
842
  are claims about the reader's clock; if the code did not derive them from that
843
843
  reader's zone, name the moment instead and let them place it.
844
844
 
845
+ ### Recurring reminders on somebody's clock
846
+
847
+ **A recurring reminder fires on each person's own clock, never the server's.**
848
+ Once a reminder repeats — "every weekday at 7" — the zone stops being one
849
+ setting on one schedule document and becomes a property of each person the
850
+ reminder is for. Everything below is per person, and none of it is the
851
+ server's day or the server's hour.
852
+
853
+ **Capture the zone as an effect, not as a question.** Do not ask them for a
854
+ timezone: the browser already knows it, so write it once when they first sign
855
+ in and leave it alone. Store it where the reminder is addressed — on the
856
+ person's own profile document when the list is shared — and guard the write so
857
+ it only ever happens on a real change, or the effect re-fires forever.
858
+
859
+ **Derive the weekday and the hour together, in that zone, from one
860
+ `formatToParts` call.** `getUTCDay()`/`getUTCHours()` answer a question about
861
+ the machine the handler happens to be running on; at 07:00 in Los Angeles it is
862
+ already a different day in UTC, so a weekday gate on the server's clock silently
863
+ drops Monday mornings and fires on Saturday ones.
864
+
865
+ **An unrecognised zone string throws.** `Intl.DateTimeFormat` raises a
866
+ `RangeError` on a zone it does not know, so in a loop over people the formatting
867
+ goes in a `try` and a bad profile is skipped — one stale record must not stop
868
+ everybody else's reminder.
869
+
870
+ The unit of work is that person's local date (`en-CA` formats it as
871
+ `YYYY-MM-DD`), keyed per person, per "A tick that repeats is not a run that
872
+ repeats" above: the interval is only how often the handler wakes up.
873
+
874
+ App.jsx — write the zone once, when they arrive:
875
+
876
+ ```jsx
877
+ useEffect(() => {
878
+ if (!me?.userHandle) return;
879
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
880
+ if (!timeZone || myProfile?.timeZone === timeZone) return; // already stored — don't churn
881
+ database.put({ _id: `profile:${me.userHandle}`, type: "profile", userHandle: me.userHandle, timeZone });
882
+ }, [me?.userHandle, myProfile?.timeZone]);
883
+ ```
884
+
885
+ backend.js — evaluate each person in their own zone:
886
+
887
+ ```js
888
+ export const config = { scheduled: { interval: "15m" } };
889
+ const WEEKDAYS = new Set(["Mon", "Tue", "Wed", "Thu", "Fri"]);
890
+
891
+ export async function scheduled(event, ctx) {
892
+ const at = new Date(event.scheduledTime);
893
+ const profiles = await ctx.db.query({ db: DB, field: "type", key: "profile", limit: 500 });
894
+ ctx.log("debug", "reminder tick", { profiles: profiles.length });
895
+
896
+ for (const profile of profiles) {
897
+ const { timeZone, userHandle } = profile;
898
+ if (!timeZone || !userHandle) continue;
899
+ let weekday, hour, dateKey;
900
+ try {
901
+ // One formatter, one zone: the weekday and the hour must agree about the day.
902
+ const parts = new Intl.DateTimeFormat("en-US", {
903
+ timeZone,
904
+ weekday: "short",
905
+ hour: "2-digit",
906
+ hourCycle: "h23",
907
+ }).formatToParts(at);
908
+ weekday = parts.find((p) => p.type === "weekday")?.value;
909
+ hour = Number(parts.find((p) => p.type === "hour")?.value);
910
+ dateKey = new Intl.DateTimeFormat("en-CA", { timeZone }).format(at); // "2026-09-18", their day
911
+ } catch {
912
+ continue; // unrecognised zone on one profile — skip the person, not the tick
913
+ }
914
+ if (!WEEKDAYS.has(weekday) || hour !== 7) continue;
915
+
916
+ const id = `morningReminder:${userHandle}:${dateKey}`; // the unit of work, per person
917
+ if (await ctx.db.get(id, { db: DB })) continue; // this morning is already done
918
+ await ctx.db.put(
919
+ { _id: id, type: "morningReminder", ownerHandle: userHandle, date: dateKey, checkedAt: at.toISOString() },
920
+ { db: DB }
921
+ );
922
+ }
923
+ }
924
+ ```
925
+
845
926
  ### An unattended tick reports what it did
846
927
 
847
928
  **An unattended tick earns its spend.** Nobody is watching a scheduled run, so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.1.37",
3
+ "version": "14.1.38",
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.37",
38
- "@vibes.diy/identity": "14.1.37",
39
- "@vibes.diy/use-vibes-types": "14.1.37",
37
+ "@vibes.diy/call-ai-v2": "14.1.38",
38
+ "@vibes.diy/identity": "14.1.38",
39
+ "@vibes.diy/use-vibes-types": "14.1.38",
40
40
  "arktype": "~2.2.3",
41
41
  "json-schema-faker": "~0.6.3"
42
42
  },