@stacksjs/defaults 0.72.97 → 0.72.99

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 (87) hide show
  1. package/ai/skills/stacks-auth/SKILL.md +35 -5
  2. package/ai/skills/stacks-config/SKILL.md +11 -1
  3. package/ai/skills/stacks-desktop/SKILL.md +46 -0
  4. package/ai/skills/stacks-env/SKILL.md +26 -0
  5. package/ai/skills/stacks-events/SKILL.md +76 -60
  6. package/ai/skills/stacks-jobs/SKILL.md +3 -0
  7. package/ai/skills/stacks-listeners/SKILL.md +39 -9
  8. package/ai/skills/stacks-middleware/SKILL.md +50 -12
  9. package/ai/skills/stacks-queue/SKILL.md +5 -0
  10. package/ai/skills/stacks-router/SKILL.md +17 -7
  11. package/ai/skills/stacks-scheduler/SKILL.md +1 -1
  12. package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +1 -1
  13. package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +1 -1
  14. package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +1 -1
  15. package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +1 -1
  16. package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +1 -1
  17. package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +1 -1
  18. package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +1 -1
  19. package/app/Actions/Dashboard/Content/content-input.ts +2 -2
  20. package/app/Actions/Dashboard/Content/post-input.ts +4 -4
  21. package/app/Actions/Dashboard/Email/inbox-request.ts +1 -1
  22. package/app/Actions/Dashboard/Email/mail-preference.ts +1 -1
  23. package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +1 -1
  24. package/app/Actions/Dashboard/Marketing/CampaignStoreAction.ts +1 -1
  25. package/app/Actions/Dashboard/Marketing/CampaignUpdateAction.ts +1 -1
  26. package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +2 -2
  27. package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +2 -2
  28. package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +5 -5
  29. package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +2 -2
  30. package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +2 -2
  31. package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +2 -2
  32. package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +2 -2
  33. package/app/Events.ts +9 -4
  34. package/app/Gates.ts +83 -85
  35. package/app/Listener.ts +34 -81
  36. package/app/Middleware.ts +22 -7
  37. package/ide/vscode/package.json +1 -1
  38. package/package.json +2 -2
  39. package/project/storage/framework/server/tsconfig.docker.json +1 -2
  40. package/resources/components/Buttons/Counter.stx +30 -21
  41. package/resources/components/Dashboard/Analytics/WebAnalyticsDashboard.stx +1 -3
  42. package/resources/components/Dashboard/Auth/ForgotPasswordDashboard.stx +1 -1
  43. package/resources/components/Dashboard/Auth/LoginDashboard.stx +2 -1
  44. package/resources/components/Dashboard/Auth/RegisterDashboard.stx +2 -1
  45. package/resources/components/Dashboard/Auth/ResetPasswordDashboard.stx +1 -1
  46. package/resources/components/Dashboard/Ci/CiDashboard.stx +9 -9
  47. package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +7 -7
  48. package/resources/components/Dashboard/Commerce/CommerceOverviewCharts.stx +2 -1
  49. package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +1 -1
  50. package/resources/components/Dashboard/Content/BlogDashboard.stx +37 -15
  51. package/resources/components/Dashboard/Content/ContentDashboard.stx +3 -26
  52. package/resources/components/Dashboard/Email/EmailActivityDashboard.stx +2 -15
  53. package/resources/components/Dashboard/Environment/EnvironmentDashboard.stx +9 -1
  54. package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +15 -13
  55. package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +10 -8
  56. package/resources/components/Dashboard/Kanban/KanbanCardDialog.stx +10 -10
  57. package/resources/components/Dashboard/Management/PermissionsDashboard.stx +3 -3
  58. package/resources/components/Dashboard/Modals/Popups/Alert.stx +4 -1
  59. package/resources/components/Dashboard/Navbar.stx +1 -1
  60. package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +11 -6
  61. package/resources/components/Dashboard/Settings/SettingsDashboard.stx +2 -2
  62. package/resources/components/Dashboard/Transaction/index.stx +6 -6
  63. package/resources/components/Dashboard/UI/Avatar.stx +2 -2
  64. package/resources/components/Dashboard/UI/DropdownItem.stx +4 -4
  65. package/resources/components/Dashboard/UI/WindowControls.stx +17 -2
  66. package/resources/components/Docs/Demo/ComboboxDemo.stx +8 -3
  67. package/resources/components/Docs/Demo/DropdownDemo.stx +3 -3
  68. package/resources/components/Docs/Demo/StepperDemo.stx +1 -1
  69. package/resources/components/Forum/ForumLayout.stx +1 -1
  70. package/resources/components/Forum/ForumReplyForm.stx +1 -1
  71. package/resources/components/Marketing/ComingSoon.stx +5 -5
  72. package/resources/components/Storefront/CartDrawer.stx +44 -22
  73. package/resources/components/Storefront/ProductGallery.stx +3 -2
  74. package/resources/components/Storefront/QuantitySelector.stx +12 -7
  75. package/resources/functions/storefront/cart-cookie.ts +58 -0
  76. package/resources/layouts/storefront.stx +3 -2
  77. package/resources/views/cart.stx +19 -3
  78. package/resources/views/checkout/contact.stx +14 -3
  79. package/resources/views/checkout/payment.stx +14 -3
  80. package/resources/views/checkout/shipping.stx +14 -3
  81. package/resources/views/cms/blocks/form.stx +16 -11
  82. package/resources/views/cms/page.stx +28 -0
  83. package/resources/views/coming-soon.stx +0 -6
  84. package/resources/views/orders/[id].stx +19 -3
  85. package/views/dashboard/composables/index.ts +1 -1
  86. package/views/dashboard/composables/useChart.ts +26 -14
  87. package/views/dashboard/layouts/default.stx +42 -25
@@ -13,29 +13,41 @@
13
13
  * lifecycle for pages that should defer the chart library.
14
14
  */
15
15
 
16
+ import type { Chart, ChartConfig, ChartData, ChartDataset, ChartOptions, ChartType } from '@stacksjs/charts'
16
17
  import { onDestroy, onMount } from '@stacksjs/stx'
17
18
 
18
- export interface ChartLike {
19
- destroy: () => void
20
- update?: () => void
21
- }
19
+ export type { ChartConfig, ChartData, ChartDataset, ChartOptions, ChartType }
20
+
21
+ /**
22
+ * The part of a chart instance this codebase touches, taken from the class
23
+ * rather than restated.
24
+ *
25
+ * `data` is included because every dashboard that refreshes a chart writes
26
+ * through it - replacing `labels` and each dataset's `data`, then calling
27
+ * `update()` - rather than destroying and rebuilding.
28
+ *
29
+ * This used to be a hand-written interface, deliberately not importing
30
+ * `@stacksjs/charts` "to keep this composable dep-free at the type level".
31
+ * The import is type-only and erases, so it costs nothing at runtime, and
32
+ * writing the shape out by hand cost plenty: it omitted `data`, so three
33
+ * dashboards each declared their own local `ChartLike` in three slightly
34
+ * different shapes to get at it, and every page then needed
35
+ * `Chart as unknown as ChartCtor` to get the real constructor past a
36
+ * signature that described it imprecisely.
37
+ */
38
+ export type ChartLike = Pick<Chart, 'data' | 'update' | 'destroy'>
22
39
 
23
- // Constructor signature mirrors Chart.js — `new Chart(canvas, config)`.
24
- // We don't import `@stacksjs/charts` here to keep this composable
25
- // dep-free at the type level; pages pass the constructor in.
26
- export type ChartCtor = new (
27
- canvas: HTMLCanvasElement | HTMLElement,
28
- config: { type: string, data: unknown, options?: unknown },
29
- ) => ChartLike
40
+ /** `new Chart(canvas, config)`, taken from the class itself. */
41
+ export type ChartCtor = new (..._args: ConstructorParameters<typeof Chart>) => ChartLike
30
42
 
31
43
  export interface UseChartOptions {
32
44
  /** Chart constructor (`new Chart(...)`). Pages import lazily and pass it in. */
33
45
  Chart: ChartCtor
34
46
  /** Element id, e.g. `'chart_1'`. */
35
47
  id: string
36
- type: string
37
- data: unknown
38
- options?: unknown
48
+ type: ChartType
49
+ data: ChartData
50
+ options?: ChartOptions
39
51
  }
40
52
 
41
53
  export interface ChartHandle {
@@ -21,15 +21,25 @@ export const sidebarSections = buildWebSidebarSections()
21
21
  // viewers who hold none of them. Emitted as an id-keyed map rather than a
22
22
  // `data-required-roles` attribute on each row, which keeps the generic
23
23
  // Sidebar component free of Stacks-specific attributes.
24
- export const sidebarRoleMap = JSON.stringify(
25
- Object.fromEntries(
26
- sidebarSections.flatMap(section =>
27
- section.items
28
- .filter(item => item.roles && item.roles.length > 0)
29
- .map(item => [item.id, item.roles]),
24
+ // Published as an object, not as JSON text. The server-to-client bridge
25
+ // serializes what it publishes, so a value stringified here arrived on the
26
+ // client double-encoded - as the JSON string rather than the map. The client
27
+ // then ran `Object.entries` over that string, which yields index/character
28
+ // pairs, and called `.some()` on a single character. Every dashboard load
29
+ // threw inside applyRoleFilter before it could filter anything.
30
+ export const sidebarRoleMap: Record<string, string[]> = Object.fromEntries(
31
+ sidebarSections.flatMap(section =>
32
+ section.items.flatMap(item =>
33
+ item.roles && item.roles.length > 0
34
+ ? [[item.id, item.roles] as const]
35
+ : [],
30
36
  ),
31
37
  ),
32
38
  )
39
+
40
+ // Declared, so the client block below gets the map typed rather than inferred
41
+ // off the implicit bridge - which is what let the mismatch above go unseen.
42
+ defineClientPayload({ sidebarRoleMap })
33
43
  </script>
34
44
 
35
45
  @head
@@ -389,6 +399,8 @@ html[data-appearance="arc"] [data-sidebar-section] > div:first-child {
389
399
  // fixed-position container below the <main> slot. Mounted in the
390
400
  // layout so every dashboard page picks it up without opting in.
391
401
  // `~/`-anchored for the same reason as the sidebar import above.
402
+ import type { ToastType } from '~/storage/framework/defaults/functions/toasts'
403
+ import type { CraftSidebarSelectEvent } from '~/storage/framework/defaults/functions/craft'
392
404
  import { useToasts, dismissToast } from '~/storage/framework/defaults/functions/toasts'
393
405
  import { ensureCraftNativeSidebarMarker, useCraftSidebarSelection } from '~/storage/framework/defaults/functions/craft'
394
406
  import { readStoredAppearance, useAppearance } from '~/storage/framework/defaults/views/dashboard/composables/useAppearance'
@@ -401,14 +413,14 @@ registerStoresClient({ authStore, kanbanStore, rbacStore })
401
413
  // Helpers below return COMPLETE class strings (static + dynamic) and are
402
414
  // applied via `:class="fn(...)"` because stx's `{{ ... }}` placeholder is
403
415
  // not reactively bound inside a `class` attribute - only inside text content.
404
- const toastIconClass = (type) => `flex-shrink-0 h-5 w-5 mt-0.5 ${({
416
+ const toastIconClass = (type: ToastType) => `flex-shrink-0 h-5 w-5 mt-0.5 ${({
405
417
  success: 'i-hugeicons-checkmark-circle-02 text-green-500 dark:text-green-400',
406
418
  error: 'i-hugeicons-alert-02 text-red-500 dark:text-red-400',
407
419
  warning: 'i-hugeicons-alert-circle text-yellow-500 dark:text-yellow-400',
408
420
  info: 'i-hugeicons-information-circle text-blue-500 dark:text-blue-400',
409
421
  }[type] || 'i-hugeicons-information-circle text-gray-500 dark:text-neutral-400')}`
410
422
 
411
- const toastWrapperClass = (type) => `pointer-events-auto overflow-hidden bg-white dark:bg-neutral-800 ring-1 ${({
423
+ const toastWrapperClass = (type: ToastType) => `pointer-events-auto overflow-hidden bg-white dark:bg-neutral-800 ring-1 ${({
412
424
  success: 'ring-green-500/20',
413
425
  error: 'ring-red-500/30',
414
426
  warning: 'ring-yellow-500/20',
@@ -434,10 +446,10 @@ let removeCraftSidebarSelection = () => {}
434
446
  ensureCraftNativeSidebarMarker()
435
447
  auth.load()
436
448
 
437
- function sidebarPanes() {
449
+ function sidebarPanes(): HTMLElement[] {
438
450
  return [sidebarHost.current, mobileSidebarHost.current]
439
- .map(host => host?.querySelector('[data-stx-sidebar]'))
440
- .filter(Boolean)
451
+ .map(host => host?.querySelector<HTMLElement>('[data-stx-sidebar]'))
452
+ .filter((pane): pane is HTMLElement => Boolean(pane))
441
453
  }
442
454
 
443
455
  function applyRoleFilter() {
@@ -445,7 +457,7 @@ function applyRoleFilter() {
445
457
  const unauth = auth.unauthenticated()
446
458
  for (const pane of sidebarPanes()) {
447
459
  for (const [itemId, required] of Object.entries(sidebarRoles)) {
448
- const row = pane.querySelector(`[data-sidebar-row][data-item-id="${itemId}"]`)
460
+ const row = pane.querySelector<HTMLElement>(`[data-sidebar-row][data-item-id="${itemId}"]`)
449
461
  if (!row) continue
450
462
  // Unauthenticated viewers see everything, which keeps the dev dashboard
451
463
  // usable. Once authenticated, role membership is authoritative. CSS
@@ -495,7 +507,7 @@ function applySectionVisibility() {
495
507
  // do nothing until a reload.
496
508
  const hidden = appearance.hiddenSections()
497
509
  for (const pane of sidebarPanes()) {
498
- for (const section of pane.querySelectorAll('[data-sidebar-section]')) {
510
+ for (const section of pane.querySelectorAll<HTMLElement>('[data-sidebar-section]')) {
499
511
  const id = section.dataset.sectionId || ''
500
512
  section.setAttribute('data-viewer-hidden', String(hidden.includes(id)))
501
513
  }
@@ -522,14 +534,14 @@ useEventListener('storage', (event) => {
522
534
  appearance.apply()
523
535
  })
524
536
 
525
- function normalizeRoutePath(path) {
537
+ function normalizeRoutePath(path: string | null | undefined): string {
526
538
  return (path || '/').replace(/\/+$/, '') || '/'
527
539
  }
528
540
 
529
541
  // Highlight the current route. Longest-prefix matching keeps a detail page
530
542
  // such as /commerce/products/42 attached to its /commerce/products row while
531
543
  // preventing /health from matching /health-check.
532
- function syncActiveSidebarRow(url) {
544
+ function syncActiveSidebarRow(url?: string): void {
533
545
  let path = route.path
534
546
  if (url) {
535
547
  try {
@@ -566,7 +578,7 @@ function syncActiveSidebarRow(url) {
566
578
  }
567
579
  }
568
580
 
569
- function handleSidebarItemClick(payload) {
581
+ function handleSidebarItemClick(payload: { item?: { href?: string }, event?: { preventDefault: () => void } } | undefined): void {
570
582
  const href = payload?.item?.href
571
583
  if (!href || href.startsWith('http')) return
572
584
  payload.event?.preventDefault()
@@ -578,10 +590,10 @@ function syncMobileSidebar() {
578
590
  syncActiveSidebarRow()
579
591
  }
580
592
 
581
- function handleCraftSidebarSelection(event) {
593
+ function handleCraftSidebarSelection(event: CraftSidebarSelectEvent | undefined): void {
582
594
  if (!event?.itemId) return
583
595
  for (const pane of sidebarPanes()) {
584
- const row = [...pane.querySelectorAll('[data-sidebar-row]')].find(
596
+ const row = [...pane.querySelectorAll<HTMLElement>('[data-sidebar-row]')].find(
585
597
  candidate => candidate.dataset.itemId === event.itemId,
586
598
  )
587
599
  const link = row?.querySelector('a[data-sidebar-item][href]')
@@ -598,7 +610,7 @@ function attachCraftSidebarSelection() {
598
610
  removeCraftSidebarSelection = useCraftSidebarSelection(handleCraftSidebarSelection)
599
611
  }
600
612
 
601
- useEventListener('stx:navigate', event => syncActiveSidebarRow(event?.detail?.url))
613
+ useEventListener('stx:navigate', (event: Event) => syncActiveSidebarRow((event as CustomEvent<{ url?: string }>).detail?.url))
602
614
  useEventListener('stx:hydrated', () => syncActiveSidebarRow(), { once: true })
603
615
  useEventListener('craft:ready', () => {
604
616
  ensureCraftNativeSidebarMarker()
@@ -629,11 +641,14 @@ onDestroy(() => {
629
641
  @itemClick="handleSidebarItemClick($event)"
630
642
  >
631
643
  <template #header>
632
- <!-- Chrome strip. The traffic lights are native (the Craft window is
633
- titlebarHidden), so this reserves their space and acts as the window
634
- drag region; in a plain browser it is the same empty strip macOS
635
- sidebars carry above their first row. -->
636
- <SidebarHeader :showWindowControls="false" />
644
+ <!-- Chrome strip. The traffic lights are the platform's, so this draws
645
+ none and reserves whatever room the host says they need: 62px in a
646
+ titlebarHidden window where they float over this sidebar, nothing in
647
+ a window that keeps them in a titlebar, and nothing in a browser,
648
+ which has no window buttons at all. It is also the window drag
649
+ region, and in a browser it is the same empty strip macOS sidebars
650
+ carry above their first row. -->
651
+ <SidebarHeader windowControls="native" />
637
652
  </template>
638
653
  </Sidebar>
639
654
  </div>
@@ -649,7 +664,9 @@ onDestroy(() => {
649
664
  @itemClick="handleSidebarItemClick($event)"
650
665
  >
651
666
  <template #header>
652
- <SidebarHeader :show-window-controls="false" />
667
+ <!-- A drawer, not the window edge: no window buttons land on it, so
668
+ there is nothing to draw and nothing to reserve. -->
669
+ <SidebarHeader windowControls="none" />
653
670
  </template>
654
671
  </Sidebar>
655
672
  </div>