create-mithaq 0.1.1 → 0.2.0

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 (348) hide show
  1. package/LICENSES/openstatus-time-picker.txt +21 -0
  2. package/index.js +457 -63
  3. package/package.json +1 -1
  4. package/template/apps/{asas-platform → asas-backoffice}/package.json +5 -3
  5. package/template/apps/{asas-platform → asas-backoffice}/src/api.ts +1 -1
  6. package/template/apps/{asas-platform → asas-backoffice}/src/auth-frame.tsx +12 -12
  7. package/template/apps/asas-backoffice/src/components.ts +35 -0
  8. package/template/apps/asas-backoffice/src/first-user.ts +14 -0
  9. package/template/apps/{asas-platform → asas-backoffice}/src/links.ts +1 -1
  10. package/template/apps/{asas-platform → asas-backoffice}/src/locale-switch.tsx +7 -5
  11. package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
  12. package/template/apps/asas-backoffice/src/redirect.ts +1 -0
  13. package/template/apps/{asas-platform → asas-backoffice}/src/request-headers.ts +1 -1
  14. package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
  15. package/template/apps/asas-backoffice/src/router.tsx +18 -0
  16. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
  17. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/screen.json +426 -64
  18. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/types.ts +18 -0
  19. package/template/apps/{asas-platform/src/routes/(screens)/platform.$.tsx → asas-backoffice/src/routes/(screens)/backoffice.$.tsx} +4 -4
  20. package/template/apps/{asas-platform/src/routes/(screens)/platform.index.tsx → asas-backoffice/src/routes/(screens)/backoffice.index.tsx} +3 -3
  21. package/template/apps/{asas-platform/src/routes/(screens)/platform.tsx → asas-backoffice/src/routes/(screens)/backoffice.tsx} +3 -3
  22. package/template/apps/{asas-platform → asas-backoffice}/src/routes/__root.tsx +2 -2
  23. package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
  24. package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
  25. package/template/apps/{asas-platform → asas-backoffice}/src/routes/auth.sign-in.tsx +9 -4
  26. package/template/apps/{asas-platform → asas-backoffice}/src/routes/unauthorized.tsx +24 -12
  27. package/template/apps/{asas-platform → asas-backoffice}/src/screen-form.tsx +5 -3
  28. package/template/apps/{asas-platform → asas-backoffice}/src/screen-list.tsx +6 -5
  29. package/template/apps/{asas-platform → asas-backoffice}/src/screen-names.ts +1 -1
  30. package/template/apps/{asas-platform → asas-backoffice}/src/screens.tsx +46 -26
  31. package/template/apps/asas-backoffice/src/server.ts +30 -0
  32. package/template/apps/{asas-platform → asas-backoffice}/src/slide.ts +1 -1
  33. package/template/apps/{asas-platform → asas-backoffice}/src/start.ts +1 -1
  34. package/template/apps/{asas-platform → asas-backoffice}/src/styles.css +9 -7
  35. package/template/apps/{asas-platform → asas-backoffice}/tsconfig.json +2 -2
  36. package/template/apps/{asas-platform → asas-backoffice}/vite.config.ts +7 -1
  37. package/template/apps/asas-tanstack/package.json +4 -2
  38. package/template/apps/asas-tanstack/src/api.ts +1 -1
  39. package/template/apps/asas-tanstack/src/auth-frame.tsx +12 -12
  40. package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
  41. package/template/apps/asas-tanstack/src/components.ts +35 -0
  42. package/template/apps/asas-tanstack/src/copy.ts +416 -79
  43. package/template/apps/asas-tanstack/src/first-run.ts +16 -0
  44. package/template/apps/asas-tanstack/src/links.ts +1 -1
  45. package/template/apps/asas-tanstack/src/locale-switch.tsx +7 -5
  46. package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
  47. package/template/apps/asas-tanstack/src/redirect.ts +20 -1
  48. package/template/apps/asas-tanstack/src/request-headers.ts +1 -1
  49. package/template/apps/asas-tanstack/src/routeTree.gen.ts +42 -0
  50. package/template/apps/asas-tanstack/src/router.tsx +12 -1
  51. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +9 -2
  52. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +475 -116
  53. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +4 -2
  54. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +7 -2
  55. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +83 -319
  56. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +3 -1
  57. package/template/apps/asas-tanstack/src/routes/__root.tsx +1 -1
  58. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +14 -7
  59. package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
  60. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +2 -2
  61. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +5 -5
  62. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +7 -100
  63. package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
  64. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +27 -15
  65. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +59 -38
  66. package/template/apps/asas-tanstack/src/routes/index.tsx +18 -14
  67. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +24 -12
  68. package/template/apps/asas-tanstack/src/screen-form.tsx +5 -3
  69. package/template/apps/asas-tanstack/src/screen-list.tsx +6 -5
  70. package/template/apps/asas-tanstack/src/screen-names.ts +1 -1
  71. package/template/apps/asas-tanstack/src/screens.tsx +46 -26
  72. package/template/apps/asas-tanstack/src/server.ts +10 -0
  73. package/template/apps/asas-tanstack/src/slide.ts +1 -1
  74. package/template/apps/asas-tanstack/src/styles.css +7 -3
  75. package/template/apps/asas-tanstack/tsconfig.json +2 -2
  76. package/template/apps/asas-tanstack/vite.config.ts +7 -1
  77. package/template/package.json +3 -3
  78. package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
  79. package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
  80. package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
  81. package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
  82. package/template/templates/asas/.env.example +22 -5
  83. package/template/templates/asas/AGENTS.md +101 -0
  84. package/template/templates/asas/CLAUDE.md +1 -0
  85. package/template/templates/asas/README.md +1056 -0
  86. package/template/templates/asas/package.json +19 -4
  87. package/template/templates/asas/src/asas.ts +81 -56
  88. package/template/templates/asas/src/{platform-http.ts → backoffice-http.ts} +48 -27
  89. package/template/templates/asas/src/dev-setup.ts +112 -0
  90. package/template/templates/asas/src/local-database.ts +22 -0
  91. package/template/templates/asas/src/messages/account.json +129 -103
  92. package/template/templates/asas/src/messages/auditLog.json +223 -105
  93. package/template/templates/asas/src/messages/backofficeLog.json +132 -0
  94. package/template/templates/asas/src/messages/courses.json +256 -214
  95. package/template/templates/asas/src/messages/enrollments.json +45 -27
  96. package/template/templates/asas/src/messages/invitation.json +72 -56
  97. package/template/templates/asas/src/messages/localUsers.json +34 -28
  98. package/template/templates/asas/src/messages/media.json +31 -21
  99. package/template/templates/asas/src/messages/member.json +59 -39
  100. package/template/templates/asas/src/messages/memberRoles.json +37 -31
  101. package/template/templates/asas/src/messages/mithaq.json +206 -162
  102. package/template/templates/asas/src/messages/organization.json +63 -41
  103. package/template/templates/asas/src/messages/organizationRole.json +59 -47
  104. package/template/templates/asas/src/messages/roles.json +59 -45
  105. package/template/templates/asas/src/messages/session.json +99 -79
  106. package/template/templates/asas/src/messages/sessions.json +110 -80
  107. package/template/templates/asas/src/messages/settings.json +51 -53
  108. package/template/templates/asas/src/messages/tags.json +31 -21
  109. package/template/templates/asas/src/messages/user.json +109 -87
  110. package/template/templates/asas/src/messages/verification.json +59 -47
  111. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
  112. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
  113. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
  114. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
  115. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
  116. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
  117. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
  118. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
  119. package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
  120. package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
  121. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
  122. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
  123. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
  124. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
  125. package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
  126. package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
  127. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
  128. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
  129. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
  130. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
  131. package/template/templates/asas/src/migrations/schema.ts +1 -0
  132. package/template/templates/asas/src/mithaq.ts +4 -406
  133. package/template/templates/asas/src/organizations.ts +3 -0
  134. package/template/templates/asas/src/seed.ts +147 -45
  135. package/template/templates/asas/src/{ui-platform → ui-backoffice}/home.tsx +9 -9
  136. package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
  137. package/template/templates/asas/src/{ui-platform → ui-backoffice}/record-page.tsx +4 -4
  138. package/template/templates/asas/src/{ui-platform → ui-backoffice}/role-cells.tsx +13 -6
  139. package/template/templates/asas/src/{ui-platform → ui-backoffice}/user-actions.tsx +46 -48
  140. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +10 -4
  141. package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
  142. package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
  143. package/template/templates/asas/src/ui-dashboard/members.tsx +637 -438
  144. package/template/templates/asas/src/ui-dashboard/versions.tsx +22 -8
  145. package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
  146. package/template/templates/asas/src/ui-portal/home.tsx +9 -13
  147. package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
  148. package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
  149. package/template/templates/features/audit-log/index.ts +4 -0
  150. package/template/templates/features/audit-log/package.json +13 -0
  151. package/template/templates/features/auth/index.ts +11 -0
  152. package/template/templates/{asas/src → features/auth}/local-users/local-users.ts +7 -7
  153. package/template/templates/{auth → features/auth}/package.json +2 -3
  154. package/template/templates/{asas/src/kayan → features}/auth/user/account.access.ts +2 -2
  155. package/template/templates/{asas/src/kayan → features}/auth/user/account.ts +2 -2
  156. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.access.ts +1 -1
  157. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.ts +3 -3
  158. package/template/templates/{asas/src/kayan → features}/auth/user/member.access.ts +4 -4
  159. package/template/templates/{asas/src/kayan → features}/auth/user/member.screens.ts +5 -5
  160. package/template/templates/{asas/src/kayan → features}/auth/user/member.ts +4 -3
  161. package/template/templates/{asas/src/kayan → features}/auth/user/organization.access.ts +6 -6
  162. package/template/templates/{asas/src/kayan → features}/auth/user/organization.screens.ts +6 -6
  163. package/template/templates/{asas/src/kayan → features}/auth/user/organization.ts +2 -1
  164. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.access.ts +2 -2
  165. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.ts +3 -3
  166. package/template/templates/{asas/src/kayan → features}/auth/user/session.access.ts +2 -2
  167. package/template/templates/{asas/src/kayan → features}/auth/user/session.ts +2 -2
  168. package/template/templates/{asas/src/kayan → features}/auth/user/user.access.ts +8 -7
  169. package/template/templates/{asas/src/kayan → features}/auth/user/user.screens.ts +9 -9
  170. package/template/templates/{asas/src/kayan → features}/auth/user/user.ts +2 -2
  171. package/template/templates/{asas/src/kayan → features}/auth/user/verification.access.ts +2 -2
  172. package/template/templates/{asas/src/kayan → features}/auth/user/verification.ts +2 -2
  173. package/template/templates/{asas/src → features/courses}/courses/courses.ts +122 -133
  174. package/template/templates/features/courses/courses/cover-reads.ts +33 -0
  175. package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
  176. package/template/templates/{asas/src → features/courses}/courses/ui/fee-cell.tsx +1 -1
  177. package/template/templates/{asas/src → features/courses}/courses/ui/status-cell.tsx +5 -3
  178. package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
  179. package/template/templates/features/courses/domain/archive.ts +39 -0
  180. package/template/templates/features/courses/domain/on-offer.ts +6 -0
  181. package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
  182. package/template/templates/{asas/src → features/courses}/enrollments/ui/session-cell.tsx +17 -7
  183. package/template/templates/{asas/src → features/courses/enrollments}/ui/session-labels.tsx +3 -3
  184. package/template/templates/features/courses/index.ts +8 -0
  185. package/template/templates/features/courses/package.json +24 -0
  186. package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
  187. package/template/templates/features/courses/portal/ui/course.tsx +176 -0
  188. package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
  189. package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
  190. package/template/templates/features/courses/portal/ui/reads.ts +76 -0
  191. package/template/templates/features/courses/portal/ui/rules.ts +87 -0
  192. package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
  193. package/template/templates/features/courses/sessions/seats.ts +42 -0
  194. package/template/templates/{asas/src → features/courses}/sessions/sessions.ts +45 -14
  195. package/template/templates/features/media/index.ts +10 -0
  196. package/template/templates/{asas/src → features/media}/media/media.ts +34 -40
  197. package/template/templates/{asas/src → features/media}/media/ui/preview.tsx +2 -2
  198. package/template/templates/features/media/package.json +19 -0
  199. package/template/templates/features/roles/index.ts +4 -0
  200. package/template/templates/{asas/src → features/roles}/member-roles/member-roles.ts +15 -15
  201. package/template/templates/features/roles/package.json +21 -0
  202. package/template/templates/{asas/src → features/roles}/roles/roles.ts +23 -23
  203. package/template/templates/{asas/src → features/roles}/roles/ui/role-form.tsx +65 -24
  204. package/template/templates/{asas/src → features/roles}/roles/ui/roles.tsx +49 -25
  205. package/template/templates/features/settings/index.ts +3 -0
  206. package/template/templates/features/settings/package.json +13 -0
  207. package/template/templates/{asas/src → features/settings}/settings/settings.ts +12 -17
  208. package/template/templates/features/tags/index.ts +3 -0
  209. package/template/templates/features/tags/package.json +14 -0
  210. package/template/templates/{asas/src → features/tags}/tags/tags.ts +15 -15
  211. package/template/templates/mithaq/package.json +33 -0
  212. package/template/templates/mithaq/src/mail/index.ts +32 -0
  213. package/template/templates/mithaq/src/mail/mailers.ts +36 -0
  214. package/template/templates/{asas/src/audit-log/sink.ts → mithaq/src/mithaq/audit-sink.ts} +31 -9
  215. package/template/templates/{auth/src/platform-roles.ts → mithaq/src/mithaq/auth/backoffice-roles.ts} +44 -23
  216. package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
  217. package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
  218. package/template/templates/{asas/src → mithaq/src/mithaq}/pages.ts +16 -7
  219. package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
  220. package/template/templates/mithaq/src/mithaq.ts +593 -0
  221. package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
  222. package/template/templates/{asas → mithaq}/src/roles/grants.ts +1 -1
  223. package/template/templates/mithaq/src/roles/member-access.ts +11 -0
  224. package/template/templates/{asas/src → mithaq/src/roles}/statements.ts +6 -4
  225. package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
  226. package/template/templates/{asas/src → mithaq/src/roles}/tenant-access.ts +96 -57
  227. package/template/templates/{asas/src → mithaq/src/roles}/tenant-roles.ts +84 -25
  228. package/template/templates/{asas → mithaq}/src/steps/expected-updated-at.ts +2 -2
  229. package/template/templates/{asas → mithaq}/src/storage/index.ts +3 -1
  230. package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
  231. package/template/templates/mithaq/src/ui/activity.tsx +527 -0
  232. package/template/templates/{asas → mithaq}/src/ui/app-shell.tsx +27 -19
  233. package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
  234. package/template/templates/{asas → mithaq}/src/ui/screen-nav.tsx +1 -1
  235. package/template/templates/{asas → mithaq}/src/ui/top-shell.tsx +8 -8
  236. package/template/apps/asas-platform/src/components/mithaq/cells.tsx +0 -112
  237. package/template/apps/asas-platform/src/components/mithaq/controls.tsx +0 -650
  238. package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +0 -53
  239. package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +0 -108
  240. package/template/apps/asas-platform/src/components/mithaq/form.tsx +0 -1467
  241. package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +0 -108
  242. package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +0 -368
  243. package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +0 -153
  244. package/template/apps/asas-platform/src/components/mithaq/list-query.ts +0 -432
  245. package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +0 -74
  246. package/template/apps/asas-platform/src/components/mithaq/list.tsx +0 -865
  247. package/template/apps/asas-platform/src/components/mithaq/nav.tsx +0 -871
  248. package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +0 -1024
  249. package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +0 -127
  250. package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +0 -400
  251. package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +0 -6
  252. package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +0 -1083
  253. package/template/apps/asas-platform/src/components/mithaq/upload.tsx +0 -686
  254. package/template/apps/asas-platform/src/components/ui/drawer.tsx +0 -216
  255. package/template/apps/asas-platform/src/redirect.ts +0 -1
  256. package/template/apps/asas-platform/src/routeTree.gen.ts +0 -181
  257. package/template/apps/asas-platform/src/router.tsx +0 -7
  258. package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +0 -4
  259. package/template/apps/asas-platform/src/routes/api/$.ts +0 -30
  260. package/template/apps/asas-platform/src/server.ts +0 -20
  261. package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +0 -112
  262. package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +0 -650
  263. package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +0 -53
  264. package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +0 -108
  265. package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +0 -1467
  266. package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +0 -108
  267. package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +0 -368
  268. package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +0 -153
  269. package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +0 -432
  270. package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +0 -74
  271. package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +0 -865
  272. package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +0 -871
  273. package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +0 -1024
  274. package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +0 -127
  275. package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +0 -400
  276. package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +0 -6
  277. package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +0 -1083
  278. package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +0 -686
  279. package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +0 -215
  280. package/template/templates/asas/src/audit-log/audit-log.ts +0 -187
  281. package/template/templates/asas/src/audit-log/ui/activity.tsx +0 -243
  282. package/template/templates/asas/src/courses/ui/archive-button.tsx +0 -25
  283. package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +0 -11
  284. package/template/templates/asas/src/courses/ui/on-offer.ts +0 -6
  285. package/template/templates/asas/src/enrollments/enrollments.ts +0 -137
  286. package/template/templates/asas/src/rich-text-nodes.ts +0 -11
  287. package/template/templates/asas/src/roles.ts +0 -16
  288. package/template/templates/asas/src/ui/rich-text-nodes.tsx +0 -15
  289. package/template/templates/asas/src/ui-platform/organization-actions.tsx +0 -114
  290. package/template/templates/auth/src/index.ts +0 -690
  291. /package/template/apps/{asas-platform → asas-backoffice}/components.json +0 -0
  292. /package/template/apps/{asas-platform → asas-backoffice}/gitignore +0 -0
  293. /package/template/apps/{asas-platform → asas-backoffice}/public/favicon.svg +0 -0
  294. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/accordion.tsx +0 -0
  295. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/alert-dialog.tsx +0 -0
  296. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/avatar.tsx +0 -0
  297. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/badge.tsx +0 -0
  298. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/breadcrumb.tsx +0 -0
  299. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button-group.tsx +0 -0
  300. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button.tsx +0 -0
  301. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/calendar.tsx +0 -0
  302. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/card.tsx +0 -0
  303. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/chart.tsx +0 -0
  304. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/checkbox.tsx +0 -0
  305. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/collapsible.tsx +0 -0
  306. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/combobox.tsx +0 -0
  307. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dialog.tsx +0 -0
  308. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/direction.tsx +0 -0
  309. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/directional-icon.tsx +0 -0
  310. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dropdown-menu.tsx +0 -0
  311. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/field.tsx +0 -0
  312. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-group.tsx +0 -0
  313. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-otp.tsx +0 -0
  314. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input.tsx +0 -0
  315. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/label.tsx +0 -0
  316. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/popover.tsx +0 -0
  317. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/radio-group.tsx +0 -0
  318. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/resizable.tsx +0 -0
  319. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/scroll-area.tsx +0 -0
  320. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/select.tsx +0 -0
  321. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/separator.tsx +0 -0
  322. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sheet.tsx +0 -0
  323. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sidebar.tsx +0 -0
  324. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/skeleton.tsx +0 -0
  325. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/slider.tsx +0 -0
  326. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/spinner.tsx +0 -0
  327. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/switch.tsx +0 -0
  328. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/table.tsx +0 -0
  329. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tabs.tsx +0 -0
  330. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/textarea.tsx +0 -0
  331. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle-group.tsx +0 -0
  332. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle.tsx +0 -0
  333. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tooltip.tsx +0 -0
  334. /package/template/apps/{asas-platform → asas-backoffice}/src/copy.ts +0 -0
  335. /package/template/apps/{asas-platform → asas-backoffice}/src/hooks/use-mobile.ts +0 -0
  336. /package/template/apps/{asas-platform → asas-backoffice}/src/lib/utils.ts +0 -0
  337. /package/template/apps/{asas-platform → asas-backoffice}/src/theme.ts +0 -0
  338. /package/template/templates/{asas/src/kayan → features}/auth/user/index.ts +0 -0
  339. /package/template/templates/{asas/src → features/courses}/courses/ui/no-courses.tsx +0 -0
  340. /package/template/templates/{asas → mithaq}/components.json +0 -0
  341. /package/template/templates/{asas → mithaq}/src/storage/defaults.ts +0 -0
  342. /package/template/templates/{asas → mithaq}/src/storage/local.ts +0 -0
  343. /package/template/templates/{asas → mithaq}/src/storage/r2.ts +0 -0
  344. /package/template/templates/{asas → mithaq}/src/storage/s3.ts +0 -0
  345. /package/template/templates/{asas → mithaq}/src/storage/uploadthing.ts +0 -0
  346. /package/template/templates/{asas → mithaq}/src/ui/kpi-card.tsx +0 -0
  347. /package/template/templates/{asas → mithaq}/src/ui/record-page.tsx +0 -0
  348. /package/template/templates/{asas → mithaq}/src/ui/table-page.tsx +0 -0
@@ -1,5 +1,6 @@
1
1
  import { Link, createFileRoute } from '@tanstack/react-router'
2
- import { BookOpen, LayoutDashboard, Languages, ShieldCheck, Users } from 'lucide-react'
2
+ import { organizations, signUp } from 'asas/organizations'
3
+ import { LayoutDashboard, Languages, ShieldCheck } from 'lucide-react'
3
4
  import { Button } from '@/components/ui/button'
4
5
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
5
6
  import { useHomeLabel } from '../auth-frame.tsx'
@@ -13,17 +14,22 @@ export const Route = createFileRoute('/')({
13
14
  component: Landing,
14
15
  })
15
16
 
17
+ /** Whether a visitor with no invitation may make an account, so the page offers one, as sign-in does. */
18
+ const offersSignUp = organizations === 'many' || signUp === 'open'
19
+
16
20
  function EntryActions({ screens, size }: { screens: readonly { name: string; path: string }[] | null; size: 'sm' | 'lg' }) {
17
21
  const { t } = useCopy()
18
22
  const label = useHomeLabel()
19
23
  if (screens === null) {
20
24
  return (
21
25
  <>
22
- <Link to="/auth/sign-up">
23
- <Button size={size}>{t('marketing.getStarted', 'Get started')}</Button>
24
- </Link>
26
+ {offersSignUp ? (
27
+ <Link to="/auth/sign-up">
28
+ <Button size={size}>{t('marketing.getStarted', 'Get started')}</Button>
29
+ </Link>
30
+ ) : null}
25
31
  <Link to="/auth/sign-in">
26
- <Button size={size} variant="outline">
32
+ <Button size={size} variant={offersSignUp ? 'outline' : 'default'}>
27
33
  {t('auth.signIn', 'Sign in')}
28
34
  </Button>
29
35
  </Link>
@@ -39,7 +45,7 @@ function EntryActions({ screens, size }: { screens: readonly { name: string; pat
39
45
  )
40
46
  }
41
47
 
42
- function Feature({ icon: Icon, title, description }: { icon: typeof BookOpen; title: string; description: string }) {
48
+ function Feature({ icon: Icon, title, description }: { icon: typeof ShieldCheck; title: string; description: string }) {
43
49
  return (
44
50
  <Card>
45
51
  <CardHeader>
@@ -69,15 +75,13 @@ function Landing() {
69
75
  // `t` answers `string | undefined` for a key no locale carries; the fallback only satisfies that type.
70
76
  const tt = (key: string, fallback: string) => t(key, fallback) ?? fallback
71
77
  const features = [
72
- { icon: BookOpen, title: tt('marketing.featureCoursesTitle', 'Courses and sessions'), description: tt('marketing.featureCoursesDescription', '') },
73
- { icon: Users, title: tt('marketing.featureEnrollmentsTitle', 'Enrollments'), description: tt('marketing.featureEnrollmentsDescription', '') },
74
78
  { icon: ShieldCheck, title: tt('marketing.featureRolesTitle', 'Roles and permissions'), description: tt('marketing.featureRolesDescription', '') },
75
79
  { icon: Languages, title: tt('marketing.featureBilingualTitle', 'Arabic and English, either direction'), description: tt('marketing.featureBilingualDescription', '') },
76
80
  { icon: LayoutDashboard, title: tt('marketing.featurePortalTitle', 'A dashboard and a customer portal'), description: tt('marketing.featurePortalDescription', '') },
77
81
  ]
78
82
  const steps = [
79
83
  { title: tt('marketing.howStep1Title', 'Set up your organization'), description: tt('marketing.howStep1Description', '') },
80
- { title: tt('marketing.howStep2Title', 'Add courses and roles'), description: tt('marketing.howStep2Description', '') },
84
+ { title: tt('marketing.howStep2Title', 'Add your records and roles'), description: tt('marketing.howStep2Description', '') },
81
85
  { title: tt('marketing.howStep3Title', 'Open the portal to your customers'), description: tt('marketing.howStep3Description', '') },
82
86
  ]
83
87
  return (
@@ -94,7 +98,7 @@ function Landing() {
94
98
 
95
99
  <main className="flex-1">
96
100
  <section className="mx-auto flex max-w-4xl flex-col items-center gap-6 px-4 py-20 text-center sm:px-6 sm:py-28">
97
- <h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('marketing.heroTitle', 'Run courses and enrollments without the busywork')}</h1>
101
+ <h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('marketing.heroTitle', 'Run your organization without the busywork')}</h1>
98
102
  <p className="max-w-2xl text-lg text-muted-foreground">{t('marketing.heroSubtitle', '')}</p>
99
103
  <div className="flex flex-col gap-3 sm:flex-row">
100
104
  <EntryActions screens={screens} size="lg" />
@@ -103,7 +107,7 @@ function Landing() {
103
107
 
104
108
  <section className="mx-auto max-w-6xl px-4 py-16 sm:px-6">
105
109
  <div className="mx-auto mb-12 max-w-2xl text-center">
106
- <h2 className="text-3xl font-bold tracking-tight">{t('marketing.featuresTitle', 'Everything a training organization needs')}</h2>
110
+ <h2 className="text-3xl font-bold tracking-tight">{t('marketing.featuresTitle', 'Everything an organization needs')}</h2>
107
111
  <p className="mt-3 text-muted-foreground">{t('marketing.featuresSubtitle', '')}</p>
108
112
  </div>
109
113
  <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
@@ -125,7 +129,7 @@ function Landing() {
125
129
  </section>
126
130
 
127
131
  <section className="mx-auto flex max-w-3xl flex-col items-center gap-6 px-4 py-20 text-center sm:px-6">
128
- <h2 className="text-3xl font-bold tracking-tight">{t('marketing.ctaTitle', 'Start running your courses on Asas')}</h2>
132
+ <h2 className="text-3xl font-bold tracking-tight">{t('marketing.ctaTitle', 'Start running your organization on Asas')}</h2>
129
133
  <p className="text-muted-foreground">{t('marketing.ctaSubtitle', '')}</p>
130
134
  <EntryActions screens={screens} size="lg" />
131
135
  </section>
@@ -133,10 +137,10 @@ function Landing() {
133
137
 
134
138
  <footer className="border-t py-8">
135
139
  <div className="mx-auto max-w-6xl px-4 text-center text-sm text-muted-foreground sm:px-6">
136
- {t('marketing.footer', 'Asas — courses and enrollments for training organizations.')}
140
+ {t('marketing.footer', 'Asas — one place for an organization\'s records.')}
137
141
  </div>
138
142
  </footer>
139
143
  </div>
140
144
  )
141
145
  }
142
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 32a4513d4cf4a984b7845056965d48edff53542ce962b466af67a87983832a6d
146
+ // mithaq-admin 0.0.0 4ea8d722cd59ec1a91778142281c6f7db6ab9abe005dfc5f8c0192ee4e1095aa
@@ -4,9 +4,17 @@ import { Button } from '@/components/ui/button'
4
4
  import { authArm } from '../api.ts'
5
5
  import { AuthFrame, useRefusal } from '../auth-frame.tsx'
6
6
  import { documentHead } from '../copy.ts'
7
+ import { NoOrganization } from '../no-organization.tsx'
7
8
  import { ImpersonationBar, loadEnterable } from '../screens.tsx'
8
9
  import { useCopy } from './__root.tsx'
9
10
 
11
+ /**
12
+ * Where someone sent here from a screen their role does not open goes without being asked: the one screen it does
13
+ * open. A page refused inside that screen is said instead, and so is an impersonation, whose bar is the way out.
14
+ */
15
+ export const onwardOf = ({ screens, refused, impersonating }: { screens: readonly { path: string }[]; refused?: unknown; impersonating?: unknown }) =>
16
+ !refused && !impersonating && screens.length === 1 ? screens[0]!.path : undefined
17
+
10
18
  export const Route = createFileRoute('/unauthorized')({
11
19
  // The path a screen refused; the server only matches it against the build's pages.
12
20
  validateSearch: (search: Record<string, unknown>): { from?: string } => (typeof search.from === 'string' ? { from: search.from } : {}),
@@ -15,6 +23,8 @@ export const Route = createFileRoute('/unauthorized')({
15
23
  const entered = await loadEnterable({ data: { from: deps.from, locale: context.locale } })
16
24
  // Carries this page's own path so a visitor whose session expired lands back here after signing in.
17
25
  if (!entered) throw redirect({ to: '/auth/sign-in', search: { redirect: '/unauthorized' } })
26
+ const onward = onwardOf(entered)
27
+ if (onward) throw redirect({ href: onward })
18
28
  return entered
19
29
  },
20
30
  // After the loader, so its data's type is inferred before this reads it.
@@ -49,19 +59,21 @@ function Unauthorized() {
49
59
  : part === '{page}' ? <bdi key={at}>{refused?.page}</bdi>
50
60
  : part)
51
61
  const link = 'text-foreground underline underline-offset-4'
62
+ // Someone who belongs to no organization is shown the ways into one, which the app draws.
63
+ const unplaced = !refused && !screens.length && !organizations.length && !others.length && !impersonating
52
64
  const screenName = (name: string) => t(`screens.${name}`, name) ?? name
53
65
  return (
54
66
  <AuthFrame
55
- title={screens.length ? t('auth.unauthorized', 'Not for your role') : t('auth.noAccess', 'Your account has no access yet')}
67
+ title={screens.length ? t('auth.unauthorized', 'Choose where to go') : t('auth.noAccess', 'Your account has no access yet')}
56
68
  description={
57
69
  <span data-no-access={screens.length ? undefined : ''}>
58
70
  {refused
59
71
  ? said(t('auth.unauthorizedPage', 'Your role does not open that page: {page}.'))
60
72
  : screens.length
61
- ? t('auth.unauthorizedDescription', 'Your role does not open that screen. These are the screens it opens:')
73
+ ? t('auth.unauthorizedDescription', 'This page isn\'t part of your account. Where would you like to go?')
62
74
  : organizations.length
63
75
  ? said(t('auth.noAccessDescription', '{email} is a member of {organization} and holds no role that opens a screen here. Ask an administrator of {organization} to give you one.'))
64
- : said(t('auth.noOrganization', '{email} belongs to no organization yet. Ask an administrator to invite you.'))}
76
+ : said(t('auth.noOrganization', '{email} belongs to no organization yet.'))}
65
77
  </span>
66
78
  }
67
79
  >
@@ -73,16 +85,16 @@ function Unauthorized() {
73
85
  {(t('auth.backTo', 'Back to {screen}') ?? 'Back to {screen}').replace('{screen}', () => screenName(refused.screen.name))}
74
86
  </a>
75
87
  ) : screens.length ? (
76
- <ul className="grid gap-2">
88
+ <div className="grid gap-2">
77
89
  {screens.map((screen) => (
78
- <li key={screen.name}>
79
- {/* An anchor: the path is the screen's own data, not a route the router knows. */}
80
- <a className={link} href={screen.path}>
81
- {screenName(screen.name)}
82
- </a>
83
- </li>
90
+ // An anchor: the path is the screen's own data, not a route the router knows.
91
+ <Button key={screen.name} nativeButton={false} className="max-sm:h-11" render={<a href={screen.path} />}>
92
+ {screenName(screen.name)}
93
+ </Button>
84
94
  ))}
85
- </ul>
95
+ </div>
96
+ ) : unplaced ? (
97
+ <NoOrganization email={email} />
86
98
  ) : null}
87
99
  {/* So a switch into an organization where the actor holds no role is not a dead end. */}
88
100
  {others.map((one) => (
@@ -119,4 +131,4 @@ function Unauthorized() {
119
131
  </AuthFrame>
120
132
  )
121
133
  }
122
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 3c5c304b3892c56759bf780f155ef3aa154ce3cc954e3d240b3537cdd97da735
134
+ // mithaq-admin 0.0.0 81d25978cdacbb38254854b0f64b738946ee27baca06a952aed51d35e6fb10af
@@ -1,9 +1,11 @@
1
1
  import { useBlocker, useRouter } from '@tanstack/react-router'
2
2
  import { useState, type ReactElement } from 'react'
3
- import { Form, type FormProps } from './components/mithaq/form.tsx'
3
+ import { Form } from 'mithaq-admin/form.tsx'
4
+ import type { FormProps } from 'mithaq-admin/form-types.tsx'
4
5
 
5
6
  /**
6
- * A kayan's form on a screen's page. A create moves to the new row's edit page, a delete to the list.
7
+ * A kayan's form on a screen's page. A create moves to where `edit` sends it, the new row's edit page or, for a page
8
+ * opened over its list, the list; a delete moves to the list.
7
9
  * While the form holds a change the router blocks navigation and the form asks its discard question.
8
10
  */
9
11
  export function ScreenForm({ edit, list, ...props }: Omit<FormProps, 'onDone' | 'onDirty' | 'onCancel' | 'blocked'> & {
@@ -31,4 +33,4 @@ export function ScreenForm({ edit, list, ...props }: Omit<FormProps, 'onDone' |
31
33
  />
32
34
  )
33
35
  }
34
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 7284b5e97770fd0c395fafa0b7459cab4ceb458e4bc982dc8233894126116170
36
+ // mithaq-admin 0.0.0 508fa95987312292d3a5737dd1fd6aba03fbd5a7dbbbf5bb3bea2812b2adfde4
@@ -2,10 +2,11 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'
2
2
  import { useState, type ReactElement } from 'react'
3
3
  import { ListContext, useScreen } from 'mithaq/admin/react'
4
4
  import { Button } from '@/components/ui/button'
5
- import { List, type ListProps, type Page } from './components/mithaq/list.tsx'
6
- import { QueryRefused, findOf, readQuery, readRaw, rulesOf, scopedTo, writeQuery, type ListQuery, type RawQuery } from './components/mithaq/list-query.ts'
7
- import { counted } from './components/mithaq/controls.tsx'
8
- import { listOf } from './components/mithaq/nodes.tsx'
5
+ import { List } from 'mithaq-admin/list.tsx'
6
+ import type { ListProps, Page } from 'mithaq-admin/list-types.tsx'
7
+ import { QueryRefused, findOf, readQuery, readRaw, rulesOf, scopedTo, writeQuery, type ListQuery, type RawQuery } from 'mithaq-admin/list-query.ts'
8
+ import { counted } from 'mithaq-admin/format.tsx'
9
+ import { listOf } from 'mithaq-admin/node-tree.ts'
9
10
 
10
11
  /**
11
12
  * The list's search as the router carries it: the URL's pairs as strings, each
@@ -136,4 +137,4 @@ export function ScreenList({
136
137
  </ListContext>
137
138
  )
138
139
  }
139
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 3c37825f9f645fd3f9c95a3d1ec554e863e85767e8329a1b2e16a40e5f5ea381
140
+ // mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 900f9707948cf18e36aec6776da8f9e77e4ed92438073443319e54e7a33a9ecf
@@ -7,4 +7,4 @@ export const screenNamed = <Known extends object>(known: Known, name: unknown):
7
7
  if (typeof name !== 'string' || !Object.hasOwn(known, name)) throw new Error(`"${String(name)}" is no screen of this app.`)
8
8
  return name as keyof Known & string
9
9
  }
10
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 878b7180f2aa169546feb42bcdfcb6002841d08e46788c94ddd6edfc4e3502ca
10
+ // mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 878b7180f2aa169546feb42bcdfcb6002841d08e46788c94ddd6edfc4e3502ca
@@ -7,17 +7,27 @@ import { ScreenContext, useScreen, type ScreenState } from 'mithaq/admin/react'
7
7
  import { actingAs, arm, authArm, caused, clientOf, readsOf, takesKey } from './api.ts'
8
8
  import { clampedHref, routedHref } from './links.ts'
9
9
  import { mergedHeaders } from './request-headers.ts'
10
+ import { nameIn } from 'asas/organizations'
10
11
  import { documentHead, localeOf, locales, translator, type Locale } from './copy.ts'
11
- import { FieldValue, type FormClient, type FormProps } from './components/mithaq/form.tsx'
12
- import type { Page } from './components/mithaq/list.tsx'
13
- import { QueryRefused, findOf, listRaw, prefixOf, readQuery, rulesOf, scopedTo, withList, type RawQuery } from './components/mithaq/list-query.ts'
14
- import { createsIn, newTitleOf, titleOf } from './components/mithaq/controls.tsx'
15
- import { NavLinks, NodeButton, OwnButton, RecordFrame, formIn, holdFocus, frameOf, listIn, nodesOf, pageButtonOf, pageOf, readsAsValues, refOf, relatedIn, rowTitle, scopeOf, type Draw, type Refs, type TreePage } from './components/mithaq/nodes.tsx'
16
- import { idsOf } from './components/mithaq/relationship-control.tsx'
12
+ import { FieldValue } from 'mithaq-admin/field-value.tsx'
13
+ import type { FormClient, FormProps } from 'mithaq-admin/form-types.tsx'
14
+ import type { Page } from 'mithaq-admin/list-types.tsx'
15
+ import { QueryRefused, findOf, listRaw, prefixOf, readQuery, rulesOf, scopedTo, withList, type RawQuery } from 'mithaq-admin/list-query.ts'
16
+ import { newTitleOf, titleOf } from 'mithaq-admin/copy.ts'
17
+ import { createsIn } from 'mithaq-admin/field-values.ts'
18
+ import { NavLinks } from 'mithaq-admin/nav-links.tsx'
19
+ import { NodeButton, OwnButton, pageButtonOf } from 'mithaq-admin/node-buttons.tsx'
20
+ import { formIn, listIn, nodesOf, readsAsValues, relatedIn, scopeOf, type Draw } from 'mithaq-admin/node-tree.ts'
21
+ import { frameOf, pageOf } from 'mithaq-admin/nodes.tsx'
22
+ import { RecordFrame, holdFocus } from 'mithaq-admin/record-frame.tsx'
23
+ import { rowTitle } from 'mithaq-admin/row-title.tsx'
24
+ import { refOf, type Refs, type TreePage } from 'mithaq-admin/screen-refs.ts'
25
+ import { idsOf } from 'mithaq-admin/relationship-control.tsx'
17
26
  import { ScreenForm } from './screen-form.tsx'
18
27
  import { ScreenList, askedFor, readsDrafts, rowReadsDrafts, rowsKey } from './screen-list.tsx'
19
28
  import { screenNamed } from './screen-names.ts'
20
29
  import { slide } from './slide.ts'
30
+ import { Toaster } from 'mithaq-admin/toast.tsx'
21
31
  import { useCopy } from './routes/__root.tsx'
22
32
 
23
33
  /**
@@ -29,13 +39,13 @@ export const getAsas = createServerOnlyFn(async () => (await import('asas')).asa
29
39
  type InProcess = Record<string, { operations: Record<string, (input: object, options: object) => Promise<unknown>> }>
30
40
 
31
41
  /**
32
- * Each kayan's operations in process. In the platform app a read of a kayan asks `tenant: 'all'`,
33
- * which the runner grants platform staff there alone, so staff read every organization's rows; a
42
+ * Each kayan's operations in process. In the backoffice app a read of a kayan asks `tenant: 'all'`,
43
+ * which the runner grants backoffice staff there alone, so staff read every organization's rows; a
34
44
  * global is one organization's row and asks for none, and a write never asks.
35
45
  */
36
46
  const inProcess = async (asas: Awaited<ReturnType<typeof getAsas>>): Promise<InProcess> => {
37
47
  const kayan = asas.kayan as unknown as InProcess
38
- if (!(await import('asas')).onPlatform()) return kayan
48
+ if (!(await import('asas')).onBackoffice()) return kayan
39
49
  const reads = new Set(asas.operations.filter((one) => one.effect === 'read' && !one.single).map((one) => `${one.kayan}/${one.name}`))
40
50
  return Object.fromEntries(Object.entries(kayan).map(([name, one]) => [name, {
41
51
  operations: Object.fromEntries(Object.entries(one.operations).map(([operation, run]) =>
@@ -117,7 +127,7 @@ type Actor = {
117
127
  readonly email?: string | null
118
128
  readonly impersonatedBy?: unknown
119
129
  readonly activeOrganizationId?: string | null
120
- readonly organizations?: readonly { readonly id: string; readonly name: string; readonly slug?: string }[]
130
+ readonly organizations?: readonly { readonly id: string; readonly name: string; readonly slug?: string; readonly names?: unknown }[]
121
131
  }
122
132
 
123
133
  // `'unauthorized'` is answered before the projection is read, so a refused screen sends no kayan, field, row or page;
@@ -140,15 +150,15 @@ const resolveScreen = async (asked: unknown) => {
140
150
  .filter(([one]) => report.collections?.[one]?.read === true)
141
151
  .map(([one, value]) => [one, { ...value, fields: value.fields.filter((field) => report.collections?.[one]?.fields?.[field.name]?.read === true) }]),
142
152
  )
143
- const { onPlatform, platformEntry, PLATFORM_SCREEN } = await import('asas')
144
- const platform = platformEntry(actor)
145
- // The other screens of this app the actor may enter. The platform's own name
146
- // is left out of a tenant app's list, so the one link to it is `platformEntry`'s alone.
153
+ const { onBackoffice, backofficeEntry, BACKOFFICE_SCREEN } = await import('asas')
154
+ const backoffice = backofficeEntry(actor)
155
+ // The other screens of this app the actor may enter. The backoffice's own name
156
+ // is left out of a tenant app's list, so the one link to it is `backofficeEntry`'s alone.
147
157
  const served = screensOf(asas.targets) as Record<string, { client: ScreenClient }>
148
158
  const siblings = (report.screens ?? [])
149
- .filter((one) => one !== name && Object.hasOwn(served, one) && (one === PLATFORM_SCREEN) === onPlatform())
159
+ .filter((one) => one !== name && Object.hasOwn(served, one) && (one === BACKOFFICE_SCREEN) === onBackoffice())
150
160
  .map((one) => ({ name: one, path: served[one]!.client.path }))
151
- const others = [...siblings, ...(platform ? [{ name: PLATFORM_SCREEN, path: platform }] : [])]
161
+ const others = [...siblings, ...(backoffice ? [{ name: BACKOFFICE_SCREEN, path: backoffice }] : [])]
152
162
  return {
153
163
  asas,
154
164
  request,
@@ -164,7 +174,8 @@ const resolveScreen = async (asked: unknown) => {
164
174
  ...(actor.impersonatedBy ? { impersonating: true } : {}),
165
175
  ...(others.length ? { screens: others } : {}),
166
176
  ...(actor.organizations && {
167
- organizations: actor.organizations.map(({ id, name, slug }) => ({ id, name, slug })),
177
+ // Every locale's, because the frame is read before the viewer's locale is known here; the shell picks one.
178
+ organizations: actor.organizations.map(({ id, name, slug, names }) => ({ id, name, slug, ...(typeof names === 'string' && { names }) })),
168
179
  activeOrganizationId: actor.activeOrganizationId ?? null,
169
180
  }),
170
181
  },
@@ -206,14 +217,14 @@ export const loadEnterable = createServerFn({ method: 'GET' })
206
217
  others?: { id: string; name: string; slug?: string }[]
207
218
  impersonating?: string
208
219
  } | null> => {
209
- const [{ getRequest }, asas, { onPlatform, PLATFORM_SCREEN }] = await Promise.all([import('@tanstack/react-start/server'), getAsas(), import('asas')])
220
+ const [{ getRequest }, asas, { onBackoffice, BACKOFFICE_SCREEN }] = await Promise.all([import('@tanstack/react-start/server'), getAsas(), import('asas')])
210
221
  const request = getRequest()
211
222
  const actor = (await asas.auth.actor(request.headers)) as Actor | null
212
223
  if (!actor?.email) return null
213
224
  const report = (await (await asas.handler(new Request(new URL('/api/access', request.url), { headers: request.headers }))).json()) as AccessReport
214
225
  const screens = screensOf(asas.targets) as Record<string, { client: ScreenClient }>
215
226
  const entered = (report.screens ?? [])
216
- .filter((name) => Object.hasOwn(screens, name) && (name === PLATFORM_SCREEN) === onPlatform())
227
+ .filter((name) => Object.hasOwn(screens, name) && (name === BACKOFFICE_SCREEN) === onBackoffice())
217
228
  .map((name) => ({ name, path: screens[name]!.client.path }))
218
229
  const impersonating = actor.impersonatedBy ? { impersonating: actor.name ?? actor.email } : {}
219
230
  if (entered.length) {
@@ -226,11 +237,14 @@ export const loadEnterable = createServerFn({ method: 'GET' })
226
237
  }
227
238
  const memberships = actor.organizations ?? []
228
239
  const active = memberships.find((one) => one.id === actor.activeOrganizationId)
240
+ // Named here, where the locale is known: the page draws what it is handed, and a name the viewer cannot read
241
+ // in the default locale would be no name to them.
242
+ const named = ({ id, name, slug, names }: NonNullable<Actor['organizations']>[number]) => ({ id, name: nameIn({ name, names }, data.locale), slug })
229
243
  return {
230
244
  screens: entered,
231
245
  email: actor.email,
232
- organizations: (active ? [active] : memberships).map(({ id, name, slug }) => ({ id, name, slug })),
233
- others: onPlatform() ? [] : memberships.filter((one) => one !== active).map(({ id, name, slug }) => ({ id, name, slug })),
246
+ organizations: (active ? [active] : memberships).map(named),
247
+ others: onBackoffice() ? [] : memberships.filter((one) => one !== active).map(named),
234
248
  ...impersonating,
235
249
  }
236
250
  })
@@ -473,7 +487,8 @@ const nameOfPage = ({ screen, name, match, row, titledBy, copy }: {
473
487
  if (match.kayan === undefined) return screen.nav.find((entry) => !entry.kayan && entry.page === match.page)?.label?.[copy.locale] ?? copy.t(`screens.${name}`, name) ?? name
474
488
  const kayan = screen.kayan[match.kayan]!
475
489
  const page = kayan.pages[match.page]
476
- const many = !!page && 'page' in page && listIn(nodesOf(page)) !== undefined
490
+ // A list page draws every row, whether a tree's list node or a component drawing it whole.
491
+ const many = match.page === 'list' || (!!page && 'page' in page && listIn(nodesOf(page)) !== undefined)
477
492
  if (match.page === 'create') return newTitleOf({ kayan, copy })
478
493
  const field = kayan.fields.find((one) => one.name === kayan.useAsTitle)
479
494
  const target = field?.kind === 'relationship' ? screen.kayan[field.relationTo ?? ''] : undefined
@@ -500,7 +515,7 @@ const useScreenState = (data: ScreenData): ScreenState => {
500
515
 
501
516
  /**
502
517
  * The bar that says the session is an impersonation, with the way out. Stopping
503
- * is a sign-out: the staff member's own session lives in the platform app under
518
+ * is a sign-out: the staff member's own session lives in the backoffice app under
504
519
  * that app's cookie, and the sign-out answers where they go next.
505
520
  */
506
521
  export function ImpersonationBar({ name }: { name: string }): ReactElement {
@@ -544,6 +559,7 @@ function ScreenBar({ user }: { user: ScreenData['user'] }): ReactElement | null
544
559
 
545
560
  /** The screen's own frame: its providers, outermost first, around the frame's bar and the page. */
546
561
  export function ScreenFrame({ files, data, children }: { files: Files; data: ScreenData; children: ReactNode }): ReactElement {
562
+ const { t } = useCopy()
547
563
  const refs = refsOf(files, data.screen)
548
564
  // One per frame: made per request on the server, so no row one actor read
549
565
  // reaches another's render; held for the frame's life in the browser.
@@ -590,6 +606,8 @@ export function ScreenFrame({ files, data, children }: { files: Files; data: Scr
590
606
  <>
591
607
  <ScreenBar user={data.user} />
592
608
  {children}
609
+ {/* The one place every write's toast is drawn, kept across the screen's pages. */}
610
+ <Toaster closeLabel={t('admin.close', 'Close') ?? 'Close'} />
593
611
  </>,
594
612
  )}
595
613
  </Suspense>
@@ -666,7 +684,7 @@ function PageBody({ files, data, query, onQuery }: PageViewProps): ReactElement
666
684
  return path ? `${base}${id ? path.replace(':id', encodeURIComponent(id)) : path}` : undefined
667
685
  }
668
686
  // A row opens its kayan's edit page, else the one page whose path draws a
669
- // row (how the platform opens a record it only reads).
687
+ // row (how the backoffice opens a record it only reads).
670
688
  const rowPage = (target: ClientKayan) => (target.pages.edit ? 'edit' : Object.keys(target.pages).find((one) => target.pages[one]!.path.includes(':id')))
671
689
  const related = relatedOf(data.screen, kayan)
672
690
  const go = (href: string | undefined) => void (href ? router.navigate({ href }) : router.invalidate())
@@ -844,7 +862,9 @@ function PageBody({ files, data, query, onQuery }: PageViewProps): ReactElement
844
862
  api={readsOf(related)}
845
863
  operations={data.operations}
846
864
  related={draw(page).list}
847
- edit={(id) => hrefOf(kayan, 'edit', id)}
865
+ // A create opened over its list closes back to it once it lands, the new row in the list, as Payload's drawer does;
866
+ // one whose kayan autosaves its drafts moves to the row's page instead, since its first autosave is a create too.
867
+ edit={(id) => (page.mode && listHref && !data.drafts?.autosave ? listHref : hrefOf(kayan, 'edit', id))}
848
868
  list={listHref}
849
869
  drafts={data.drafts}
850
870
  expectedUpdatedAt={data.expectedUpdatedAt}
@@ -865,4 +885,4 @@ function PageBody({ files, data, query, onQuery }: PageViewProps): ReactElement
865
885
  }
866
886
  return form
867
887
  }
868
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 d96a3dd603b5aead8e3d82e8792fa40d5f4dac1bfbee2e7064ab2be888b595a6
888
+ // mithaq-admin 0.0.0 da2e742262e16a7f9f8dab050c2a4201517478795101af68736907464a0d52ce
@@ -1,6 +1,16 @@
1
1
  import handler, { createServerEntry } from '@tanstack/react-start/server-entry'
2
+ import type { ScreenClient } from 'mithaq/admin'
3
+ import { checkComponents } from 'mithaq-admin/components.tsx'
4
+ import { components } from './components.ts'
2
5
  import { withQuietAbort } from './quiet-abort.ts'
3
6
 
7
+ // Every screen this app serves, checked against its components map as it starts, so a field whose kind has no
8
+ // control fails the start, naming the kind and the field, and never reaches a page. Read on the server alone,
9
+ // since a screen.json names every field of the screen.
10
+ for (const client of Object.values(import.meta.glob<ScreenClient>('./routes/(screens)/*/screen.json', { eager: true, import: 'default' }))) {
11
+ checkComponents({ client, components })
12
+ }
13
+
4
14
  // A production start connects once before handing on any request, and `connect()` refuses to
5
15
  // start while a migration is unapplied; a refused start exits 1. NODE_ENV is read off `process`
6
16
  // so Vite cannot replace it at build time, and a built server started without
@@ -19,4 +19,4 @@ export const slide = async ({
19
19
  // Set once with every strategy's cookies: a second set replaces the first.
20
20
  if (cookies.length) setResponseHeader('set-cookie', cookies)
21
21
  }
22
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 bc0221e1088103f8638e0333b84aafb6b326e3991a187ced198bce2d090ecbe9
22
+ // mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 bc0221e1088103f8638e0333b84aafb6b326e3991a187ced198bce2d090ecbe9
@@ -2,17 +2,21 @@
2
2
  imports, custom-variant, :root and .dark variables, the @theme inline
3
3
  block and the @layer base rules, unchanged. Only the @source line below
4
4
  differs, pointing at this project's template instead of TB's monorepo.
5
- Tailwind finds this app's own files, the copied `src/components/mithaq`
5
+ Tailwind finds this app's own files, an ejected `src/components/mithaq`
6
6
  among them, without one. */
7
7
  @import 'tailwindcss';
8
8
  @import 'tw-animate-css';
9
9
  @import 'shadcn/tailwind.css';
10
+ /* The admin's pieces in node_modules, which Tailwind never scans on its own. */
11
+ @import 'mithaq-admin/source.css';
10
12
  @import '@fontsource-variable/raleway';
11
13
  @import '@fontsource/ibm-plex-sans-arabic';
12
14
 
13
15
  /* The project's own shells, pages and components, which the screens load
14
- through the same linked package (`templates/asas/src/ui/app-shell.tsx`). */
15
- @source '../node_modules/asas/src';
16
+ (the language package's `src/ui/app-shell.tsx`), in every package of the project.
17
+ Named by their folder, which every package manager leaves where it is, since
18
+ each links the package elsewhere. */
19
+ @source '../../../templates';
16
20
 
17
21
  @custom-variant dark (&:is(.dark *));
18
22
 
@@ -13,7 +13,7 @@
13
13
  "allowImportingTsExtensions": true,
14
14
  "resolveJsonModule": true,
15
15
  "types": ["node", "vite/client"],
16
- "paths": { "@/*": ["./src/*"] }
16
+ "paths": { "@/*": ["./src/*"], "mithaq-admin/*": ["./src/components/mithaq/*", "./node_modules/mithaq-admin/registry/*"] }
17
17
  },
18
- "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "../../templates/asas/src/ui/*.tsx", "../../templates/asas/src/ui-*/*.tsx", "../../templates/asas/src/*/ui/*.tsx"]
18
+ "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "../../templates/mithaq/src/ui/*.tsx", "../../templates/asas/src/ui-*/*.tsx", "../../templates/features/*/*/ui/*.tsx"]
19
19
  }
@@ -1,20 +1,26 @@
1
1
  import tailwindcss from '@tailwindcss/vite'
2
2
  import { tanstackStart } from '@tanstack/react-start/plugin/vite'
3
3
  import viteReact from '@vitejs/plugin-react'
4
+ import { mithaqAdmin } from 'mithaq/vite'
4
5
  import { defineConfig } from 'vite'
5
6
  import { fileURLToPath } from 'node:url'
6
7
 
7
8
  export default defineConfig({
8
9
  // An alias rather than `resolve.tsconfigPaths`, which applies only to files the nearest
9
- // tsconfig claims: the project's shells in `templates/asas/src` import `@/` too.
10
+ // tsconfig claims: the project's shells in the language package's `src/ui/` import `@/` too.
10
11
  // `^@/` and not `@`, so `@tanstack/*` is left alone.
11
12
  resolve: { alias: [{ find: /^@\//, replacement: fileURLToPath(new URL('./src/', import.meta.url)) }] },
12
13
  plugins: [
13
14
  tailwindcss(),
15
+ // A file under this folder takes the place of mithaq-admin's file of the same name, for every importer.
16
+ mithaqAdmin(fileURLToPath(new URL('./src/components/mithaq/', import.meta.url))),
14
17
  // `mithaq generate screens` writes `types.ts` and `files.ts` beside the route files, and neither is a route.
15
18
  tanstackStart({ router: { routeFileIgnorePattern: '^(files|types)\\.ts$' } }),
16
19
  viteReact(),
17
20
  ],
21
+ // Behind the proxy that routes by Host: bound to the loopback address it targets, and answering
22
+ // the public name in BETTER_AUTH_URL, which Vite refuses in a Host header unless named here.
23
+ preview: { host: '127.0.0.1', allowedHosts: process.env.BETTER_AUTH_URL ? [new URL(process.env.BETTER_AUTH_URL).hostname] : [] },
18
24
  environments: {
19
25
  ssr: {
20
26
  // `asas` and `mithaq` stay external: the drizzle adapter imports `drizzle-kit/api-postgres`
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "private": true,
3
3
  "type": "module",
4
- "packageManager": "bun@1.3.14",
5
4
  "engines": {
6
5
  "node": ">=24"
7
6
  },
8
7
  "workspaces": [
9
- "templates/*",
8
+ "packages/mithaq",
9
+ "packages/features/*",
10
+ "packages/asas",
10
11
  "apps/*"
11
12
  ],
12
13
  "devDependencies": {
13
- "typescript": "^7.0.2",
14
14
  "@types/node": "^24.12.3"
15
15
  }
16
16
  }
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: add-field
3
+ description: Add, change or remove a field of an existing kayan in this mithaq project and write the migration for it. Use when asked to add a column, change a field's kind or rules, or write a migration.
4
+ ---
5
+
6
+ # Add a field and its migration
7
+
8
+ The guide is "A field" under "Add a feature to an existing kayan" in `README.md`, and "Migrations" in `AGENTS.md`. The installed mithaq is `packages/asas/node_modules/mithaq/`, or `node_modules/mithaq/` where the package manager hoists it to the root: every kind and rule is in its `docs/packages/mithaq/README.md`, and `mithaq migrate` in its `docs/packages/mithaq/src/asas/cli/README.md`.
9
+
10
+ 1. Edit `fields` in the kayan's file. A kind or rule the language does not hold changes the vocabulary: stop and ask the developer.
11
+ 2. A new `required` field on a table with rows needs a literal `defaultValue`, or goes in without `required` and becomes required in a later migration.
12
+ 3. Add the field to the pages that should draw it, and to the roles' grants when they list fields one by one (`<kayan>.fields.<field>`).
13
+ 4. From `packages/asas/`, with `DATABASE_URL` pointing at a database built from the migrations (never a pushed one; `AGENTS.md` "Migrations" says how to build one):
14
+
15
+ ```sh
16
+ bunx mithaq generate screens # when a page draws it
17
+ bunx mithaq generate messages # then fill every empty translation
18
+ bunx mithaq migrate --name <slug>
19
+ bunx mithaq generate --check
20
+ ```
21
+
22
+ 5. When `mithaq migrate` stops and asks for an answer in `mithaq.hints.json`, it is a rename or a drop. A drop deletes rows: ask the developer before you answer it.
23
+ 6. From the project root, `bunx --package typescript tsc -p .` and the same with `-p` naming each app.
24
+
25
+ Commit the migration folder.
26
+
27
+ In a project made with `--layout single`, read each path as `AGENTS.md` maps it; the installed mithaq is `node_modules/mithaq/`.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: add-kayan
3
+ description: Add a kayan, one data type with its table, operations, access and admin pages, to this mithaq project. Use when asked for a new entity, table, resource or list in the dashboard.
4
+ ---
5
+
6
+ # Add a kayan
7
+
8
+ The steps are "Adding a kayan" in `AGENTS.md`, and the guide's worked example is "Add a kayan" in `README.md`. The installed mithaq is `packages/asas/node_modules/mithaq/`, or `node_modules/mithaq/` where the package manager hoists it to the root; its `docs/packages/mithaq/README.md` lists every kind and rule a field can take.
9
+
10
+ 1. Ask, unless the request says: does each row belong to one organization (place `organization`, the usual answer) or does every organization share it?
11
+ 2. Follow `AGENTS.md` "Adding a kayan", copying `packages/features/tags/`. Use only the kinds and rules the language holds; a new one changes the vocabulary, so stop and ask the developer.
12
+ 3. Grant its keys to the roles that use it, as step 4 there says. A kayan no role is granted answers no one.
13
+ 4. From `packages/asas/`, with `DATABASE_URL` pointing at a database built from the migrations (never a pushed one; `AGENTS.md` "Migrations" says how to build one):
14
+
15
+ ```sh
16
+ bunx mithaq generate screens # when a screen draws it
17
+ bunx mithaq generate messages # then fill every empty translation
18
+ bunx mithaq migrate --name add_<name>
19
+ bunx mithaq generate --check
20
+ ```
21
+
22
+ 5. From the project root, `bunx --package typescript tsc -p .` and `bunx --package typescript tsc -p apps/asas-tanstack` (and `apps/asas-backoffice` when the project has it).
23
+
24
+ Commit the migration folder. Never edit a file whose first line reads ``// Written by `mithaq <command>` ``; run the command again.
25
+
26
+ In a project made with `--layout single`, read each path as `AGENTS.md` maps it; the installed mithaq is `node_modules/mithaq/`.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: eject-admin-piece
3
+ description: Change how the admin draws something in this mithaq project, by replacing a control, cell or node in the components map or by ejecting a mithaq-admin piece. Use when asked to restyle or change an admin control, list, form, shell or page piece.
4
+ ---
5
+
6
+ # Change an admin piece
7
+
8
+ The installed mithaq is `packages/asas/node_modules/mithaq/`, or `node_modules/mithaq/` where the package manager hoists it to the root. The guide is the admin's, its `docs/packages/mithaq-admin/README.md` ("Add or replace a control", "Replace a node", "Eject a piece"), and `eject` in its `docs/packages/mithaq/src/asas/cli/README.md`.
9
+
10
+ 1. Reach for the components map first: a control, a cell or a node is replaced in the app's `src/components.ts` without ejecting anything.
11
+ 2. Otherwise eject the piece, in the app that draws it (`apps/asas-tanstack` or `apps/asas-backoffice`):
12
+
13
+ ```sh
14
+ bunx mithaq eject <item> # an unknown name lists every item
15
+ ```
16
+
17
+ It writes the piece into `src/components/mithaq/`, drawn in place of the package's, and names any shadcn primitive or npm package to add. Restart the dev server.
18
+ 3. Edit the ejected file in place; it is the project's now. Keep its last line, the `// mithaq-admin` marker, which `upgrade --check` reads.
19
+ 4. Check, in the app:
20
+
21
+ ```sh
22
+ bunx mithaq upgrade --check # the held files against the installed mithaq-admin
23
+ bunx --package typescript tsc -p .
24
+ ```
25
+
26
+ To go back to the package's piece, delete the file, or run `bunx mithaq eject --prune`, which deletes every ejected file nobody edited.
27
+
28
+ In a project made with `--layout single`, read each path as `AGENTS.md` maps it; the installed mithaq is `node_modules/mithaq/`.