@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
@@ -1,9 +1,10 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  /**
6
- * `DELETE /api/dashboard/kanban/columns/:id` (stacksjs/stacks#1846 Phase 2).
7
+ * `DELETE /api/dashboard/kanban/columns/:id`.
7
8
  *
8
9
  * Hard-deletes the column and cascade-cleans every card inside it
9
10
  * (plus the pivot rows referencing those cards). Same convention as
@@ -19,9 +20,8 @@ export default new Action({
19
20
  description: 'Hard-deletes a column and its cards.',
20
21
  method: 'DELETE',
21
22
  apiResponse: true,
22
- async handle(request) {
23
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
24
- const id = Number(rawId)
23
+ async handle(request: RequestInstance) {
24
+ const id = Number(request.getParam('id'))
25
25
  if (!Number.isFinite(id) || id <= 0) {
26
26
  return kanbanError('Invalid column id', 400)
27
27
  }
@@ -37,7 +37,7 @@ export default new Action({
37
37
  'DELETE FROM card_assignees WHERE card_id IN (SELECT id FROM cards WHERE column_id = ?)',
38
38
  [id],
39
39
  ).execute()
40
- // Card comments (Phase 3, stacksjs/stacks#1846).
40
+ // Card comments are card-scoped children.
41
41
  await qb.unsafe(
42
42
  'DELETE FROM card_comments WHERE card_id IN (SELECT id FROM cards WHERE column_id = ?)',
43
43
  [id],
@@ -48,8 +48,7 @@ export default new Action({
48
48
  return { deleted: true, id }
49
49
  }
50
50
  catch (err) {
51
- console.error('[dashboard/kanban] ColumnDestroyAction failed:', err)
52
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
51
+ return kanbanActionError(err, 'ColumnDestroyAction')
53
52
  }
54
53
  },
55
54
  })
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
4
  import { Board, BoardColumn } from '@stacksjs/orm'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface ColumnInput {
7
8
  boardId?: unknown
@@ -11,7 +12,7 @@ interface ColumnInput {
11
12
  }
12
13
 
13
14
  /**
14
- * `POST /api/dashboard/kanban/columns` (stacksjs/stacks#1846 Phase 2).
15
+ * `POST /api/dashboard/kanban/columns`.
15
16
  *
16
17
  * Appends a new column to the end of the target board. Validates that
17
18
  * `boardId` points at a real board so a typo doesn't silently create
@@ -22,8 +23,8 @@ export default new Action({
22
23
  description: 'Creates a new column on a board.',
23
24
  method: 'POST',
24
25
  apiResponse: true,
25
- async handle(request) {
26
- const body = (request as any).jsonBody as ColumnInput | undefined ?? {}
26
+ async handle(request: RequestInstance<ColumnInput>) {
27
+ const body = request.all()
27
28
 
28
29
  const boardId = Number(body.boardId)
29
30
  if (!Number.isFinite(boardId) || boardId <= 0) {
@@ -74,8 +75,7 @@ export default new Action({
74
75
  }
75
76
  }
76
77
  catch (err) {
77
- console.error('[dashboard/kanban] ColumnStoreAction failed:', err)
78
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
78
+ return kanbanActionError(err, 'ColumnStoreAction')
79
79
  }
80
80
  },
81
81
  })
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { BoardColumn } from '@stacksjs/orm'
3
4
  import { modelNullableString, modelNumber, modelString, modelValue, refreshModel } from './kanban-model'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface ColumnInput {
7
8
  name?: unknown
@@ -10,7 +11,7 @@ interface ColumnInput {
10
11
  }
11
12
 
12
13
  /**
13
- * `PATCH /api/dashboard/kanban/columns/:id` (stacksjs/stacks#1846 Phase 2).
14
+ * `PATCH /api/dashboard/kanban/columns/:id`.
14
15
  *
15
16
  * Partial update for name / color / card limit. `position` and
16
17
  * `boardId` are NOT updatable here — moving a column between boards
@@ -23,14 +24,13 @@ export default new Action({
23
24
  description: 'Partial update of a board column.',
24
25
  method: 'PATCH',
25
26
  apiResponse: true,
26
- async handle(request) {
27
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
28
- const id = Number(rawId)
27
+ async handle(request: RequestInstance<ColumnInput>) {
28
+ const id = Number(request.getParam('id'))
29
29
  if (!Number.isFinite(id) || id <= 0) {
30
30
  return kanbanError('Invalid column id', 400)
31
31
  }
32
32
 
33
- const body = (request as any).jsonBody as ColumnInput | undefined ?? {}
33
+ const body = request.all()
34
34
  const set: Record<string, unknown> = {}
35
35
 
36
36
  if (typeof body.name === 'string') {
@@ -80,8 +80,7 @@ export default new Action({
80
80
  }
81
81
  }
82
82
  catch (err) {
83
- console.error('[dashboard/kanban] ColumnUpdateAction failed:', err)
84
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
83
+ return kanbanActionError(err, 'ColumnUpdateAction')
85
84
  }
86
85
  },
87
86
  })
@@ -1,6 +1,7 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  interface ReorderInput {
6
7
  boardId?: unknown
@@ -8,7 +9,7 @@ interface ReorderInput {
8
9
  }
9
10
 
10
11
  /**
11
- * `POST /api/dashboard/kanban/columns/reorder` (stacksjs/stacks#1846 Phase 2).
12
+ * `POST /api/dashboard/kanban/columns/reorder`.
12
13
  *
13
14
  * Bulk rewrite column positions for a board. The submitted `order`
14
15
  * is an array of column ids in their new display order; index becomes
@@ -27,8 +28,8 @@ export default new Action({
27
28
  description: 'Bulk-rewrite `position` on a board\'s columns.',
28
29
  method: 'POST',
29
30
  apiResponse: true,
30
- async handle(request) {
31
- const body = (request as any).jsonBody as ReorderInput | undefined ?? {}
31
+ async handle(request: RequestInstance<ReorderInput>) {
32
+ const body = request.all()
32
33
 
33
34
  const boardId = Number(body.boardId)
34
35
  if (!Number.isFinite(boardId) || boardId <= 0) {
@@ -74,8 +75,7 @@ export default new Action({
74
75
  return { reordered: ids.length }
75
76
  }
76
77
  catch (err) {
77
- console.error('[dashboard/kanban] ColumnsReorderAction failed:', err)
78
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
78
+ return kanbanActionError(err, 'ColumnsReorderAction')
79
79
  }
80
80
  },
81
81
  })
@@ -1,9 +1,10 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  /**
6
- * `DELETE /api/dashboard/kanban/labels/:id` (stacksjs/stacks#1846 Phase 3).
7
+ * `DELETE /api/dashboard/kanban/labels/:id`.
7
8
  *
8
9
  * Removes a label and detaches it from every card that carries it.
9
10
  * The label row goes from the `labels` table; every pivot row in
@@ -15,9 +16,8 @@ export default new Action({
15
16
  description: 'Hard-deletes a label and its card_labels pivot rows.',
16
17
  method: 'DELETE',
17
18
  apiResponse: true,
18
- async handle(request) {
19
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
20
- const id = Number(rawId)
19
+ async handle(request: RequestInstance) {
20
+ const id = Number(request.getParam('id'))
21
21
  if (!Number.isFinite(id) || id <= 0)
22
22
  return kanbanError('Invalid label id', 400)
23
23
 
@@ -30,8 +30,7 @@ export default new Action({
30
30
  return { deleted: true, id }
31
31
  }
32
32
  catch (err) {
33
- console.error('[dashboard/kanban] LabelDestroyAction failed:', err)
34
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
33
+ return kanbanActionError(err, 'LabelDestroyAction')
35
34
  }
36
35
  },
37
36
  })
@@ -1,6 +1,7 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { Board, Label } from '@stacksjs/orm'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  interface LabelInput {
6
7
  boardId?: unknown
@@ -9,7 +10,7 @@ interface LabelInput {
9
10
  }
10
11
 
11
12
  /**
12
- * `POST /api/dashboard/kanban/labels` (stacksjs/stacks#1846 Phase 3).
13
+ * `POST /api/dashboard/kanban/labels`.
13
14
  *
14
15
  * Creates a label scoped to a board. Labels are unique per
15
16
  * `(board_id, name)` — the migration enforces this via a unique
@@ -21,8 +22,8 @@ export default new Action({
21
22
  description: 'Creates a label on a board.',
22
23
  method: 'POST',
23
24
  apiResponse: true,
24
- async handle(request) {
25
- const body = (request as any).jsonBody as LabelInput | undefined ?? {}
25
+ async handle(request: RequestInstance<LabelInput>) {
26
+ const body = request.all()
26
27
 
27
28
  const boardId = Number(body.boardId)
28
29
  if (!Number.isFinite(boardId) || boardId <= 0) {
@@ -50,8 +51,7 @@ export default new Action({
50
51
  }
51
52
  }
52
53
  catch (err) {
53
- console.error('[dashboard/kanban] LabelStoreAction failed:', err)
54
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
54
+ return kanbanActionError(err, 'LabelStoreAction')
55
55
  }
56
56
  },
57
57
  })
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { Label } from '@stacksjs/orm'
3
4
  import { modelNumber, modelString, refreshModel } from './kanban-model'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface LabelInput {
7
8
  name?: unknown
@@ -9,7 +10,7 @@ interface LabelInput {
9
10
  }
10
11
 
11
12
  /**
12
- * `PATCH /api/dashboard/kanban/labels/:id` (stacksjs/stacks#1846 Phase 3).
13
+ * `PATCH /api/dashboard/kanban/labels/:id`.
13
14
  *
14
15
  * Partial update for label name + color. `board_id` is not movable —
15
16
  * a label on board A renaming to "Bug" must not collide with a
@@ -21,14 +22,13 @@ export default new Action({
21
22
  description: 'Partial update of a label name / color.',
22
23
  method: 'PATCH',
23
24
  apiResponse: true,
24
- async handle(request) {
25
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
26
- const id = Number(rawId)
25
+ async handle(request: RequestInstance<LabelInput>) {
26
+ const id = Number(request.getParam('id'))
27
27
  if (!Number.isFinite(id) || id <= 0) {
28
28
  return kanbanError('Invalid label id', 400)
29
29
  }
30
30
 
31
- const body = (request as any).jsonBody as LabelInput | undefined ?? {}
31
+ const body = request.all()
32
32
  const set: Record<string, unknown> = {}
33
33
  let renamingTo: string | null = null
34
34
 
@@ -74,8 +74,7 @@ export default new Action({
74
74
  }
75
75
  }
76
76
  catch (err) {
77
- console.error('[dashboard/kanban] LabelUpdateAction failed:', err)
78
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
77
+ return kanbanActionError(err, 'LabelUpdateAction')
79
78
  }
80
79
  },
81
80
  })
@@ -1,9 +1,9 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
3
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
4
 
5
5
  /**
6
- * `GET /api/dashboard/kanban/users` (stacksjs/stacks#1846 Phase 3).
6
+ * `GET /api/dashboard/kanban/users`.
7
7
  *
8
8
  * Lightweight user list for the assignee picker on the card detail
9
9
  * modal. Returns `{ id, name, email }` only — no roles, no profile,
@@ -30,8 +30,7 @@ export default new Action({
30
30
  }
31
31
  }
32
32
  catch (err) {
33
- console.error('[dashboard/kanban] UsersListAction failed:', err)
34
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
33
+ return kanbanActionError(err, 'UsersListAction')
35
34
  }
36
35
  },
37
36
  })
@@ -0,0 +1,14 @@
1
+ import { modelNullableNumber, modelNullableString, modelNumber, modelString } from './kanban-model'
2
+
3
+ export function cardCommentResponse(record: object, author?: object | null) {
4
+ return {
5
+ id: modelNumber(record, 'id'),
6
+ uuid: modelNullableString(record, 'uuid'),
7
+ userId: modelNullableNumber(record, 'userId', 'user_id'),
8
+ body: modelString(record, 'body'),
9
+ authorName: author ? modelNullableString(author, 'name') : null,
10
+ authorEmail: author ? modelNullableString(author, 'email') : null,
11
+ createdAt: modelNullableString(record, 'createdAt', 'created_at'),
12
+ updatedAt: modelNullableString(record, 'updatedAt', 'updated_at'),
13
+ }
14
+ }
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
+ import { cardCommentResponse } from './kanban-comment'
2
3
  import {
3
4
  modelBoolean,
5
+ modelNullableNumber,
4
6
  modelNullableString,
5
7
  modelNumber,
6
8
  modelString,
@@ -31,12 +33,26 @@ describe('kanban model response values', () => {
31
33
  })
32
34
 
33
35
  test('normalizes nullable strings and persisted booleans', () => {
36
+ expect(modelNullableNumber(record({ value: null }), 'value')).toBeNull()
37
+ expect(modelNullableNumber(record({ value: '42' }), 'value')).toBe(42)
34
38
  expect(modelNullableString(record({ value: null }), 'value')).toBeNull()
35
39
  expect(modelBoolean(record({ enabled: 1 }), 'enabled')).toBe(true)
36
40
  expect(modelBoolean(record({ enabled: 'true' }), 'enabled')).toBe(true)
37
41
  expect(modelBoolean(record({ enabled: 0 }), 'enabled')).toBe(false)
38
42
  })
39
43
 
44
+ test('reads raw query rows as well as ORM model records', () => {
45
+ const row = {
46
+ board_id: 4,
47
+ archived: 1,
48
+ created_at: '2026-08-10 12:00:00',
49
+ }
50
+
51
+ expect(modelNumber(row, 'boardId', 'board_id')).toBe(4)
52
+ expect(modelBoolean(row, 'archived')).toBe(true)
53
+ expect(modelString(row, 'createdAt', 'created_at')).toBe('2026-08-10 12:00:00')
54
+ })
55
+
40
56
  test('refreshes generated database values after a write', async () => {
41
57
  const stale = {
42
58
  ...record({ updated_at: null }),
@@ -46,4 +62,24 @@ describe('kanban model response values', () => {
46
62
  const refreshed = await refreshModel(stale)
47
63
  expect(modelString(refreshed, 'updatedAt', 'updated_at')).toBe('2026-07-31 04:00:00')
48
64
  })
65
+
66
+ test('serializes model-backed comments with their author', () => {
67
+ expect(cardCommentResponse(record({
68
+ id: 7,
69
+ uuid: 'comment-uuid',
70
+ userId: 3,
71
+ body: 'Updated note',
72
+ createdAt: '2026-08-10 20:00:00',
73
+ updatedAt: '2026-08-10 21:00:00',
74
+ }), record({ name: 'Chris', email: 'chris@example.com' }))).toEqual({
75
+ id: 7,
76
+ uuid: 'comment-uuid',
77
+ userId: 3,
78
+ body: 'Updated note',
79
+ authorName: 'Chris',
80
+ authorEmail: 'chris@example.com',
81
+ createdAt: '2026-08-10 20:00:00',
82
+ updatedAt: '2026-08-10 21:00:00',
83
+ })
84
+ })
49
85
  })
@@ -10,28 +10,35 @@ export async function refreshModel(record: RefreshableKanbanModelRecord): Promis
10
10
  return await record.fresh() ?? record
11
11
  }
12
12
 
13
- export function modelValue(record: KanbanModelRecord, camelKey: string, snakeKey = camelKey): unknown {
14
- const camelValue = record.get(camelKey)
13
+ export function modelValue(record: object, camelKey: string, snakeKey = camelKey): unknown {
14
+ const model = record as Partial<KanbanModelRecord>
15
+ const row = record as Record<string, unknown>
16
+ const camelValue = typeof model.get === 'function' ? model.get(camelKey) : row[camelKey]
15
17
  if (camelValue !== undefined)
16
18
  return camelValue
17
- return record.get(snakeKey)
19
+ return typeof model.get === 'function' ? model.get(snakeKey) : row[snakeKey]
18
20
  }
19
21
 
20
- export function modelNumber(record: KanbanModelRecord, camelKey: string, snakeKey = camelKey): number {
22
+ export function modelNumber(record: object, camelKey: string, snakeKey = camelKey): number {
21
23
  return Number(modelValue(record, camelKey, snakeKey))
22
24
  }
23
25
 
24
- export function modelString(record: KanbanModelRecord, camelKey: string, snakeKey = camelKey): string {
26
+ export function modelNullableNumber(record: object, camelKey: string, snakeKey = camelKey): number | null {
27
+ const value = modelValue(record, camelKey, snakeKey)
28
+ return value === undefined || value === null ? null : Number(value)
29
+ }
30
+
31
+ export function modelString(record: object, camelKey: string, snakeKey = camelKey): string {
25
32
  const value = modelValue(record, camelKey, snakeKey)
26
33
  return value === undefined || value === null ? '' : String(value)
27
34
  }
28
35
 
29
- export function modelNullableString(record: KanbanModelRecord, camelKey: string, snakeKey = camelKey): string | null {
36
+ export function modelNullableString(record: object, camelKey: string, snakeKey = camelKey): string | null {
30
37
  const value = modelValue(record, camelKey, snakeKey)
31
38
  return value === undefined || value === null ? null : String(value)
32
39
  }
33
40
 
34
- export function modelBoolean(record: KanbanModelRecord, camelKey: string, snakeKey = camelKey): boolean {
41
+ export function modelBoolean(record: object, camelKey: string, snakeKey = camelKey): boolean {
35
42
  const value = modelValue(record, camelKey, snakeKey)
36
43
  return value === true || value === 1 || value === '1' || value === 'true'
37
44
  }
@@ -3,3 +3,8 @@ import { response } from '@stacksjs/router'
3
3
  export function kanbanError(error: string, status: number): Response {
4
4
  return response.json({ error }, status)
5
5
  }
6
+
7
+ export function kanbanActionError(error: unknown, action: string): Response {
8
+ console.error(`[dashboard/kanban] ${action} failed:`, error)
9
+ return kanbanError('The Kanban request could not be completed.', 500)
10
+ }
@@ -3,12 +3,14 @@ import { existsSync } from 'node:fs'
3
3
  import { Action, createComponent as scaffoldComponent } from '@stacksjs/actions'
4
4
  import { userComponentsPath } from '@stacksjs/path'
5
5
  import { response } from '@stacksjs/router'
6
+ import { dashboardOperationalError } from '../dashboard-response'
6
7
  import { componentSourceRows } from './library-source'
7
8
 
8
9
  export default new Action({
9
10
  name: 'CreateComponent',
10
11
  description: 'Creates a new STX component.',
11
12
  method: 'POST',
13
+ apiResponse: true,
12
14
 
13
15
  async handle(request: RequestInstance) {
14
16
  const name = String(request.get('name') || '').trim()
@@ -16,13 +18,20 @@ export default new Action({
16
18
  if (!/^[A-Z][A-Za-z0-9]*$/.test(name))
17
19
  return response.json({ message: 'Use a PascalCase component name.' }, 422)
18
20
 
19
- const destination = userComponentsPath(`${name}.stx`)
20
- if (existsSync(destination))
21
- return response.json({ message: 'A component with that name already exists.' }, 409)
21
+ try {
22
+ const destination = userComponentsPath(`${name}.stx`)
23
+ if (existsSync(destination))
24
+ return response.json({ message: 'A component with that name already exists.' }, 409)
22
25
 
23
- await scaffoldComponent({ name })
26
+ await scaffoldComponent({ name })
24
27
 
25
- const created = componentSourceRows().find(component => component.name === name)
26
- return response.json({ component: created }, 201)
28
+ const created = componentSourceRows().find(component => component.name === name)
29
+ if (!created)
30
+ throw new Error(`Scaffolded component ${name} could not be found in resources/components.`)
31
+ return response.json({ component: created }, 201)
32
+ }
33
+ catch (error) {
34
+ return dashboardOperationalError(error, 'The component could not be created.', 'CreateComponent', 500)
35
+ }
27
36
  },
28
37
  })
@@ -3,12 +3,14 @@ import { existsSync } from 'node:fs'
3
3
  import { Action, createFunction as scaffoldFunction } from '@stacksjs/actions'
4
4
  import { userFunctionsPath } from '@stacksjs/path'
5
5
  import { response } from '@stacksjs/router'
6
+ import { dashboardOperationalError } from '../dashboard-response'
6
7
  import { functionSourceRows } from './library-source'
7
8
 
8
9
  export default new Action({
9
10
  name: 'CreateFunction',
10
11
  description: 'Creates a new function.',
11
12
  method: 'POST',
13
+ apiResponse: true,
12
14
 
13
15
  async handle(request: RequestInstance) {
14
16
  const name = String(request.get('name') || '').trim().toLowerCase()
@@ -16,13 +18,20 @@ export default new Action({
16
18
  if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(name))
17
19
  return response.json({ message: 'Use a lowercase kebab-case function name.' }, 422)
18
20
 
19
- const destination = userFunctionsPath(`${name}.ts`)
20
- if (existsSync(destination))
21
- return response.json({ message: 'A function with that name already exists.' }, 409)
21
+ try {
22
+ const destination = userFunctionsPath(`${name}.ts`)
23
+ if (existsSync(destination))
24
+ return response.json({ message: 'A function with that name already exists.' }, 409)
22
25
 
23
- await scaffoldFunction({ name })
26
+ await scaffoldFunction({ name })
24
27
 
25
- const created = functionSourceRows().find(fn => fn.name === name)
26
- return response.json({ function: created }, 201)
28
+ const created = functionSourceRows().find(fn => fn.name === name)
29
+ if (!created)
30
+ throw new Error(`Scaffolded function ${name} could not be found in resources/functions.`)
31
+ return response.json({ function: created }, 201)
32
+ }
33
+ catch (error) {
34
+ return dashboardOperationalError(error, 'The function could not be created.', 'CreateFunction', 500)
35
+ }
27
36
  },
28
37
  })