@stacksjs/defaults 0.70.354 → 0.70.356

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 (214) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +17 -2
  2. package/ai/skills/stacks-browse/scripts/browse.ts +207 -6
  3. package/ai/skills/stacks-email/SKILL.md +73 -16
  4. package/app/Actions/ContactAction.ts +2 -2
  5. package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +2 -3
  6. package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +2 -3
  7. package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +2 -3
  8. package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +9 -3
  9. package/app/Actions/Dashboard/Auth/MeAction.ts +12 -14
  10. package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
  11. package/app/Actions/Dashboard/Ci/RepoRunJobsAction.ts +10 -15
  12. package/app/Actions/Dashboard/Ci/RepoRunsAction.ts +12 -14
  13. package/app/Actions/Dashboard/Ci/RunnerHistoryAction.ts +11 -10
  14. package/app/Actions/Dashboard/Ci/StatusAction.ts +2 -11
  15. package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +8 -1
  16. package/app/Actions/Dashboard/Cloud/ServerlessIndexAction.ts +15 -8
  17. package/app/Actions/Dashboard/Commerce/CommerceCategoriesAction.ts +2 -4
  18. package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +2 -4
  19. package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +2 -4
  20. package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +2 -4
  21. package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +2 -4
  22. package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +2 -4
  23. package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +2 -4
  24. package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +2 -4
  25. package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +2 -4
  26. package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +76 -61
  27. package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
  28. package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +4 -7
  29. package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +4 -4
  30. package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +2 -4
  31. package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +2 -4
  32. package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +2 -4
  33. package/app/Actions/Dashboard/Commerce/DigitalDeliveryIndexAction.ts +2 -4
  34. package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +2 -4
  35. package/app/Actions/Dashboard/Commerce/LicenseKeyIndexAction.ts +2 -4
  36. package/app/Actions/Dashboard/Commerce/LicenseKeyOptionsAction.ts +2 -4
  37. package/app/Actions/Dashboard/Commerce/ManufacturerIndexAction.ts +2 -4
  38. package/app/Actions/Dashboard/Commerce/PaymentRefundAction.ts +1 -0
  39. package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +1 -0
  40. package/app/Actions/Dashboard/Commerce/ProductUnitIndexAction.ts +2 -4
  41. package/app/Actions/Dashboard/Commerce/ProductVariantIndexAction.ts +2 -4
  42. package/app/Actions/Dashboard/Commerce/ProductWaitlistIndexAction.ts +2 -4
  43. package/app/Actions/Dashboard/Commerce/RestaurantWaitlistIndexAction.ts +2 -4
  44. package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +2 -4
  45. package/app/Actions/Dashboard/Commerce/ShippingMethodIndexAction.ts +2 -4
  46. package/app/Actions/Dashboard/Commerce/ShippingRateIndexAction.ts +2 -4
  47. package/app/Actions/Dashboard/Commerce/ShippingZoneIndexAction.ts +2 -4
  48. package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +1 -0
  49. package/app/Actions/Dashboard/DashboardActivityAction.ts +2 -5
  50. package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +2 -5
  51. package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +2 -5
  52. package/app/Actions/Dashboard/Data/TeamIndexAction.ts +2 -5
  53. package/app/Actions/Dashboard/Data/UserIndexAction.ts +2 -5
  54. package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +26 -21
  55. package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +8 -2
  56. package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +3 -6
  57. package/app/Actions/Dashboard/Deployments/GetDeployment.ts +5 -5
  58. package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +7 -1
  59. package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +10 -9
  60. package/app/Actions/Dashboard/Deployments/GetDeployments.ts +2 -4
  61. package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +8 -2
  62. package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +21 -7
  63. package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
  64. package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
  65. package/app/Actions/Dashboard/Email/InboxActivityAction.ts +28 -16
  66. package/app/Actions/Dashboard/Email/InboxAttachmentDownloadAction.ts +45 -0
  67. package/app/Actions/Dashboard/Email/InboxDeleteAction.ts +11 -6
  68. package/app/Actions/Dashboard/Email/InboxIndexAction.ts +5 -7
  69. package/app/Actions/Dashboard/Email/InboxMarkReadAction.ts +11 -6
  70. package/app/Actions/Dashboard/Email/InboxMarkUnreadAction.ts +11 -6
  71. package/app/Actions/Dashboard/Email/InboxPreferenceShowAction.ts +10 -4
  72. package/app/Actions/Dashboard/Email/InboxPreferenceUpdateAction.ts +8 -9
  73. package/app/Actions/Dashboard/Email/InboxShowAction.ts +7 -7
  74. package/app/Actions/Dashboard/Email/InboxStatsAction.ts +5 -7
  75. package/app/Actions/Dashboard/Email/inbox-request.ts +33 -0
  76. package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
  77. package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +4 -11
  78. package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +3 -6
  79. package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +9 -18
  80. package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +4 -3
  81. package/app/Actions/Dashboard/Jobs/JobShowAction.ts +16 -11
  82. package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
  83. package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +7 -8
  84. package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +11 -19
  85. package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +6 -6
  86. package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +7 -8
  87. package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +3 -5
  88. package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +6 -6
  89. package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +13 -12
  90. package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +12 -15
  91. package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +21 -46
  92. package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +43 -0
  93. package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +7 -8
  94. package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +8 -9
  95. package/app/Actions/Dashboard/Kanban/CardShowAction.ts +6 -7
  96. package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +9 -9
  97. package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +7 -8
  98. package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +6 -6
  99. package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +7 -8
  100. package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +6 -6
  101. package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +7 -8
  102. package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +6 -6
  103. package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +6 -7
  104. package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +6 -6
  105. package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +7 -8
  106. package/app/Actions/Dashboard/Kanban/UsersListAction.ts +3 -4
  107. package/app/Actions/Dashboard/Kanban/kanban-comment.ts +14 -0
  108. package/app/Actions/Dashboard/Kanban/kanban-model.test.ts +36 -0
  109. package/app/Actions/Dashboard/Kanban/kanban-model.ts +14 -7
  110. package/app/Actions/Dashboard/Kanban/kanban-response.ts +5 -0
  111. package/app/Actions/Dashboard/Library/CreateComponent.ts +15 -6
  112. package/app/Actions/Dashboard/Library/CreateFunction.ts +15 -6
  113. package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +88 -77
  114. package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +21 -15
  115. package/app/Actions/Dashboard/Library/GetComponents.ts +14 -7
  116. package/app/Actions/Dashboard/Library/GetDownloadCount.ts +14 -5
  117. package/app/Actions/Dashboard/Library/GetFunctions.ts +12 -5
  118. package/app/Actions/Dashboard/Library/GetReleaseCount.ts +14 -5
  119. package/app/Actions/Dashboard/Library/GetReleases.ts +17 -10
  120. package/app/Actions/Dashboard/Library/PackageIndexAction.ts +14 -7
  121. package/app/Actions/Dashboard/Models/ModelDestroyAction.ts +11 -8
  122. package/app/Actions/Dashboard/Models/ModelShowAction.ts +97 -192
  123. package/app/Actions/Dashboard/Models/ModelStoreAction.ts +14 -12
  124. package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +18 -16
  125. package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +50 -19
  126. package/app/Actions/Dashboard/Models/model-api.ts +27 -0
  127. package/app/Actions/Dashboard/Models/model-write.ts +22 -15
  128. package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +49 -40
  129. package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +20 -10
  130. package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +96 -84
  131. package/app/Actions/Dashboard/Notifications/NotificationDeliveryRetryAction.ts +34 -21
  132. package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +2 -4
  133. package/app/Actions/Dashboard/Queries/QueryShowAction.ts +3 -3
  134. package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +2 -4
  135. package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +2 -4
  136. package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +6 -6
  137. package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +5 -4
  138. package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
  139. package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +6 -6
  140. package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +6 -6
  141. package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +6 -9
  142. package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +5 -4
  143. package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
  144. package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +6 -7
  145. package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +6 -13
  146. package/app/Actions/Dashboard/Rbac/UsersListAction.ts +2 -3
  147. package/app/Actions/Dashboard/Rbac/rbac-response.ts +10 -0
  148. package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +2 -4
  149. package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +69 -55
  150. package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +34 -16
  151. package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +52 -29
  152. package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +104 -73
  153. package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
  154. package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +41 -28
  155. package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +71 -58
  156. package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +29 -7
  157. package/app/Actions/Dashboard/Teams/team-member-count.ts +4 -2
  158. package/app/Actions/Dashboard/Teams/team-records.test.ts +2 -0
  159. package/app/Actions/Dashboard/Teams/team-records.ts +2 -0
  160. package/app/Actions/Dashboard/dashboard-request.ts +13 -0
  161. package/app/Actions/Dashboard/dashboard-response.ts +16 -0
  162. package/app/Actions/SendWelcomeEmail.ts +1 -1
  163. package/app/Actions/Teams/AcceptInvitationAction.ts +74 -51
  164. package/app/Actions/Teams/ShowInvitationAction.ts +21 -14
  165. package/app/Actions/Teams/team-response.ts +23 -0
  166. package/app/Mail/OrderConfirmation.ts +1 -1
  167. package/app/Mail/SubscriptionConfirmation.ts +1 -1
  168. package/app/Mail/TeamInvitation.ts +1 -1
  169. package/app/Models/CardComment.ts +3 -4
  170. package/app/Models/EmailIdempotency.ts +64 -0
  171. package/app/Models/EmailSuppression.ts +52 -0
  172. package/app/Models/EmailWebhookEvent.ts +51 -0
  173. package/app/Models/TeamInvitation.ts +13 -0
  174. package/functions/dashboard-api.ts +51 -10
  175. package/functions/data.ts +0 -1
  176. package/functions/inbox.test.ts +2 -0
  177. package/functions/inbox.ts +27 -2
  178. package/functions/jobs.ts +0 -1
  179. package/functions/public-application-url.test.ts +13 -0
  180. package/functions/public-application-url.ts +27 -0
  181. package/ide/vscode/package.json +1 -1
  182. package/package.json +1 -1
  183. package/resources/components/Dashboard/Auth/AccessTokens.stx +15 -0
  184. package/resources/components/Dashboard/Ci/CiDashboard.stx +24 -6
  185. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
  186. package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +8 -7
  187. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +8 -7
  188. package/resources/components/Dashboard/Content/BlogDashboard.stx +14 -2
  189. package/resources/components/Dashboard/Content/PostsDashboard.stx +7 -1
  190. package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
  191. package/resources/components/Dashboard/Deployments/DeploymentDetail.stx +16 -2
  192. package/resources/components/Dashboard/Email/EmailNavigation.stx +5 -5
  193. package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
  194. package/resources/components/Dashboard/Email/InboxAttachmentList.stx +53 -0
  195. package/resources/components/Dashboard/Email/InboxDashboard.stx +5 -4
  196. package/resources/components/Dashboard/Email/InboxMessageDetail.stx +6 -4
  197. package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
  198. package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +63 -6
  199. package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +3 -6
  200. package/resources/components/Dashboard/Management/PermissionsDashboard.stx +4 -4
  201. package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
  202. package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
  203. package/resources/components/Dashboard/NotFoundDashboard.stx +2 -5
  204. package/resources/components/Dashboard/Queries/QueryDashboard.stx +6 -2
  205. package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +4 -4
  206. package/resources/components/Dashboard/UI/EmptyState.stx +2 -2
  207. package/resources/components/Dashboard/UI/PageLayout.stx +3 -3
  208. package/resources/functions/dashboard/sidebar.ts +4 -4
  209. package/routes/dashboard-api.ts +8 -20
  210. package/views/dashboard/.discovered-models.json +54 -0
  211. package/views/dashboard/layouts/default.stx +19 -16
  212. package/views/dashboard/stores/auth.ts +15 -14
  213. package/views/dashboard/stores/ci.ts +13 -7
  214. package/views/dashboard/stores/kanban.ts +48 -11
@@ -121,7 +121,7 @@ async function openEmail(value: DashboardInboxEmail | CustomEvent<DashboardInbox
121
121
  deletePending.set(false)
122
122
  replyText.set('')
123
123
 
124
- if (!email.bodyHtml && !email.bodyText) {
124
+ if (!email.detailsLoaded) {
125
125
  try {
126
126
  const body = await fetchDashboardInboxBody(email.id, mailbox())
127
127
  emails.set(emails().map(item => item.id === email.id ? { ...item, ...body } : item))
@@ -228,7 +228,7 @@ onMount(() => {
228
228
  </script>
229
229
 
230
230
  <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
231
- <div class="flex min-h-[calc(100vh-8rem)]">
231
+ <div class="flex flex-col md:flex-row min-h-[calc(100vh-8rem)]">
232
232
  <EmailSidebar
233
233
  activeFolder="inbox"
234
234
  :folders="folders"
@@ -237,10 +237,10 @@ onMount(() => {
237
237
  @compose="compose"
238
238
  />
239
239
 
240
- <div class="flex overflow-hidden flex-1 flex-col">
240
+ <div class="flex overflow-hidden flex-1 flex-col min-w-0">
241
241
  <header class="px-4 py-3 bg-white dark:bg-neutral-700 border-b border-gray-200 dark:border-neutral-600">
242
242
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
243
- <div class="flex gap-4 items-center">
243
+ <div class="flex flex-col gap-2 sm:flex-row sm:gap-4 sm:items-center min-w-0">
244
244
  <h1 class="font-bold text-gray-900 text-xl dark:text-white">Inbox</h1>
245
245
  <EmailNavigation active="inbox" />
246
246
  </div>
@@ -277,6 +277,7 @@ onMount(() => {
277
277
  :replying="isReplying"
278
278
  :replyText="replyText"
279
279
  :canSendReply="canSendReply"
280
+ :mailbox="mailbox"
280
281
  @close="closeEmail"
281
282
  @toggle-read="toggleSelectedRead"
282
283
  @request-delete="requestDelete"
@@ -7,6 +7,7 @@ const deleting = useReactiveProp('deleting', false)
7
7
  const replying = useReactiveProp('replying', false)
8
8
  const replyText = useReactiveProp('replyText', '')
9
9
  const canSendReply = useReactiveProp('canSendReply', false)
10
+ const mailbox = useReactiveProp('mailbox', '')
10
11
  const emit = defineEmits()
11
12
 
12
13
  function message(): DashboardInboxEmail | null {
@@ -69,10 +70,11 @@ function formatDate(value: string): string {
69
70
  :text="message()?.bodyText || ''"
70
71
  :title="message()?.subject || 'Inbound email preview'"
71
72
  />
72
- <div :if="message()?.hasAttachments" class="flex gap-3 items-start mb-4 mx-4 p-3 text-gray-700 text-sm dark:text-neutral-300 bg-gray-50 dark:bg-neutral-800 border border-gray-200 rounded-md dark:border-neutral-600">
73
- <span class="flex-shrink-0 mt-0.5 h-5 w-5 i-hugeicons-attachment-01"></span>
74
- <p>This message includes attachments. Attachment downloads are not exposed by the inbox API yet.</p>
75
- </div>
73
+ <InboxAttachmentList
74
+ :attachments="message()?.attachments || []"
75
+ :mailbox="mailbox"
76
+ :messageId="message()?.id || ''"
77
+ />
76
78
  </div>
77
79
 
78
80
  <div class="p-4 bg-white dark:bg-neutral-700 border-gray-200 border-t dark:border-neutral-600">
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import type { DashboardJob } from '../../../../functions/jobs'
3
+ import { DashboardApiError } from '../../../../functions/dashboard-api'
3
4
  import { fetchJob, retryJob } from '../../../../functions/jobs'
4
5
  import { pushToast } from '../../../../functions/toasts'
5
6
 
@@ -53,11 +54,11 @@ async function loadJob(): Promise<void> {
53
54
  try {
54
55
  const response = await fetchJob(jobId())
55
56
  job.set(response.job)
56
- loadError.set(response.error || '')
57
57
  }
58
58
  catch (error) {
59
59
  job.set(null)
60
- loadError.set(error instanceof Error ? error.message : 'Job could not be loaded.')
60
+ if (!(error instanceof DashboardApiError && error.status === 404))
61
+ loadError.set(error instanceof Error ? error.message : 'Job could not be loaded.')
61
62
  }
62
63
  finally {
63
64
  isLoading.set(false)
@@ -1,5 +1,5 @@
1
1
  <script client>
2
- // Kanban board view (stacksjs/stacks#1846 Phase 2).
2
+ // Kanban board view.
3
3
  //
4
4
  // Optimistic-update pattern:
5
5
  // 1. The DnD handler mutates the store immediately (so the card
@@ -29,7 +29,7 @@
29
29
  const columns = derived(() => kanban.currentColumns())
30
30
  const allLabels = derived(() => kanban.currentLabels())
31
31
 
32
- // Card detail modal state read straight from the store. The
32
+ // Card detail modal state - read straight from the store. The
33
33
  // modal itself lives at the bottom of this file.
34
34
  const openCard = derived(() => kanban.openCard())
35
35
  const loadingCard = derived(() => kanban.loadingCard())
@@ -48,6 +48,9 @@
48
48
  const titleDraft = state('')
49
49
  const descDraft = state('')
50
50
  const commentDraft = state('')
51
+ const editingCommentId = state(null)
52
+ const commentEditDraft = state('')
53
+ const savingCommentId = state(null)
51
54
  const showLabelPicker = state(false)
52
55
  const showAssigneePicker = state(false)
53
56
  const showNewLabelComposer = state(false)
@@ -83,6 +86,9 @@
83
86
  descDraft.set(c ? (c.description || '') : '')
84
87
  dueDateDraft.set(c?.dueDate ? c.dueDate.slice(0, 10) : '')
85
88
  commentDraft.set('')
89
+ editingCommentId.set(null)
90
+ commentEditDraft.set('')
91
+ savingCommentId.set(null)
86
92
  }
87
93
  // Sync drafts when the open card changes (open / close / refetch).
88
94
  effect(() => { openCard(); Promise.resolve().then(syncDraftsFromCard) })
@@ -134,6 +140,40 @@
134
140
  if (ok) commentDraft.set('')
135
141
  }
136
142
 
143
+ function startCommentEdit(id, body) {
144
+ editingCommentId.set(id)
145
+ commentEditDraft.set(body)
146
+ }
147
+
148
+ function cancelCommentEdit() {
149
+ editingCommentId.set(null)
150
+ commentEditDraft.set('')
151
+ }
152
+
153
+ async function saveCommentEdit(id) {
154
+ const body = commentEditDraft().trim()
155
+ if (!body || savingCommentId()) return
156
+ savingCommentId.set(id)
157
+ try {
158
+ if (await kanban.updateComment(id, body))
159
+ cancelCommentEdit()
160
+ }
161
+ finally {
162
+ savingCommentId.set(null)
163
+ }
164
+ }
165
+
166
+ function handleCommentEditKeydown(event, id) {
167
+ if (event.key === 'Escape') {
168
+ event.preventDefault()
169
+ cancelCommentEdit()
170
+ }
171
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
172
+ event.preventDefault()
173
+ void saveCommentEdit(id)
174
+ }
175
+ }
176
+
137
177
  function requestDelete(kind: DeleteKind, id: number, hasCards = false): void {
138
178
  pendingDelete.set({ kind, id, hasCards })
139
179
  }
@@ -219,7 +259,7 @@
219
259
  }
220
260
 
221
261
  // Pull the id from the URL through the navigation composable.
222
- // (stacksjs/stacks#1838 no vanilla DOM in dashboard pages).
262
+ // (stacksjs/stacks#1838 - no vanilla DOM in dashboard pages).
223
263
  const id = useNavigation().urlIdParam()
224
264
  if (id !== null)
225
265
  kanban.loadBoard(id)
@@ -647,7 +687,7 @@
647
687
  </div>
648
688
  </div>
649
689
 
650
- <!-- ───────────── Card detail modal (Phase 3) ───────────── -->
690
+ <!-- Card detail modal -->
651
691
  <!-- Opens on card click. Fixed positioned with a backdrop; closes
652
692
  on backdrop click + Escape. Sections (top → bottom):
653
693
  · Title (inline-editable, blurs to commit)
@@ -739,9 +779,26 @@
739
779
  <div class="flex gap-2 items-baseline">
740
780
  <span class="font-medium text-xs text-zinc-700 dark:text-zinc-200" :text="cm.authorName || cm.authorEmail || 'Unknown'"></span>
741
781
  <span class="text-[10px] text-zinc-400" :text="formatTimestamp(cm.createdAt)"></span>
742
- <Button variant="ghost" size="xs" @click="requestDelete('comment', cm.id)"><span class="text-red-500">Delete</span></Button>
782
+ <template :if="editingCommentId() !== cm.id">
783
+ <Button variant="ghost" size="xs" @click="startCommentEdit(cm.id, cm.body)">Edit</Button>
784
+ <Button variant="ghost" size="xs" @click="requestDelete('comment', cm.id)"><span class="text-red-500">Delete</span></Button>
785
+ </template>
786
+ </div>
787
+ <p :if="editingCommentId() !== cm.id" class="mt-0.5 text-xs text-zinc-700 whitespace-pre-wrap dark:text-zinc-300" :text="cm.body"></p>
788
+ <div :if="editingCommentId() === cm.id" class="mt-2">
789
+ <textarea
790
+ x-model="commentEditDraft"
791
+ @keydown="handleCommentEditKeydown($event, cm.id)"
792
+ rows="3"
793
+ maxlength="10000"
794
+ :aria-label="'Edit comment by ' + (cm.authorName || cm.authorEmail || 'Unknown')"
795
+ class="px-3 py-2 w-full text-sm text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-950 border border-zinc-200 rounded dark:border-zinc-800 focus:border-violet-400 focus:outline-none dark:focus:border-violet-600 resize-y"
796
+ ></textarea>
797
+ <div class="flex gap-2 justify-end mt-2">
798
+ <Button variant="secondary" size="xs" :disabled="savingCommentId() === cm.id" @click="cancelCommentEdit()">Cancel</Button>
799
+ <Button size="xs" :loading="savingCommentId() === cm.id" :disabled="!commentEditDraft().trim()" @click="saveCommentEdit(cm.id)">Save comment</Button>
800
+ </div>
743
801
  </div>
744
- <p class="mt-0.5 text-xs text-zinc-700 whitespace-pre-wrap dark:text-zinc-300" :text="cm.body"></p>
745
802
  </div>
746
803
  </li>
747
804
  </ul>
@@ -1,12 +1,9 @@
1
1
  <script client>
2
- // Kanban — Boards index (stacksjs/stacks#1846, Phase 1).
3
- //
4
2
  // Lists every non-archived board with a card-count badge. Clicking a
5
- // board navigates to `/kanban/[id]` Phase 2 ships the actual
6
- // drag-and-drop board view at that URL.
3
+ // board navigates to its complete drag-and-drop view.
7
4
  //
8
5
  // Follows #1838 conventions: no <script server>, no vanilla DOM
9
- // reads, all state via the store. Role gate via useRole() the
6
+ // reads, all state via the store. Role gate via useRole() - the
10
7
  // surface is dev-mode-only (admins + devs see it, clients don't).
11
8
  import { useRole } from '~/storage/framework/defaults/views/dashboard/composables/useRole'
12
9
  import { kanbanStore } from '~/storage/framework/defaults/views/dashboard/stores/kanban'
@@ -175,7 +172,7 @@
175
172
  <p class="mt-2 text-xs text-zinc-500">Most common: migrations haven't run yet. Try <code>./buddy migrate</code>.</p>
176
173
  </div>
177
174
 
178
- <!-- Empty state DB reachable but no boards yet -->
175
+ <!-- Empty state - DB reachable but no boards yet -->
179
176
  <div :if="!loading && !error && !hasBoards" class="px-6 py-12 text-center border border-dashed border-zinc-300 rounded-lg dark:border-zinc-700">
180
177
  <span class="block mb-3 mx-auto h-10 w-10 text-zinc-400 i-hugeicons-layout-3-column"></span>
181
178
  <p class="font-medium text-sm text-zinc-700 dark:text-zinc-300">No boards yet</p>
@@ -5,7 +5,7 @@
5
5
  // and the role/permission matrix against the live `user_roles` /
6
6
  // `role_permissions` / `user_permissions` pivots. All persistence
7
7
  // flows through the dashboard RBAC endpoints, which delegate
8
- // to `@stacksjs/auth`'s `Rbac` facade no direct DB writes from the
8
+ // to `@stacksjs/auth`'s `Rbac` facade - no direct DB writes from the
9
9
  // page.
10
10
  //
11
11
  // Follows #1838 conventions:
@@ -13,13 +13,13 @@
13
13
  // - All state in the rbacStore; the page is pure presentation.
14
14
  // - Mutations are optimistic with rollback on failure (store handles
15
15
  // the snapshot/rollback dance).
16
- import { useStore } from '@stacksjs/stx'
17
16
  import { useRole } from '~/storage/framework/defaults/views/dashboard/composables/useRole'
17
+ import { rbacStore } from '~/storage/framework/defaults/views/dashboard/stores/rbac'
18
18
 
19
- const rbac = useStore('rbac')
19
+ const rbac = rbacStore
20
20
  // Admin-only surface. Per `useRole.ts`, `isAdmin()` returns true for
21
21
  // any user with the `admin` role *or* an unauthenticated viewer on
22
- // the localhost dev dashboard same permissive-dev defaults the rest
22
+ // the localhost dev dashboard - same permissive-dev defaults the rest
23
23
  // of the management surface uses.
24
24
  const { isAdmin } = useRole()
25
25
  const canManage = derived(() => isAdmin())
@@ -7,7 +7,7 @@
7
7
  // works the same on a table with 10 rows and one with a million. Rows and
8
8
  // writes go through the model, so scopes, casts and observers apply.
9
9
  import { useNavigation } from '~/storage/framework/defaults/views/dashboard/composables/useNavigation'
10
- import { dashboardApi } from '~/storage/framework/defaults/functions/dashboard-api'
10
+ import { DashboardApiError, dashboardApi } from '~/storage/framework/defaults/functions/dashboard-api'
11
11
  import { pushToast } from '~/storage/framework/defaults/functions/toasts'
12
12
 
13
13
  interface ColumnMeta {
@@ -46,7 +46,6 @@ interface ResponseShape {
46
46
  writable: boolean
47
47
  writeCapabilities: WriteCapabilities
48
48
  createFields: CreateField[]
49
- source: 'orm' | 'sqlite-fallback'
50
49
  error: string | null
51
50
  }
52
51
 
@@ -67,7 +66,6 @@ const search = state(navigation.urlQuery('q') || '')
67
66
  const filters = state<Record<string, string>>({})
68
67
  const writeCapabilities = state<WriteCapabilities>({ create: false, update: false, destroy: false })
69
68
  const createFields = state<CreateField[]>([])
70
- const source = state<'orm' | 'sqlite-fallback'>('orm')
71
69
  const error = state<string | null>(null)
72
70
  const loading = state(true)
73
71
  const showFilters = state(false)
@@ -86,14 +84,6 @@ const createErrors = state<Record<string, string[]>>({})
86
84
 
87
85
  const slug = derived(() => navigation.urlSegment(-1) || '')
88
86
 
89
- const sourceLabel = derived(() => {
90
- if (source() === 'orm')
91
- return 'via Stacks ORM'
92
- return Object.values(writeCapabilities()).some(Boolean)
93
- ? 'SQLite query, ORM writes'
94
- : 'raw SQLite, read-only'
95
- })
96
-
97
87
  const recordLabel = derived(() => {
98
88
  const n = totalCount()
99
89
  return `${n.toLocaleString()} ${n === 1 ? 'record' : 'records'}`
@@ -223,7 +213,6 @@ async function load() {
223
213
  sort.set(data.sort ?? '')
224
214
  writeCapabilities.set(data.writeCapabilities ?? { create: false, update: false, destroy: false })
225
215
  createFields.set(data.createFields ?? [])
226
- source.set(data.source ?? 'orm')
227
216
  error.set(data.error ?? null)
228
217
  }
229
218
  catch (e) {
@@ -319,6 +308,11 @@ async function createRecord(fields: Record<string, unknown>): Promise<void> {
319
308
  }
320
309
  catch (reason) {
321
310
  createError.set(reason instanceof Error ? reason.message : String(reason))
311
+ if (reason instanceof DashboardApiError && reason.fields) {
312
+ createErrors.set(Object.fromEntries(
313
+ Object.entries(reason.fields).map(([field, message]) => [field, [message]]),
314
+ ))
315
+ }
322
316
  }
323
317
  finally {
324
318
  creating.set(false)
@@ -402,7 +396,7 @@ void load()
402
396
  <div class="flex flex-wrap gap-4 items-start justify-between">
403
397
  <div class="min-w-0">
404
398
  <h1 class="font-semibold text-2xl text-neutral-900 truncate dark:text-white" :text="modelName()"></h1>
405
- <p class="mt-1 text-[13px] text-neutral-500 dark:text-neutral-400" :text="recordLabel() + ' · ' + tableName() + ' · ' + sourceLabel()"></p>
399
+ <p class="mt-1 text-[13px] text-neutral-500 dark:text-neutral-400" :text="recordLabel() + ' · ' + tableName() + ' · via Stacks ORM'"></p>
406
400
  </div>
407
401
  <div class="flex gap-2 items-center">
408
402
  <Button
@@ -129,7 +129,7 @@ onMount(() => {
129
129
 
130
130
  <div :if="!loadError() && unavailableModels() > 0" role="status" class="flex gap-3 items-start px-4 py-3 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/40 border border-amber-200 rounded-lg dark:border-amber-900">
131
131
  <span class="flex-none mt-0.5 h-4 w-4 i-hugeicons-alert-02"></span>
132
- <span>{{ unavailableModels() }} model {{ unavailableModels() === 1 ? 'count is' : 'counts are' }} unavailable. Affected cards show the query error instead of reporting zero records.</span>
132
+ <span>{{ unavailableModels() }} model {{ unavailableModels() === 1 ? 'count is' : 'counts are' }} unavailable. Affected cards remain unavailable instead of reporting zero records.</span>
133
133
  </div>
134
134
 
135
135
  <Card padding="none">
@@ -11,19 +11,16 @@
11
11
  Sorry, we couldn't find the page you're looking for. The page might have been moved or deleted.
12
12
  </p>
13
13
  <div class="flex gap-x-6 items-center justify-center mt-10">
14
- <Button tag="a" href="/dashboard">
14
+ <Button tag="a" href="/">
15
15
  Return to dashboard
16
16
  </Button>
17
- <Button tag="a" href="/" variant="ghost">
18
- Go to home <span aria-hidden="true">&rarr;</span>
19
- </Button>
20
17
  </div>
21
18
  </div>
22
19
 
23
20
  <div>
24
21
  <h2 class="font-semibold leading-6 text-base text-center text-gray-900 dark:text-white">You might be looking for</h2>
25
22
  <div class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 mt-6">
26
- <StxLink to="/dashboard" class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow hover:shadow-md duration-200 transition-shadow">
23
+ <StxLink to="/" class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow hover:shadow-md duration-200 transition-shadow">
27
24
  <div class="p-5">
28
25
  <div class="flex items-center">
29
26
  <div class="flex-shrink-0 p-3 bg-blue-500 rounded-md">
@@ -97,6 +97,10 @@ function formatDate(value: string): string {
97
97
  return Number.isNaN(date.getTime()) ? value : date.toLocaleString()
98
98
  }
99
99
 
100
+ function queryDetailsPath(id: DashboardQueryLog['id']): string {
101
+ return `/queries/${encodeURIComponent(String(id))}`
102
+ }
103
+
100
104
  function typeClass(value: DashboardQueryLog['type']): string {
101
105
  const classes: Record<DashboardQueryLog['type'], string> = {
102
106
  SELECT: 'text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40',
@@ -248,9 +252,9 @@ onMount(() => {
248
252
  </td>
249
253
  <td class="px-4 py-3.5 text-neutral-500 text-right text-xs whitespace-nowrap dark:text-neutral-400">{{ formatDate(query.executedAt) }}</td>
250
254
  <td class="pr-5 py-3.5 text-right">
251
- <StxLink :to="'/queries/' + query.id" :aria-label="'View query ' + query.id" class="inline-flex p-1.5 text-neutral-500 dark:text-neutral-400 hover:text-blue-600 dark:hover:text-blue-400 rounded-md">
255
+ <a :href="queryDetailsPath(query.id)" :aria-label="'View query ' + query.id" class="inline-flex p-1.5 text-neutral-500 dark:text-neutral-400 hover:text-blue-600 dark:hover:text-blue-400 rounded-md">
252
256
  <span class="h-4 w-4 i-hugeicons-arrow-right-01"></span>
253
- </StxLink>
257
+ </a>
254
258
  </td>
255
259
  </tr>
256
260
  </template>
@@ -1,6 +1,6 @@
1
1
  <script server>
2
2
  // The navigation the toggles below control. Read from the same builder the
3
- // layout uses, so the list can never drift from what the sidebar renders
3
+ // layout uses, so the list can never drift from what the sidebar renders -
4
4
  // including sections a project has switched off in config/dashboard.ts, which
5
5
  // simply are not here to toggle.
6
6
  import { buildWebSidebarSections } from '~/storage/framework/defaults/resources/functions/dashboard/sidebar'
@@ -29,7 +29,7 @@ const appearance = useAppearance()
29
29
  // see until you navigate away is the thing this is avoiding.
30
30
  const styleOptions = SIDEBAR_STYLE_OPTIONS
31
31
  const colorModes = COLOR_MODE_OPTIONS
32
- const sections = navSections
32
+ const sections = {{ navSections }}
33
33
 
34
34
  const sidebarStyle = appearance.sidebarStyle
35
35
  const colorMode = appearance.colorMode
@@ -58,9 +58,9 @@ function resetAll() {
58
58
  }
59
59
 
60
60
  // Selected state travels as a data attribute rather than as a swapped class
61
- // string. It began as a workaround Crosswind could not see class names that
61
+ // string. It began as a workaround - Crosswind could not see class names that
62
62
  // existed only inside a client-script expression, so those utilities were never
63
- // emitted and the control rendered unstyled and that gap is now closed
63
+ // emitted and the control rendered unstyled - and that gap is now closed
64
64
  // upstream. It stays because it is the better shape regardless: one selector
65
65
  // per state in the block below, rather than two class strings kept in sync by
66
66
  // hand inside a ternary.
@@ -19,9 +19,9 @@ const actionHref = useReactiveProp('actionHref', '')
19
19
  <p :if="description()" class="mb-6 max-w-sm text-neutral-500 text-sm dark:text-neutral-400" :text="description()"></p>
20
20
 
21
21
  <Button
22
- :if="actionLabel()"
22
+ :if="actionLabel() && actionHref()"
23
23
  tag="a"
24
- :href="actionHref() || '#'"
24
+ :href="actionHref()"
25
25
  >
26
26
  <span :if="actionIcon()" :class="actionIcon() + ' h-4 w-4'"></span>
27
27
  <span :text="actionLabel()"></span>
@@ -3,11 +3,11 @@ interface PageLayoutProps {
3
3
  /**
4
4
  * Visual variant.
5
5
  *
6
- * - `default` standard centered column with `mx-auto` + max-width
6
+ * - `default` - standard centered column with `mx-auto` + max-width
7
7
  * gutter. Use for almost every page.
8
- * - `centered` vertically centered hero block on a min-height
8
+ * - `centered` - vertically centered hero block on a min-height
9
9
  * container. Use for splash / 404 / empty-route pages.
10
- * - `bare` no container at all. Use when the page wants to own
10
+ * - `bare` - no container at all. Use when the page wants to own
11
11
  * its own gutter (e.g. a settings page with side-by-side panels
12
12
  * that already pad themselves).
13
13
  */
@@ -428,11 +428,11 @@ export function buildNavSections(
428
428
  // The layout's role map hides the row from client-role viewers.
429
429
  { to: '/kanban', icon: 'table', text: 'Kanban', roles: ['admin', 'dev'] },
430
430
  ]
431
- // CI tracking (stacksjs/stacks#1844) opt-in via `ci.enabled` in
432
- // config/dashboard.ts. The page additionally checks `useRole().isDev()`
433
- // (stub for #1843) before rendering.
431
+ // CI tracking (stacksjs/stacks#1844) - opt-in via `ci.enabled` in
432
+ // config/dashboard.ts. Keep the row gate aligned with the page's
433
+ // `useRole().isDev()` check so client-only viewers never get a dead link.
434
434
  if (toggles.ci)
435
- managementItems.push({ to: '/ci', icon: 'check-circle', text: 'CI' })
435
+ managementItems.push({ to: '/ci', icon: 'check-circle', text: 'CI', roles: ['dev'] })
436
436
  sections.push(['management', 'management', managementItems])
437
437
  }
438
438
 
@@ -264,28 +264,15 @@ route.group({ prefix: '/api/dashboard', apiResponse: true }, () => {
264
264
  // requests — see the action for the soft-fallback shape.
265
265
  route.get('/auth/me', 'Actions/Dashboard/Auth/MeAction')
266
266
 
267
- // Kanban surface (stacksjs/stacks#1846). Phase 1: read-only.
268
- //
269
- // Phase 2 lands write endpoints under the same prefix:
270
- // POST /kanban/boards — store
271
- // PATCH /kanban/boards/{id} — update
272
- // DELETE /kanban/boards/{id} — destroy
273
- // POST /kanban/boards/reorder — bulk position update
274
- // POST /kanban/columns — store
275
- // PATCH /kanban/columns/{id} — update
276
- // POST /kanban/columns/reorder — bulk position update
277
- // POST /kanban/cards — store
278
- // PATCH /kanban/cards/{id} — update (incl. column move)
279
- // POST /kanban/cards/reorder — bulk position update
280
- //
267
+ // Kanban reads and mutations share the guarded dashboard surface.
281
268
  // Kanban data is local-only without authentication. Outside local
282
269
  // environments every read and write uses the same auth + admin guard as
283
270
  // the other operational dashboard surfaces.
284
- // Reads (Phase 1)
271
+ // Reads
285
272
  guard(route.get('/kanban/boards', 'Actions/Dashboard/Kanban/BoardsIndexAction'))
286
273
  guard(route.get('/kanban/boards/{id}', 'Actions/Dashboard/Kanban/BoardShowAction'))
287
274
 
288
- // Writes (Phase 2). The reorder endpoints are POST not PATCH because
275
+ // Writes. The reorder endpoints are POST not PATCH because
289
276
  // their semantics — "here's the full new state of this slice of the
290
277
  // board" — match the resource-replacement intent better than PATCH's
291
278
  // "apply this delta" verb. They also accept a body shape that PATCH
@@ -305,7 +292,7 @@ route.group({ prefix: '/api/dashboard', apiResponse: true }, () => {
305
292
  guard(route.delete('/kanban/cards/{id}', 'Actions/Dashboard/Kanban/CardDestroyAction'))
306
293
  guard(route.post('/kanban/cards/reorder', 'Actions/Dashboard/Kanban/CardsReorderAction'))
307
294
 
308
- // Phase 3 — card detail + labels + assignees + comments.
295
+ // Card detail, labels, assignees, and comments.
309
296
  //
310
297
  // The card-show endpoint is the only "single card with everything"
311
298
  // read; the boards/{id} response already embeds labels + assignees
@@ -325,10 +312,10 @@ route.group({ prefix: '/api/dashboard', apiResponse: true }, () => {
325
312
  guard(route.post('/kanban/cards/{id}/labels', 'Actions/Dashboard/Kanban/CardLabelsSyncAction'))
326
313
  guard(route.post('/kanban/cards/{id}/assignees', 'Actions/Dashboard/Kanban/CardAssigneesSyncAction'))
327
314
 
328
- // Comments. Append-only thread: store + destroy, no edit yet — the
329
- // history-preservation argument outweighs the "fix a typo" argument
330
- // until someone explicitly asks for editing.
315
+ // Card-scoped comment thread. Writes stay under the dashboard guard while
316
+ // the CardComment model also exposes its conventional generated API.
331
317
  guard(route.post('/kanban/cards/{id}/comments', 'Actions/Dashboard/Kanban/CardCommentStoreAction'))
318
+ guard(route.patch('/kanban/comments/{id}', 'Actions/Dashboard/Kanban/CardCommentUpdateAction'))
332
319
  guard(route.delete('/kanban/comments/{id}', 'Actions/Dashboard/Kanban/CardCommentDestroyAction'))
333
320
 
334
321
  // Lightweight user list for the assignee picker. Distinct from the
@@ -466,6 +453,7 @@ route.group({ prefix: '/api/dashboard', apiResponse: true }, () => {
466
453
  // the per-mailbox inbox.json index. Both are guarded outside local/dev
467
454
  // because they touch real mailbox state and PII.
468
455
  guard(route.get('/email/inbox', 'Actions/Dashboard/Email/InboxIndexAction'))
456
+ guard(route.get('/email/inbox/{id}/attachments/{attachmentId}', 'Actions/Dashboard/Email/InboxAttachmentDownloadAction'))
469
457
  guard(route.get('/email/inbox/{id}', 'Actions/Dashboard/Email/InboxShowAction'))
470
458
  guard(route.get('/email/stats', 'Actions/Dashboard/Email/InboxStatsAction'))
471
459
  guard(route.get('/email/activity', 'Actions/Dashboard/Email/InboxActivityAction'))
@@ -138,6 +138,16 @@
138
138
  "highlight": true
139
139
  }
140
140
  },
141
+ {
142
+ "id": "delivery-stop",
143
+ "name": "DeliveryStop",
144
+ "icon": "tablecells.fill",
145
+ "hasDedicatedPage": false,
146
+ "category": "commerce",
147
+ "dashboard": {
148
+ "highlight": true
149
+ }
150
+ },
141
151
  {
142
152
  "id": "deployment",
143
153
  "name": "Deployment",
@@ -168,6 +178,23 @@
168
178
  "highlight": true
169
179
  }
170
180
  },
181
+ {
182
+ "id": "driver-ping",
183
+ "name": "DriverPing",
184
+ "icon": "tablecells.fill",
185
+ "hasDedicatedPage": false,
186
+ "category": "commerce"
187
+ },
188
+ {
189
+ "id": "email-idempotency",
190
+ "name": "EmailIdempotency",
191
+ "icon": "tablecells.fill",
192
+ "hasDedicatedPage": false,
193
+ "category": "data",
194
+ "dashboard": {
195
+ "enabled": false
196
+ }
197
+ },
171
198
  {
172
199
  "id": "email-list",
173
200
  "name": "EmailList",
@@ -182,6 +209,26 @@
182
209
  "hasDedicatedPage": false,
183
210
  "category": "data"
184
211
  },
212
+ {
213
+ "id": "email-suppression",
214
+ "name": "EmailSuppression",
215
+ "icon": "tablecells.fill",
216
+ "hasDedicatedPage": false,
217
+ "category": "data",
218
+ "dashboard": {
219
+ "enabled": false
220
+ }
221
+ },
222
+ {
223
+ "id": "email-webhook-event",
224
+ "name": "EmailWebhookEvent",
225
+ "icon": "tablecells.fill",
226
+ "hasDedicatedPage": false,
227
+ "category": "data",
228
+ "dashboard": {
229
+ "enabled": false
230
+ }
231
+ },
185
232
  {
186
233
  "id": "gift-card",
187
234
  "name": "GiftCard",
@@ -456,6 +503,13 @@
456
503
  "highlight": true
457
504
  }
458
505
  },
506
+ {
507
+ "id": "social-account",
508
+ "name": "SocialAccount",
509
+ "icon": "tablecells.fill",
510
+ "hasDedicatedPage": false,
511
+ "category": "data"
512
+ },
459
513
  {
460
514
  "id": "social-post",
461
515
  "name": "SocialPost",