@stacksjs/defaults 0.75.318 → 0.75.321

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 (252) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +8 -0
  2. package/ai/skills/stacks-browse/scripts/browse.ts +16 -6
  3. package/ai/skills/stacks-browse/scripts/chrome.ts +4 -0
  4. package/ai/skills/stacks-database/SKILL.md +1 -1
  5. package/ai/skills/stacks-desktop/SKILL.md +23 -0
  6. package/ai/skills/stacks-migrations/SKILL.md +2 -2
  7. package/ai/skills/stacks-native/CATALOG.md +8 -8
  8. package/app/Actions/Cms/AuthorUpdateAction.ts +4 -4
  9. package/app/Actions/Cms/CategorizableUpdateAction.ts +4 -4
  10. package/app/Actions/Cms/PostUpdateAction.ts +4 -5
  11. package/app/Actions/Cms/TaggableUpdateAction.ts +4 -2
  12. package/app/Actions/Commerce/CustomerStoreAction.ts +4 -6
  13. package/app/Actions/Commerce/CustomerUpdateAction.ts +4 -6
  14. package/app/Actions/Commerce/PaymentStoreAction.ts +5 -8
  15. package/app/Actions/Commerce/PaymentUpdateAction.ts +20 -17
  16. package/app/Actions/Dashboard/Commerce/commerce-product-records.test.ts +7 -0
  17. package/app/Actions/Dashboard/Commerce/commerce-product-records.ts +14 -3
  18. package/app/Actions/Dashboard/Commerce/review-records.test.ts +12 -0
  19. package/app/Actions/Dashboard/Commerce/review-records.ts +10 -5
  20. package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +2 -0
  21. package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +4 -1
  22. package/app/Actions/Dashboard/Content/PageStoreAction.ts +2 -0
  23. package/app/Actions/Dashboard/Content/PostStoreAction.ts +2 -0
  24. package/app/Actions/Dashboard/Content/TagStoreAction.ts +2 -0
  25. package/app/Actions/Dashboard/Content/content-input.ts +9 -4
  26. package/app/Actions/Dashboard/Content/content-record-input.test.ts +20 -1
  27. package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +1 -1
  28. package/app/Actions/Dashboard/Marketing/ListIndexAction.ts +1 -1
  29. package/app/Actions/Dashboard/Models/ModelStoreAction.ts +5 -1
  30. package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +5 -1
  31. package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +1 -1
  32. package/app/Actions/Storefront/AddToCartAction.ts +5 -2
  33. package/app/Actions/Storefront/PlaceOrderAction.ts +6 -11
  34. package/app/Actions/Storefront/SubmitReviewAction.ts +6 -5
  35. package/app/Models/Activity.ts +1 -0
  36. package/app/Models/AnalyticsEvent.ts +1 -0
  37. package/app/Models/AutomationRun.ts +1 -1
  38. package/app/Models/BoardColumn.ts +0 -1
  39. package/app/Models/Campaign.ts +1 -1
  40. package/app/Models/CampaignSend.ts +3 -3
  41. package/app/Models/Card.ts +0 -1
  42. package/app/Models/CardComment.ts +0 -2
  43. package/app/Models/Content/Author.ts +2 -0
  44. package/app/Models/Content/Categorizable.ts +1 -0
  45. package/app/Models/Content/Menu.ts +2 -0
  46. package/app/Models/Content/MenuItem.ts +2 -0
  47. package/app/Models/Content/PageRevision.ts +2 -0
  48. package/app/Models/Content/Post.ts +2 -0
  49. package/app/Models/Content/Redirect.ts +2 -0
  50. package/app/Models/EmailListSubscriber.ts +2 -2
  51. package/app/Models/Error.ts +5 -0
  52. package/app/Models/Forms/Form.ts +2 -0
  53. package/app/Models/Forms/FormField.ts +2 -0
  54. package/app/Models/Forms/FormSubmission.ts +2 -0
  55. package/app/Models/Label.ts +0 -1
  56. package/app/Models/Log.ts +3 -1
  57. package/app/Models/MagicLinkToken.ts +0 -7
  58. package/app/Models/PaymentProduct.ts +1 -0
  59. package/app/Models/PaymentTransaction.ts +1 -0
  60. package/app/Models/Release.ts +3 -0
  61. package/app/Models/Request.ts +1 -0
  62. package/app/Models/SenderDomain.ts +2 -0
  63. package/app/Models/SmsOptOut.ts +0 -7
  64. package/app/Models/Subscription.ts +2 -0
  65. package/app/Models/commerce/Auction.ts +1 -0
  66. package/app/Models/commerce/AuctionItem.ts +1 -0
  67. package/app/Models/commerce/Cart.ts +83 -2
  68. package/app/Models/commerce/Category.ts +5 -1
  69. package/app/Models/commerce/Coupon.ts +4 -1
  70. package/app/Models/commerce/DigitalDelivery.ts +1 -0
  71. package/app/Models/commerce/GiftCard.ts +1 -0
  72. package/app/Models/commerce/LoyaltyPoint.ts +1 -0
  73. package/app/Models/commerce/LoyaltyReward.ts +3 -1
  74. package/app/Models/commerce/Order.ts +4 -1
  75. package/app/Models/commerce/OrderItem.ts +1 -0
  76. package/app/Models/commerce/Payment.ts +1 -0
  77. package/app/Models/commerce/Product.ts +15 -0
  78. package/app/Models/commerce/ProductVariant.ts +2 -0
  79. package/app/Models/commerce/Receipt.ts +1 -0
  80. package/app/Models/commerce/Review.ts +3 -1
  81. package/app/Models/commerce/Transaction.ts +1 -0
  82. package/app/Models/realtime/Websocket.ts +4 -1
  83. package/functions/dashboard-form.test.ts +10 -1
  84. package/functions/dashboard-form.ts +17 -0
  85. package/ide/vscode/package.json +1 -1
  86. package/package.json +4 -4
  87. package/resources/components/Dashboard/Analytics/AnalyticsPageHeader.stx +1 -1
  88. package/resources/components/Dashboard/App/RequestDetailsDialog.stx +1 -1
  89. package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +1 -1
  90. package/resources/components/Dashboard/Auth/ForgotPassword.stx +1 -1
  91. package/resources/components/Dashboard/Auth/Login.stx +1 -1
  92. package/resources/components/Dashboard/Auth/Register.stx +1 -1
  93. package/resources/components/Dashboard/Auth/ResetPassword.stx +1 -1
  94. package/resources/components/Dashboard/Auth/TwoFactorLogin.stx +1 -1
  95. package/resources/components/Dashboard/CodeEditor.stx +1 -1
  96. package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +1 -1
  97. package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +1 -1
  98. package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +1 -1
  99. package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +1 -1
  100. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +4 -13
  101. package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +1 -1
  102. package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +1 -1
  103. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +1 -1
  104. package/resources/components/Dashboard/Commerce/CommerceCouponsTable.stx +1 -1
  105. package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +1 -1
  106. package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +1 -1
  107. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +1 -1
  108. package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +1 -1
  109. package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +1 -1
  110. package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +1 -1
  111. package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +1 -1
  112. package/resources/components/Dashboard/Commerce/CommerceGiftCardsTable.stx +1 -1
  113. package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +1 -1
  114. package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +1 -1
  115. package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +1 -1
  116. package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +1 -1
  117. package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +1 -1
  118. package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +1 -1
  119. package/resources/components/Dashboard/Commerce/CommerceOrdersTable.stx +1 -1
  120. package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +1 -1
  121. package/resources/components/Dashboard/Commerce/CommercePosCart.stx +1 -1
  122. package/resources/components/Dashboard/Commerce/CommercePosCatalog.stx +1 -1
  123. package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +1 -1
  124. package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +1 -1
  125. package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +1 -1
  126. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +27 -2
  127. package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +1 -1
  128. package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +1 -1
  129. package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +7 -5
  130. package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +12 -4
  131. package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +3 -3
  132. package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +1 -1
  133. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
  134. package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
  135. package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +1 -1
  136. package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +1 -1
  137. package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +1 -1
  138. package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +1 -1
  139. package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +1 -1
  140. package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +1 -1
  141. package/resources/components/Dashboard/Commerce/Delivery/CourierDeleteDialog.stx +1 -1
  142. package/resources/components/Dashboard/Commerce/Delivery/CourierDialog.stx +1 -1
  143. package/resources/components/Dashboard/Commerce/Delivery/CouriersTable.stx +1 -1
  144. package/resources/components/Dashboard/Commerce/Delivery/DeliveryPagination.stx +1 -1
  145. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +1 -1
  146. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +1 -1
  147. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +1 -1
  148. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +1 -1
  149. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +1 -1
  150. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +1 -1
  151. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +1 -1
  152. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +1 -1
  153. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +1 -1
  154. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +1 -1
  155. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +1 -1
  156. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +1 -1
  157. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +1 -1
  158. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +1 -1
  159. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +1 -1
  160. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +1 -1
  161. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +1 -1
  162. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +1 -1
  163. package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +1 -1
  164. package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +1 -1
  165. package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +1 -1
  166. package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +1 -1
  167. package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +1 -1
  168. package/resources/components/Dashboard/Commerce/PrintDevicesTable.stx +1 -1
  169. package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +1 -1
  170. package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +1 -1
  171. package/resources/components/Dashboard/Commerce/PrintLogsTable.stx +1 -1
  172. package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +1 -1
  173. package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +1 -1
  174. package/resources/components/Dashboard/Commerce/ProductWaitlistTable.stx +1 -1
  175. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +1 -1
  176. package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +1 -1
  177. package/resources/components/Dashboard/Commerce/RestaurantWaitlistTable.stx +1 -1
  178. package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +1 -1
  179. package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +1 -1
  180. package/resources/components/Dashboard/Content/CreateFolderDialog.stx +1 -1
  181. package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +1 -1
  182. package/resources/components/Dashboard/Content/FileUploadDialog.stx +1 -1
  183. package/resources/components/Dashboard/Content/PageEditorDialog.stx +1 -1
  184. package/resources/components/Dashboard/Content/PostEditorDialog.stx +1 -1
  185. package/resources/components/Dashboard/Content/SeoEditorDialog.stx +1 -1
  186. package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +1 -1
  187. package/resources/components/Dashboard/CreateRecordModal.stx +1 -1
  188. package/resources/components/Dashboard/Deployments/DeploymentPreviewDialog.stx +1 -1
  189. package/resources/components/Dashboard/Elements/DropdownNavigation.stx +1 -1
  190. package/resources/components/Dashboard/Email/CapturedMailList.stx +1 -1
  191. package/resources/components/Dashboard/Email/EmailSettingsList.stx +1 -1
  192. package/resources/components/Dashboard/Email/EmailSettingsNavigation.stx +1 -1
  193. package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
  194. package/resources/components/Dashboard/Email/InboxComposer.stx +1 -1
  195. package/resources/components/Dashboard/Email/InboxMessageDetail.stx +1 -1
  196. package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
  197. package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +1 -1
  198. package/resources/components/Dashboard/Infrastructure/CloudResourceButton.stx +1 -1
  199. package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +1 -1
  200. package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +1 -1
  201. package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +1 -1
  202. package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +1 -1
  203. package/resources/components/Dashboard/Jobs/JobTable.stx +1 -1
  204. package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +1 -1
  205. package/resources/components/Dashboard/Management/AssignmentDrawer.stx +1 -1
  206. package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +1 -1
  207. package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +1 -1
  208. package/resources/components/Dashboard/Marketing/CampaignDialog.stx +1 -1
  209. package/resources/components/Dashboard/Marketing/CampaignsTable.stx +1 -1
  210. package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +1 -1
  211. package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +1 -1
  212. package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
  213. package/resources/components/Dashboard/Marketing/RecoveryCampaignDialog.stx +1 -1
  214. package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +1 -1
  215. package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +1 -1
  216. package/resources/components/Dashboard/Marketing/SocialPostsTable.stx +1 -1
  217. package/resources/components/Dashboard/MobileSidebar.stx +1 -1
  218. package/resources/components/Dashboard/Modals/BaseModal.stx +1 -1
  219. package/resources/components/Dashboard/Modals/Popups/Alert.stx +1 -1
  220. package/resources/components/Dashboard/Modals/Popups/Toast.stx +1 -1
  221. package/resources/components/Dashboard/Modals/ToastWrapper.stx +1 -1
  222. package/resources/components/Dashboard/Models/EditRecordModal.stx +1 -1
  223. package/resources/components/Dashboard/Models/ModelRecordActions.stx +1 -1
  224. package/resources/components/Dashboard/Monitoring/ErrorTable.stx +1 -1
  225. package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +1 -1
  226. package/resources/components/Dashboard/Notifications/NotificationDeliveryTable.stx +1 -1
  227. package/resources/components/Dashboard/Operations/RecoveryDestinationDialog.stx +1 -1
  228. package/resources/components/Dashboard/Operations/RecoveryPointTable.stx +1 -1
  229. package/resources/components/Dashboard/Operations/RecoveryPolicyDialog.stx +1 -1
  230. package/resources/components/Dashboard/Operations/RecoveryRestoreDialog.stx +1 -1
  231. package/resources/components/Dashboard/Operations/SchedulerTaskTable.stx +1 -1
  232. package/resources/components/Dashboard/UI/Checkbox.stx +1 -1
  233. package/resources/components/Dashboard/UI/ConfirmDialog.stx +1 -1
  234. package/resources/components/Dashboard/UI/Drawer.stx +1 -1
  235. package/resources/components/Dashboard/UI/Dropdown.stx +1 -1
  236. package/resources/components/Dashboard/UI/Input.stx +1 -1
  237. package/resources/components/Dashboard/UI/Modal.stx +1 -1
  238. package/resources/components/Dashboard/UI/Pagination.stx +1 -1
  239. package/resources/components/Dashboard/UI/SearchFilter.stx +1 -1
  240. package/resources/components/Dashboard/UI/Select.stx +1 -1
  241. package/resources/components/Dashboard/UI/Table.stx +1 -1
  242. package/resources/components/Dashboard/UI/Tabs.stx +1 -1
  243. package/resources/components/Dashboard/UI/Textarea.stx +1 -1
  244. package/resources/components/Dashboard/UI/Toggle.stx +1 -1
  245. package/resources/components/Dashboard/UI/WindowControls.stx +1 -1
  246. package/resources/components/NativeActionSheet.stx +1 -1
  247. package/resources/components/NativeContextMenu.stx +1 -1
  248. package/resources/components/NativeHealthButton.stx +1 -1
  249. package/resources/components/NativePermissionButton.stx +1 -1
  250. package/resources/components/NativePullToRefresh.stx +1 -1
  251. package/resources/components/NativeSegmentedControl.stx +1 -1
  252. package/resources/components/NativeSheet.stx +1 -1
@@ -26,6 +26,13 @@ bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts <comman
26
26
  Every command prints a JSON result. Screenshots are written under `storage/framework/runtime/shots/` by
27
27
  default; **Read the PNG** to view it.
28
28
 
29
+ Every command accepts `--scale N` for the native device-pixel ratio, defaulting
30
+ to `1`. It must be one positive finite number. The driver applies it before
31
+ navigation, including scenario runs and every responsive or crawl page. To
32
+ qualify Retina behavior, inspect the page's actual `devicePixelRatio`, canvas
33
+ backing dimensions or resolution media query; a requested value alone is not
34
+ evidence. Scenario reports include the requested `scale`.
35
+
29
36
  ## Browser discovery (no install step)
30
37
 
31
38
  The driver finds a browser at runtime in this order, and falls through to the next if one
@@ -112,6 +119,7 @@ Extracts headings, links (`text -> href`), buttons, forms (action + field count)
112
119
  ### Scenario (stateful SPA interactions)
113
120
  ```bash
114
121
  bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts scenario <url> \
122
+ --scale 2 \
115
123
  --step '{"action":"click","selector":"button[data-open]"}' \
116
124
  --step '{"action":"focus","selector":"input[name=name]"}' \
117
125
  --step '{"action":"evaluate","expression":"window.__stxDevtools?.stats()"}' \
@@ -600,6 +600,14 @@ function parseViewport(value: string | undefined): { w: number, h: number } {
600
600
  return { w, h }
601
601
  }
602
602
 
603
+ export function parseDeviceScale(value: string | boolean | Array<string | boolean> | undefined): number {
604
+ if (value === undefined) return 1
605
+ if (typeof value !== 'string' || !value.trim()) throw new TypeError('--scale requires one positive finite device-pixel ratio, for example --scale 2.')
606
+ const scale = Number(value)
607
+ if (!Number.isFinite(scale) || scale <= 0) throw new TypeError(`Invalid --scale "${value}". Expected a positive finite device-pixel ratio.`)
608
+ return scale
609
+ }
610
+
603
611
  /** `--scroll-y` on `screenshot`: an explicit document Y to jump to before
604
612
  * capturing just the viewport, for content a full-page capture cannot show
605
613
  * (sticky/fixed elements, scroll-linked animation) at a specific fold. */
@@ -851,6 +859,7 @@ async function main() {
851
859
  process.exit(url ? 0 : 1)
852
860
  }
853
861
 
862
+ const scale = parseDeviceScale(flags.scale)
854
863
  let session = await launch()
855
864
  const cookies = flagList(flags.cookie)
856
865
  const settleMs = flags.settle ? Number(flags.settle) : undefined
@@ -869,7 +878,7 @@ async function main() {
869
878
  if (command === 'navigate' || command === 'go') {
870
879
  const cdp = await openPage(session.port)
871
880
  const t0 = performance.now()
872
- const state = await gotoAndInstrument(cdp, url, { cookies, settleMs, scheme })
881
+ const state = await gotoAndInstrument(cdp, url, { scale, cookies, settleMs, scheme })
873
882
  const loadMs = Math.round(performance.now() - t0)
874
883
  console.log(JSON.stringify({
875
884
  browser: session.browser,
@@ -887,7 +896,6 @@ async function main() {
887
896
  else if (command === 'screenshot') {
888
897
  const cdp = await openPage(session.port)
889
898
  const viewport = parseViewport(typeof flags.viewport === 'string' ? flags.viewport : undefined)
890
- const scale = flags.scale ? Number(flags.scale) : 1
891
899
  const scrollY = parseScrollY(flags['scroll-y'])
892
900
  const modes = [flags.full && 'full', flags.element && 'element', scrollY != null && 'scroll-y'].filter(Boolean)
893
901
  if (modes.length > 1)
@@ -908,7 +916,7 @@ async function main() {
908
916
  const results: any[] = []
909
917
  for (const bp of BREAKPOINTS) {
910
918
  const cdp = await openPage(session.port)
911
- const state = await gotoAndInstrument(cdp, url, { viewport: { w: bp.w, h: bp.h }, cookies, settleMs, scheme, reducedMotion: true })
919
+ const state = await gotoAndInstrument(cdp, url, { viewport: { w: bp.w, h: bp.h }, scale, cookies, settleMs, scheme, reducedMotion: true })
912
920
  const overflow = await cdp.send('Runtime.evaluate', {
913
921
  // Mobile Chrome may expand innerWidth to fit overflowing content.
914
922
  // Compare the page with the requested device width, not that expansion.
@@ -927,7 +935,7 @@ async function main() {
927
935
  else if (command === 'monitor') {
928
936
  const cdp = await openPage(session.port)
929
937
  const ms = flags.ms ? Number(flags.ms) : 5000
930
- const state = await gotoAndInstrument(cdp, url, { cookies, settleMs, scheme })
938
+ const state = await gotoAndInstrument(cdp, url, { scale, cookies, settleMs, scheme })
931
939
  await Bun.sleep(ms)
932
940
  const failed = state.responses.filter(r => r.status >= 400)
933
941
  const slow = state.responses.filter(r => r.ms > 3000)
@@ -950,7 +958,7 @@ async function main() {
950
958
  // auto-imported `state` signal factory, which has no `dispose`, so
951
959
  // `browse snapshot` threw on its last line and left the CDP listeners
952
960
  // attached.
953
- const state = await gotoAndInstrument(cdp, url, { cookies, settleMs, scheme })
961
+ const state = await gotoAndInstrument(cdp, url, { scale, cookies, settleMs, scheme })
954
962
  const expr = `(() => {
955
963
  const sel = (q) => Array.from(document.querySelectorAll(q));
956
964
  const txt = (e) => (e.innerText || e.textContent || '').trim().slice(0, 80);
@@ -976,7 +984,7 @@ async function main() {
976
984
 
977
985
  const cdp = await openPage(session.port)
978
986
  const viewport = parseViewport(typeof flags.viewport === 'string' ? flags.viewport : undefined)
979
- const state = await gotoAndInstrument(cdp, url, { viewport, cookies, settleMs, scheme, reducedMotion: !!flags.full })
987
+ const state = await gotoAndInstrument(cdp, url, { viewport, scale, cookies, settleMs, scheme, reducedMotion: !!flags.full })
980
988
  const results: Record<string, unknown>[] = []
981
989
  await cdp.send('Page.bringToFront')
982
990
  await cdp.send('Runtime.evaluate', {
@@ -1048,6 +1056,7 @@ async function main() {
1048
1056
  console.log(JSON.stringify({
1049
1057
  url,
1050
1058
  viewport: `${viewport.w}x${viewport.h}`,
1059
+ scale,
1051
1060
  steps: results,
1052
1061
  final: finalState,
1053
1062
  screenshot,
@@ -1115,6 +1124,7 @@ async function main() {
1115
1124
  let state: PageState | undefined
1116
1125
  try {
1117
1126
  state = await gotoAndInstrument(cdp, current, {
1127
+ scale,
1118
1128
  cookies,
1119
1129
  settleMs: crawlSettleMs,
1120
1130
  scheme,
@@ -115,6 +115,10 @@ export function prepareMacChrome(options: { source?: string, directory?: string
115
115
  }
116
116
  try {
117
117
  run(['/bin/cp', '-cR', sourceApp, staged], 'Cloning Chrome')
118
+ // The clone keeps the installed app's extended attributes (Finder info,
119
+ // provenance, quarantine), and codesign refuses to sign over them:
120
+ // "resource fork, Finder information, or similar detritus not allowed".
121
+ run(['/usr/bin/xattr', '-cr', staged], 'Clearing extended attributes')
118
122
  const stagedInfo = join(staged, 'Contents/Info.plist')
119
123
  for (const key of UPDATER_KEYS) plist(stagedInfo, `Delete :${key}`)
120
124
  run(['/usr/libexec/PlistBuddy', '-c', `Set :CFBundleIdentifier ${PRIVATE_BUNDLE_ID}`, stagedInfo], 'Renaming the Chrome bundle')
@@ -12,7 +12,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
12
12
  - Database package: `storage/framework/core/database/src/`
13
13
  - Configuration: `config/database.ts`
14
14
  - QB config: `config/query-builder.ts`
15
- - Migrations: `database/migrations/` (239 migration files, `.sql` format)
15
+ - Migrations: `database/migrations/` (247 migration files, `.sql` format)
16
16
  - QB state: `.qb/`
17
17
  - ORM: `storage/framework/orm/`
18
18
 
@@ -100,6 +100,29 @@ interface Desktop {
100
100
 
101
101
  ## Local-first apps: owning the launcher
102
102
 
103
+ ### Model-backed offline native apps
104
+
105
+ An app created with `buddy new <name> --native` can use `craft.config.ts`'s
106
+ `backend: { entry: 'app/Desktop/api.ts' }` to bundle a Bun backend. The native
107
+ host owns an authenticated ephemeral loopback session and the backend's lifetime.
108
+ The binary does not need an installed Bun, external server or source files.
109
+
110
+ Define models under `app/Models` with `defineModel` from `@stacksjs/orm/define-model`.
111
+ In the backend, import those models explicitly and use `openNativeDatabase` and
112
+ `createNativeModelApi` from `@stacksjs/orm/native`. Pass an absolute SQLite path
113
+ inside Application Support and an explicit model registry. The same query-builder
114
+ migration planner derives the schema and atomically records its snapshot in the
115
+ database. Additive upgrades preserve rows; destructive changes are refused.
116
+
117
+ CRUD follows `traits.useApi.uri` and its explicit `routes` list. Native models
118
+ declare `middleware: []` because the transport owns session authorization.
119
+ `createNativeModelApi` requires an `authorize(request, model, ability)` callback
120
+ on every request. Only return true unconditionally when the handler runs solely
121
+ behind stx's authenticated native backend. Never expose that handler through an
122
+ unprotected HTTP server. Web middleware declarations are refused by this native
123
+ adapter instead of silently skipped. Writes use the model's validation, fillable
124
+ allowlist and hidden-field serialization; index requests have bounded pagination.
125
+
103
126
  The launcher Stacks compiles opens a Craft window on the URL in `desktop.json`,
104
127
  which is what a hosted Stacks application wants. An app whose subject is the
105
128
  machine it runs on - a disk cleaner, a log viewer, a device tool - has no such
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-migrations
3
- description: Use when working with database migrations in a Stacks application - creating migration files, running migrations, fresh migration (drop + recreate), seeding after migration, migration file naming conventions, or the 239 built-in migration files. For the database API itself (queries, connections, SQL helpers), see stacks-database.
3
+ description: Use when working with database migrations in a Stacks application - creating migration files, running migrations, fresh migration (drop + recreate), seeding after migration, migration file naming conventions, or the 247 built-in migration files. For the database API itself (queries, connections, SQL helpers), see stacks-database.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript, SQLite >= 3.47.2
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -236,7 +236,7 @@ lossless widenings; reports everything destructive), then
236
236
  - Do not commit a second snapshot under `.qb/`; that indicates a missing `snapshotDir` configuration
237
237
  - If a generated SQLite migration rebuilds tables, test it against a copy of the current database and run `PRAGMA integrity_check` plus `PRAGMA foreign_key_check`
238
238
  - `--seed` flag after `migrate:fresh` seeds the database with factory data
239
- - 239 migration files exist by default for all framework models
239
+ - 247 migration files exist by default for all framework models
240
240
  - SQLite >= 3.47.2 is required (system requirement)
241
241
  - For the database API (queries, connections), see the `stacks-database` skill
242
242
 
@@ -35,7 +35,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
35
35
  | `@stacksjs/composables` | [`stacks-composables`](../stacks-composables/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/composables/src/index.ts) | 18 |
36
36
  | `@stacksjs/config` | [`stacks-config`](../stacks-config/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/config/src/index.ts) | 16 |
37
37
  | `@stacksjs/cron` | [`stacks-cron`](../stacks-cron/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cron/src/index.ts) | 4 |
38
- | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 132 |
38
+ | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 135 |
39
39
  | `@stacksjs/datetime` | [`stacks-datetime`](../stacks-datetime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/datetime/src/index.ts) | 13 |
40
40
  | `@stacksjs/defaults` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/defaults/package.json) | 2 |
41
41
  | `@stacksjs/deploy` | [`stacks-deploy`](../stacks-deploy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/deploy/package.json) | 1 |
@@ -65,7 +65,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
65
65
  | `@stacksjs/newsletter` | [`stacks-newsletter`](../stacks-newsletter/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/newsletter/src/index.ts) | 7 |
66
66
  | `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 16 |
67
67
  | `@stacksjs/objects` | [`stacks-objects`](../stacks-objects/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/objects/src/index.ts) | 4 |
68
- | `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 72 |
68
+ | `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 73 |
69
69
  | `@stacksjs/pagination` | [`stacks-pagination`](../stacks-pagination/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/pagination/src/index.ts) | 0 |
70
70
  | `@stacksjs/path` | [`stacks-path`](../stacks-path/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/path/src/index.ts) | 13 |
71
71
  | `@stacksjs/payments` | [`stacks-payments`](../stacks-payments/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/payments/src/index.ts) | 29 |
@@ -75,7 +75,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
75
75
  | `@stacksjs/realtime` | [`stacks-realtime`](../stacks-realtime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/realtime/src/index.ts) | 12 |
76
76
  | `@stacksjs/registry` | [`stacks-registry`](../stacks-registry/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/registry/src/index.ts) | 1 |
77
77
  | `@stacksjs/repl` | [`stacks-repl`](../stacks-repl/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/repl/src/index.ts) | 2 |
78
- | `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) | 95 |
78
+ | `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) | 96 |
79
79
  | `@stacksjs/scheduler` | [`stacks-scheduler`](../stacks-scheduler/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/index.ts) | 14 |
80
80
  | `@stacksjs/search-engine` | [`stacks-search-engine`](../stacks-search-engine/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/search-engine/src/index.ts) | 11 |
81
81
  | `@stacksjs/security` | [`stacks-security`](../stacks-security/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/security/src/index.ts) | 11 |
@@ -83,7 +83,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
83
83
  | `@stacksjs/shell` | [`stacks-shell`](../stacks-shell/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/shell/src/index.ts) | 1 |
84
84
  | `@stacksjs/sites` | [`stacks-sites`](../stacks-sites/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sites/src/index.ts) | 3 |
85
85
  | `@stacksjs/skills` | [`stacks-skills`](../stacks-skills/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/skills/src/index.ts) | 1 |
86
- | `@stacksjs/slug` | [`stacks-slug`](../stacks-slug/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/src/index.ts) | 4 |
86
+ | `@stacksjs/slug` | [`stacks-slug`](../stacks-slug/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/src/index.ts) | 5 |
87
87
  | `@stacksjs/sms` | [`stacks-sms`](../stacks-sms/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/index.ts) | 5 |
88
88
  | `@stacksjs/socials` | [`stacks-socials`](../stacks-socials/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/socials/src/index.ts) | 17 |
89
89
  | `@stacksjs/storage` | [`stacks-storage`](../stacks-storage/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/index.ts) | 42 |
@@ -353,7 +353,7 @@ Declared runtime exports: `Database`, `EntityQueryBuilder`, `__flushAfterCommitN
353
353
 
354
354
  Local source modules: [src/affected-rows.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/affected-rows.ts), [src/auth-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/auth-tables.ts), [src/custom/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/custom/index.ts), [src/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/database.ts), [src/datetime-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/datetime-columns.ts), [src/ddl-constraints.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ddl-constraints.ts), [src/defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/defaults.ts), [src/dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/dialect.ts), [src/driver-config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/driver-config.ts), [src/drivers/dynamodb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/dynamodb.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/index.ts), [src/ensure-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ensure-database.ts), [src/erd.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/erd.ts), [src/fk-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/fk-audit.ts), [src/framework-renames.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-renames.ts), [src/framework-route-admission.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-route-admission.ts), [src/hand-written-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/hand-written-migrations.ts), [src/ignored-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ignored-migrations.ts), [src/index-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index-shape.ts), [src/managed-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/managed-columns.ts), [src/migration-dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-dialect.ts), [src/migration-ledger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-ledger.ts), [src/migration-names.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-names.ts), [src/migration-path.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-path.ts), [src/migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migrations.ts), [src/model-sources.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/model-sources.ts), [src/notification-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/notification-tables.ts), [src/package-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-migrations.ts), [src/package-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-models.ts), [src/query-logger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/query-logger.ts), [src/rbac-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/rbac-tables.ts), [src/relation-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/relation-columns.ts), [src/replicas.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/replicas.ts), [src/row-lock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lock.ts), [src/safe-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/safe-migrations.ts), [src/schema-align.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-align.ts), [src/schema-drift.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-drift.ts), [src/seeder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/seeder.ts), [src/shadowed-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/shadowed-models.ts), [src/sql-helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/sql-helpers.ts), [src/trait-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/trait-tables.ts), [src/transaction-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-context.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/types.ts), [src/unique-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/unique-audit.ts), [src/utc-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utc-defaults.ts), [src/utils.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utils.ts), [src/uuid-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/uuid-columns.ts), [src/vschema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/vschema.ts).
355
355
 
356
- Evidence directory: [132 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
356
+ Evidence directory: [135 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
357
357
 
358
358
  ### @stacksjs/datetime
359
359
 
@@ -635,7 +635,7 @@ Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`
635
635
 
636
636
  Local source modules: [src/auto-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/auto-crud.ts), [src/batch-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/batch-loader.ts), [src/db.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/db.ts), [src/define-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/define-model.ts), [src/extend-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/extend-model.ts), [src/gdpr/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/gdpr/index.ts), [src/generate-database-schema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/generate-database-schema.ts), [src/locked-row.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/locked-row.ts), [src/messaging.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/messaging.ts), [src/model-types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/model-types.ts), [src/ownership.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/ownership.ts), [src/paginator-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/paginator-request.ts), [src/row-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/row-token.ts), [src/subquery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/subquery.ts), [src/traits/audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/audit.ts), [src/traits/billable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/billable.ts), [src/traits/categorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/categorizable.ts), [src/traits/commentable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/commentable.ts), [src/traits/likeable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/likeable.ts), [src/traits/taggable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/taggable.ts), [src/transaction.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/transaction.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/types.ts), [src/utils/encrypted.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/encrypted.ts), [src/utils/prunable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/prunable.ts).
637
637
 
638
- Evidence directory: [72 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
638
+ Evidence directory: [73 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
639
639
 
640
640
  ### @stacksjs/pagination
641
641
 
@@ -725,7 +725,7 @@ Declared runtime exports: `DASHBOARD_ROUTE_GROUPS`, `DEFAULT_ROUTE_BUNDLE_FEATUR
725
725
 
726
726
  Local source modules: [src/api-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/api-shape.ts), [src/body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/body.ts), [src/dashboard-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/dashboard-access.ts), [src/encrypted-session-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/encrypted-session-store.ts), [src/error-handler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/error-handler.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/health.ts), [src/json.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/json.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/middleware.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/path-sanitize.ts), [src/rate-limit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/rate-limit.ts), [src/request-augmentation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-augmentation.ts), [src/request-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-context.ts), [src/route-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-loader.ts), [src/route-model-binding.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-model-binding.ts), [src/security-settings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/security-settings.ts), [src/session-factory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/session-factory.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/signed-url.ts), [src/stacks-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/stacks-router.ts), [src/typed-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/typed-router.ts).
727
727
 
728
- Evidence directory: [95 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
728
+ Evidence directory: [96 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
729
729
 
730
730
  ### @stacksjs/scheduler
731
731
 
@@ -803,7 +803,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
803
803
 
804
804
  Local source modules: [src/unique.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/src/unique.ts).
805
805
 
806
- Evidence directory: [4 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
806
+ Evidence directory: [5 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
807
807
 
808
808
  ### @stacksjs/sms
809
809
 
@@ -14,10 +14,10 @@ export default new Action({
14
14
  return response.json({ error: 'Author not found' }, 404)
15
15
  }
16
16
 
17
- const result = await author.update({
18
- name: request.get('name'),
19
- email: request.get('email'),
20
- })
17
+ // Only the fields the request sent: an absent one copied through
18
+ // request.get() reached the query as undefined, which binds NULL and
19
+ // erased the stored value (stacksjs/stacks#3010).
20
+ const result = await author.update(request.only(['name', 'email']))
21
21
 
22
22
  return response.json(result)
23
23
  },
@@ -31,10 +31,10 @@ export default new Action({
31
31
 
32
32
  const id = Number(request.getParam('id'))
33
33
 
34
- const data = {
35
- name: request.get('name'),
36
- description: request.get('description'),
37
- }
34
+ // Only the fields the request sent: an absent one copied through
35
+ // request.get() reached the query as undefined, which binds NULL and
36
+ // erased the stored value (stacksjs/stacks#3010).
37
+ const data = request.only(['name', 'description'])
38
38
 
39
39
  const model = await categorizable.update({ id, ...data })
40
40
 
@@ -16,12 +16,11 @@ export default new Action({
16
16
 
17
17
  const id = Number(request.getParam('id'))
18
18
 
19
+ // Only the fields the request sent: an absent one copied through
20
+ // request.get() reached the query as undefined, which binds NULL and
21
+ // erased the stored value (stacksjs/stacks#3010).
19
22
  const data = {
20
- title: request.get('title'),
21
- excerpt: request.get('excerpt'),
22
- content: request.get('content'),
23
- status: request.get('status'),
24
- poster: request.get('poster'),
23
+ ...request.only(['title', 'excerpt', 'content', 'status', 'poster']),
25
24
  updated_at: formatDate(new Date()),
26
25
  }
27
26
 
@@ -30,10 +30,12 @@ export default new Action({
30
30
  })
31
31
 
32
32
  const id = Number(request.getParam('id'))
33
+ // Only the fields the request sent: an absent one copied through
34
+ // request.get() reached the query as undefined, which binds NULL and
35
+ // erased the stored value (stacksjs/stacks#3010).
33
36
  const data = {
34
37
  id,
35
- name: request.get('name'),
36
- description: request.get('description'),
38
+ ...request.only(['name', 'description']),
37
39
  }
38
40
 
39
41
  const model = await tags.update(data)
@@ -14,13 +14,11 @@ export default new Action({
14
14
  async handle(request) {
15
15
  await request.validate()
16
16
 
17
+ // Only the fields the request sent: an absent one copied through
18
+ // request.get() reached the query as undefined, which binds NULL and
19
+ // erased the stored value or the column default (stacksjs/stacks#3010).
17
20
  const data = toSnakeCaseKeys({
18
- name: request.get('name'),
19
- email: request.get('email'),
20
- phone: request.get('phone'),
21
- totalSpent: request.get('totalSpent'),
22
- status: request.get('status'),
23
- avatar: request.get('avatar'),
21
+ ...request.only(['name', 'email', 'phone', 'totalSpent', 'status', 'avatar']),
24
22
  uuid: randomUUIDv7(),
25
23
  ...(request.get('lastOrder') && { lastOrder: request.get('lastOrder') }),
26
24
  })
@@ -18,13 +18,11 @@ export default new Action({
18
18
  const { id } = identifier
19
19
 
20
20
  await request.validate()
21
+ // Only the fields the request sent: an absent one copied through
22
+ // request.get() reached the query as undefined, which binds NULL and
23
+ // erased the stored value or the column default (stacksjs/stacks#3010).
21
24
  const data = toSnakeCaseKeys({
22
- name: request.get('name'),
23
- email: request.get('email'),
24
- phone: request.get('phone'),
25
- totalSpent: request.get('totalSpent'),
26
- status: request.get('status'),
27
- avatar: request.get('avatar'),
25
+ ...request.only(['name', 'email', 'phone', 'totalSpent', 'status', 'avatar']),
28
26
  ...(request.get('lastOrder') && { lastOrder: request.get('lastOrder') }),
29
27
  })
30
28
 
@@ -9,15 +9,12 @@ export default new Action({
9
9
  async handle(request: RequestInstance) {
10
10
  await request.validate()
11
11
 
12
- const data = {
13
- order_id: request.get<number>('order_id'),
14
- customer_id: request.get<number>('customer_id'),
15
- amount: request.get<number>('amount'),
16
- method: request.get('method'),
17
- status: request.get('status'),
18
- }
12
+ // Only the fields the request sent: an absent one copied through
13
+ // request.get() reached the query as undefined, which binds NULL and
14
+ // erased the stored value or the column default (stacksjs/stacks#3010).
15
+ const data = request.only(['order_id', 'customer_id', 'amount', 'method', 'status'])
19
16
 
20
- const model = await payments.store(data)
17
+ const model = await payments.store(data as Parameters<typeof payments.store>[0])
21
18
 
22
19
  return response.json(model)
23
20
  },
@@ -14,24 +14,27 @@ export default new Action({
14
14
  const { id } = identifier
15
15
 
16
16
  await request.validate()
17
- const data = {
18
- order_id: request.get<number>('order_id'),
19
- customer_id: request.get<number>('customer_id'),
20
- amount: request.get<number>('amount'),
21
- method: request.get('method'),
22
- status: request.get('status'),
23
- currency: request.get('currency'),
24
- reference_number: request.get('reference_number'),
25
- card_last_four: request.get('card_last_four'),
26
- card_brand: request.get('card_brand'),
27
- billing_email: request.get('billing_email'),
28
- transaction_id: request.get('transaction_id'),
29
- payment_provider: request.get('payment_provider'),
30
- refund_amount: request.get<number>('refund_amount'),
31
- notes: request.get('notes'),
32
- }
17
+ // Only the fields the request sent: an absent one copied through
18
+ // request.get() reached the query as undefined, which binds NULL and
19
+ // erased the stored value or the column default (stacksjs/stacks#3010).
20
+ const data = request.only([
21
+ 'order_id',
22
+ 'customer_id',
23
+ 'amount',
24
+ 'method',
25
+ 'status',
26
+ 'currency',
27
+ 'reference_number',
28
+ 'card_last_four',
29
+ 'card_brand',
30
+ 'billing_email',
31
+ 'transaction_id',
32
+ 'payment_provider',
33
+ 'refund_amount',
34
+ 'notes',
35
+ ])
33
36
 
34
- const model = await payments.update(id, data)
37
+ const model = await payments.update(id, data as Parameters<typeof payments.update>[1])
35
38
  if (!model)
36
39
  return commerceNotFound('Payment', id)
37
40
 
@@ -15,6 +15,7 @@ describe('dashboard commerce product records', () => {
15
15
  const record = normalizeCommerceProductRecord({
16
16
  id: 5,
17
17
  name: 'Native Kit',
18
+ slug: 'native-kit',
18
19
  price: 125,
19
20
  is_available: 1,
20
21
  inventory_count: 7,
@@ -29,6 +30,7 @@ describe('dashboard commerce product records', () => {
29
30
  expect(record).toMatchObject({
30
31
  id: '5',
31
32
  name: 'Native Kit',
33
+ slug: 'native-kit',
32
34
  price: 125,
33
35
  isAvailable: true,
34
36
  inventoryCount: 7,
@@ -72,6 +74,7 @@ describe('dashboard commerce product records', () => {
72
74
  normalizeCommerceProductRecord({
73
75
  id: 1,
74
76
  name: 'Available',
77
+ slug: 'available',
75
78
  price: 100,
76
79
  isAvailable: true,
77
80
  inventoryCount: 3,
@@ -81,6 +84,7 @@ describe('dashboard commerce product records', () => {
81
84
  normalizeCommerceProductRecord({
82
85
  id: 2,
83
86
  name: 'Unavailable',
87
+ slug: 'unavailable',
84
88
  price: 100,
85
89
  isAvailable: false,
86
90
  inventoryCount: 0,
@@ -101,6 +105,7 @@ describe('dashboard commerce product records', () => {
101
105
  const base = {
102
106
  id: 1,
103
107
  name: 'Native Kit',
108
+ slug: 'native-kit',
104
109
  price: 125,
105
110
  is_available: 1,
106
111
  inventory_count: 7,
@@ -132,6 +137,8 @@ describe('dashboard commerce product records', () => {
132
137
  new Map(),
133
138
  new Map(),
134
139
  )).toThrow('references missing Category 99')
140
+ // An unrated review counts as a review, not as a 0-star one (#2999).
141
+ expect(summarizeProductReviews([{ rating: null, is_approved: 1 }, { rating: 4, is_approved: 0 }]).averageRating).toBe(4)
135
142
  expect(() => summarizeProductReviews([{ rating: 'unknown', is_approved: 1 }]))
136
143
  .toThrow('Review 1.rating must be a finite number')
137
144
  expect(() => normalizeCommerceCurrency('US')).toThrow('three-letter currency code')
@@ -4,6 +4,8 @@ import { commerceTimestamp } from './commerce-record'
4
4
  export interface CommerceProductRecord {
5
5
  id: string
6
6
  name: string
7
+ /** The storefront address, /products/{slug} (stacksjs/stacks#3005). */
8
+ slug: string
7
9
  description: string
8
10
  /** Integer minor units of the store currency (1999 is $19.99 in USD). */
9
11
  price: number
@@ -68,6 +70,7 @@ export interface ProductReviewSummary {
68
70
  */
69
71
  export interface ProductFormFields {
70
72
  name: string
73
+ slug: string
71
74
  description: string
72
75
  price: string
73
76
  imageUrl: string
@@ -83,6 +86,7 @@ export interface ProductFormFields {
83
86
  /** What the dialog sends to `POST`/`PATCH /api/dashboard/commerce/products`. */
84
87
  export interface ProductWritePayload {
85
88
  name: string
89
+ slug: string
86
90
  description: string
87
91
  /** Integer minor units, never a decimal (stacksjs/stacks#2851). */
88
92
  price: number
@@ -116,6 +120,7 @@ export function productWritePayload(form: ProductFormFields, currency: string):
116
120
  const allergens = form.allergens.split(',').map(item => item.trim()).filter(Boolean)
117
121
  return {
118
122
  name: form.name.trim(),
123
+ slug: form.slug.trim(),
119
124
  description: form.description.trim(),
120
125
  price: parseMoneyInput(form.price, currency, { min: MIN_PRODUCT_PRICE }),
121
126
  imageUrl: form.imageUrl.trim(),
@@ -285,6 +290,7 @@ export function normalizeCommerceProductRecord(
285
290
  return {
286
291
  id,
287
292
  name: requiredText(value(product, 'name'), source, 'name'),
293
+ slug: requiredText(value(product, 'slug'), source, 'slug'),
288
294
  description: optionalText(value(product, 'description'), source, 'description'),
289
295
  price: number(value(product, 'price'), source, 'price', { min: 0 }),
290
296
  imageUrl: optionalText(value(product, 'image_url', 'imageUrl'), source, 'image_url'),
@@ -352,9 +358,14 @@ export function summarizeCommerceProducts(records: CommerceProductRecord[]): Com
352
358
  }
353
359
 
354
360
  export function summarizeProductReviews(records: any[]): ProductReviewSummary {
355
- const ratings = records.map((record, index) =>
356
- number(value(record, 'rating'), `Review ${index + 1}`, 'rating', { min: 1, max: 5 }),
357
- )
361
+ // A review can be words without stars; it counts toward the total, not the
362
+ // average.
363
+ const ratings = records.flatMap((record, index) => {
364
+ const rating = value(record, 'rating')
365
+ return rating === null || rating === undefined
366
+ ? []
367
+ : [number(rating, `Review ${index + 1}`, 'rating', { min: 1, max: 5 })]
368
+ })
358
369
  const approved = records.filter((record, index) =>
359
370
  boolean(value(record, 'is_approved', 'isApproved'), `Review ${index + 1}`, 'is_approved'),
360
371
  ).length
@@ -108,6 +108,18 @@ describe('dashboard review records', () => {
108
108
  })
109
109
  })
110
110
 
111
+ // A review can be words with no stars (9b8dd223e8), and one unrated review
112
+ // made the whole list answer 503 (stacksjs/stacks#2999).
113
+ test('keeps a review without a star rating, and averages only the rated ones', () => {
114
+ const unrated = normalizeReviewRecord(review({ rating: null }), new Map(), new Map())
115
+ expect(unrated.rating).toBeNull()
116
+ expect(() => normalizeReviewRecord(review({ rating: 6 }), new Map(), new Map())).toThrow('rating must be at most 5')
117
+
118
+ const rated = normalizeReviewRecord(review({ id: 10, rating: 4 }), new Map(), new Map())
119
+ expect(summarizeReviews([unrated, rated]).averageRating).toBe(4)
120
+ expect(summarizeReviews([unrated]).averageRating).toBe(0)
121
+ })
122
+
111
123
  test('validates Product options and Customer context records', () => {
112
124
  expect(normalizeReviewProductOption({ id: 4, name: 'Native Kit' }))
113
125
  .toEqual({ id: '4', label: 'Native Kit' })