@vibes.diy/prompts 10.8.4 → 11.0.0

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 CHANGED
@@ -31,11 +31,18 @@ type AccessDescriptor = {
31
31
  roles?: Record<roleName, string[]>; // role → channel grants (reduced by union)
32
32
  public?: string[]; // member-public read — any member, no channel grant needed
33
33
  };
34
+ audience?: { followersOf: userHandle } | { mutualsOf: userHandle }; // platform follow-graph reach
34
35
  expiry?: string | number | null; // ISO date or unix seconds
35
36
  allowAnonymous?: boolean; // opt-in for null-user writes
36
37
  };
37
38
  ```
38
39
 
40
+ `audience` is the one field resolved against the PLATFORM follow graph instead of this app's own grants: add it to an otherwise normal result, always keyed on the live writer (`audience: { followersOf: user.userHandle }`), and the platform decides at read time who that reaches. The arm you emit is only a ceiling — the subject's own visibility setting for this app picks the actual rung (off / their followers / anyone at all), and it can shrink that reach but never grow it. The full teaching — which arm, the three-rung ladder, the `useSocial()` / `useInterest()` surfaces, and the copy rules that go with them — is in the social section of the system prompt; don't re-derive it here.
41
+
42
+ ### Person or place: `audience` or `grant`
43
+
44
+ The question that decides which one a doc wants is whose thing it is: **personal content belongs to the PERSON; a contribution to a shared artifact belongs to the PLACE.** Someone's picks, favorites, streaks, or profile items are the person's — give them an `audience` arm keyed on their own handle, so the reach tracks that person's settings and follows them everywhere. Guestbook entries, posts on a communal wall, items on a shared board are the place's — route them to the place's channel with `grant.public` or explicit grants exactly as the rest of this doc shows, unaffected by the author's later profile flips: a guestbook entry doesn't vanish because the signer went private.
45
+
39
46
  ### The ImgGen version-append nuance
40
47
 
41
48
  The platform predicate `ctx.isImgGenVersionAppend(doc, oldDoc)` accepts exactly one legitimate ImgGen version append by a non-author — prior versions/files and all other fields must be unchanged, so a field you didn't anticipate stays author-protected by default; deletes stay author-only. Because `versions`/`currentVersion` are among the version fields it allows, a non-author can also advance the DISPLAYED version (the shared-generation feature); guard `currentVersion` specifically for author-only display control. Write-once docs can simply `if (oldDoc === null) {} else throw`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "10.8.4",
3
+ "version": "11.0.0",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "description": "",
@@ -24,9 +24,9 @@
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
26
  "@adviser/cement": "~0.5.34",
27
- "@vibes.diy/call-ai-v2": "^10.8.4",
28
- "@vibes.diy/identity": "^10.8.4",
29
- "@vibes.diy/use-vibes-types": "^10.8.4",
27
+ "@vibes.diy/call-ai-v2": "^11.0.0",
28
+ "@vibes.diy/identity": "^11.0.0",
29
+ "@vibes.diy/use-vibes-types": "^11.0.0",
30
30
  "arktype": "~2.2.3",
31
31
  "json-schema-faker": "~0.6.2"
32
32
  },
package/system-prompt.md CHANGED
@@ -431,8 +431,28 @@ docs in your database, and never build follow UI state machines.
431
431
  channel and let the item's own audience carry it, keeping each `followersOf`/`mutualsOf` subject
432
432
  `user.userHandle`. Keep at least one real channel; keep PRIVATE data
433
433
  on channels+grant without `audience`; never write channel names starting with `~`.
434
+ - **`followersOf` is a CEILING, not the audience.** The arm your access function emits says who this
435
+ doc may reach at most; WHO actually receives it is the subject's own visibility setting for this
436
+ app, resolved live at read time. Three rungs, per person per app:
437
+ - **not armed** — nobody but the writer. Arming is the master off-switch (see
438
+ `requestFollowersAccess()` below), and it is ON by default for a normal sharing app.
439
+ - **armed, visibility `"public"`** — everyone, signed-out visitors included. Both arms widen to
440
+ everyone at this rung.
441
+ - **armed, visibility `"followers"`** — the graph only: `followersOf` reaches the subject's
442
+ followers, `mutualsOf` only people they follow back. This is the default rung.
443
+
444
+ A subject's own setting can only SHRINK the reach, never grow it past what your arm allows — and a
445
+ privacy-sensitive app (a diary, a personal tracker) never reaches the public rung at all. So pick the arm
446
+ for the widest audience the content could ever legitimately want (`followersOf` for shared picks,
447
+ `mutualsOf` for both-directions-only), emit it the same way in every branch, and let the person's
448
+ own setting choose the rung. Your code never reads the rung to decide what to emit.
434
449
  - Copy: "followers can see your picks" / "people you follow" — never "friends".
435
450
  Following someone is low-stakes (it reveals none of YOUR data) — no confirm dialogs.
451
+ Next to a control that widens reach, say what it actually does: "Anyone can see this while your
452
+ profile is public"; for one that narrows it, "Going private keeps your existing followers" —
453
+ never "removes you from feeds". Turning sharing off, unfollowing, and blocking all mean the other
454
+ side **stops receiving** your new content; never write copy promising their existing copies are
455
+ erased or that they "can no longer see" what they already have.
436
456
  - `useSocial()` also returns `followersEnabled: boolean` (read-only: can THIS app's
437
457
  audience-gated docs currently reach the viewer's followers?) and
438
458
  `requestFollowersAccess(): Promise<void>` (the preflight consent path). For a normal app,
@@ -440,7 +460,7 @@ docs in your database, and never build follow UI state machines.
440
460
  audience UI on it or make users flip a switch first. It is `false` only for the minority:
441
461
  the viewer explicitly turned this app off, chose a never-share standing preference, or the app
442
462
  is marked privacy-sensitive (a diary, a personal tracker), where sharing stays off until a
443
- deliberate opt-in. Show a small "open to followers so followers can see your picks" callout
463
+ deliberate opt-in. Show a small "open to followers so people can see your picks" callout
444
464
  wired to `requestFollowersAccess()` ONLY when `!followersEnabled` (that minority), for an app
445
465
  whose whole point is sharing; for a privacy-sensitive app this is the deliberate-consent
446
466
  prompt. `requestFollowersAccess()` resolves quietly with no state change (including when the
@@ -448,14 +468,33 @@ docs in your database, and never build follow UI state machines.
448
468
  on every render, or more than once per explicit user action. Don't invent a callout an app's
449
469
  own privacy doesn't call for.
450
470
  - For an explicit visibility control (a settings toggle inside your app), `useSocial()` returns
451
- `visibility: "followers" | "private"` (read-only: the effective level for THIS viewer/app —
452
- `followersEnabled` is just `visibility !== "private"`) and
453
- `setVisibility(level): Promise<void>` to change it. `"followers"` means followers can see;
454
- `"private"` turns this app's sharing off for the viewer. Call `setVisibility` only from an
455
- explicit user action (a toggle), then re-render from `visibility`it resolves after the
456
- change lands. (`"public"`/world-readable is not settable yet; don't offer it.) Most apps don't
457
- need this the default already shares with followers; reach for it only when the app's own UX
458
- wants an in-app privacy switch.
471
+ `visibility: "public" | "followers" | "private"` (read-only: the effective level for THIS
472
+ viewer/app — `followersEnabled` is just `visibility !== "private"`) and
473
+ `setVisibility(level): Promise<void>` to change it. `"public"` means anyone can see, follower or
474
+ not, signed in or not; `"followers"` means the graph only (the default); `"private"` turns this
475
+ app's sharing off for the viewer. All three are settable a three-rung control ("Anyone" /
476
+ "Followers" / "Only me") is the right shape for an app built around showing people your stuff.
477
+ The exception is a privacy-sensitive app (a diary, a personal tracker): a NEW `"public"` request
478
+ on one is REFUSED — the level stays exactly where it was, and like every other refusal
479
+ `setVisibility` resolves quietly with no error, so an "Anyone" rung there is a button that
480
+ silently does nothing. On a privacy-sensitive app, leave the "Anyone" rung out (or disable it)
481
+ and offer "Followers" / "Only me" only. (Read side, for completeness: a level stored `"public"`
482
+ before an app became sensitive is capped down to `"followers"` when anyone reads it.) Whichever
483
+ rungs you render, re-render from `visibility` after the await and show what actually took effect,
484
+ never what you asked for. Call `setVisibility` only from an explicit user action (a toggle).
485
+ Most apps don't need this — the default already shares with followers; reach for it only when
486
+ the app's own UX wants an in-app privacy switch.
487
+ - To show ANOTHER person's content on a profile page, hold an interest in them while their page is
488
+ open: `useInterest(handle)` from `use-vibes` (`useInterest(handle)` at the top of the page
489
+ component; a `null`/absent handle is a no-op, so call it unconditionally while the route param
490
+ resolves). Their content streams into this replica while you hold it and stops when the component
491
+ unmounts. It works for signed-out visitors too — a scanned QR code is often first contact — so
492
+ never gate it on being signed in, and never call it for the viewer's own content (you already
493
+ have that). Interest is DELIVERY only: the server hands over exactly what this viewer could
494
+ already read, so it never widens who may see anything, and asking about a private subject simply
495
+ delivers nothing. Dropping it is not revocation — docs already pulled stay put; what stops is
496
+ future delivery. Following is the durable upgrade: a follower's replica keeps pulling with no
497
+ interest held, so present follow as "keep seeing their picks", not as a permission request.
459
498
  - To grow the graph, `useSocial()` returns
460
499
  `invite(email: string): Promise<"ok" | "rate-limited" | "cancelled">` — asks the PLATFORM to
461
500
  email a non-user an invitation to connect with the viewer. The platform shows a consent prompt,