@vibes.diy/prompts 14.3.59 → 14.3.65

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 (160) hide show
  1. package/llms/access.md +4 -0
  2. package/llms/index.d.ts +29 -1
  3. package/llms/index.js +84 -0
  4. package/llms/index.js.map +1 -1
  5. package/llms/pouf-blog.d.ts +2 -0
  6. package/llms/pouf-blog.js +6 -0
  7. package/llms/pouf-blog.js.map +1 -0
  8. package/llms/pouf-blog.md +305 -0
  9. package/llms/pouf-booking.d.ts +2 -0
  10. package/llms/pouf-booking.js +6 -0
  11. package/llms/pouf-booking.js.map +1 -0
  12. package/llms/pouf-booking.md +397 -0
  13. package/llms/pouf-calendar.d.ts +2 -0
  14. package/llms/pouf-calendar.js +6 -0
  15. package/llms/pouf-calendar.js.map +1 -0
  16. package/llms/pouf-calendar.md +352 -0
  17. package/llms/pouf-chat.d.ts +2 -0
  18. package/llms/pouf-chat.js +6 -0
  19. package/llms/pouf-chat.js.map +1 -0
  20. package/llms/pouf-chat.md +220 -0
  21. package/llms/pouf-contact.d.ts +2 -0
  22. package/llms/pouf-contact.js +6 -0
  23. package/llms/pouf-contact.js.map +1 -0
  24. package/llms/pouf-contact.md +231 -0
  25. package/llms/pouf-course.d.ts +2 -0
  26. package/llms/pouf-course.js +6 -0
  27. package/llms/pouf-course.js.map +1 -0
  28. package/llms/pouf-course.md +299 -0
  29. package/llms/pouf-crm.d.ts +2 -0
  30. package/llms/pouf-crm.js +6 -0
  31. package/llms/pouf-crm.js.map +1 -0
  32. package/llms/pouf-crm.md +330 -0
  33. package/llms/pouf-dashboard.d.ts +2 -0
  34. package/llms/pouf-dashboard.js +6 -0
  35. package/llms/pouf-dashboard.js.map +1 -0
  36. package/llms/pouf-dashboard.md +269 -0
  37. package/llms/pouf-editorial.d.ts +2 -0
  38. package/llms/pouf-editorial.js +6 -0
  39. package/llms/pouf-editorial.js.map +1 -0
  40. package/llms/pouf-editorial.md +247 -0
  41. package/llms/pouf-event.d.ts +2 -0
  42. package/llms/pouf-event.js +6 -0
  43. package/llms/pouf-event.js.map +1 -0
  44. package/llms/pouf-event.md +237 -0
  45. package/llms/pouf-feed.d.ts +2 -0
  46. package/llms/pouf-feed.js +6 -0
  47. package/llms/pouf-feed.js.map +1 -0
  48. package/llms/pouf-feed.md +249 -0
  49. package/llms/pouf-game.d.ts +2 -0
  50. package/llms/pouf-game.js +6 -0
  51. package/llms/pouf-game.js.map +1 -0
  52. package/llms/pouf-game.md +273 -0
  53. package/llms/pouf-inbox.d.ts +2 -0
  54. package/llms/pouf-inbox.js +6 -0
  55. package/llms/pouf-inbox.js.map +1 -0
  56. package/llms/pouf-inbox.md +317 -0
  57. package/llms/pouf-inventory.d.ts +2 -0
  58. package/llms/pouf-inventory.js +6 -0
  59. package/llms/pouf-inventory.js.map +1 -0
  60. package/llms/pouf-inventory.md +345 -0
  61. package/llms/pouf-kanban.d.ts +2 -0
  62. package/llms/pouf-kanban.js +6 -0
  63. package/llms/pouf-kanban.js.map +1 -0
  64. package/llms/pouf-kanban.md +235 -0
  65. package/llms/pouf-landing.d.ts +2 -0
  66. package/llms/pouf-landing.js +6 -0
  67. package/llms/pouf-landing.js.map +1 -0
  68. package/llms/pouf-landing.md +222 -0
  69. package/llms/pouf-login.d.ts +2 -0
  70. package/llms/pouf-login.js +6 -0
  71. package/llms/pouf-login.js.map +1 -0
  72. package/llms/pouf-login.md +162 -0
  73. package/llms/pouf-music.d.ts +2 -0
  74. package/llms/pouf-music.js +6 -0
  75. package/llms/pouf-music.js.map +1 -0
  76. package/llms/pouf-music.md +190 -0
  77. package/llms/pouf-onboarding.d.ts +2 -0
  78. package/llms/pouf-onboarding.js +6 -0
  79. package/llms/pouf-onboarding.js.map +1 -0
  80. package/llms/pouf-onboarding.md +227 -0
  81. package/llms/pouf-pricing.d.ts +2 -0
  82. package/llms/pouf-pricing.js +6 -0
  83. package/llms/pouf-pricing.js.map +1 -0
  84. package/llms/pouf-pricing.md +193 -0
  85. package/llms/pouf-profile.d.ts +2 -0
  86. package/llms/pouf-profile.js +6 -0
  87. package/llms/pouf-profile.js.map +1 -0
  88. package/llms/pouf-profile.md +177 -0
  89. package/llms/pouf-quiz.d.ts +2 -0
  90. package/llms/pouf-quiz.js +6 -0
  91. package/llms/pouf-quiz.js.map +1 -0
  92. package/llms/pouf-quiz.md +267 -0
  93. package/llms/pouf-settings.d.ts +2 -0
  94. package/llms/pouf-settings.js +6 -0
  95. package/llms/pouf-settings.js.map +1 -0
  96. package/llms/pouf-settings.md +220 -0
  97. package/llms/pouf-storefront.d.ts +2 -0
  98. package/llms/pouf-storefront.js +6 -0
  99. package/llms/pouf-storefront.js.map +1 -0
  100. package/llms/pouf-storefront.md +293 -0
  101. package/llms/pouf-support.d.ts +2 -0
  102. package/llms/pouf-support.js +6 -0
  103. package/llms/pouf-support.js.map +1 -0
  104. package/llms/pouf-support.md +322 -0
  105. package/llms/pouf-testimonials.d.ts +2 -0
  106. package/llms/pouf-testimonials.js +6 -0
  107. package/llms/pouf-testimonials.js.map +1 -0
  108. package/llms/pouf-testimonials.md +156 -0
  109. package/llms/pouf-todo.d.ts +2 -0
  110. package/llms/pouf-todo.js +6 -0
  111. package/llms/pouf-todo.js.map +1 -0
  112. package/llms/pouf-todo.md +179 -0
  113. package/llms/pouf-weather.d.ts +2 -0
  114. package/llms/pouf-weather.js +6 -0
  115. package/llms/pouf-weather.js.map +1 -0
  116. package/llms/pouf-weather.md +186 -0
  117. package/llms/use-viewer.md +3 -0
  118. package/package.json +5 -4
  119. package/prompts.js +2 -2
  120. package/prompts.js.map +1 -1
  121. package/style-prompts.d.ts +1 -1
  122. package/style-prompts.js +2 -0
  123. package/style-prompts.js.map +1 -1
  124. package/themes/colors/aqua.yaml +79 -0
  125. package/themes/colors/brutalist.yaml +4 -4
  126. package/themes/colors/celery.yaml +79 -0
  127. package/themes/colors/home.yaml +79 -0
  128. package/themes/colors/house.yaml +2 -2
  129. package/themes/colors/lilac.yaml +79 -0
  130. package/themes/colors/mint.yaml +79 -0
  131. package/themes/colors/peach.yaml +79 -0
  132. package/themes/colors/periwinkle.yaml +79 -0
  133. package/themes/colors/rose.yaml +79 -0
  134. package/themes/colorsets-bundle.js +654 -6
  135. package/themes/colorsets-bundle.js.map +1 -1
  136. package/themes/colorsets.d.ts +3 -2
  137. package/themes/colorsets.js +84 -2
  138. package/themes/colorsets.js.map +1 -1
  139. package/themes/index.d.ts +1 -0
  140. package/themes/index.js +1 -0
  141. package/themes/index.js.map +1 -1
  142. package/themes/looks/index.d.ts +3 -1
  143. package/themes/looks/index.js +44 -7
  144. package/themes/looks/index.js.map +1 -1
  145. package/themes/looks/pouf/brief.md +144 -0
  146. package/themes/looks/pouf/catalog.generated.d.ts +16 -0
  147. package/themes/looks/pouf/catalog.generated.js +764 -0
  148. package/themes/looks/pouf/catalog.generated.js.map +1 -0
  149. package/themes/looks/pouf/variety.d.ts +4 -0
  150. package/themes/looks/pouf/variety.js +36 -0
  151. package/themes/looks/pouf/variety.js.map +1 -0
  152. package/themes/palette-gen.d.ts +5 -0
  153. package/themes/palette-gen.js +55 -1
  154. package/themes/palette-gen.js.map +1 -1
  155. package/themes/pouf-rooms.d.ts +47 -0
  156. package/themes/pouf-rooms.js +274 -0
  157. package/themes/pouf-rooms.js.map +1 -0
  158. package/themes/style-presets.d.ts +2 -0
  159. package/themes/style-presets.js +29 -1
  160. package/themes/style-presets.js.map +1 -1
package/llms/access.md CHANGED
@@ -455,6 +455,8 @@ Every item a visitor creates lands on their own `user:<handle>` channel, private
455
455
 
456
456
  **When a user's work is private by default, show it — and offer a way to publish.** If everything a user does routes to a channel only they can read (a per-user `user:<handle>`, a private journal/notes/tracker with no `grant.public`), the UI must say so: a small, persistent "Only you can see this" / "Private to you" cue near their content, so no one wonders who's watching their unfinished work. Then, where sharing fits the app, give them a publish control — but publish at the granularity of a **channel**, not a doc: channels are the unit of read isolation, so adding `grant.public` to the shared `user:<handle>` channel would expose _every_ private item on it, not just the one they meant to share. To publish a single item, route it to its **own** channel and flip only that channel's read-grant from a `visibility` field the access fn reads — exactly the per-item-channel shape the worked example below uses (`const ch = \`entry:${doc._id}\`; const grant = { users: { [user.userHandle]: [ch] } }; if (doc.visibility === "public") grant.public = [ch];`) — for anonymous visitors, or grant a shared app channel for all granted members. Gate the control on `useVibe(dbName).can`, and reflect the result back in the affordance ("Published — anyone can see this", with an unpublish to flip it back). Making the user's _whole_ space public is fine when that's the intent; silently leaking their other private items by publishing one is the trap to avoid. **If the person asked, in their own words, to keep it to themselves — "only I", "just me", "nobody else" — do not add publish/share controls.** A privacy-sensitive domain on its own (a journal, a health tracker) is not that ask: it gets the two controls from _Two controls_ above, off until the person says so. Keep publish opt-in: for only-me apps, omit the publish UI entirely; for share-capable apps, make publish one tap for user-selected items. **Every app that is neither privacy-sensitive nor an only-me ask ships with a sharing path already in it: either an always-on shared channel its records land on, or a publish control.** **An open-nature app — an image or meme wall, a bingo card, a party game — puts its records on an always-on `grant.public` channel from the first rules it ships, builds no publish step, and admits anonymous readers**, so everyone with the link sees what people make the moment they make it. The always-on channel is the least-surprise shape for anything playful and low-stakes (an image or meme wall, a group list, a guestbook): a `grant.public` channel, a members' channel, or an `audience` arm, so a thing someone makes is seen the moment they make it. The publish control is the shape for work people draft first: the per-item channel whose read-grant flips from a `visibility` field, behind one tap. A publish control that names its destination — "Publish to the wall", "Share to the group" — is the best shape of the second, not a requirement. An ordinary app with neither is a bug: every record lands on a channel only its writer can read, and nobody asked for that.
457
457
 
458
+ **A share control is a link only where the record's channel admits anonymous readers; everywhere else it is the person picker, which takes a handle or an email and shows the invite as pending until they arrive.** A link — Copy link, the browser's share sheet, a QR code — is a share only when the person who opens it can read the thing: a record on a `grant.public` channel, always on or flipped by its `visibility` field. A record whose channel is reached only by grants is unreadable through a link, so sharing it means letting a person in: the `HandleInput` picker writes the share doc that grants their handle, exactly as the buddy picker above does, and the same picker takes an email address for someone who is not on the platform yet — it mails them an invite and hands the app an opaque `invite:<id>` subject to store as the invitee, which binds to their handle when they arrive, so the app never holds the address and `ViewerTag` shows them as invited until then and by their handle after. The one link that works on a grant-only thing is the one that lands on a `request` doc: it invites a knock, not a read. An app with a publish control changes the share control's shape with the record's state — the picker before the flip, the link after it.
459
+
458
460
  **Worked example — a shared catalog people track against, with per-thing visibility (a social habit app, a reading challenge, a fitness ladder).** The catalog items are public objects anyone proposes; each person's progress is their own; and each person chooses — _once per item, never per entry_ — whether their streak is public (on the leaderboard) or buddy-only. The visibility choice lives on a per-`(person, item)` **tracking** record that sets the read-grant the entries routed to it inherit.
459
461
 
460
462
  ### `access.js`
@@ -977,6 +979,8 @@ const addCard = async (text, boardId = `default-${me.userHandle}`) => {
977
979
  {canInvite(board._id) && <HandleInput onChange={(h) => addMember(board._id, h)} placeholder="Add a friend…" />}
978
980
  ```
979
981
 
982
+ An email typed into that `HandleInput` reaches `addMember` as an opaque `invite:<id>` subject, stored as `userHandle` like any handle, and `ViewerTag` shows it as invited until they arrive.
983
+
980
984
  App.jsx — the same board list serves the creator and the invited recipient. `BoardContents` uses `board._id` for its content queries, calls `addCard(text, board._id)` and `addMember(board._id, handle)`, and gates its write controls with `can`. Pass the `ensureDefaultBoard` helper above into `BoardWorkspace`; its creation action stays reachable with an empty readable list and selects the person's new board after saving it.
981
985
 
982
986
  ### `App.jsx`
package/llms/index.d.ts CHANGED
@@ -17,6 +17,34 @@ export { connectionsConfig } from "./connections.js";
17
17
  export { youtubeConfig } from "./youtube.js";
18
18
  export { blueskyConfig } from "./bluesky.js";
19
19
  export { spotifyConfig } from "./spotify.js";
20
+ export { poufTodoConfig } from "./pouf-todo.js";
21
+ export { poufFeedConfig } from "./pouf-feed.js";
22
+ export { poufChatConfig } from "./pouf-chat.js";
23
+ export { poufKanbanConfig } from "./pouf-kanban.js";
24
+ export { poufInboxConfig } from "./pouf-inbox.js";
25
+ export { poufSupportConfig } from "./pouf-support.js";
26
+ export { poufCrmConfig } from "./pouf-crm.js";
27
+ export { poufInventoryConfig } from "./pouf-inventory.js";
28
+ export { poufEditorialConfig } from "./pouf-editorial.js";
29
+ export { poufEventConfig } from "./pouf-event.js";
30
+ export { poufCourseConfig } from "./pouf-course.js";
31
+ export { poufLoginConfig } from "./pouf-login.js";
32
+ export { poufSettingsConfig } from "./pouf-settings.js";
33
+ export { poufOnboardingConfig } from "./pouf-onboarding.js";
34
+ export { poufContactConfig } from "./pouf-contact.js";
35
+ export { poufStorefrontConfig } from "./pouf-storefront.js";
36
+ export { poufBookingConfig } from "./pouf-booking.js";
37
+ export { poufDashboardConfig } from "./pouf-dashboard.js";
38
+ export { poufGameConfig } from "./pouf-game.js";
39
+ export { poufPricingConfig } from "./pouf-pricing.js";
40
+ export { poufBlogConfig } from "./pouf-blog.js";
41
+ export { poufProfileConfig } from "./pouf-profile.js";
42
+ export { poufMusicConfig } from "./pouf-music.js";
43
+ export { poufWeatherConfig } from "./pouf-weather.js";
44
+ export { poufQuizConfig } from "./pouf-quiz.js";
45
+ export { poufLandingConfig } from "./pouf-landing.js";
46
+ export { poufCalendarConfig } from "./pouf-calendar.js";
47
+ export { poufTestimonialsConfig } from "./pouf-testimonials.js";
20
48
  export type { LlmConfig } from "./types.js";
21
- export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
49
+ export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
22
50
  export declare const FAST_SKILLS: readonly ["callai", "image-gen", "fireproof", "three-js", "d3", "webxr", "calendar", "spotify"];
package/llms/index.js CHANGED
@@ -17,6 +17,34 @@ import { connectionsConfig } from "./connections.js";
17
17
  import { youtubeConfig } from "./youtube.js";
18
18
  import { blueskyConfig } from "./bluesky.js";
19
19
  import { spotifyConfig } from "./spotify.js";
20
+ import { poufTodoConfig } from "./pouf-todo.js";
21
+ import { poufFeedConfig } from "./pouf-feed.js";
22
+ import { poufChatConfig } from "./pouf-chat.js";
23
+ import { poufKanbanConfig } from "./pouf-kanban.js";
24
+ import { poufInboxConfig } from "./pouf-inbox.js";
25
+ import { poufSupportConfig } from "./pouf-support.js";
26
+ import { poufCrmConfig } from "./pouf-crm.js";
27
+ import { poufInventoryConfig } from "./pouf-inventory.js";
28
+ import { poufEditorialConfig } from "./pouf-editorial.js";
29
+ import { poufEventConfig } from "./pouf-event.js";
30
+ import { poufCourseConfig } from "./pouf-course.js";
31
+ import { poufLoginConfig } from "./pouf-login.js";
32
+ import { poufSettingsConfig } from "./pouf-settings.js";
33
+ import { poufOnboardingConfig } from "./pouf-onboarding.js";
34
+ import { poufContactConfig } from "./pouf-contact.js";
35
+ import { poufStorefrontConfig } from "./pouf-storefront.js";
36
+ import { poufBookingConfig } from "./pouf-booking.js";
37
+ import { poufDashboardConfig } from "./pouf-dashboard.js";
38
+ import { poufGameConfig } from "./pouf-game.js";
39
+ import { poufPricingConfig } from "./pouf-pricing.js";
40
+ import { poufBlogConfig } from "./pouf-blog.js";
41
+ import { poufProfileConfig } from "./pouf-profile.js";
42
+ import { poufMusicConfig } from "./pouf-music.js";
43
+ import { poufWeatherConfig } from "./pouf-weather.js";
44
+ import { poufQuizConfig } from "./pouf-quiz.js";
45
+ import { poufLandingConfig } from "./pouf-landing.js";
46
+ import { poufCalendarConfig } from "./pouf-calendar.js";
47
+ import { poufTestimonialsConfig } from "./pouf-testimonials.js";
20
48
  export { backendConfig } from "./backend.js";
21
49
  export { calendarConfig } from "./calendar.js";
22
50
  export { callaiConfig } from "./callai.js";
@@ -36,6 +64,34 @@ export { connectionsConfig } from "./connections.js";
36
64
  export { youtubeConfig } from "./youtube.js";
37
65
  export { blueskyConfig } from "./bluesky.js";
38
66
  export { spotifyConfig } from "./spotify.js";
67
+ export { poufTodoConfig } from "./pouf-todo.js";
68
+ export { poufFeedConfig } from "./pouf-feed.js";
69
+ export { poufChatConfig } from "./pouf-chat.js";
70
+ export { poufKanbanConfig } from "./pouf-kanban.js";
71
+ export { poufInboxConfig } from "./pouf-inbox.js";
72
+ export { poufSupportConfig } from "./pouf-support.js";
73
+ export { poufCrmConfig } from "./pouf-crm.js";
74
+ export { poufInventoryConfig } from "./pouf-inventory.js";
75
+ export { poufEditorialConfig } from "./pouf-editorial.js";
76
+ export { poufEventConfig } from "./pouf-event.js";
77
+ export { poufCourseConfig } from "./pouf-course.js";
78
+ export { poufLoginConfig } from "./pouf-login.js";
79
+ export { poufSettingsConfig } from "./pouf-settings.js";
80
+ export { poufOnboardingConfig } from "./pouf-onboarding.js";
81
+ export { poufContactConfig } from "./pouf-contact.js";
82
+ export { poufStorefrontConfig } from "./pouf-storefront.js";
83
+ export { poufBookingConfig } from "./pouf-booking.js";
84
+ export { poufDashboardConfig } from "./pouf-dashboard.js";
85
+ export { poufGameConfig } from "./pouf-game.js";
86
+ export { poufPricingConfig } from "./pouf-pricing.js";
87
+ export { poufBlogConfig } from "./pouf-blog.js";
88
+ export { poufProfileConfig } from "./pouf-profile.js";
89
+ export { poufMusicConfig } from "./pouf-music.js";
90
+ export { poufWeatherConfig } from "./pouf-weather.js";
91
+ export { poufQuizConfig } from "./pouf-quiz.js";
92
+ export { poufLandingConfig } from "./pouf-landing.js";
93
+ export { poufCalendarConfig } from "./pouf-calendar.js";
94
+ export { poufTestimonialsConfig } from "./pouf-testimonials.js";
39
95
  export const allConfigs = [
40
96
  callaiConfig,
41
97
  imageGenConfig,
@@ -56,6 +112,34 @@ export const allConfigs = [
56
112
  youtubeConfig,
57
113
  blueskyConfig,
58
114
  spotifyConfig,
115
+ poufTodoConfig,
116
+ poufFeedConfig,
117
+ poufChatConfig,
118
+ poufKanbanConfig,
119
+ poufInboxConfig,
120
+ poufSupportConfig,
121
+ poufCrmConfig,
122
+ poufInventoryConfig,
123
+ poufEditorialConfig,
124
+ poufEventConfig,
125
+ poufCourseConfig,
126
+ poufLoginConfig,
127
+ poufSettingsConfig,
128
+ poufOnboardingConfig,
129
+ poufContactConfig,
130
+ poufStorefrontConfig,
131
+ poufBookingConfig,
132
+ poufDashboardConfig,
133
+ poufGameConfig,
134
+ poufPricingConfig,
135
+ poufBlogConfig,
136
+ poufProfileConfig,
137
+ poufMusicConfig,
138
+ poufWeatherConfig,
139
+ poufQuizConfig,
140
+ poufLandingConfig,
141
+ poufCalendarConfig,
142
+ poufTestimonialsConfig,
59
143
  ];
60
144
  export const FAST_SKILLS = ["callai", "image-gen", "fireproof", "three-js", "d3", "webxr", "calendar", "spotify"];
61
145
  //# sourceMappingURL=index.js.map
package/llms/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7C,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,QAAQ;IACR,aAAa;IACb,QAAQ;IACR,WAAW;IACX,eAAe;IACf,WAAW;IACX,eAAe;IACf,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,cAAc;IACd,YAAY;IACZ,iBAAiB;IACjB,aAAa;IACb,aAAa;IACb,aAAa;CACL,CAAC;AAaX,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,CAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { LlmConfig } from "./types.js";
2
+ export declare const poufBlogConfig: LlmConfig;
@@ -0,0 +1,6 @@
1
+ export const poufBlogConfig = {
2
+ name: "pouf-blog",
3
+ label: "Pouf blog block",
4
+ description: "Blog with a featured post, category filter, pages and a writer",
5
+ };
6
+ //# sourceMappingURL=pouf-blog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pouf-blog.js","sourceRoot":"","sources":["../../jsr/llms/pouf-blog.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,cAAc,GAAc;IACvC,IAAI,EAAE,WAAW;IACjB,KAAK,EAAE,iBAAiB;IACxB,WAAW,EAAE,gEAAgE;CAC9E,CAAC"}
@@ -0,0 +1,305 @@
1
+ # Pouf Blog
2
+
3
+ A blog with a front page and a reading view: a bakery's recipe notes, a travel journal, a club's match reports, a studio's news. Reach for it whenever the app's core is posts written by a few people and read by many, newest first, sorted into a handful of categories. Each post is its own document, so the blog is there on the next visit and on every device, and a new post appears for every reader as soon as it is published.
4
+
5
+ Built from @vibes.diy/pouf components; persisted state lives in Fireproof. Every write surface is gated on `useVibe(dbName).can`.
6
+
7
+ ## The block
8
+
9
+ Two views, one job each. The front page opens with an `Eyebrow`, the `Heading` and a muted post count beside a `Write a post` button, the front page's one lead, which opens a `Dialog` with the writing form (title, category, body, each in its own `Field` with its own error). The newest post leads on a `Card` laid out with `Grid cols="sidebar"` beside a decorative `orange` `Blob`; under it a `Segmented` category filter (`tone="info"`) narrows a two-column `Grid` of post `Card`s, each with an `info` category `Badge`, the title, an excerpt in muted `Text`, the writer as a `ViewerTag` with a muted date, and a quiet `Read` button; `Pagination` pages through them and an `Empty` stands in when a category has nothing. Opening a post swaps in the reading view: a quiet `IconButton` back, the title as the `Heading`, the byline and the body as `Text` paragraphs, with a quiet `Delete post` (toned `down`) behind a `Confirm` wearing `tone="down"`, both showing `loading` while the delete lands. Every write waits on its `can` verdict from `useVibe("blog")`, the database the posts live in: the write button gives way to a `Skeleton` inside an `aria-busy` wrapper while the verdict resolves, to a quiet `Sign in to write` wired to `useViewer().requestLogin()` for a visitor who has not signed in, and to a sentence in the app's own words when writing is closed; `Delete post` renders only where `can.delete(post).ok`.
10
+
11
+ ### `App.jsx`
12
+ ```jsx
13
+ import React, { useState } from "react";
14
+ import { useFireproof } from "use-fireproof";
15
+ import { useViewer, useVibe } from "use-vibes";
16
+ import {
17
+ Badge,
18
+ Blob,
19
+ Button,
20
+ Card,
21
+ Empty,
22
+ Eyebrow,
23
+ Field,
24
+ Grid,
25
+ Heading,
26
+ Icon,
27
+ IconButton,
28
+ Input,
29
+ Pagination,
30
+ Row,
31
+ Segmented,
32
+ Skeleton,
33
+ Stack,
34
+ Text,
35
+ Textarea,
36
+ } from "@vibes.diy/pouf";
37
+ import { Confirm, Dialog, Select, Toaster, toast } from "@vibes.diy/pouf/interactive";
38
+
39
+ const CATEGORIES = [
40
+ { value: "recipes", label: "Recipes" },
41
+ { value: "news", label: "News" },
42
+ { value: "behind", label: "Behind the counter" },
43
+ ];
44
+ const FILTERS = [{ value: "all", label: "All" }, ...CATEGORIES];
45
+ const PAGE_SIZE = 4;
46
+ const labelOf = (category) => CATEGORIES.find((c) => c.value === category)?.label ?? category;
47
+ const excerpt = (body) => (body.length > 160 ? `${body.slice(0, 157).trimEnd()}…` : body);
48
+ const day = (ms) => new Date(ms).toLocaleDateString(undefined, { month: "short", day: "numeric" });
49
+ const EMPTY_DRAFT = { title: "", category: "recipes", body: "" };
50
+
51
+ function Byline({ post }) {
52
+ const { ViewerTag } = useViewer();
53
+ return (
54
+ <Row gap={2} wrap={false}>
55
+ <ViewerTag userHandle={post.authorHandle} />
56
+ <Text size="sm" muted>
57
+ {day(post.createdAt)}
58
+ </Text>
59
+ </Row>
60
+ );
61
+ }
62
+
63
+ export default function App() {
64
+ const { database, useLiveQuery } = useFireproof("blog");
65
+ const { docs } = useLiveQuery("type", { key: "post" });
66
+ const { can, me } = useVibe("blog");
67
+ const { requestLogin } = useViewer();
68
+ const posts = [...docs].sort((a, b) => b.createdAt - a.createdAt);
69
+
70
+ const [filter, setFilter] = useState("all");
71
+ const [page, setPage] = useState(1);
72
+ const [openId, setOpenId] = useState(undefined);
73
+ const [writing, setWriting] = useState(false);
74
+ const [draft, setDraft] = useState(EMPTY_DRAFT);
75
+ const [errors, setErrors] = useState({});
76
+ const [publishing, setPublishing] = useState(false);
77
+ const [removing, setRemoving] = useState(false);
78
+
79
+ const featured = posts[0];
80
+ const rest = posts.slice(1).filter((p) => filter === "all" || p.category === filter);
81
+ const pages = Math.max(1, Math.ceil(rest.length / PAGE_SIZE));
82
+ // Clamped, so a deleted post can't strand the reader on a page that no longer exists.
83
+ const current = Math.min(page, pages);
84
+ const shown = rest.slice((current - 1) * PAGE_SIZE, current * PAGE_SIZE);
85
+ const open = posts.find((p) => p._id === openId);
86
+
87
+ // The verdict for the doc publish writes, carrying the same fields it stamps.
88
+ const canWrite = can.create({ type: "post", authorHandle: me?.userHandle });
89
+
90
+ function edit(field, value) {
91
+ setDraft((d) => ({ ...d, [field]: value }));
92
+ if (errors[field]) setErrors((e) => ({ ...e, [field]: undefined }));
93
+ }
94
+
95
+ function pick(next) {
96
+ setFilter(next);
97
+ // A stale page number under a shorter filtered list reads as broken pagination.
98
+ setPage(1);
99
+ }
100
+
101
+ async function publish(e) {
102
+ e.preventDefault();
103
+ const next = {};
104
+ if (draft.title.trim() === "") next.title = "Give the post a title.";
105
+ if (draft.body.trim() === "") next.body = "Write the post itself.";
106
+ setErrors(next);
107
+ if (Object.keys(next).length > 0) return;
108
+ setPublishing(true);
109
+ try {
110
+ await database.put({ type: "post", title: draft.title.trim(), category: draft.category, body: draft.body.trim(), authorHandle: me?.userHandle, createdAt: Date.now() });
111
+ // Cleared only once the write lands; the filter resets so the new post is in view.
112
+ setDraft(EMPTY_DRAFT);
113
+ setWriting(false);
114
+ pick("all");
115
+ toast.success("Post published");
116
+ } finally {
117
+ setPublishing(false);
118
+ }
119
+ }
120
+
121
+ async function remove(post) {
122
+ setRemoving(true);
123
+ try {
124
+ await database.del(post._id);
125
+ setOpenId(undefined);
126
+ toast.success("Post deleted");
127
+ } finally {
128
+ setRemoving(false);
129
+ }
130
+ }
131
+
132
+ if (open) {
133
+ return (
134
+ <main className="mx-auto max-w-2xl px-4 py-8">
135
+ <Stack gap={5}>
136
+ <Row gap={2}>
137
+ <IconButton icon={<Icon name="prev" size="sm" />} label="Back to all posts" onClick={() => setOpenId(undefined)} />
138
+ <Badge tone="info">{labelOf(open.category)}</Badge>
139
+ </Row>
140
+ <Stack gap={3}>
141
+ <Heading level={1}>{open.title}</Heading>
142
+ <Byline post={open} />
143
+ </Stack>
144
+ <Stack gap={3}>
145
+ {open.body.split(/\n\s*\n/).map((para, i) => (
146
+ <Text key={i}>{para}</Text>
147
+ ))}
148
+ </Stack>
149
+ {can.delete(open).ok && (
150
+ <Row>
151
+ <Confirm
152
+ title="Delete this post?"
153
+ body="The post is removed for every reader. This can't be undone."
154
+ confirmLabel="Delete post"
155
+ cancelLabel="Keep it"
156
+ tone="down"
157
+ loading={removing}
158
+ onConfirm={() => remove(open)}
159
+ >
160
+ <Button variant="quiet" tone="down" loading={removing}>
161
+ Delete post
162
+ </Button>
163
+ </Confirm>
164
+ </Row>
165
+ )}
166
+ </Stack>
167
+ <div className="pouf-toasts">
168
+ <Toaster />
169
+ </div>
170
+ </main>
171
+ );
172
+ }
173
+
174
+ let writeAction;
175
+ if (canWrite.pending)
176
+ writeAction = (
177
+ <div aria-busy="true">
178
+ <Skeleton variant="text" />
179
+ </div>
180
+ );
181
+ else if (!me)
182
+ writeAction = (
183
+ <Button variant="quiet" onClick={requestLogin}>
184
+ Sign in to write
185
+ </Button>
186
+ );
187
+ else if (!canWrite.ok) writeAction = <Text muted>Posts here are written by the bakery team.</Text>;
188
+ else
189
+ writeAction = (
190
+ <Dialog title="Write a post" open={writing} onOpenChange={setWriting} trigger={<Button>Write a post</Button>}>
191
+ <form onSubmit={publish} noValidate>
192
+ <Stack gap={3}>
193
+ <Field label="Title" error={errors.title}>
194
+ {(id, describedBy) => (
195
+ <Input id={id} describedBy={describedBy} value={draft.title} onChange={(v) => edit("title", v)} placeholder="Our sourdough, step by step" invalid={errors.title !== undefined} />
196
+ )}
197
+ </Field>
198
+ <Field label="Category">
199
+ {(id, describedBy) => <Select id={id} describedBy={describedBy} value={draft.category} onChange={(v) => edit("category", v)} options={CATEGORIES} />}
200
+ </Field>
201
+ <Field label="Post" hint="Leave a blank line between paragraphs." error={errors.body}>
202
+ {(id, describedBy) => (
203
+ <Textarea id={id} describedBy={describedBy} value={draft.body} onChange={(v) => edit("body", v)} rows={8} invalid={errors.body !== undefined} />
204
+ )}
205
+ </Field>
206
+ <Row justify="end">
207
+ <Button type="submit" loading={publishing}>
208
+ Publish post
209
+ </Button>
210
+ </Row>
211
+ </Stack>
212
+ </form>
213
+ </Dialog>
214
+ );
215
+
216
+ return (
217
+ <main className="mx-auto max-w-5xl px-4 py-8">
218
+ <Stack gap={6}>
219
+ <Row justify="between">
220
+ <Stack gap={1}>
221
+ <Eyebrow>From the bakery</Eyebrow>
222
+ <Heading level={1}>Notes from the oven</Heading>
223
+ <Text muted>{posts.length === 1 ? "1 post" : `${posts.length} posts`}</Text>
224
+ </Stack>
225
+ {writeAction}
226
+ </Row>
227
+
228
+ {featured === undefined ? (
229
+ <Empty icon="draft" title="No posts yet">
230
+ The first post you publish leads the page here.
231
+ </Empty>
232
+ ) : (
233
+ <>
234
+ <Card>
235
+ <Grid cols="sidebar">
236
+ <Stack gap={3}>
237
+ <Row>
238
+ <Badge tone="info">{labelOf(featured.category)}</Badge>
239
+ </Row>
240
+ <Heading level={2}>{featured.title}</Heading>
241
+ <Text muted>{excerpt(featured.body)}</Text>
242
+ <Byline post={featured} />
243
+ <Row>
244
+ <Button variant="quiet" onClick={() => setOpenId(featured._id)}>
245
+ Read the latest
246
+ </Button>
247
+ </Row>
248
+ </Stack>
249
+ <Blob icon="sparkle" tone="orange" size="lg" />
250
+ </Grid>
251
+ </Card>
252
+
253
+ <Segmented label="Category" tone="info" value={filter} onChange={pick} options={FILTERS} />
254
+
255
+ {shown.length === 0 ? (
256
+ <Empty icon="search" title="Nothing here yet">
257
+ {filter === "all" ? "Older posts collect here once there are a few." : "Pick another category to see the rest."}
258
+ </Empty>
259
+ ) : (
260
+ <Grid cols={2}>
261
+ {shown.map((post) => (
262
+ <Card key={post._id} variant="tight">
263
+ <Stack gap={3}>
264
+ <Row>
265
+ <Badge tone="info">{labelOf(post.category)}</Badge>
266
+ </Row>
267
+ <Heading level={3}>{post.title}</Heading>
268
+ <Text size="sm" muted>
269
+ {excerpt(post.body)}
270
+ </Text>
271
+ <Byline post={post} />
272
+ <Row>
273
+ <Button size="sm" variant="quiet" onClick={() => setOpenId(post._id)}>
274
+ Read
275
+ </Button>
276
+ </Row>
277
+ </Stack>
278
+ </Card>
279
+ ))}
280
+ </Grid>
281
+ )}
282
+
283
+ <Row justify="center">
284
+ <Pagination page={current} total={pages} onChange={setPage} />
285
+ </Row>
286
+ </>
287
+ )}
288
+ </Stack>
289
+ <div className="pouf-toasts">
290
+ <Toaster />
291
+ </div>
292
+ </main>
293
+ );
294
+ }
295
+ ```
296
+
297
+ ## Adapting
298
+
299
+ - Swap `CATEGORIES` for the blog's own sections; the filter and the form's `Select` both read from it, so a new section appears in both. Keep the category `Badge`s on one neutral tone (`info`): a category is a label, not a verdict.
300
+ - Keep one document per post with its `authorHandle`; the byline is always a `ViewerTag`, never a typed name.
301
+ - A cover photo is a `_files` attachment on the post, shown with `Figure` in place of the featured `Blob`; drafts that aren't ready yet are a `status` field filtered out of the front page (the editorial block walks posts through that pipeline).
302
+ - Editing a post is the same form, prefilled, writing behind `can.edit(post)`; keep `Delete post` quiet and behind its `Confirm`.
303
+ - A new write surface gets its own verdict: `can.create(draft)` with the fields the write stamps, `can.edit(doc)` or `can.delete(doc)` on the document it changes, always on `useVibe` of the database it writes.
304
+
305
+ Adapted from 1st-Pouf (MIT) https://1st-pouf.worksonmy.dev/r/blog.json
@@ -0,0 +1,2 @@
1
+ import type { LlmConfig } from "./types.js";
2
+ export declare const poufBookingConfig: LlmConfig;
@@ -0,0 +1,6 @@
1
+ export const poufBookingConfig = {
2
+ name: "pouf-booking",
3
+ label: "Pouf booking block",
4
+ description: "Appointment booking: pick a service, day and free slot, confirm",
5
+ };
6
+ //# sourceMappingURL=pouf-booking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pouf-booking.js","sourceRoot":"","sources":["../../jsr/llms/pouf-booking.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EAAE,iEAAiE;CAC/E,CAAC"}