@stacksjs/defaults 0.70.207 → 0.70.208

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 (408) hide show
  1. package/TODO.md +21 -0
  2. package/ai/AGENTS.md +79 -0
  3. package/ai/README.md +45 -0
  4. package/ai/claude/launch.json +12 -0
  5. package/ai/skills/stacks-actions/SKILL.md +128 -0
  6. package/ai/skills/stacks-ai/SKILL.md +276 -0
  7. package/ai/skills/stacks-alias/SKILL.md +94 -0
  8. package/ai/skills/stacks-analytics/SKILL.md +91 -0
  9. package/ai/skills/stacks-api/SKILL.md +738 -0
  10. package/ai/skills/stacks-arrays/SKILL.md +277 -0
  11. package/ai/skills/stacks-auth/SKILL.md +422 -0
  12. package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
  13. package/ai/skills/stacks-brandkit/SKILL.md +811 -0
  14. package/ai/skills/stacks-browse/SKILL.md +127 -0
  15. package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
  16. package/ai/skills/stacks-browser/SKILL.md +145 -0
  17. package/ai/skills/stacks-buddy/SKILL.md +676 -0
  18. package/ai/skills/stacks-build/SKILL.md +163 -0
  19. package/ai/skills/stacks-cache/SKILL.md +125 -0
  20. package/ai/skills/stacks-calendar/SKILL.md +113 -0
  21. package/ai/skills/stacks-chat/SKILL.md +371 -0
  22. package/ai/skills/stacks-cli/SKILL.md +112 -0
  23. package/ai/skills/stacks-cloud/SKILL.md +192 -0
  24. package/ai/skills/stacks-cms/SKILL.md +217 -0
  25. package/ai/skills/stacks-collections/SKILL.md +82 -0
  26. package/ai/skills/stacks-commerce/SKILL.md +114 -0
  27. package/ai/skills/stacks-composables/SKILL.md +161 -0
  28. package/ai/skills/stacks-config/SKILL.md +144 -0
  29. package/ai/skills/stacks-configuration/SKILL.md +154 -0
  30. package/ai/skills/stacks-cron/SKILL.md +123 -0
  31. package/ai/skills/stacks-crosswind/SKILL.md +190 -0
  32. package/ai/skills/stacks-dashboard/SKILL.md +173 -0
  33. package/ai/skills/stacks-database/SKILL.md +286 -0
  34. package/ai/skills/stacks-datetime/SKILL.md +269 -0
  35. package/ai/skills/stacks-dependencies/SKILL.md +111 -0
  36. package/ai/skills/stacks-deploy/SKILL.md +102 -0
  37. package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
  38. package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
  39. package/ai/skills/stacks-design-output/SKILL.md +55 -0
  40. package/ai/skills/stacks-design-soft/SKILL.md +156 -0
  41. package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
  42. package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
  43. package/ai/skills/stacks-desktop/SKILL.md +78 -0
  44. package/ai/skills/stacks-development/SKILL.md +298 -0
  45. package/ai/skills/stacks-dns/SKILL.md +149 -0
  46. package/ai/skills/stacks-docs/SKILL.md +109 -0
  47. package/ai/skills/stacks-email/SKILL.md +236 -0
  48. package/ai/skills/stacks-enums/SKILL.md +100 -0
  49. package/ai/skills/stacks-env/SKILL.md +181 -0
  50. package/ai/skills/stacks-error-handling/SKILL.md +137 -0
  51. package/ai/skills/stacks-events/SKILL.md +228 -0
  52. package/ai/skills/stacks-faker/SKILL.md +158 -0
  53. package/ai/skills/stacks-git/SKILL.md +158 -0
  54. package/ai/skills/stacks-guard/SKILL.md +98 -0
  55. package/ai/skills/stacks-health/SKILL.md +75 -0
  56. package/ai/skills/stacks-http/SKILL.md +316 -0
  57. package/ai/skills/stacks-i18n/SKILL.md +198 -0
  58. package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
  59. package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
  60. package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
  61. package/ai/skills/stacks-investigate/SKILL.md +119 -0
  62. package/ai/skills/stacks-jobs/SKILL.md +136 -0
  63. package/ai/skills/stacks-lint/SKILL.md +184 -0
  64. package/ai/skills/stacks-listeners/SKILL.md +99 -0
  65. package/ai/skills/stacks-logging/SKILL.md +78 -0
  66. package/ai/skills/stacks-mail/SKILL.md +120 -0
  67. package/ai/skills/stacks-middleware/SKILL.md +249 -0
  68. package/ai/skills/stacks-migrations/SKILL.md +105 -0
  69. package/ai/skills/stacks-models/SKILL.md +267 -0
  70. package/ai/skills/stacks-new-feature/SKILL.md +206 -0
  71. package/ai/skills/stacks-notifications/SKILL.md +210 -0
  72. package/ai/skills/stacks-objects/SKILL.md +205 -0
  73. package/ai/skills/stacks-office-hours/SKILL.md +134 -0
  74. package/ai/skills/stacks-orm/SKILL.md +342 -0
  75. package/ai/skills/stacks-path/SKILL.md +118 -0
  76. package/ai/skills/stacks-payments/SKILL.md +373 -0
  77. package/ai/skills/stacks-plan-review/SKILL.md +152 -0
  78. package/ai/skills/stacks-plugins/SKILL.md +149 -0
  79. package/ai/skills/stacks-push/SKILL.md +376 -0
  80. package/ai/skills/stacks-query-builder/SKILL.md +128 -0
  81. package/ai/skills/stacks-queue/SKILL.md +559 -0
  82. package/ai/skills/stacks-realtime/SKILL.md +442 -0
  83. package/ai/skills/stacks-redesign/SKILL.md +182 -0
  84. package/ai/skills/stacks-registry/SKILL.md +58 -0
  85. package/ai/skills/stacks-repl/SKILL.md +80 -0
  86. package/ai/skills/stacks-retro/SKILL.md +122 -0
  87. package/ai/skills/stacks-review/SKILL.md +135 -0
  88. package/ai/skills/stacks-router/SKILL.md +231 -0
  89. package/ai/skills/stacks-routes/SKILL.md +105 -0
  90. package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
  91. package/ai/skills/stacks-scheduler/SKILL.md +363 -0
  92. package/ai/skills/stacks-search-engine/SKILL.md +112 -0
  93. package/ai/skills/stacks-security/SKILL.md +129 -0
  94. package/ai/skills/stacks-security-audit/SKILL.md +128 -0
  95. package/ai/skills/stacks-server/SKILL.md +458 -0
  96. package/ai/skills/stacks-shell/SKILL.md +62 -0
  97. package/ai/skills/stacks-slug/SKILL.md +87 -0
  98. package/ai/skills/stacks-sms/SKILL.md +385 -0
  99. package/ai/skills/stacks-socials/SKILL.md +303 -0
  100. package/ai/skills/stacks-storage/SKILL.md +576 -0
  101. package/ai/skills/stacks-strings/SKILL.md +416 -0
  102. package/ai/skills/stacks-stx/SKILL.md +176 -0
  103. package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
  104. package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
  105. package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
  106. package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
  107. package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
  108. package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
  109. package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
  110. package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
  111. package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
  112. package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
  113. package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
  114. package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
  115. package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
  116. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
  117. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
  118. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
  119. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
  120. package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
  121. package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
  122. package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
  123. package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
  124. package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
  125. package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
  126. package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
  127. package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
  128. package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
  129. package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
  130. package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
  131. package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
  132. package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
  133. package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
  134. package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
  135. package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
  136. package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
  137. package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
  138. package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
  139. package/ai/skills/stacks-testing/SKILL.md +129 -0
  140. package/ai/skills/stacks-tunnel/SKILL.md +125 -0
  141. package/ai/skills/stacks-types/SKILL.md +178 -0
  142. package/ai/skills/stacks-ui/SKILL.md +246 -0
  143. package/ai/skills/stacks-utils/SKILL.md +444 -0
  144. package/ai/skills/stacks-validation/SKILL.md +371 -0
  145. package/ai/skills/stacks-whois/SKILL.md +315 -0
  146. package/assets/world-map-data.ts +121 -0
  147. package/bootstrap.ts +114 -0
  148. package/docs/api-examples.md +46 -0
  149. package/docs/index.md +24 -0
  150. package/docs/markdown-examples.md +87 -0
  151. package/functions/analytics.ts +97 -0
  152. package/functions/api.ts +143 -0
  153. package/functions/auth.ts +208 -0
  154. package/functions/billing/payments.ts +149 -0
  155. package/functions/blog.ts +120 -0
  156. package/functions/cms/categorizables.ts +107 -0
  157. package/functions/cms/pages.ts +108 -0
  158. package/functions/cms/posts.ts +100 -0
  159. package/functions/cms/taggables.ts +102 -0
  160. package/functions/commerce/coupons.ts +128 -0
  161. package/functions/commerce/customers.ts +128 -0
  162. package/functions/commerce/gift-cards.ts +119 -0
  163. package/functions/commerce/orders.ts +112 -0
  164. package/functions/commerce/payments.ts +119 -0
  165. package/functions/commerce/products/categories.ts +123 -0
  166. package/functions/commerce/products/manufacturers.ts +123 -0
  167. package/functions/commerce/products/products.ts +123 -0
  168. package/functions/commerce/products/reviews.ts +123 -0
  169. package/functions/commerce/products/units.ts +123 -0
  170. package/functions/commerce/products/variants.ts +123 -0
  171. package/functions/commerce/shippings/delivery-routes.ts +123 -0
  172. package/functions/commerce/shippings/digital-deliveries.ts +119 -0
  173. package/functions/commerce/shippings/drivers.ts +123 -0
  174. package/functions/commerce/shippings/license-keys.ts +123 -0
  175. package/functions/commerce/shippings/shipping-methods.ts +123 -0
  176. package/functions/commerce/shippings/shipping-rates.ts +119 -0
  177. package/functions/commerce/shippings/shipping-zones.ts +119 -0
  178. package/functions/commerce/taxes.ts +119 -0
  179. package/functions/commerce/waitlist/products.ts +123 -0
  180. package/functions/commerce/waitlist/restaurants.ts +123 -0
  181. package/functions/craft.ts +107 -0
  182. package/functions/dashboard.ts +153 -0
  183. package/functions/inbox.ts +95 -0
  184. package/functions/jobs.ts +86 -0
  185. package/functions/monitoring/errors.ts +295 -0
  186. package/functions/queue.ts +111 -0
  187. package/functions/realtime/websockets.ts +96 -0
  188. package/functions/realtime.ts +52 -0
  189. package/functions/releases.ts +49 -0
  190. package/functions/search.ts +126 -0
  191. package/functions/toasts.ts +95 -0
  192. package/functions/uploads.ts +216 -0
  193. package/ide/.gitignore +4 -0
  194. package/ide/cursor/rules/auto-imports.mdc +574 -0
  195. package/ide/cursor/rules/code-style.mdc +12 -0
  196. package/ide/cursor/rules/documentation.mdc +9 -0
  197. package/ide/cursor/rules/error-handling-validation.mdc +11 -0
  198. package/ide/cursor/rules/key-conventions.mdc +11 -0
  199. package/ide/cursor/rules/project-structure.mdc +169 -0
  200. package/ide/cursor/rules/readme.mdc +318 -0
  201. package/ide/cursor/rules/syntax-formatting.mdc +9 -0
  202. package/ide/cursor/rules/testing.mdc +10 -0
  203. package/ide/cursor/rules/typescript.mdc +9 -0
  204. package/ide/cursor/rules/ui-styling.mdc +0 -0
  205. package/ide/dictionary.txt +397 -0
  206. package/ide/jetbrains/.fleet/settings.json +4 -0
  207. package/ide/jetbrains/.idea/.gitignore +5 -0
  208. package/ide/jetbrains/.idea/.name +1 -0
  209. package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
  210. package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
  211. package/ide/jetbrains/.idea/modules.xml +8 -0
  212. package/ide/jetbrains/.idea/stacks.iml +22 -0
  213. package/ide/jetbrains/.idea/vcs.xml +6 -0
  214. package/ide/jetbrains/.idea/webResources.xml +14 -0
  215. package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
  216. package/ide/jetbrains/installed.txt +4 -0
  217. package/ide/jetbrains/options/github-copilot.xml +5 -0
  218. package/ide/jetbrains/options/nodejs.xml +7 -0
  219. package/ide/jetbrains/options/templates.xml +124 -0
  220. package/ide/jetbrains/templates/JavaScript.xml +6 -0
  221. package/ide/jetbrains/templates/Stacks.xml +8 -0
  222. package/ide/vscode/.vscode/README.md +23 -0
  223. package/ide/vscode/.vscode/extensions.json +5 -0
  224. package/ide/vscode/.vscode/settings.json +176 -0
  225. package/ide/vscode/.vscode/stacks.code-snippets +181 -0
  226. package/ide/vscode/LICENSE.md +21 -0
  227. package/ide/vscode/README.md +36 -0
  228. package/ide/vscode/logo.png +0 -0
  229. package/ide/vscode/package.json +79 -0
  230. package/ide/zed/.zed/settings.json +139 -0
  231. package/lang/README.md +28 -0
  232. package/lang/de.yml +14 -0
  233. package/lang/en.yml +15 -0
  234. package/package.json +19 -3
  235. package/project/bootstrap +161 -0
  236. package/project/buddy +175 -0
  237. package/project/storage/framework/tsconfig.app.json +50 -0
  238. package/project/storage/framework/tsconfig.base.json +58 -0
  239. package/routes/core.ts +78 -0
  240. package/routes/dashboard-api.ts +233 -0
  241. package/routes/dashboard.ts +739 -0
  242. package/routes/email.ts +129 -0
  243. package/stores/[name].stx +16 -0
  244. package/stores/git.ts +71 -0
  245. package/stores/payment.ts +474 -0
  246. package/stores/queue.ts +43 -0
  247. package/stores/user.ts +29 -0
  248. package/stx-components-plugin.ts +25 -0
  249. package/styles/design-tokens.css +597 -0
  250. package/types/dashboard.ts +70 -0
  251. package/types/defaults.ts +438 -0
  252. package/vcs/github/CODE_OF_CONDUCT.md +83 -0
  253. package/vcs/github/CONTRIBUTING.md +187 -0
  254. package/vcs/github/EXPLANATIONS.md +41 -0
  255. package/vcs/github/FUNDING.yml +1 -0
  256. package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
  257. package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
  258. package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
  259. package/vcs/github/SECURITY.md +70 -0
  260. package/vcs/github/renovate.json +5 -0
  261. package/vcs/github/stale.yml +14 -0
  262. package/vcs/github/workflows/README.md +16 -0
  263. package/vcs/github/workflows/ci.yml +114 -0
  264. package/vcs/github/workflows/export-size.yml +25 -0
  265. package/vcs/github/workflows/labeler.yml +13 -0
  266. package/vcs/github/workflows/release.yml +219 -0
  267. package/views/[...all].stx +8 -0
  268. package/views/about.md +3 -0
  269. package/views/auth/login.stx +1 -0
  270. package/views/auth/signup.stx +85 -0
  271. package/views/dashboard/.discovered-models.json +542 -0
  272. package/views/dashboard/AUDIT.md +904 -0
  273. package/views/dashboard/[...all].stx +89 -0
  274. package/views/dashboard/access-tokens/index.stx +177 -0
  275. package/views/dashboard/actions/index.stx +129 -0
  276. package/views/dashboard/analytics/blog/index.stx +356 -0
  277. package/views/dashboard/analytics/browsers/index.stx +263 -0
  278. package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
  279. package/views/dashboard/analytics/commerce/web/index.stx +379 -0
  280. package/views/dashboard/analytics/countries/index.stx +289 -0
  281. package/views/dashboard/analytics/devices/index.stx +305 -0
  282. package/views/dashboard/analytics/events/index.stx +536 -0
  283. package/views/dashboard/analytics/index.stx +88 -0
  284. package/views/dashboard/analytics/marketing/index.stx +315 -0
  285. package/views/dashboard/analytics/pages/index.stx +271 -0
  286. package/views/dashboard/analytics/referrers/index.stx +319 -0
  287. package/views/dashboard/analytics/web/index.stx +356 -0
  288. package/views/dashboard/buddy/index.stx +198 -0
  289. package/views/dashboard/ci/index.stx +462 -0
  290. package/views/dashboard/cloud/index.stx +452 -0
  291. package/views/dashboard/commands/index.stx +295 -0
  292. package/views/dashboard/commerce/categories/index.stx +406 -0
  293. package/views/dashboard/commerce/coupons/index.stx +485 -0
  294. package/views/dashboard/commerce/customers/index.stx +410 -0
  295. package/views/dashboard/commerce/dashboard/index.stx +287 -0
  296. package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
  297. package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
  298. package/views/dashboard/commerce/delivery/drivers.stx +397 -0
  299. package/views/dashboard/commerce/delivery/index.stx +18 -0
  300. package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
  301. package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
  302. package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
  303. package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
  304. package/views/dashboard/commerce/delivery/tabs.ts +25 -0
  305. package/views/dashboard/commerce/gift-cards/index.stx +561 -0
  306. package/views/dashboard/commerce/manufacturers/index.stx +308 -0
  307. package/views/dashboard/commerce/orders/index.stx +247 -0
  308. package/views/dashboard/commerce/payments/index.stx +535 -0
  309. package/views/dashboard/commerce/pos/index.stx +653 -0
  310. package/views/dashboard/commerce/printers/devices/index.stx +273 -0
  311. package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
  312. package/views/dashboard/commerce/products/detail.stx +563 -0
  313. package/views/dashboard/commerce/products/index.stx +370 -0
  314. package/views/dashboard/commerce/reviews/index.stx +656 -0
  315. package/views/dashboard/commerce/reviews/index.ts +0 -0
  316. package/views/dashboard/commerce/taxes/index.stx +377 -0
  317. package/views/dashboard/commerce/units/index.stx +317 -0
  318. package/views/dashboard/commerce/variants/index.stx +319 -0
  319. package/views/dashboard/commerce/waitlist/products.stx +942 -0
  320. package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
  321. package/views/dashboard/components/index.stx +330 -0
  322. package/views/dashboard/composables/index.ts +19 -0
  323. package/views/dashboard/composables/useChart.ts +77 -0
  324. package/views/dashboard/composables/useNavigation.ts +99 -0
  325. package/views/dashboard/composables/useRole.ts +58 -0
  326. package/views/dashboard/composables/useTheme.ts +74 -0
  327. package/views/dashboard/content/authors/index.stx +171 -0
  328. package/views/dashboard/content/blog/index.stx +493 -0
  329. package/views/dashboard/content/categories/index.stx +169 -0
  330. package/views/dashboard/content/comments/index.stx +189 -0
  331. package/views/dashboard/content/dashboard.stx +383 -0
  332. package/views/dashboard/content/files/index.stx +706 -0
  333. package/views/dashboard/content/pages/index.stx +174 -0
  334. package/views/dashboard/content/posts/index.stx +269 -0
  335. package/views/dashboard/content/seo/index.stx +240 -0
  336. package/views/dashboard/content/tags/index.stx +167 -0
  337. package/views/dashboard/data/activity/index.stx +261 -0
  338. package/views/dashboard/data/dashboard/index.stx +116 -0
  339. package/views/dashboard/data/subscribers.stx +270 -0
  340. package/views/dashboard/data/teams.stx +275 -0
  341. package/views/dashboard/data/users.stx +279 -0
  342. package/views/dashboard/dependencies/index.stx +178 -0
  343. package/views/dashboard/deployments/[id].stx +234 -0
  344. package/views/dashboard/deployments/index.stx +9 -0
  345. package/views/dashboard/dns/index.stx +163 -0
  346. package/views/dashboard/environment/index.stx +283 -0
  347. package/views/dashboard/errors/index.stx +274 -0
  348. package/views/dashboard/forgot-password.stx +57 -0
  349. package/views/dashboard/functions/index.stx +159 -0
  350. package/views/dashboard/health/index.stx +248 -0
  351. package/views/dashboard/inbox/activity.stx +350 -0
  352. package/views/dashboard/inbox/index.stx +452 -0
  353. package/views/dashboard/inbox/settings.stx +290 -0
  354. package/views/dashboard/index.stx +252 -0
  355. package/views/dashboard/insights/index.stx +529 -0
  356. package/views/dashboard/jobs/[id].stx +430 -0
  357. package/views/dashboard/jobs/history.stx +222 -0
  358. package/views/dashboard/jobs/index.stx +214 -0
  359. package/views/dashboard/kanban/[id].stx +899 -0
  360. package/views/dashboard/kanban/index.stx +260 -0
  361. package/views/dashboard/layouts/default.stx +565 -0
  362. package/views/dashboard/layouts/guest.stx +10 -0
  363. package/views/dashboard/login.stx +48 -0
  364. package/views/dashboard/logs/index.stx +540 -0
  365. package/views/dashboard/mailboxes/index.stx +149 -0
  366. package/views/dashboard/management/permissions/index.stx +490 -0
  367. package/views/dashboard/marketing/campaigns/index.stx +604 -0
  368. package/views/dashboard/marketing/lists/index.stx +514 -0
  369. package/views/dashboard/marketing/reviews/index.stx +475 -0
  370. package/views/dashboard/marketing/social-posts/index.stx +560 -0
  371. package/views/dashboard/models/[model].stx +195 -0
  372. package/views/dashboard/models/index.stx +159 -0
  373. package/views/dashboard/monitoring/errors/index.stx +415 -0
  374. package/views/dashboard/notifications/dashboard.stx +383 -0
  375. package/views/dashboard/notifications/email.stx +336 -0
  376. package/views/dashboard/notifications/history.stx +373 -0
  377. package/views/dashboard/notifications/sms.stx +332 -0
  378. package/views/dashboard/packages/index.stx +195 -0
  379. package/views/dashboard/queries/[id].stx +381 -0
  380. package/views/dashboard/queries/history.stx +227 -0
  381. package/views/dashboard/queries/index.stx +335 -0
  382. package/views/dashboard/queries/slow.stx +318 -0
  383. package/views/dashboard/queue/index.stx +427 -0
  384. package/views/dashboard/realtime/index.stx +429 -0
  385. package/views/dashboard/register.stx +46 -0
  386. package/views/dashboard/releases/index.stx +389 -0
  387. package/views/dashboard/requests/index.stx +462 -0
  388. package/views/dashboard/serverless/index.stx +475 -0
  389. package/views/dashboard/servers/[id].stx +299 -0
  390. package/views/dashboard/servers/index.stx +483 -0
  391. package/views/dashboard/settings/billing.stx +50 -0
  392. package/views/dashboard/settings/index.stx +256 -0
  393. package/views/dashboard/settings/mail.stx +252 -0
  394. package/views/dashboard/stores/auth.ts +123 -0
  395. package/views/dashboard/stores/ci.ts +342 -0
  396. package/views/dashboard/stores/index.ts +129 -0
  397. package/views/dashboard/stores/kanban.ts +723 -0
  398. package/views/dashboard/stores/rbac.ts +350 -0
  399. package/views/dashboard/stx.config.ts +34 -0
  400. package/views/dashboard/teams/[id].stx +170 -0
  401. package/views/forum/category.stx +92 -0
  402. package/views/forum/index.stx +209 -0
  403. package/views/forum/new-topic.stx +113 -0
  404. package/views/forum/profile.stx +1 -0
  405. package/views/forum/topic.stx +143 -0
  406. package/views/hello-world/[name].stx +47 -0
  407. package/views/index.stx +21 -0
  408. package/views/system-tray/index.stx +147 -0
@@ -0,0 +1,330 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Components')
5
+ <div class="py-4 lg:py-8 min-h-screen dark:bg-neutral-800">
6
+ <div class="mb-8 px-4 sm:px-6 lg:px-8">
7
+ <div>
8
+ <h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Last 30 days</h3>
9
+
10
+ <dl class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 mt-5">
11
+ <div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-700 rounded-lg shadow">
12
+ <dt>
13
+ <div class="absolute p-3 bg-blue-500 rounded-md">
14
+ <div class="h-6 w-6 text-white i-hugeicons-cloud-download"></div>
15
+ </div>
16
+ <p class="ml-16 font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Total Downloads</p>
17
+ </dt>
18
+ <dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
19
+ <p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">71,897</p>
20
+ <p class="flex items-baseline ml-2 font-semibold text-green-600 text-sm dark:text-green-400">
21
+ <svg class="flex-shrink-0 self-center h-5 w-5 text-green-500 dark:text-green-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
22
+ <path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" />
23
+ </svg>
24
+ <span class="sr-only"> Increased by </span>
25
+ 122
26
+ </p>
27
+ </dd>
28
+ </div>
29
+
30
+ <div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-700 rounded-lg shadow">
31
+ <dt>
32
+ <div class="absolute p-3 bg-blue-500 rounded-md">
33
+ <div class="h-6 w-6 text-white i-hugeicons-clock-01"></div>
34
+ </div>
35
+ <p class="ml-16 font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Avg. Release Time</p>
36
+ </dt>
37
+ <dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
38
+ <p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">3.65 mins</p>
39
+ <p class="flex items-baseline ml-2 font-semibold text-green-600 text-sm dark:text-green-400">
40
+ <svg class="flex-shrink-0 self-center h-5 w-5 text-green-500 dark:text-green-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
41
+ <path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" />
42
+ </svg>
43
+ <span class="sr-only"> Increased by </span>
44
+ 12 s
45
+ </p>
46
+ </dd>
47
+ </div>
48
+
49
+ <div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-700 rounded-lg shadow">
50
+ <dt>
51
+ <div class="absolute p-3 bg-blue-500 rounded-md">
52
+ <div class="h-6 w-6 text-white i-hugeicons-rocket-01"></div>
53
+ </div>
54
+ <p class="ml-16 font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Releases</p>
55
+ </dt>
56
+ <dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
57
+ <p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">420</p>
58
+ <p class="flex items-baseline ml-2 font-semibold text-red-600 text-sm dark:text-red-400">
59
+ <svg class="flex-shrink-0 self-center h-5 w-5 text-red-500 dark:text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
60
+ <path fill-rule="evenodd" d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z" clip-rule="evenodd" />
61
+ </svg>
62
+ <span class="sr-only"> Decreased by </span>
63
+ 3.2%
64
+ </p>
65
+ </dd>
66
+ </div>
67
+ </dl>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="mb-8 px-4 sm:px-6 lg:px-8">
72
+ <div class="bg-white dark:bg-neutral-700 rounded-lg shadow">
73
+ <div class="p-6">
74
+ <div class="flex items-center justify-between mb-6">
75
+ <div>
76
+ <h3 class="font-medium text-base text-gray-900 dark:text-neutral-100">Download Activity</h3>
77
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Compare package downloads over time</p>
78
+ </div>
79
+ <div class="flex items-center space-x-4">
80
+ <div class="flex items-center space-x-2">
81
+ <input x-model="comparePackage" type="text" placeholder="Compare with package..." @keyup.enter="handleCompare()" class="block py-1.5 w-64 text-gray-900 sm:leading-6 sm:text-sm dark:text-neutral-100 placeholder:text-gray-400 dark:bg-neutral-600 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 focus:ring-inset shadow-sm" />
82
+ <button @click="handleCompare()" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 shadow-sm">Compare</button>
83
+ </div>
84
+ <select x-model="timeRange" @change="onTimeRangeChange()" class="pl-3 pr-8 py-1.5 text-gray-900 text-sm dark:text-neutral-100 bg-gray-50 dark:bg-neutral-600 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600">
85
+ <option value="7">Last 7 days</option>
86
+ <option value="30">Last 30 days</option>
87
+ <option value="90">Last 90 days</option>
88
+ <option value="365">Last year</option>
89
+ </select>
90
+ </div>
91
+ </div>
92
+ <div class="h-[400px]">
93
+ <canvas id="components_downloads_chart"></canvas>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+
99
+ <div class="pt-12 px-4 sm:px-6 lg:px-8">
100
+ <div class="sm:flex sm:items-center">
101
+ <div class="sm:flex-auto">
102
+ <h1 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Components</h1>
103
+ <p class="mt-2 text-gray-700 text-sm dark:text-neutral-300">A list of all the components in your library.</p>
104
+ </div>
105
+
106
+ <div class="sm:flex-none mt-4 sm:ml-16 sm:mt-0">
107
+ <button type="button" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 shadow-sm">Create Component</button>
108
+ </div>
109
+ </div>
110
+
111
+ <div class="flow-root mt-8">
112
+ <div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
113
+ <div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
114
+ <div class="overflow-hidden ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-neutral-600 shadow">
115
+ <table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
116
+ <thead class="bg-gray-50 dark:bg-neutral-700">
117
+ <tr>
118
+ <th scope="col" class="pl-4 pr-3 py-3.5 sm:pl-6 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Name</th>
119
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Size</th>
120
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Path</th>
121
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Updated At</th>
122
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Created At</th>
123
+ <th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Edit</span></th>
124
+ </tr>
125
+ </thead>
126
+
127
+ <tbody class="bg-white dark:bg-neutral-700 divide-gray-200 divide-y dark:divide-neutral-600">
128
+ <template :for="component in componentList">
129
+ <tr>
130
+ <td class="pl-4 pr-3 py-4 sm:pl-6 font-medium text-gray-900 text-sm whitespace-nowrap dark:text-neutral-100">{{ component.name }}</td>
131
+ <td class="px-3 py-4 font-mono text-gray-500 text-sm whitespace-nowrap dark:text-neutral-300">{{ component.size }}</td>
132
+ <td class="px-3 py-4 font-mono text-gray-500 text-sm whitespace-nowrap dark:text-neutral-300">{{ component.path }}</td>
133
+ <td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-300">{{ component.updated_at }}</td>
134
+ <td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-300">{{ component.created_at }}</td>
135
+ <td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
136
+ <a href="#" class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300">Edit<span class="sr-only">, {{ component.name }}, in IDE</span></a>
137
+ </td>
138
+ </tr>
139
+ </template>
140
+ </tbody>
141
+ </table>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <script client>
150
+ // Demo component rows — moved out of the former `<script server>` block
151
+ // (stacksjs/stacks#1838). Replace with a real component scan when the
152
+ // API lands.
153
+ const componentList = [
154
+ { name: 'Component A', size: '30kb', path: './resources/components/component-a.stx', updated_at: '01-03-2023 08:23:15', created_at: '04-19-2022 09:04:20' },
155
+ { name: 'Component B', size: '30kb', path: './resources/components/component-b.stx', updated_at: '01-03-2023 08:23:15', created_at: '04-19-2022 09:04:20' },
156
+ { name: 'Component C', size: '30kb', path: './resources/components/component-c.stx', updated_at: '01-03-2023 08:23:15', created_at: '04-19-2022 09:04:20' },
157
+ ]
158
+
159
+ interface DownloadStats {
160
+ labels: string[]
161
+ data: number[]
162
+ compareData: number[]
163
+ }
164
+
165
+ interface ChartLike { destroy: () => void, update: () => void, data: { labels: string[], datasets: Array<{ label?: string, data: number[], borderColor?: string, backgroundColor?: string, fill?: boolean }> } }
166
+ type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
167
+
168
+ function formatDate(daysAgo: number): string {
169
+ const date = new Date()
170
+ date.setDate(date.getDate() - daysAgo)
171
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
172
+ }
173
+
174
+ function generateDateLabels(days: number): string[] {
175
+ return Array.from({ length: days }, (_, i) => formatDate(i))
176
+ }
177
+
178
+ const downloadStats = state<Record<string, DownloadStats>>({
179
+ '7': {
180
+ labels: generateDateLabels(7),
181
+ data: [12500, 15800, 14200, 16800, 19200, 18100, 17200],
182
+ compareData: [9500, 11800, 10200, 12800, 15200, 14100, 13200],
183
+ },
184
+ '30': {
185
+ labels: generateDateLabels(30),
186
+ data: Array.from({ length: 30 }, () => Math.floor(Math.random() * 20000) + 10000),
187
+ compareData: Array.from({ length: 30 }, () => Math.floor(Math.random() * 15000) + 8000),
188
+ },
189
+ '90': {
190
+ labels: generateDateLabels(90),
191
+ data: Array.from({ length: 90 }, () => Math.floor(Math.random() * 20000) + 10000),
192
+ compareData: Array.from({ length: 90 }, () => Math.floor(Math.random() * 15000) + 8000),
193
+ },
194
+ '365': {
195
+ labels: generateDateLabels(365),
196
+ data: Array.from({ length: 365 }, () => Math.floor(Math.random() * 20000) + 10000),
197
+ compareData: Array.from({ length: 365 }, () => Math.floor(Math.random() * 15000) + 8000),
198
+ },
199
+ })
200
+
201
+ const comparePackage = state('')
202
+ const timeRange = state('7')
203
+ const isLoading = state(false)
204
+ const fetchError = state('')
205
+
206
+ let chartHandle: ChartLike | null = null
207
+
208
+ function downloadsData() {
209
+ const range = downloadStats()[timeRange()]
210
+ if (!range)
211
+ return { labels: [], datasets: [] }
212
+ const datasets: Array<{ label?: string, data: number[], borderColor?: string, backgroundColor?: string, fill?: boolean }> = [
213
+ { label: '@your/package', data: range.data, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true },
214
+ ]
215
+ if (comparePackage()) {
216
+ datasets.push({ label: comparePackage(), data: range.compareData, borderColor: 'rgb(234, 179, 8)', backgroundColor: 'rgba(234, 179, 8, 0.1)', fill: true })
217
+ }
218
+ return { labels: range.labels, datasets }
219
+ }
220
+
221
+ const chartOptions = {
222
+ responsive: true,
223
+ maintainAspectRatio: false,
224
+ scales: {
225
+ y: {
226
+ beginAtZero: true,
227
+ grid: { color: 'rgba(200, 200, 200, 0.1)' },
228
+ ticks: {
229
+ color: 'rgb(156, 163, 175)',
230
+ font: { family: "'JetBrains Mono', monospace" },
231
+ callback: (value: number | string) => {
232
+ const v = Number(value)
233
+ if (v >= 1000000)
234
+ return `${(v / 1000000).toFixed(1)}M`
235
+ if (v >= 1000)
236
+ return `${(v / 1000).toFixed(1)}k`
237
+ return v
238
+ },
239
+ },
240
+ },
241
+ x: { grid: { display: false }, ticks: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" } } },
242
+ },
243
+ plugins: {
244
+ legend: { display: true, position: 'top', align: 'end', labels: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" }, boxWidth: 12, padding: 15 } },
245
+ tooltip: {
246
+ callbacks: {
247
+ label: (context: { dataset: { label?: string }, parsed: { y: number | null } }) => {
248
+ let label = context.dataset.label || ''
249
+ if (label)
250
+ label += ': '
251
+ if (context.parsed.y !== null) {
252
+ const v = context.parsed.y
253
+ if (v >= 1000000)
254
+ label += `${(v / 1000000).toFixed(1)}M`
255
+ else if (v >= 1000)
256
+ label += `${(v / 1000).toFixed(1)}k`
257
+ else label += v
258
+ }
259
+ return `${label} downloads`
260
+ },
261
+ },
262
+ },
263
+ },
264
+ elements: { line: { tension: 0.4 } },
265
+ }
266
+
267
+ async function fetchDownloadData(days: string): Promise<void> {
268
+ isLoading.set(true)
269
+ fetchError.set('')
270
+ try {
271
+ const baseUrl = `http://localhost:${(globalThis as { process?: { env?: { PORT_API?: string } } }).process?.env?.PORT_API || '3008'}`
272
+ const response = await fetch(`${baseUrl}/components/stats?days=${days}`, {
273
+ headers: { Accept: 'application/json' },
274
+ })
275
+ if (response.ok) {
276
+ const data = await response.json()
277
+ if (data.stats) {
278
+ const next = { ...downloadStats() }
279
+ next[timeRange()] = data.stats
280
+ downloadStats.set(next)
281
+ }
282
+ }
283
+ }
284
+ catch (error) {
285
+ fetchError.set('Failed to load component data. Using cached data.')
286
+ // eslint-disable-next-line no-console
287
+ console.error('Failed to fetch component data:', error)
288
+ }
289
+ finally {
290
+ isLoading.set(false)
291
+ }
292
+ }
293
+
294
+ function refreshChart(): void {
295
+ if (!chartHandle)
296
+ return
297
+ const next = downloadsData()
298
+ chartHandle.data.labels = next.labels
299
+ chartHandle.data.datasets = next.datasets
300
+ chartHandle.update()
301
+ }
302
+
303
+ async function onTimeRangeChange(): Promise<void> {
304
+ await fetchDownloadData(timeRange())
305
+ refreshChart()
306
+ }
307
+
308
+ async function handleCompare(): Promise<void> {
309
+ if (comparePackage()) {
310
+ await fetchDownloadData(timeRange())
311
+ refreshChart()
312
+ }
313
+ }
314
+
315
+ onMount(async () => {
316
+ await fetchDownloadData(timeRange())
317
+ const { Chart, registerables } = await import('@stacksjs/charts')
318
+ Chart.register(...registerables)
319
+ const Ctor = Chart as unknown as ChartCtor
320
+ const handle = useChart({ Chart: Ctor, id: 'components_downloads_chart', type: 'line', data: downloadsData(), options: chartOptions })
321
+ chartHandle = handle.instance as ChartLike | null
322
+ })
323
+
324
+ onDestroy(() => {
325
+ chartHandle?.destroy()
326
+ chartHandle = null
327
+ })
328
+ </script>
329
+
330
+ @endsection
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Dashboard-local composables.
3
+ *
4
+ * Most reactive primitives are auto-imported by stx in `<script client>`
5
+ * blocks (`state`, `derived`, `effect`, `onMount`, `onDestroy`, `navigate`,
6
+ * `goBack`, `useColorMode`, `useDark`, `useFetch`, `useEventListener`, …) —
7
+ * reach for those first. This barrel only ships things stx doesn't.
8
+ *
9
+ * See stacksjs/stacks#1838 for the wider sweep this is part of.
10
+ */
11
+
12
+ export type { ChartCtor, ChartHandle, ChartLike, UseChartOptions } from './useChart'
13
+ export { useChart, useCharts } from './useChart'
14
+ export type { NavigationSnapshot } from './useNavigation'
15
+ export { useNavigation } from './useNavigation'
16
+ export type { RoleSnapshot } from './useRole'
17
+ export { useRole } from './useRole'
18
+ export type { ThemeMode, ThemeSnapshot } from './useTheme'
19
+ export { isDarkTheme, useTheme } from './useTheme'
@@ -0,0 +1,77 @@
1
+ /**
2
+ * useChart — wires `@stacksjs/charts` to a canvas without leaking
3
+ * `document.getElementById` into pages.
4
+ *
5
+ * The dashboard repeats the same shape across feature pages:
6
+ *
7
+ * const ctx = document.getElementById('chart_1')
8
+ * if (ctx) new Chart(ctx, { type, data, options })
9
+ *
10
+ * `useChart` packages that into one call, returns a destroyer so SPA
11
+ * navigation can tear charts down, and tolerates SSR (no-ops on the
12
+ * server). It does *not* import the chart lib eagerly — pages can
13
+ * still `await import('@stacksjs/charts')` to keep the browser bundle
14
+ * lazy, then pass the `Chart` constructor in.
15
+ */
16
+
17
+ export interface ChartLike {
18
+ destroy: () => void
19
+ }
20
+
21
+ // Constructor signature mirrors Chart.js — `new Chart(canvas, config)`.
22
+ // We don't import `@stacksjs/charts` here to keep this composable
23
+ // dep-free at the type level; pages pass the constructor in.
24
+ export type ChartCtor = new (
25
+ canvas: HTMLCanvasElement | HTMLElement,
26
+ config: { type: string, data: unknown, options?: unknown },
27
+ ) => ChartLike
28
+
29
+ export interface UseChartOptions {
30
+ /** Chart constructor (`new Chart(...)`). Pages import lazily and pass it in. */
31
+ Chart: ChartCtor
32
+ /** Element id, e.g. `'chart_1'`. */
33
+ id: string
34
+ type: string
35
+ data: unknown
36
+ options?: unknown
37
+ }
38
+
39
+ export interface ChartHandle {
40
+ /** The constructed chart, or `null` if the element was missing or we're SSR. */
41
+ readonly instance: ChartLike | null
42
+ destroy: () => void
43
+ }
44
+
45
+ export function useChart(opts: UseChartOptions): ChartHandle {
46
+ // SSR-safe — no document means no canvas.
47
+ if (typeof document === 'undefined')
48
+ return { instance: null, destroy: () => {} }
49
+
50
+ const el = document.getElementById(opts.id)
51
+ if (!el) {
52
+ // Canvas not in the DOM yet (or wrong id). Returning a no-op handle
53
+ // is friendlier than throwing — pages stay rendered, devs see the
54
+ // missing chart and can grep the id.
55
+ // eslint-disable-next-line no-console
56
+ console.warn(`[useChart] no element with id "${opts.id}" — chart not initialised`)
57
+ return { instance: null, destroy: () => {} }
58
+ }
59
+
60
+ const instance = new opts.Chart(el, { type: opts.type, data: opts.data, options: opts.options })
61
+ return {
62
+ instance,
63
+ destroy: () => instance.destroy(),
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Convenience for the common "init N charts at once" pattern. Returns
69
+ * a single destroyer that tears them all down.
70
+ */
71
+ export function useCharts(specs: UseChartOptions[]): { handles: ChartHandle[], destroyAll: () => void } {
72
+ const handles = specs.map(useChart)
73
+ return {
74
+ handles,
75
+ destroyAll: () => handles.forEach(h => h.destroy()),
76
+ }
77
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * useNavigation — wraps `window.location` / `history.pushState` so
3
+ * dashboard pages can read URL state and navigate without leaking
4
+ * vanilla DOM access into `.stx` files (stacksjs/stacks#1838).
5
+ *
6
+ * stx already exposes a top-level `navigate(url)` for SPA navigation, so
7
+ * this composable focuses on the *read* side (current path, segment
8
+ * extraction, query params) and on a couple of write helpers that stx's
9
+ * primitive doesn't cover (hash setters, opening external URLs).
10
+ *
11
+ * All accessors are SSR-safe — they return sensible defaults when
12
+ * `window` is undefined so importing this in a page that hydrates
13
+ * server-side doesn't crash.
14
+ */
15
+
16
+ export interface NavigationSnapshot {
17
+ /**
18
+ * Current pathname, e.g. `/kanban/12`. Empty string on the server.
19
+ */
20
+ path: () => string
21
+
22
+ /**
23
+ * Path segment by index, supporting negative indexes (Pythonic). The
24
+ * pathname is split on `/` with empty segments dropped — so
25
+ * `urlSegment(-1)` on `/kanban/12/cards` returns `cards`, and
26
+ * `urlSegment(0)` returns `kanban`. Returns an empty string when the
27
+ * index is out of bounds or on the server.
28
+ *
29
+ * Replaces the recurring `window.location.pathname.split('/').pop()`
30
+ * pattern from feature pages.
31
+ */
32
+ urlSegment: (index: number) => string
33
+
34
+ /**
35
+ * Numeric route parameter — convenience wrapper for the very common
36
+ * `/feature/[id]` shape. Parses `urlSegment(-1)` as a positive integer
37
+ * and returns `null` when it isn't one.
38
+ */
39
+ urlIdParam: () => number | null
40
+
41
+ /**
42
+ * Query string value, e.g. `urlQuery('q')` on `/search?q=foo` → `foo`.
43
+ * Returns `null` when the param is absent or on the server.
44
+ */
45
+ urlQuery: (name: string) => string | null
46
+
47
+ /**
48
+ * Replace the current URL without a history entry (uses
49
+ * `history.replaceState`). Useful for tab/state changes that
50
+ * shouldn't pollute the back button.
51
+ */
52
+ replacePath: (next: string) => void
53
+
54
+ /**
55
+ * Open a URL in a new tab. Forwards `noopener,noreferrer` automatically
56
+ * — pages shouldn't have to remember the security flags every time.
57
+ */
58
+ openExternal: (url: string) => void
59
+ }
60
+
61
+ export function useNavigation(): NavigationSnapshot {
62
+ return {
63
+ path: () => {
64
+ if (typeof window === 'undefined') return ''
65
+ return window.location.pathname
66
+ },
67
+
68
+ urlSegment: (index: number) => {
69
+ if (typeof window === 'undefined') return ''
70
+ const parts = window.location.pathname.split('/').filter(Boolean)
71
+ if (parts.length === 0) return ''
72
+ const i = index < 0 ? parts.length + index : index
73
+ return parts[i] ?? ''
74
+ },
75
+
76
+ urlIdParam: () => {
77
+ if (typeof window === 'undefined') return null
78
+ const last = window.location.pathname.split('/').filter(Boolean).pop()
79
+ if (!last) return null
80
+ const n = Number(last)
81
+ return Number.isFinite(n) && n > 0 ? n : null
82
+ },
83
+
84
+ urlQuery: (name: string) => {
85
+ if (typeof window === 'undefined') return null
86
+ return new URL(window.location.href).searchParams.get(name)
87
+ },
88
+
89
+ replacePath: (next: string) => {
90
+ if (typeof window === 'undefined') return
91
+ window.history.replaceState({}, '', next)
92
+ },
93
+
94
+ openExternal: (url: string) => {
95
+ if (typeof window === 'undefined') return
96
+ window.open(url, '_blank', 'noopener,noreferrer')
97
+ },
98
+ }
99
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Role-aware visibility hook for dashboard features.
3
+ *
4
+ * Backs gating decisions across the dashboard
5
+ * (stacksjs/stacks#1843, first consumer: CI tracking in #1844).
6
+ *
7
+ * Resolution chain (in order):
8
+ *
9
+ * 1. **Authenticated user with roles** — Reads the role names returned
10
+ * by `/api/dashboard/auth/me` (Action: `Dashboard/Auth/MeAction` →
11
+ * `@stacksjs/auth` → `BqbRbacStore` → `user_roles` pivot). Role
12
+ * membership is the authoritative answer.
13
+ * 2. **Authenticated user with no role records** — The user exists but
14
+ * has nothing in `user_roles`. Treated as `client` (most restrictive)
15
+ * so a brand-new account doesn't accidentally see dev tools before
16
+ * an admin assigns a role.
17
+ * 3. **Unauthenticated** — No bearer token, no session. The dev
18
+ * dashboard on localhost lands here. Defaults to `isDev: true` so
19
+ * the surface stays usable without forcing anyone to log in against
20
+ * a local DB. Production deployments that gate `/api/dashboard/*`
21
+ * behind the `auth` middleware never hit this branch — the endpoint
22
+ * returns a real user/roles and case (1) applies.
23
+ *
24
+ * Consumers should `derived(() => isDev())` so the signal flips
25
+ * reactively if the user's roles change (admin assigns/revokes via the
26
+ * RBAC UI without a page reload).
27
+ */
28
+ import { useStore } from '@stacksjs/stx'
29
+
30
+ export interface RoleSnapshot {
31
+ /** Admin: every dashboard surface is visible. */
32
+ isAdmin: () => boolean
33
+ /** Dev: infra/dev-mode surfaces (CI tracking, runner alerts, queries). */
34
+ isDev: () => boolean
35
+ /** Client: minimal surface — content / orders / billing only. */
36
+ isClient: () => boolean
37
+ /** Trigger the identity round-trip. Safe to call repeatedly; the store
38
+ * dedupes in-flight + cached responses. */
39
+ load: () => Promise<void>
40
+ /** Force a re-fetch (e.g. after an admin assigns the viewer a new role). */
41
+ refresh: () => Promise<void>
42
+ }
43
+
44
+ export function useRole(): RoleSnapshot {
45
+ const auth = useStore('auth')
46
+
47
+ // Fire the load lazily on first consumer. The store guards against
48
+ // double-fetch so multiple `useRole()` calls in one page are safe.
49
+ auth.load()
50
+
51
+ return {
52
+ isAdmin: () => auth.isAdmin(),
53
+ isDev: () => auth.isDev(),
54
+ isClient: () => auth.isClient(),
55
+ load: () => auth.load(),
56
+ refresh: () => auth.refresh(),
57
+ }
58
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * useTheme — single owner of the OS dark-mode preference signal
3
+ * (stacksjs/stacks#1838).
4
+ *
5
+ * The original incident: `dashboardStore.isDark` derived its OS
6
+ * preference by calling `window.matchMedia(...)` *inside the store
7
+ * factory* — a store that imported window for its own state was one of
8
+ * the audit's main offenders. This composable lifts that wiring out so
9
+ * the store consumes a clean signal and pages can ask `useTheme()`
10
+ * directly without re-reading `matchMedia` per call site.
11
+ *
12
+ * Behaviour:
13
+ *
14
+ * - On the server, `prefersDark()` resolves to `false`. The dashboard
15
+ * defaults to its light palette during SSR; once the page hydrates
16
+ * the listener attaches and `prefersDark()` flips if the OS theme
17
+ * is dark, so the first repaint reflects reality.
18
+ * - In the browser, the listener is installed once per module load.
19
+ * Subsequent `useTheme()` calls reuse the same signal — no
20
+ * listener storm.
21
+ *
22
+ * Note: stx exposes its own `useDark()` / `useColorMode()` composables
23
+ * via auto-import. Those are the right primitives for "what theme is
24
+ * the user currently *viewing*" (which considers user preference +
25
+ * stored override). `useTheme().prefersDark` is specifically the *OS
26
+ * preference* — that's what `dashboardStore` resolves against when its
27
+ * own `theme` setting is `'system'`.
28
+ */
29
+ import { state } from '@stacksjs/stx'
30
+
31
+ export type ThemeMode = 'light' | 'dark' | 'system'
32
+
33
+ export interface ThemeSnapshot {
34
+ /**
35
+ * OS-level dark-mode preference. Tracks `prefers-color-scheme: dark`
36
+ * via a single `matchMedia` listener installed lazily on first import.
37
+ */
38
+ prefersDark: () => boolean
39
+ }
40
+
41
+ const prefersDarkSignal = state(false)
42
+ let installed = false
43
+
44
+ function install(): void {
45
+ if (installed) return
46
+ installed = true
47
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
48
+ return
49
+ const mql = window.matchMedia('(prefers-color-scheme: dark)')
50
+ prefersDarkSignal.set(mql.matches)
51
+ mql.addEventListener('change', e => prefersDarkSignal.set(e.matches))
52
+ }
53
+
54
+ // Install at module load. The window guard inside `install()` keeps this
55
+ // safe for SSR — the signal stays at its `false` default until the
56
+ // page hydrates and re-imports the composable in the browser.
57
+ install()
58
+
59
+ export function useTheme(): ThemeSnapshot {
60
+ return {
61
+ prefersDark: () => prefersDarkSignal(),
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Resolve the *effective* dark state given a user theme mode +
67
+ * the OS preference. Pulled out as a small pure helper so consumers
68
+ * (e.g. `dashboardStore.isDark`) can compose it without re-deriving
69
+ * the same `'system' ? prefersDark : mode === 'dark'` ternary.
70
+ */
71
+ export function isDarkTheme(mode: ThemeMode, prefersDark: boolean): boolean {
72
+ if (mode === 'system') return prefersDark
73
+ return mode === 'dark'
74
+ }