@svgrid/enterprise 2.5.2 → 2.6.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 (620) hide show
  1. package/dist/SvAlertRuleEditor.svelte.d.ts +13 -0
  2. package/dist/SvAlertsManager.svelte.d.ts +14 -0
  3. package/dist/SvAlertsPanel.svelte.d.ts +9 -0
  4. package/dist/SvAuthGate.svelte.d.ts +22 -0
  5. package/dist/SvBoard.svelte.d.ts +35 -0
  6. package/dist/SvExportMenu.svelte.d.ts +61 -0
  7. package/dist/SvExpressionEditor.svelte.d.ts +12 -0
  8. package/dist/SvFileInput.svelte.d.ts +20 -0
  9. package/dist/SvGridAlerts.svelte.d.ts +77 -0
  10. package/dist/SvGridBoard.svelte.d.ts +30 -0
  11. package/dist/SvGridEditPanel.svelte.d.ts +73 -0
  12. package/dist/SvGridMasterDetail.svelte.d.ts +49 -0
  13. package/dist/SvGridScheduler.svelte.d.ts +30 -0
  14. package/dist/SvImportDialog.svelte.d.ts +88 -0
  15. package/dist/SvLookupInput.svelte.d.ts +21 -0
  16. package/dist/SvPivotDesigner.svelte.d.ts +176 -0
  17. package/dist/SvRecordDetail.svelte.d.ts +58 -0
  18. package/dist/SvSchedule.svelte.d.ts +29 -0
  19. package/dist/SvSchemaChart.svelte.d.ts +61 -0
  20. package/dist/SvSchemaDashboard.svelte.d.ts +46 -0
  21. package/dist/alerts/alert-engine-attach.d.ts +79 -0
  22. package/dist/alerts/alert-engine-attach.js +106 -0
  23. package/dist/alerts/alert-engine.d.ts +42 -0
  24. package/dist/alerts/alert-engine.js +196 -0
  25. package/dist/alerts/alert-formats.d.ts +26 -0
  26. package/dist/alerts/alert-formats.js +58 -0
  27. package/dist/alerts/alert-observer.d.ts +54 -0
  28. package/dist/alerts/alert-observer.js +156 -0
  29. package/dist/alerts/alert-scheduler.d.ts +30 -0
  30. package/dist/alerts/alert-scheduler.js +54 -0
  31. package/dist/alerts/alert-storage.d.ts +31 -0
  32. package/dist/alerts/alert-storage.js +92 -0
  33. package/dist/alerts/alert-store.svelte.d.ts +24 -0
  34. package/dist/alerts/alert-store.svelte.js +69 -0
  35. package/dist/alerts/alert-types.d.ts +86 -0
  36. package/dist/alerts/alert-types.js +1 -0
  37. package/dist/alerts.d.ts +2 -0
  38. package/{src/alerts.ts → dist/alerts.js} +9 -9
  39. package/dist/board.d.ts +7 -0
  40. package/{src/board.ts → dist/board.js} +13 -14
  41. package/dist/cdn/svgrid-enterprise.svelte-external.js +2987 -2981
  42. package/dist/designer/assets/{index-CM5xvaOy.js → index-BF0UH638.js} +3 -3
  43. package/dist/designer/assets/{src-D-ET9Mmo.js → src-BPJruwB9.js} +184 -184
  44. package/dist/designer/index.html +2 -2
  45. package/dist/edit-panel.d.ts +105 -0
  46. package/dist/edit-panel.js +350 -0
  47. package/dist/export-conditional.d.ts +31 -0
  48. package/dist/export-conditional.js +65 -0
  49. package/dist/export-ooxml.d.ts +93 -0
  50. package/dist/export-ooxml.js +385 -0
  51. package/dist/export-pdf.d.ts +107 -0
  52. package/dist/export-pdf.js +144 -0
  53. package/dist/export-print.d.ts +55 -0
  54. package/dist/export-print.js +91 -0
  55. package/{src/export-serialize.ts → dist/export-serialize.d.ts} +11 -24
  56. package/dist/export-serialize.js +11 -0
  57. package/dist/export-xls.d.ts +26 -0
  58. package/dist/export-xls.js +157 -0
  59. package/dist/export.d.ts +323 -0
  60. package/dist/export.js +1021 -0
  61. package/dist/expressions/evaluate.d.ts +17 -0
  62. package/dist/expressions/evaluate.js +214 -0
  63. package/dist/expressions/expression-columns.d.ts +61 -0
  64. package/dist/expressions/expression-columns.js +72 -0
  65. package/dist/expressions/expression-types.d.ts +125 -0
  66. package/dist/expressions/expression-types.js +1 -0
  67. package/dist/expressions/parse.d.ts +25 -0
  68. package/dist/expressions/parse.js +537 -0
  69. package/dist/import.d.ts +232 -0
  70. package/dist/import.js +846 -0
  71. package/dist/index.d.ts +66 -0
  72. package/dist/index.js +91 -0
  73. package/dist/install.d.ts +70 -0
  74. package/dist/install.js +61 -0
  75. package/dist/license-core.d.ts +37 -0
  76. package/dist/license-core.js +81 -0
  77. package/dist/license.d.ts +30 -0
  78. package/dist/license.js +139 -0
  79. package/dist/master-detail.d.ts +23 -0
  80. package/dist/master-detail.js +35 -0
  81. package/dist/node/studio.js +1 -1
  82. package/dist/pivot-chart.d.ts +28 -0
  83. package/dist/pivot-chart.js +71 -0
  84. package/dist/pivot-designer.d.ts +69 -0
  85. package/dist/pivot-designer.js +38 -0
  86. package/dist/pivot-enable.d.ts +6 -0
  87. package/{src/pivot-enable.ts → dist/pivot-enable.js} +13 -18
  88. package/dist/pivot.d.ts +111 -0
  89. package/dist/pivot.js +353 -0
  90. package/dist/print.d.ts +33 -0
  91. package/dist/print.js +61 -0
  92. package/dist/revoked.d.ts +1 -0
  93. package/{src/revoked.ts → dist/revoked.js} +5 -6
  94. package/dist/scheduler-assignments.d.ts +64 -0
  95. package/dist/scheduler-assignments.js +92 -0
  96. package/dist/scheduler-axis.d.ts +81 -0
  97. package/dist/scheduler-axis.js +180 -0
  98. package/{src/scheduler-booking.ts → dist/scheduler-booking.d.ts} +10 -43
  99. package/dist/scheduler-booking.js +50 -0
  100. package/dist/scheduler-config.d.ts +177 -0
  101. package/dist/scheduler-config.js +1 -0
  102. package/dist/scheduler-dependencies.d.ts +89 -0
  103. package/dist/scheduler-dependencies.js +179 -0
  104. package/dist/scheduler-freebusy.d.ts +19 -0
  105. package/{src/scheduler-freebusy.ts → dist/scheduler-freebusy.js} +9 -19
  106. package/{src/scheduler-heatmap.ts → dist/scheduler-heatmap.d.ts} +12 -35
  107. package/dist/scheduler-heatmap.js +41 -0
  108. package/dist/scheduler-resource-tree.d.ts +48 -0
  109. package/dist/scheduler-resource-tree.js +78 -0
  110. package/dist/scheduler-slots.d.ts +37 -0
  111. package/dist/scheduler-slots.js +80 -0
  112. package/dist/scheduler-summary.d.ts +41 -0
  113. package/dist/scheduler-summary.js +54 -0
  114. package/dist/scheduler.d.ts +7 -0
  115. package/{src/scheduler.ts → dist/scheduler.js} +13 -14
  116. package/dist/scheduling.d.ts +132 -0
  117. package/dist/scheduling.js +228 -0
  118. package/dist/schema-designer.d.ts +42 -0
  119. package/dist/schema-designer.js +117 -0
  120. package/dist/schema.d.ts +369 -0
  121. package/dist/schema.js +216 -0
  122. package/dist/smart-shim.d.ts +62 -0
  123. package/dist/smart-shim.js +34 -0
  124. package/dist/sources/aggregate.d.ts +43 -0
  125. package/dist/sources/aggregate.js +60 -0
  126. package/dist/sources/auth-supabase.d.ts +69 -0
  127. package/dist/sources/auth-supabase.js +39 -0
  128. package/dist/sources/dashboard.d.ts +65 -0
  129. package/dist/sources/dashboard.js +95 -0
  130. package/dist/sources/field-inference.d.ts +23 -0
  131. package/dist/sources/field-inference.js +52 -0
  132. package/dist/sources/filters.d.ts +35 -0
  133. package/dist/sources/filters.js +52 -0
  134. package/dist/sources/index.d.ts +18 -0
  135. package/dist/sources/index.js +18 -0
  136. package/dist/sources/introspect-supabase.d.ts +30 -0
  137. package/dist/sources/introspect-supabase.js +134 -0
  138. package/dist/sources/realtime-supabase.d.ts +37 -0
  139. package/dist/sources/realtime-supabase.js +52 -0
  140. package/dist/sources/relation-lookup.d.ts +50 -0
  141. package/dist/sources/relation-lookup.js +77 -0
  142. package/dist/sources/rest-adapters.d.ts +48 -0
  143. package/dist/sources/rest-adapters.js +85 -0
  144. package/dist/sources/rest.d.ts +38 -0
  145. package/dist/sources/rest.js +86 -0
  146. package/dist/sources/schema-from-columns.d.ts +37 -0
  147. package/dist/sources/schema-from-columns.js +48 -0
  148. package/dist/sources/supabase.d.ts +33 -0
  149. package/dist/sources/supabase.js +74 -0
  150. package/dist/sources/with-entity-rules.d.ts +25 -0
  151. package/dist/sources/with-entity-rules.js +47 -0
  152. package/dist/sources/with-relation-labels.d.ts +26 -0
  153. package/dist/sources/with-relation-labels.js +38 -0
  154. package/dist/staged-editing.d.ts +85 -0
  155. package/dist/staged-editing.js +0 -0
  156. package/dist/studio/bug-report.d.ts +61 -0
  157. package/dist/studio/bug-report.js +126 -0
  158. package/dist/studio/cli.d.ts +56 -0
  159. package/dist/studio/cli.js +70 -0
  160. package/dist/studio/copilot-core.d.ts +22 -0
  161. package/{src/studio/copilot-core.ts → dist/studio/copilot-core.js} +34 -38
  162. package/dist/studio/csv.d.ts +36 -0
  163. package/dist/studio/csv.js +173 -0
  164. package/dist/studio/db-connect-string.d.ts +45 -0
  165. package/dist/studio/db-connect-string.js +113 -0
  166. package/dist/studio/deploy-cli.d.ts +35 -0
  167. package/dist/studio/deploy-cli.js +81 -0
  168. package/dist/studio/emit-project.d.ts +77 -0
  169. package/dist/studio/emit-project.js +5389 -0
  170. package/dist/studio/emit-schema.d.ts +110 -0
  171. package/dist/studio/emit-schema.js +1128 -0
  172. package/dist/studio/index.d.ts +35 -0
  173. package/dist/studio/index.js +38 -0
  174. package/dist/studio/init-flow.d.ts +58 -0
  175. package/dist/studio/init-flow.js +451 -0
  176. package/dist/studio/introspect-db.d.ts +47 -0
  177. package/dist/studio/introspect-db.js +255 -0
  178. package/dist/studio/introspect-openapi.d.ts +19 -0
  179. package/dist/studio/introspect-openapi.js +244 -0
  180. package/dist/studio/introspect-prisma.d.ts +25 -0
  181. package/dist/studio/introspect-prisma.js +164 -0
  182. package/dist/studio/introspect.d.ts +35 -0
  183. package/dist/studio/introspect.js +306 -0
  184. package/dist/studio/project.d.ts +1327 -0
  185. package/dist/studio/project.js +1798 -0
  186. package/dist/studio/sample-data.d.ts +19 -0
  187. package/dist/studio/sample-data.js +207 -0
  188. package/dist/studio/samples/ats.d.ts +2 -0
  189. package/dist/studio/samples/ats.js +194 -0
  190. package/dist/studio/samples/clinic.d.ts +2 -0
  191. package/dist/studio/samples/clinic.js +171 -0
  192. package/dist/studio/samples/crm.d.ts +2 -0
  193. package/dist/studio/samples/crm.js +280 -0
  194. package/dist/studio/samples/datasets.d.ts +38 -0
  195. package/dist/studio/samples/datasets.js +296 -0
  196. package/dist/studio/samples/ecommerce.d.ts +2 -0
  197. package/dist/studio/samples/ecommerce.js +178 -0
  198. package/dist/studio/samples/events.d.ts +2 -0
  199. package/dist/studio/samples/events.js +192 -0
  200. package/dist/studio/samples/fleet.d.ts +2 -0
  201. package/dist/studio/samples/fleet.js +203 -0
  202. package/dist/studio/samples/gym.d.ts +2 -0
  203. package/dist/studio/samples/gym.js +205 -0
  204. package/dist/studio/samples/hr.d.ts +2 -0
  205. package/dist/studio/samples/hr.js +186 -0
  206. package/dist/studio/samples/index.d.ts +13 -0
  207. package/dist/studio/samples/index.js +44 -0
  208. package/dist/studio/samples/insurance.d.ts +2 -0
  209. package/dist/studio/samples/insurance.js +220 -0
  210. package/dist/studio/samples/inventory.d.ts +2 -0
  211. package/dist/studio/samples/inventory.js +204 -0
  212. package/dist/studio/samples/invoicing.d.ts +2 -0
  213. package/dist/studio/samples/invoicing.js +154 -0
  214. package/dist/studio/samples/library.d.ts +2 -0
  215. package/dist/studio/samples/library.js +171 -0
  216. package/dist/studio/samples/live-data.d.ts +5 -0
  217. package/dist/studio/samples/live-data.js +261 -0
  218. package/dist/studio/samples/projects.d.ts +2 -0
  219. package/dist/studio/samples/projects.js +203 -0
  220. package/dist/studio/samples/realestate.d.ts +2 -0
  221. package/dist/studio/samples/realestate.js +187 -0
  222. package/dist/studio/samples/restaurant.d.ts +2 -0
  223. package/dist/studio/samples/restaurant.js +180 -0
  224. package/dist/studio/samples/school.d.ts +2 -0
  225. package/dist/studio/samples/school.js +188 -0
  226. package/dist/studio/samples/shared.d.ts +142 -0
  227. package/dist/studio/samples/shared.js +164 -0
  228. package/dist/studio/samples/starter.d.ts +4 -0
  229. package/dist/studio/samples/starter.js +237 -0
  230. package/dist/studio/samples/subscriptions.d.ts +2 -0
  231. package/dist/studio/samples/subscriptions.js +175 -0
  232. package/dist/studio/samples/support.d.ts +2 -0
  233. package/dist/studio/samples/support.js +209 -0
  234. package/dist/studio/scaffold-app.d.ts +22 -0
  235. package/{src/studio/scaffold-app.ts → dist/studio/scaffold-app.js} +45 -63
  236. package/dist/studio/scaffold.d.ts +63 -0
  237. package/dist/studio/scaffold.js +346 -0
  238. package/dist/studio/screen-suites.d.ts +193 -0
  239. package/dist/studio/screen-suites.js +297 -0
  240. package/dist/studio/themes.d.ts +34 -0
  241. package/dist/studio/themes.js +41 -0
  242. package/dist/studio/ui-components.d.ts +89 -0
  243. package/dist/studio/ui-components.generated.d.ts +29 -0
  244. package/dist/studio/ui-components.generated.js +7139 -0
  245. package/dist/studio/ui-components.js +673 -0
  246. package/dist/studio/user-error.d.ts +23 -0
  247. package/{src/studio/user-error.ts → dist/studio/user-error.js} +10 -13
  248. package/dist/studio/verify.d.ts +34 -0
  249. package/dist/studio/verify.js +47 -0
  250. package/dist/sveltekit/in-memory.d.ts +14 -0
  251. package/dist/sveltekit/in-memory.js +113 -0
  252. package/dist/sveltekit/index.d.ts +6 -0
  253. package/dist/sveltekit/index.js +5 -0
  254. package/dist/sveltekit/query-plan.d.ts +44 -0
  255. package/dist/sveltekit/query-plan.js +76 -0
  256. package/dist/sveltekit/sql-source.d.ts +47 -0
  257. package/dist/sveltekit/sql-source.js +106 -0
  258. package/dist/sveltekit/sql.d.ts +48 -0
  259. package/dist/sveltekit/sql.js +73 -0
  260. package/dist/sveltekit/transport.d.ts +209 -0
  261. package/dist/sveltekit/transport.js +219 -0
  262. package/{src/sveltekit/types.ts → dist/sveltekit/types.d.ts} +8 -11
  263. package/dist/sveltekit/types.js +1 -0
  264. package/dist/upgrade-prompt.d.ts +16 -0
  265. package/dist/upgrade-prompt.js +141 -0
  266. package/dist/version.d.ts +13 -0
  267. package/{src/version.ts → dist/version.js} +1 -1
  268. package/dist/watermark.d.ts +2 -0
  269. package/dist/watermark.js +146 -0
  270. package/package.json +26 -24
  271. package/src/SvBoard.dom.test.ts +0 -67
  272. package/src/SvGridAlerts.dom.test.ts +0 -113
  273. package/src/SvGridEditPanel.dom.test.ts +0 -320
  274. package/src/SvRecordDetail.dom.test.ts +0 -137
  275. package/src/SvSchedule.dom.test.ts +0 -57
  276. package/src/ai-export-pdf.dom.test.ts +0 -77
  277. package/src/ai-export-xlsx.dom.test.ts +0 -90
  278. package/src/ai-export.dom.test.ts +0 -114
  279. package/src/alerts/alert-engine-attach.ts +0 -165
  280. package/src/alerts/alert-engine.test.ts +0 -135
  281. package/src/alerts/alert-engine.ts +0 -260
  282. package/src/alerts/alert-formats.test.ts +0 -87
  283. package/src/alerts/alert-formats.ts +0 -77
  284. package/src/alerts/alert-observer.test.ts +0 -189
  285. package/src/alerts/alert-observer.ts +0 -208
  286. package/src/alerts/alert-scheduler.ts +0 -96
  287. package/src/alerts/alert-storage.test.ts +0 -54
  288. package/src/alerts/alert-storage.ts +0 -116
  289. package/src/alerts/alert-store.svelte.ts +0 -80
  290. package/src/alerts/alert-types.ts +0 -94
  291. package/src/board.dom.test.ts +0 -941
  292. package/src/edit-panel.test.ts +0 -322
  293. package/src/edit-panel.ts +0 -379
  294. package/src/export-conditional.test.ts +0 -60
  295. package/src/export-conditional.ts +0 -94
  296. package/src/export-ooxml.test.ts +0 -152
  297. package/src/export-ooxml.ts +0 -489
  298. package/src/export-pdf.test.ts +0 -138
  299. package/src/export-pdf.ts +0 -245
  300. package/src/export-print.test.ts +0 -66
  301. package/src/export-print.ts +0 -132
  302. package/src/export-serialize.test.ts +0 -56
  303. package/src/export-xls.ts +0 -199
  304. package/src/export-xlsx-roundtrip.test.ts +0 -120
  305. package/src/export-xlsx.test.ts +0 -150
  306. package/src/export.test.ts +0 -349
  307. package/src/export.ts +0 -1464
  308. package/src/expressions/evaluate.test.ts +0 -111
  309. package/src/expressions/evaluate.ts +0 -237
  310. package/src/expressions/expression-columns.ts +0 -133
  311. package/src/expressions/expression-types.ts +0 -84
  312. package/src/expressions/parse.test.ts +0 -106
  313. package/src/expressions/parse.ts +0 -610
  314. package/src/import-automap.test.ts +0 -131
  315. package/src/import-hardening.test.ts +0 -158
  316. package/src/import.test.ts +0 -415
  317. package/src/import.ts +0 -1044
  318. package/src/index.ts +0 -569
  319. package/src/install.ts +0 -154
  320. package/src/license-core.test.ts +0 -23
  321. package/src/license-core.ts +0 -107
  322. package/src/license-expiry.test.ts +0 -127
  323. package/src/license.ts +0 -137
  324. package/src/master-detail.test.ts +0 -61
  325. package/src/master-detail.ts +0 -42
  326. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -178
  327. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +0 -3875
  328. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +0 -1
  329. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +0 -21
  330. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +0 -1
  331. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +0 -1938
  332. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +0 -1
  333. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +0 -205
  334. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +0 -1
  335. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +0 -18
  336. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +0 -1
  337. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +0 -264
  338. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +0 -1
  339. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +0 -697
  340. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +0 -1
  341. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -134
  342. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -1
  343. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -55
  344. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -1
  345. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -2
  346. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -46843
  347. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -1
  348. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -237
  349. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -1
  350. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -2
  351. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -4
  352. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -1
  353. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -7
  354. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -4
  355. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -1
  356. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -5
  357. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -1
  358. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -5
  359. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -1
  360. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -5
  361. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -1
  362. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -6
  363. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -83
  364. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -1
  365. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -559
  366. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -1
  367. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -24
  368. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -1
  369. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -122
  370. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -1
  371. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -2
  372. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -89
  373. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -1
  374. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -241
  375. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -1
  376. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +0 -4938
  377. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +0 -1
  378. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +0 -495
  379. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +0 -1
  380. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +0 -190
  381. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +0 -1
  382. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +0 -8
  383. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +0 -1
  384. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +0 -138
  385. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +0 -1
  386. package/src/pivot-chart.test.ts +0 -86
  387. package/src/pivot-chart.ts +0 -112
  388. package/src/pivot-designer.ts +0 -101
  389. package/src/pivot.test.ts +0 -335
  390. package/src/pivot.ts +0 -578
  391. package/src/print.ts +0 -107
  392. package/src/scheduler-assignments.test.ts +0 -97
  393. package/src/scheduler-assignments.ts +0 -134
  394. package/src/scheduler-axis.test.ts +0 -108
  395. package/src/scheduler-axis.ts +0 -238
  396. package/src/scheduler-booking.test.ts +0 -57
  397. package/src/scheduler-config.ts +0 -179
  398. package/src/scheduler-dependencies.test.ts +0 -155
  399. package/src/scheduler-dependencies.ts +0 -223
  400. package/src/scheduler-freebusy.test.ts +0 -41
  401. package/src/scheduler-heatmap.test.ts +0 -39
  402. package/src/scheduler-resource-tree.test.ts +0 -84
  403. package/src/scheduler-resource-tree.ts +0 -107
  404. package/src/scheduler-slots.test.ts +0 -53
  405. package/src/scheduler-slots.ts +0 -94
  406. package/src/scheduler-summary.test.ts +0 -47
  407. package/src/scheduler-summary.ts +0 -81
  408. package/src/scheduling.test.ts +0 -194
  409. package/src/scheduling.ts +0 -293
  410. package/src/schema-designer.test.ts +0 -121
  411. package/src/schema-designer.ts +0 -142
  412. package/src/schema.test.ts +0 -268
  413. package/src/schema.ts +0 -562
  414. package/src/smart-shim.ts +0 -107
  415. package/src/sources/aggregate.test.ts +0 -79
  416. package/src/sources/aggregate.ts +0 -102
  417. package/src/sources/auth-supabase.test.ts +0 -80
  418. package/src/sources/auth-supabase.ts +0 -87
  419. package/src/sources/dashboard.kpi.test.ts +0 -50
  420. package/src/sources/dashboard.test.ts +0 -61
  421. package/src/sources/dashboard.ts +0 -136
  422. package/src/sources/field-inference.test.ts +0 -60
  423. package/src/sources/field-inference.ts +0 -63
  424. package/src/sources/filters.test.ts +0 -58
  425. package/src/sources/filters.ts +0 -70
  426. package/src/sources/index.ts +0 -67
  427. package/src/sources/introspect-supabase.test.ts +0 -112
  428. package/src/sources/introspect-supabase.ts +0 -139
  429. package/src/sources/realtime-supabase.test.ts +0 -93
  430. package/src/sources/realtime-supabase.ts +0 -99
  431. package/src/sources/relation-lookup.test.ts +0 -99
  432. package/src/sources/relation-lookup.ts +0 -128
  433. package/src/sources/rest-adapters.test.ts +0 -95
  434. package/src/sources/rest-adapters.ts +0 -120
  435. package/src/sources/rest.test.ts +0 -104
  436. package/src/sources/rest.ts +0 -129
  437. package/src/sources/schema-from-columns.test.ts +0 -106
  438. package/src/sources/schema-from-columns.ts +0 -88
  439. package/src/sources/supabase.test.ts +0 -110
  440. package/src/sources/supabase.ts +0 -99
  441. package/src/sources/with-entity-rules.test.ts +0 -104
  442. package/src/sources/with-entity-rules.ts +0 -78
  443. package/src/sources/with-relation-labels.test.ts +0 -75
  444. package/src/sources/with-relation-labels.ts +0 -73
  445. package/src/staged-editing.ts +0 -0
  446. package/src/studio/bug-report.test.ts +0 -101
  447. package/src/studio/bug-report.ts +0 -165
  448. package/src/studio/cli.test.ts +0 -187
  449. package/src/studio/cli.ts +0 -117
  450. package/src/studio/copilot-core.test.ts +0 -45
  451. package/src/studio/csv.test.ts +0 -90
  452. package/src/studio/csv.ts +0 -165
  453. package/src/studio/db-connect-string.test.ts +0 -94
  454. package/src/studio/db-connect-string.ts +0 -128
  455. package/src/studio/deploy-cli.test.ts +0 -56
  456. package/src/studio/deploy-cli.ts +0 -100
  457. package/src/studio/emit-project.test.ts +0 -3324
  458. package/src/studio/emit-project.ts +0 -5377
  459. package/src/studio/emit-schema.test.ts +0 -111
  460. package/src/studio/emit-schema.ts +0 -1181
  461. package/src/studio/index.ts +0 -380
  462. package/src/studio/init-flow.test.ts +0 -514
  463. package/src/studio/init-flow.ts +0 -556
  464. package/src/studio/introspect-db.test.ts +0 -186
  465. package/src/studio/introspect-db.ts +0 -312
  466. package/src/studio/introspect-openapi.test.ts +0 -84
  467. package/src/studio/introspect-openapi.ts +0 -252
  468. package/src/studio/introspect-prisma.test.ts +0 -99
  469. package/src/studio/introspect-prisma.ts +0 -175
  470. package/src/studio/introspect.test.ts +0 -172
  471. package/src/studio/introspect.ts +0 -310
  472. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -1
  473. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -3
  474. package/src/studio/pipeline.test.ts +0 -42
  475. package/src/studio/project-robust.test.ts +0 -157
  476. package/src/studio/project.test.ts +0 -929
  477. package/src/studio/project.ts +0 -2394
  478. package/src/studio/sample-data.test.ts +0 -58
  479. package/src/studio/sample-data.ts +0 -203
  480. package/src/studio/samples/ats.ts +0 -202
  481. package/src/studio/samples/clinic.ts +0 -179
  482. package/src/studio/samples/crm.ts +0 -291
  483. package/src/studio/samples/datasets.test.ts +0 -84
  484. package/src/studio/samples/datasets.ts +0 -340
  485. package/src/studio/samples/ecommerce.ts +0 -187
  486. package/src/studio/samples/events.ts +0 -201
  487. package/src/studio/samples/fleet.ts +0 -213
  488. package/src/studio/samples/gym.ts +0 -215
  489. package/src/studio/samples/hr.ts +0 -195
  490. package/src/studio/samples/index.ts +0 -55
  491. package/src/studio/samples/insurance.ts +0 -229
  492. package/src/studio/samples/inventory.ts +0 -213
  493. package/src/studio/samples/invoicing.ts +0 -162
  494. package/src/studio/samples/library.ts +0 -180
  495. package/src/studio/samples/live-data.test.ts +0 -99
  496. package/src/studio/samples/live-data.ts +0 -306
  497. package/src/studio/samples/projects.ts +0 -212
  498. package/src/studio/samples/realestate.ts +0 -196
  499. package/src/studio/samples/restaurant.ts +0 -189
  500. package/src/studio/samples/samples.test.ts +0 -304
  501. package/src/studio/samples/school.ts +0 -197
  502. package/src/studio/samples/seed-floor.test.ts +0 -25
  503. package/src/studio/samples/shared.ts +0 -237
  504. package/src/studio/samples/starter.ts +0 -251
  505. package/src/studio/samples/subscriptions.ts +0 -183
  506. package/src/studio/samples/support.ts +0 -216
  507. package/src/studio/scaffold-app.test.ts +0 -91
  508. package/src/studio/scaffold.test.ts +0 -192
  509. package/src/studio/scaffold.ts +0 -390
  510. package/src/studio/screen-suites.test.ts +0 -269
  511. package/src/studio/screen-suites.ts +0 -448
  512. package/src/studio/themes.ts +0 -62
  513. package/src/studio/ui-components-surface.test.ts +0 -185
  514. package/src/studio/ui-components.generated.ts +0 -7154
  515. package/src/studio/ui-components.ts +0 -742
  516. package/src/studio/verify.test.ts +0 -47
  517. package/src/studio/verify.ts +0 -79
  518. package/src/sveltekit/in-memory.test.ts +0 -104
  519. package/src/sveltekit/in-memory.ts +0 -129
  520. package/src/sveltekit/index.ts +0 -24
  521. package/src/sveltekit/query-plan.test.ts +0 -109
  522. package/src/sveltekit/query-plan.ts +0 -125
  523. package/src/sveltekit/sql-source.test.ts +0 -198
  524. package/src/sveltekit/sql-source.ts +0 -171
  525. package/src/sveltekit/sql.test.ts +0 -80
  526. package/src/sveltekit/sql.ts +0 -131
  527. package/src/sveltekit/transport-scope.test.ts +0 -125
  528. package/src/sveltekit/transport.test.ts +0 -201
  529. package/src/sveltekit/transport.ts +0 -373
  530. package/src/upgrade-prompt.test.ts +0 -71
  531. package/src/upgrade-prompt.ts +0 -162
  532. package/src/version.test.ts +0 -17
  533. package/src/watermark.test.ts +0 -97
  534. package/src/watermark.ts +0 -156
  535. /package/{src → dist}/SvAlertRuleEditor.svelte +0 -0
  536. /package/{src → dist}/SvAlertsManager.svelte +0 -0
  537. /package/{src → dist}/SvAlertsPanel.svelte +0 -0
  538. /package/{src → dist}/SvAuthGate.svelte +0 -0
  539. /package/{src → dist}/SvBoard.svelte +0 -0
  540. /package/{src → dist}/SvExportMenu.svelte +0 -0
  541. /package/{src → dist}/SvExpressionEditor.svelte +0 -0
  542. /package/{src → dist}/SvFileInput.svelte +0 -0
  543. /package/{src → dist}/SvGridAlerts.svelte +0 -0
  544. /package/{src → dist}/SvGridBoard.svelte +0 -0
  545. /package/{src → dist}/SvGridEditPanel.svelte +0 -0
  546. /package/{src → dist}/SvGridMasterDetail.svelte +0 -0
  547. /package/{src → dist}/SvGridScheduler.svelte +0 -0
  548. /package/{src → dist}/SvImportDialog.svelte +0 -0
  549. /package/{src → dist}/SvLookupInput.svelte +0 -0
  550. /package/{src → dist}/SvPivotDesigner.svelte +0 -0
  551. /package/{src → dist}/SvRecordDetail.svelte +0 -0
  552. /package/{src → dist}/SvSchedule.svelte +0 -0
  553. /package/{src → dist}/SvSchemaChart.svelte +0 -0
  554. /package/{src → dist}/SvSchemaDashboard.svelte +0 -0
  555. /package/{src → dist}/pdfmake-shims.d.ts +0 -0
  556. /package/{src → dist}/smart.export.d.ts +0 -0
  557. /package/{src → dist}/smart.export.js +0 -0
  558. /package/{src → dist}/studio/HANDLERS-DESIGN.md +0 -0
  559. /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -0
  560. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -0
  561. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +0 -0
  562. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +0 -0
  563. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +0 -0
  564. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +0 -0
  565. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +0 -0
  566. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +0 -0
  567. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +0 -0
  568. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +0 -0
  569. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +0 -0
  570. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +0 -0
  571. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +0 -0
  572. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +0 -0
  573. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +0 -0
  574. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +0 -0
  575. /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -0
  576. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -0
  577. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -0
  578. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -0
  579. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -0
  580. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -0
  581. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -0
  582. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -0
  583. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -0
  584. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -0
  585. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -0
  586. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -0
  587. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -0
  588. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -0
  589. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -0
  590. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -0
  591. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -0
  592. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -0
  593. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -0
  594. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -0
  595. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -0
  596. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -0
  597. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -0
  598. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -0
  599. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -0
  600. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -0
  601. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -0
  602. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -0
  603. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -0
  604. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -0
  605. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -0
  606. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -0
  607. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -0
  608. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -0
  609. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -0
  610. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -0
  611. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +0 -0
  612. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +0 -0
  613. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +0 -0
  614. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +0 -0
  615. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +0 -0
  616. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +0 -0
  617. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +0 -0
  618. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +0 -0
  619. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +0 -0
  620. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +0 -0
@@ -1,929 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import type { EntitySchema } from '../schema'
3
- import {
4
- addBlock,
5
- applyGridPreset,
6
- addStateVar,
7
- setDataLayer,
8
- setDeployTarget,
9
- updateStateVar,
10
- removeStateVar,
11
- stateInitExpr,
12
- compileStep,
13
- setTrigger,
14
- triggersOf,
15
- compileTriggerSteps,
16
- setScreenLayout,
17
- setLayoutOpts,
18
- gridOpts,
19
- dockOpts,
20
- canvasOpts,
21
- canvasRectOf,
22
- setCanvasRect,
23
- duplicateBlock,
24
- blockColumns,
25
- blockStyleCss,
26
- mergeBlockStyle,
27
- addBlockAt,
28
- addComponentBlock,
29
- addEntity,
30
- addFreestandingScreen,
31
- addScreen,
32
- addScreenAction,
33
- addScreenFromTemplate,
34
- removeScreenAction,
35
- parseProject,
36
- screenFromTemplate,
37
- serializeProject,
38
- setTheme,
39
- blockPalette,
40
- createProject,
41
- defaultBlockConfig,
42
- gridColumns,
43
- moveBlock,
44
- removeBlock,
45
- removeEntity,
46
- reorderBlock,
47
- sanitizeProject,
48
- setEntityDataSource,
49
- entityDataSource,
50
- defaultEntitySource,
51
- setShell,
52
- updateBlock,
53
- updateEntity,
54
- updateScreen,
55
- duplicateScreen,
56
- reorderScreen,
57
- insertBlock,
58
- validateProject,
59
- setAuth,
60
- addTab,
61
- removeTab,
62
- renameTab,
63
- addTabBlock,
64
- addTabComponent,
65
- removeTabBlock,
66
- addAccordionSection,
67
- removeAccordionSection,
68
- renameAccordionSection,
69
- setAccordionMultiple,
70
- addAccordionBlock,
71
- addAccordionComponent,
72
- removeAccordionBlock,
73
- flattenBlocks,
74
- TAB_CHILD_KINDS,
75
- type MasterDetailConfig,
76
- type TabsConfig,
77
- type AccordionConfig,
78
- type StudioProject,
79
- } from './project'
80
-
81
- const customers: EntitySchema = {
82
- name: 'customers', label: 'Customer', idField: 'id',
83
- fields: [
84
- { field: 'id', type: 'text', primaryKey: true, hidden: { form: true } },
85
- { field: 'name', type: 'text' },
86
- { field: 'secret', type: 'text', hidden: { grid: true } },
87
- { field: 'tier', type: 'enum', options: [{ value: 'free', label: 'Free' }, { value: 'pro', label: 'Pro' }] },
88
- { field: 'mrr', type: 'number' },
89
- ],
90
- }
91
- const orders: EntitySchema = {
92
- name: 'orders', label: 'Order', idField: 'id',
93
- fields: [{ field: 'id', type: 'text', primaryKey: true }, { field: 'total', type: 'number' }],
94
- }
95
-
96
- describe('per-block style', () => {
97
- it('blockStyleCss emits override declarations and sanitizes the color', () => {
98
- expect(blockStyleCss(undefined)).toBe('')
99
- expect(blockStyleCss({})).toBe('')
100
- expect(blockStyleCss({ border: false })).toBe('border: none')
101
- expect(blockStyleCss({ border: true })).toContain('border: 1px solid')
102
- expect(blockStyleCss({ shadow: false })).toBe('box-shadow: none')
103
- expect(blockStyleCss({ padding: 16, margin: 8, radius: 12 })).toBe('padding: 16px; margin: 8px; border-radius: 12px')
104
- // A malicious color can't break out of the inline style attribute.
105
- expect(blockStyleCss({ background: '#fff' })).toBe('background: #fff')
106
- expect(blockStyleCss({ background: 'red;"><script>' })).toBe('background: redscript')
107
- })
108
-
109
- it('mergeBlockStyle merges and drops cleared keys (undefined)', () => {
110
- expect(mergeBlockStyle(undefined, { padding: 12 })).toEqual({ padding: 12 })
111
- expect(mergeBlockStyle({ padding: 12, border: false }, { margin: 4 })).toEqual({ padding: 12, border: false, margin: 4 })
112
- expect(mergeBlockStyle({ padding: 12, border: false }, { border: undefined })).toEqual({ padding: 12 })
113
- expect(mergeBlockStyle({ padding: 12 }, { padding: undefined })).toBeUndefined()
114
- })
115
-
116
- it('updateBlock merges a style patch onto the block, round-trips through parse', () => {
117
- let p = createProject([customers])
118
- const sid = p.screens[0]!.id
119
- const bid = p.screens[0]!.blocks[0]!.id
120
- p = updateBlock(p, sid, bid, { style: { border: false, padding: 24 } })
121
- expect(p.screens[0]!.blocks[0]!.style).toEqual({ border: false, padding: 24 })
122
- p = updateBlock(p, sid, bid, { style: { padding: undefined } })
123
- expect(p.screens[0]!.blocks[0]!.style).toEqual({ border: false })
124
- const round = parseProject(serializeProject(p))
125
- expect(round.screens[0]!.blocks[0]!.style).toEqual({ border: false })
126
- })
127
- })
128
-
129
- describe('Tabs container ops', () => {
130
- const base = () => defaultBlockConfig('tabs', customers) as TabsConfig
131
-
132
- it('defaults to two empty tabs', () => {
133
- const cfg = base()
134
- expect(cfg.tabs.map((t) => t.label)).toEqual(['Overview', 'Details'])
135
- expect(cfg.tabs.every((t) => t.blocks.length === 0)).toBe(true)
136
- })
137
-
138
- it('adds, renames, and removes tabs (keeps at least one)', () => {
139
- let cfg = addTab(base(), 'Extra')
140
- expect(cfg.tabs).toHaveLength(3)
141
- cfg = renameTab(cfg, 0, 'Home')
142
- expect(cfg.tabs[0]!.label).toBe('Home')
143
- cfg = removeTab(cfg, 2)
144
- expect(cfg.tabs).toHaveLength(2)
145
- const one = removeTab(removeTab(cfg, 1), 0)
146
- expect(one.tabs).toHaveLength(1) // never drops the last tab
147
- })
148
-
149
- it('adds only allowed child kinds, with unique ids, and removes them', () => {
150
- let cfg = addTabBlock(base(), 0, 'chart', customers)
151
- cfg = addTabBlock(cfg, 0, 'kpi', customers)
152
- expect(cfg.tabs[0]!.blocks.map((b) => b.config.kind)).toEqual(['chart', 'kpi'])
153
- const ids = cfg.tabs[0]!.blocks.map((b) => b.id)
154
- expect(new Set(ids).size).toBe(2) // unique ids
155
- // grid is controller-bound -> rejected inside tabs
156
- expect(addTabBlock(cfg, 0, 'grid', customers)).toBe(cfg)
157
- expect(TAB_CHILD_KINDS).not.toContain('grid')
158
- const removed = removeTabBlock(cfg, 0, ids[0]!)
159
- expect(removed.tabs[0]!.blocks.map((b) => b.config.kind)).toEqual(['kpi'])
160
- })
161
-
162
- it('flattenBlocks surfaces blocks nested in tabs', () => {
163
- const cfg = addTabBlock(addTabBlock(base(), 0, 'chart', customers), 1, 'gauge', customers)
164
- const block = { id: 'tabs-1', span: 3 as const, config: cfg }
165
- const kinds = flattenBlocks([block]).map((b) => b.config.kind)
166
- expect(kinds).toEqual(['tabs', 'chart', 'gauge'])
167
- })
168
-
169
- it('updateBlock / removeBlock reach a block nested inside a tab', () => {
170
- // A screen whose only block is a Tabs container with a chart in tab 0.
171
- let p = createProject([customers])
172
- const sid = p.screens[0]!.id
173
- p = { ...p, screens: p.screens.map((s) => (s.id === sid ? { ...s, blocks: [] } : s)) }
174
- p = addBlock(p, sid, 'tabs')
175
- const tb = p.screens.find((s) => s.id === sid)!.blocks[0]!
176
- p = updateBlock(p, sid, tb.id, { config: addTabBlock(tb.config as TabsConfig, 0, 'chart', customers) })
177
- const childId = (p.screens.find((s) => s.id === sid)!.blocks[0]!.config as TabsConfig).tabs[0]!.blocks[0]!.id
178
-
179
- // Update the nested chart's config through the normal updateBlock path.
180
- p = updateBlock(p, sid, childId, { config: { measure: 'mrr', reduce: 'avg' } as Partial<import('./project').ChartConfig> })
181
- const child = (p.screens.find((s) => s.id === sid)!.blocks[0]!.config as TabsConfig).tabs[0]!.blocks[0]!
182
- expect(child.config).toMatchObject({ kind: 'chart', measure: 'mrr', reduce: 'avg' })
183
-
184
- // Remove the nested chart; the tab is left empty, the container survives.
185
- p = removeBlock(p, sid, childId)
186
- expect((p.screens.find((s) => s.id === sid)!.blocks[0]!.config as TabsConfig).tabs[0]!.blocks).toHaveLength(0)
187
- expect(p.screens.find((s) => s.id === sid)!.blocks).toHaveLength(1) // container intact
188
- })
189
-
190
- it('a component can be dropped into a tab (specific registry key -> configured child)', () => {
191
- const base = defaultBlockConfig('tabs', customers) as TabsConfig
192
- const cfg = addTabComponent(base, 0, 'badge')
193
- const child = cfg.tabs[0]!.blocks[0]!
194
- expect(child.config.kind).toBe('component')
195
- expect((child.config as { component: string }).component).toBe('badge')
196
- // Seeded with the registry's default props + content, so it renders immediately.
197
- expect((child.config as { props: Record<string, unknown> }).props).toMatchObject({ variant: 'neutral', _content: 'Badge' })
198
- // An unknown component key is a no-op (never inserts a broken child).
199
- expect(addTabComponent(base, 0, 'not-real')).toBe(base)
200
- })
201
- })
202
-
203
- describe('Accordion container (mirrors Tabs)', () => {
204
- const base = () => defaultBlockConfig('accordion', customers) as AccordionConfig
205
-
206
- it('defaults to two empty sections, single-open', () => {
207
- const cfg = base()
208
- expect(cfg.sections.map((s) => s.label)).toEqual(['Section 1', 'Section 2'])
209
- expect(cfg.sections.every((s) => s.blocks.length === 0)).toBe(true)
210
- expect(cfg.multiple).toBe(false)
211
- })
212
-
213
- it('adds / renames / removes sections (keeps at least one) + toggles multiple', () => {
214
- let cfg = addAccordionSection(base(), 'Extra')
215
- expect(cfg.sections).toHaveLength(3)
216
- cfg = renameAccordionSection(cfg, 0, 'First')
217
- expect(cfg.sections[0]!.label).toBe('First')
218
- cfg = setAccordionMultiple(cfg, true)
219
- expect(cfg.multiple).toBe(true)
220
- cfg = removeAccordionSection(cfg, 2)
221
- expect(cfg.sections).toHaveLength(2)
222
- const one = removeAccordionSection(removeAccordionSection(cfg, 1), 0)
223
- expect(one.sections).toHaveLength(1) // never drops the last section
224
- })
225
-
226
- it('hosts display blocks + components with unique ids, and removes them', () => {
227
- let cfg = addAccordionBlock(base(), 0, 'chart', customers)
228
- cfg = addAccordionComponent(cfg, 0, 'stat')
229
- expect(cfg.sections[0]!.blocks.map((b) => b.config.kind)).toEqual(['chart', 'component'])
230
- const ids = cfg.sections[0]!.blocks.map((b) => b.id)
231
- expect(new Set(ids).size).toBe(2)
232
- // grid is controller-bound -> rejected inside a section
233
- expect(addAccordionBlock(cfg, 0, 'grid', customers)).toBe(cfg)
234
- cfg = removeAccordionBlock(cfg, 0, ids[0]!)
235
- expect(cfg.sections[0]!.blocks.map((b) => b.config.kind)).toEqual(['component'])
236
- })
237
-
238
- it('flattenBlocks + updateBlock + removeBlock reach a block nested in a section', () => {
239
- let p = createProject([customers])
240
- const sid = p.screens[0]!.id
241
- p = { ...p, screens: p.screens.map((s) => (s.id === sid ? { ...s, blocks: [] } : s)) }
242
- p = addBlock(p, sid, 'accordion')
243
- const ab = p.screens.find((s) => s.id === sid)!.blocks[0]!
244
- p = updateBlock(p, sid, ab.id, { config: addAccordionBlock(ab.config as AccordionConfig, 1, 'gauge', customers) })
245
- const cfg0 = p.screens.find((s) => s.id === sid)!.blocks[0]!.config as AccordionConfig
246
- const childId = cfg0.sections[1]!.blocks[0]!.id
247
- expect(flattenBlocks(p.screens.find((s) => s.id === sid)!.blocks).map((b) => b.config.kind)).toEqual(['accordion', 'gauge'])
248
-
249
- p = updateBlock(p, sid, childId, { config: { measure: 'mrr', reduce: 'avg' } as Partial<import('./project').GaugeConfig> })
250
- const child = (p.screens.find((s) => s.id === sid)!.blocks[0]!.config as AccordionConfig).sections[1]!.blocks[0]!
251
- expect(child.config).toMatchObject({ kind: 'gauge', measure: 'mrr' })
252
-
253
- p = removeBlock(p, sid, childId)
254
- expect((p.screens.find((s) => s.id === sid)!.blocks[0]!.config as AccordionConfig).sections[1]!.blocks).toHaveLength(0)
255
- expect(p.screens.find((s) => s.id === sid)!.blocks).toHaveLength(1)
256
- })
257
- })
258
-
259
- describe('createProject / defaultScreenFor', () => {
260
- it('makes one default screen per entity, each with a grid (editing = form)', () => {
261
- const p = createProject([customers, orders])
262
- expect(p.screens.map((s) => s.entity)).toEqual(['customers', 'orders'])
263
- const screen = p.screens[0]!
264
- expect(screen.blocks.map((b) => b.config.kind)).toEqual(['grid']) // editing is a grid property now
265
- expect((screen.blocks[0]!.config as import('./project').GridConfig).editing).toBe('form')
266
- expect(p.dataSource).toBe('memory')
267
- })
268
-
269
- it('grid columns include non-grid-hidden fields, shown', () => {
270
- const cols = gridColumns(customers)
271
- expect(cols.map((c) => c.field)).toEqual(['id', 'name', 'tier', 'mrr']) // secret (hidden.grid) excluded
272
- expect(cols.find((c) => c.field === 'id')!.show).toBe(false) // raw id column hidden by default
273
- expect(cols.filter((c) => c.field !== 'id').every((c) => c.show)).toBe(true)
274
- })
275
-
276
- it('a chart block defaults to a low-cardinality dimension + numeric measure', () => {
277
- const cfg = defaultBlockConfig('chart', customers)
278
- expect(cfg).toMatchObject({ kind: 'chart', dimension: 'tier', measure: 'mrr', reduce: 'sum', type: 'bar' })
279
- })
280
- })
281
-
282
- describe('block ops', () => {
283
- const base = createProject([customers])
284
- const sid = base.screens[0]!.id
285
-
286
- it('adds, removes, moves, reorders, and updates blocks immutably', () => {
287
- const added = addBlock(base, sid, 'chart')
288
- expect(added).not.toBe(base)
289
- expect(added.screens[0]!.blocks.map((b) => b.config.kind)).toEqual(['grid', 'chart'])
290
-
291
- const chartId = added.screens[0]!.blocks[1]!.id
292
- const removed = removeBlock(added, sid, chartId)
293
- expect(removed.screens[0]!.blocks).toHaveLength(1)
294
-
295
- const moved = moveBlock(added, sid, chartId, -1)
296
- expect(moved.screens[0]!.blocks.map((b) => b.config.kind)).toEqual(['chart', 'grid'])
297
-
298
- const reordered = reorderBlock(added, sid, chartId, 0)
299
- expect(reordered.screens[0]!.blocks[0]!.id).toBe(chartId)
300
-
301
- const gridId = base.screens[0]!.blocks[0]!.id
302
- const patched = updateBlock(base, sid, gridId, { span: 2, config: { pageSize: 25 } as Partial<import('./project').GridConfig> })
303
- const grid = patched.screens[0]!.blocks[0]!
304
- expect(grid.span).toBe(2)
305
- expect((grid.config as import('./project').GridConfig).pageSize).toBe(25)
306
- })
307
-
308
- it('duplicateBlock clones config with a fresh id, right after the original', () => {
309
- const base = createProject([customers])
310
- const sid = base.screens[0]!.id
311
- const gridId = base.screens[0]!.blocks[0]!.id
312
- const dup = duplicateBlock(base, sid, gridId)
313
- const blocks = dup.screens[0]!.blocks
314
- expect(blocks).toHaveLength(2)
315
- expect(blocks[1]!.id).not.toBe(gridId) // fresh id
316
- expect(blocks[1]!.config.kind).toBe('grid') // same kind
317
- expect(blocks[1]!.config).not.toBe(blocks[0]!.config) // deep-cloned config
318
- })
319
-
320
- it('blockColumns maps legacy span to 12-col and honors colSpan', () => {
321
- expect(blockColumns({ span: 1 })).toBe(4)
322
- expect(blockColumns({ span: 3 })).toBe(12)
323
- expect(blockColumns({ span: 1, colSpan: 5 })).toBe(5) // colSpan overrides
324
- expect(blockColumns({ span: 2, colSpan: 99 })).toBe(12) // clamped
325
- })
326
-
327
- it('applyGridPreset rewrites block column spans by pattern', () => {
328
- let p = addBlock(base, sid, 'kpi') // base already has a grid; now [grid, kpi]
329
- p = addBlock(p, sid, 'chart') // [grid, kpi, chart]
330
- const spans = (pp: typeof p) => pp.screens[0]!.blocks.map((b) => blockColumns(b))
331
- expect(spans(applyGridPreset(p, sid, 'full'))).toEqual([12, 12, 12])
332
- expect(spans(applyGridPreset(p, sid, 'two-col'))).toEqual([6, 6, 6])
333
- expect(spans(applyGridPreset(p, sid, 'three-col'))).toEqual([4, 4, 4])
334
- expect(spans(applyGridPreset(p, sid, 'sidebar'))).toEqual([4, 8, 8]) // first narrow, rest wide
335
- expect(spans(applyGridPreset(p, sid, 'kpi-row'))).toEqual([12, 3, 12]) // only the kpi block is a 3-wide strip cell
336
- })
337
-
338
- it('setScreenLayout(split/dock) gives every block its OWN leaf, not one tabs stack', () => {
339
- // Two "center" blocks (grid + chart) used to collapse into a single tabs leaf,
340
- // trapping them as tabs (unresizable when locked). Each must be its own pane.
341
- let p = addBlock(base, sid, 'chart') // base has a grid; now [grid, chart] - both center
342
- p = setScreenLayout(p, sid, 'split')
343
- const dock = p.screens[0]!.dock!
344
- const leaves: { panes: number }[] = []
345
- const walk = (n: any) => { if (n.type === 'tabs') leaves.push({ panes: n.panes.length }); else n.children.forEach(walk) }
346
- walk(dock.main)
347
- expect(leaves.length).toBe(2) // grid + chart => two separate leaves
348
- expect(leaves.every((l) => l.panes === 1)).toBe(true) // never stacked as tabs
349
- })
350
-
351
- it('setScreenLayout re-seeds a fresh pane arrangement when coming from a non-pane layout', () => {
352
- let p = addBlock(base, sid, 'chart')
353
- p = setScreenLayout(p, sid, 'split')
354
- const firstDock = p.screens[0]!.dock
355
- // grid -> split -> grid -> split must NOT carry a stale arrangement (fresh seed).
356
- p = setScreenLayout(p, sid, 'grid')
357
- p = setScreenLayout(p, sid, 'split')
358
- expect(p.screens[0]!.dock).not.toBe(firstDock) // re-seeded, not the old object
359
- // ...but split <-> dock preserves the arrangement.
360
- const beforeToggle = p.screens[0]!.dock
361
- p = setScreenLayout(p, sid, 'dock')
362
- expect(p.screens[0]!.dock).toBe(beforeToggle)
363
- })
364
-
365
- it('setLayoutOpts merges per-mode settings; getters fall back to defaults; round-trips', () => {
366
- let p = setLayoutOpts(base, sid, 'grid', { colGap: 24 })
367
- p = setLayoutOpts(p, sid, 'grid', { maxWidth: 1200 }) // merges, keeps colGap
368
- p = setLayoutOpts(p, sid, 'dock', { allowPopout: true, headerPosition: 'bottom' })
369
- const s = p.screens[0]!
370
- expect(gridOpts(s).colGap).toBe(24)
371
- expect(gridOpts(s).maxWidth).toBe(1200)
372
- expect(gridOpts(s).rowGap).toBe(16) // default preserved
373
- expect(dockOpts(s).allowPopout).toBe(true)
374
- expect(dockOpts(s).headerPosition).toBe('bottom')
375
- expect(canvasOpts(s).cols).toBe(12) // untouched mode -> defaults
376
- const round = parseProject(serializeProject(p)).screens[0]!
377
- expect(gridOpts(round).colGap).toBe(24)
378
- expect(dockOpts(round).allowPopout).toBe(true)
379
- })
380
-
381
- it('canvas column count clamps stored rects + drives new placements', () => {
382
- let p = addBlock(base, sid, 'kpi')
383
- p = setScreenLayout(p, sid, 'canvas')
384
- const kpi = p.screens[0]!.blocks[1]!
385
- p = setCanvasRect(p, sid, kpi.id, { col: 8, colSpan: 4 }) // fits in 12
386
- // Shrink the grid to 6 columns: the col-8/span-4 rect must clamp on read.
387
- p = setLayoutOpts(p, sid, 'canvas', { cols: 6 })
388
- const r = canvasRectOf(p.screens[0]!, kpi.id)
389
- expect(r.colSpan).toBeLessThanOrEqual(6)
390
- expect(r.col + r.colSpan).toBeLessThanOrEqual(6)
391
- })
392
-
393
- it('state variables: add / update / remove keep names valid + unique', () => {
394
- let p = addStateVar(base, sid, { name: 'count', type: 'number', initial: '5' })
395
- p = addStateVar(p, sid, { name: 'count' }) // dup -> count2
396
- p = addStateVar(p, sid, { name: '2bad name!' }) // sanitized
397
- const names = p.screens[0]!.state!.map((v) => v.name)
398
- expect(names).toEqual(['count', 'count2', '_2bad_name_'])
399
- expect(stateInitExpr(p.screens[0]!.state![0]!)).toBe('5')
400
- p = updateStateVar(p, sid, 'count', { type: 'boolean', initial: 'true' })
401
- expect(stateInitExpr(p.screens[0]!.state![0]!)).toBe('true')
402
- p = removeStateVar(p, sid, 'count2')
403
- expect(p.screens[0]!.state!.map((v) => v.name)).toEqual(['count', '_2bad_name_'])
404
- // round-trips
405
- expect(parseProject(serializeProject(p)).screens[0]!.state).toEqual(p.screens[0]!.state)
406
- })
407
-
408
- it('entity triggers: setTrigger / triggersOf + compile to server code (payload = v)', () => {
409
- let p = createProject([customers])
410
- p = setTrigger(p, 'customers', 'beforeCreate', [
411
- { type: 'requireField', field: 'name' },
412
- { type: 'setField', field: 'name', value: { kind: 'field', name: 'name' } },
413
- { type: 'reject', condition: { left: { kind: 'field', name: 'age' }, op: 'lt', right: { kind: 'literal', value: '0' } }, message: 'age must be >= 0' },
414
- ])
415
- expect(triggersOf(p, 'customers').beforeCreate).toHaveLength(3)
416
- const body = compileTriggerSteps(triggersOf(p, 'customers').beforeCreate!)
417
- expect(body).toContain("if (v['name'] == null || v['name'] === '') throw new Error('name is required')")
418
- expect(body).toContain("v['name'] = v?.['name']")
419
- expect(body).toContain("if (Number(v?.['age']) < Number(0)) throw new Error('age must be >= 0')")
420
- // clearing an event with null drops it (and the entity when empty)
421
- p = setTrigger(p, 'customers', 'beforeCreate', null)
422
- expect(p.triggers).toBeUndefined()
423
- // round-trip
424
- p = setTrigger(p, 'customers', 'afterCreate', [{ type: 'code', code: 'console.log(v)' }])
425
- expect(parseProject(serializeProject(p)).triggers).toEqual(p.triggers)
426
- })
427
-
428
- it('logic-core step verbs compile to ctx code', () => {
429
- expect(compileStep({ type: 'setVar', name: 'q', value: { kind: 'literal', value: 'hi' } })).toBe("ctx.state.q = 'hi'")
430
- expect(compileStep({ type: 'setVar', name: 'sel', value: { kind: 'state', name: 'row' } })).toBe('ctx.state.sel = ctx.state.row')
431
- expect(compileStep({ type: 'createRecord', values: [{ field: 'name', value: { kind: 'param', name: 'q' } }] })).toBe("await ctx.data.create({ name: ctx.params['q'] })")
432
- expect(compileStep({ type: 'updateRecord', id: { kind: 'field', name: 'id' }, values: [{ field: 'done', value: { kind: 'literal', value: 'true' } }] })).toBe("await ctx.data.update(row?.['id'], { done: true })")
433
- expect(compileStep({ type: 'deleteRecord', id: { kind: 'state', name: 'selId' } })).toBe('await ctx.data.delete(ctx.state.selId)')
434
- // branch nests + indents its body
435
- const br = compileStep({ type: 'branch', condition: { left: { kind: 'state', name: 'n' }, op: 'gt', right: { kind: 'literal', value: '3' } }, then: [{ type: 'alert', message: 'big' }], else: [{ type: 'alert', message: 'small' }] })
436
- expect(br).toBe("if (Number(ctx.state.n) > Number(3)) {\n alert('big')\n} else {\n alert('small')\n}")
437
- })
438
-
439
- it('setScreenLayout(canvas) seeds cell rects; setCanvasRect clamps to the grid + round-trips', () => {
440
- let p = addBlock(base, sid, 'kpi') // [grid, kpi]
441
- p = setScreenLayout(p, sid, 'canvas')
442
- const s = () => p.screens[0]!
443
- expect(s().layout).toBe('canvas')
444
- // Every block got a rect.
445
- for (const b of s().blocks) expect(canvasRectOf(s(), b.id)).toBeTruthy()
446
- const kpi = s().blocks[1]!
447
- // Move + oversize: col clamps so col + colSpan <= 12; spans clamp to >= 1.
448
- p = setCanvasRect(p, sid, kpi.id, { col: 20, row: -5, colSpan: 99, rowSpan: 0 })
449
- const r = canvasRectOf(s(), kpi.id)
450
- expect(r.colSpan).toBe(12)
451
- expect(r.col).toBe(0) // 12-span can only sit at col 0
452
- expect(r.row).toBe(0)
453
- expect(r.rowSpan).toBe(1)
454
- // Survives serialize/parse.
455
- const round = parseProject(serializeProject(p))
456
- expect(canvasRectOf(round.screens[0]!, kpi.id)).toEqual(r)
457
- })
458
-
459
- it('addBlockAt inserts at the given index (palette drop)', () => {
460
- const p = addBlockAt(base, sid, 'chart', 0) // before the grid
461
- expect(p.screens[0]!.blocks.map((b) => b.config.kind)).toEqual(['chart', 'grid'])
462
- const end = addBlockAt(base, sid, 'kpi', 99) // clamps to end
463
- expect(end.screens[0]!.blocks.at(-1)!.config.kind).toBe('kpi')
464
- })
465
-
466
- it('gives every added block a unique id', () => {
467
- let p = createProject([customers])
468
- p = addBlock(p, sid, 'chart')
469
- p = addBlock(p, sid, 'chart')
470
- const ids = p.screens[0]!.blocks.map((b) => b.id)
471
- expect(new Set(ids).size).toBe(ids.length)
472
- })
473
- })
474
-
475
- describe('entity + screen ops', () => {
476
- it('adds/removes an entity with its screen', () => {
477
- let p = createProject([customers])
478
- p = addEntity(p, orders)
479
- expect(p.entities).toHaveLength(2)
480
- expect(p.screens.some((s) => s.entity === 'orders')).toBe(true)
481
- p = removeEntity(p, 'orders')
482
- expect(p.entities).toHaveLength(1)
483
- expect(p.screens.some((s) => s.entity === 'orders')).toBe(false)
484
- })
485
-
486
- it('updateEntity retargets screens when the entity is renamed', () => {
487
- const p = createProject([customers])
488
- const renamed = updateEntity(p, 'customers', { ...customers, name: 'clients' })
489
- expect(renamed.entities[0]!.name).toBe('clients')
490
- expect(renamed.screens[0]!.entity).toBe('clients')
491
- })
492
-
493
- it('updateEntity retargets a master-detail childEntity when the child is renamed', () => {
494
- let p = createProject([customers, orders])
495
- const sid = p.screens.find((s) => s.entity === 'customers')!.id
496
- p = addBlock(p, sid, 'master-detail')
497
- const mdId = p.screens.find((s) => s.id === sid)!.blocks.at(-1)!.id
498
- p = updateBlock(p, sid, mdId, { config: { childEntity: 'orders', foreignKey: 'id' } as Partial<MasterDetailConfig> })
499
- const renamed = updateEntity(p, 'orders', { ...orders, name: 'sales' })
500
- const md = renamed.screens.find((s) => s.id === sid)!.blocks.find((b) => b.id === mdId)!
501
- expect((md.config as MasterDetailConfig).childEntity).toBe('sales')
502
- })
503
-
504
- it('a second screen for an entity gets a unique id', () => {
505
- const p = addScreen(createProject([customers]), 'customers')
506
- const ids = p.screens.map((s) => s.id)
507
- expect(new Set(ids).size).toBe(ids.length)
508
- })
509
-
510
- it('updateScreen patches title/route', () => {
511
- const p = createProject([customers])
512
- const up = updateScreen(p, p.screens[0]!.id, { title: 'People', route: 'people' })
513
- expect(up.screens[0]).toMatchObject({ title: 'People', route: 'people' })
514
- })
515
- })
516
-
517
- describe('freestanding screens + custom actions', () => {
518
- it('addFreestandingScreen creates a screen with no entity and no blocks', () => {
519
- const p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
520
- const s = p.screens.find((s) => s.title === 'Reports')!
521
- expect(s.entity).toBeUndefined()
522
- expect(s.blocks).toEqual([])
523
- })
524
-
525
- it('a second freestanding screen gets a unique id and route', () => {
526
- let p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
527
- p = addFreestandingScreen(p, { title: 'Sync' })
528
- const freestanding = p.screens.filter((s) => s.entity === undefined)
529
- expect(freestanding).toHaveLength(2)
530
- expect(new Set(freestanding.map((s) => s.id)).size).toBe(2)
531
- expect(new Set(freestanding.map((s) => s.route)).size).toBe(2)
532
- })
533
-
534
- it('addScreenAction generates a unique id and appends to the screen; removeScreenAction removes it', () => {
535
- const base = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
536
- const screenId = base.screens.find((s) => s.title === 'Reports')!.id
537
- let p = addScreenAction(base, screenId, { label: 'Run report', confirm: 'Run the monthly report?' })
538
- const screen = p.screens.find((s) => s.id === screenId)!
539
- expect(screen.actions).toHaveLength(1)
540
- expect(screen.actions![0]).toMatchObject({ label: 'Run report', confirm: 'Run the monthly report?' })
541
- const actionId = screen.actions![0]!.id
542
- expect(actionId).toBeTruthy()
543
-
544
- p = removeScreenAction(p, screenId, actionId)
545
- expect(p.screens.find((s) => s.id === screenId)!.actions).toEqual([])
546
- })
547
-
548
- it('addScreenAction ids stay unique project-wide, not just per-screen', () => {
549
- let p = createProject([customers])
550
- const s1 = p.screens[0]!.id
551
- p = addFreestandingScreen(p, { title: 'Reports' })
552
- const s2 = p.screens.find((s) => s.title === 'Reports')!.id
553
- p = addScreenAction(p, s1, { label: 'First' })
554
- p = addScreenAction(p, s2, { label: 'Second' })
555
- const id1 = p.screens.find((s) => s.id === s1)!.actions![0]!.id
556
- const id2 = p.screens.find((s) => s.id === s2)!.actions![0]!.id
557
- expect(id1).not.toBe(id2)
558
- })
559
- })
560
-
561
- describe('component blocks', () => {
562
- it('addComponentBlock adds a component block to a freestanding screen (no entity needed)', () => {
563
- const p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
564
- const sid = p.screens.find((s) => s.title === 'Reports')!.id
565
- const next = addComponentBlock(p, sid, 'button', { variant: 'primary' })
566
- const screen = next.screens.find((s) => s.id === sid)!
567
- expect(screen.blocks).toHaveLength(1)
568
- expect(screen.blocks[0]!.config).toMatchObject({ kind: 'component', component: 'button', props: { variant: 'primary' } })
569
- })
570
-
571
- it('addComponentBlock also works mixed onto an entity-bound screen', () => {
572
- const p = createProject([customers])
573
- const sid = p.screens[0]!.id
574
- const before = p.screens[0]!.blocks.length
575
- const next = addComponentBlock(p, sid, 'alert', {})
576
- const screen = next.screens.find((s) => s.id === sid)!
577
- expect(screen.blocks).toHaveLength(before + 1)
578
- expect(screen.blocks.at(-1)!.config).toMatchObject({ kind: 'component', component: 'alert' })
579
- })
580
-
581
- it('addComponentBlock inserts at an index and gives the block a unique id', () => {
582
- let p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
583
- const sid = p.screens.find((s) => s.title === 'Reports')!.id
584
- p = addComponentBlock(p, sid, 'button', {})
585
- p = addComponentBlock(p, sid, 'badge', {}, 0)
586
- const screen = p.screens.find((s) => s.id === sid)!
587
- expect(screen.blocks.map((b) => (b.config as { component: string }).component)).toEqual(['badge', 'button'])
588
- expect(new Set(screen.blocks.map((b) => b.id)).size).toBe(2)
589
- })
590
-
591
- it('validateProject warns on a component block with no component selected', () => {
592
- let p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
593
- const sid = p.screens.find((s) => s.title === 'Reports')!.id
594
- p = addComponentBlock(p, sid, '', {})
595
- expect(validateProject(p).some((i) => /Component block has no component selected/.test(i.message))).toBe(true)
596
- })
597
-
598
- it('a freestanding screen with only a component block does not warn about being empty', () => {
599
- let p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
600
- const sid = p.screens.find((s) => s.title === 'Reports')!.id
601
- p = addComponentBlock(p, sid, 'button', {})
602
- expect(validateProject(p).some((i) => /is empty/.test(i.message))).toBe(false)
603
- })
604
-
605
- it('Supabase Auth warns without a Supabase connection, and clears once one is set', () => {
606
- let p = setAuth(createProject([customers]), { enabled: true, provider: 'supabase' })
607
- const needsConn = (pr: typeof p) => validateProject(pr).some((i) => /Supabase Auth needs a Supabase connection/.test(i.message))
608
- expect(needsConn(p)).toBe(true)
609
- // Shared project connection satisfies it.
610
- p = { ...p, supabase: { url: 'https://x.supabase.co', key: 'anon' } }
611
- expect(needsConn(p)).toBe(false)
612
- })
613
-
614
- it('Supabase Auth + RBAC warns that the client session does not populate the server role', () => {
615
- let p = createProject([customers])
616
- p = { ...p, supabase: { url: 'https://x.supabase.co', key: 'anon' }, access: { enabled: true, roles: [] } as never }
617
- p = setAuth(p, { enabled: true, provider: 'supabase' })
618
- expect(validateProject(p).some((i) => /Row Level Security/.test(i.message))).toBe(true)
619
- })
620
- })
621
-
622
- describe('validateProject', () => {
623
- it('catches a form condition that reads a field which does not exist', () => {
624
- // Silently, such a field would compare against undefined and sit hidden for
625
- // good, with nothing on screen to explain why.
626
- const p = createProject([customers])
627
- const typo = {
628
- ...p,
629
- entities: [{
630
- ...p.entities[0]!,
631
- fields: p.entities[0]!.fields.map((f) => (f.field === 'name'
632
- ? { ...f, when: { visible: { kind: 'cmp', column: 'stage', op: 'equals', value: 'lost' } as never } }
633
- : f)),
634
- }],
635
- }
636
- const issue = validateProject(typo).find((i) => /not a field/.test(i.message))
637
- expect(issue?.level).toBe('error')
638
- expect(issue?.message).toContain('stage')
639
-
640
- // The same condition against a real field is clean.
641
- const ok = {
642
- ...typo,
643
- entities: [{
644
- ...typo.entities[0]!,
645
- fields: typo.entities[0]!.fields.map((f) => (f.field === 'name'
646
- ? { ...f, when: { visible: { kind: 'cmp', column: 'tier', op: 'equals', value: 'pro' } as never } }
647
- : f)),
648
- }],
649
- }
650
- expect(validateProject(ok).filter((i) => /not a field/.test(i.message))).toEqual([])
651
- })
652
-
653
- it('addScreen dedupes the route so two screens for one entity do not collide', () => {
654
- const p = addScreen(createProject([customers]), 'customers')
655
- expect(new Set(p.screens.map((s) => s.route)).size).toBe(2) // customers, customers-1
656
- expect(validateProject(p).some((i) => /Duplicate route/.test(i.message))).toBe(false)
657
- })
658
-
659
- it('flags a genuine duplicate route and a missing-entity screen', () => {
660
- const p = createProject([customers])
661
- const twoSameRoute = { ...p, screens: [p.screens[0]!, { ...p.screens[0]!, id: 'dup' }] }
662
- const dup = validateProject(twoSameRoute).find((i) => /Duplicate route/.test(i.message))
663
- expect(dup?.level).toBe('error')
664
-
665
- const broken = { ...p, screens: [{ ...p.screens[0]!, entity: 'ghosts' }] }
666
- expect(validateProject(broken).some((i) => /missing entity/.test(i.message))).toBe(true)
667
- })
668
-
669
- it('warns when SQL entities have no Drizzle data layer (tables must pre-exist)', () => {
670
- let p = createProject([customers])
671
- p = setEntityDataSource(p, 'customers', { kind: 'sql', table: 'customers', dialect: 'postgres' })
672
- const warn = validateProject(p).find((i) => /tables must already exist/.test(i.message))
673
- expect(warn?.level).toBe('warning')
674
- // Enabling the Drizzle layer clears it.
675
- expect(validateProject(setDataLayer(p, true)).some((i) => /tables must already exist/.test(i.message))).toBe(false)
676
- })
677
-
678
- it('warns on cloudflare deploy + a socket Postgres driver', () => {
679
- let p = createProject([customers])
680
- p = setEntityDataSource(p, 'customers', { kind: 'sql', table: 'customers', dialect: 'postgres' })
681
- p = setDeployTarget(p, 'cloudflare')
682
- expect(validateProject(p).some((i) => /Cloudflare Workers cannot run the socket/.test(i.message))).toBe(true)
683
- // sqlite on cloudflare doesn't trip the pg warning.
684
- p = setEntityDataSource(p, 'customers', { kind: 'sql', table: 'customers', dialect: 'sqlite' })
685
- expect(validateProject(p).some((i) => /Cloudflare Workers cannot run the socket/.test(i.message))).toBe(false)
686
- })
687
-
688
- it('warns when a supabase source has no url/key (stub client)', () => {
689
- let p = createProject([customers])
690
- p = setEntityDataSource(p, 'customers', { kind: 'supabase', table: 'customers' })
691
- expect(validateProject(p).some((i) => /has no URL\/key/.test(i.message))).toBe(true)
692
- p = setEntityDataSource(p, 'customers', { kind: 'supabase', table: 'customers', url: 'https://x.supabase.co', key: 'anon' })
693
- expect(validateProject(p).some((i) => /has no URL\/key/.test(i.message))).toBe(false)
694
- })
695
-
696
- it('the palette lists every block kind', () => {
697
- expect(blockPalette.map((p) => p.kind)).toContain('grid')
698
- expect(blockPalette.map((p) => p.kind)).toContain('dashboard')
699
- })
700
-
701
- it('warns when a master-detail points at a missing child entity', () => {
702
- let p = createProject([customers])
703
- const sid = p.screens[0]!.id
704
- p = addBlock(p, sid, 'master-detail')
705
- const mdId = p.screens[0]!.blocks.at(-1)!.id
706
- p = updateBlock(p, sid, mdId, { config: { childEntity: 'ghosts', foreignKey: 'id' } as Partial<MasterDetailConfig> })
707
- expect(validateProject(p).some((i) => /missing child entity "ghosts"/.test(i.message))).toBe(true)
708
- })
709
-
710
- it('attributes a block issue to its screen + block id (for inline badges + jump)', () => {
711
- let p = createProject([customers])
712
- const sid = p.screens[0]!.id
713
- p = addBlock(p, sid, 'master-detail') // unconfigured -> warns
714
- const mdId = p.screens.find((s) => s.id === sid)!.blocks.at(-1)!.id
715
- const issue = validateProject(p).find((i) => i.block === mdId)
716
- expect(issue).toBeDefined()
717
- expect(issue!.screen).toBe(sid)
718
- expect(issue!.message).toMatch(/child entity/)
719
- })
720
-
721
- it('flags an empty filter panel and a tree without its fields', () => {
722
- let p = createProject([customers])
723
- const sid = p.screens[0]!.id
724
- // A tree with no label/parent field warns.
725
- p = addBlock(p, sid, 'tree')
726
- const treeId = p.screens.find((s) => s.id === sid)!.blocks.at(-1)!.id
727
- p = updateBlock(p, sid, treeId, { config: { labelField: '', parentField: '' } as Partial<import('./project').TreeConfig> })
728
- const treeIssue = validateProject(p).find((i) => i.block === treeId)
729
- expect(treeIssue?.message).toMatch(/label field/)
730
- })
731
-
732
- it('a freestanding screen (no entity) does not trigger the missing-entity error', () => {
733
- const p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
734
- expect(validateProject(p).some((i) => /missing entity/.test(i.message))).toBe(false)
735
- })
736
-
737
- it('warns that an empty freestanding screen needs an action or content', () => {
738
- const p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
739
- expect(validateProject(p).some((i) => /is empty - add an action or some content/.test(i.message))).toBe(true)
740
- })
741
-
742
- it('a freestanding screen with a custom action does not warn about being empty', () => {
743
- let p = addFreestandingScreen(createProject([customers]), { title: 'Reports' })
744
- const sid = p.screens.find((s) => s.title === 'Reports')!.id
745
- p = addScreenAction(p, sid, { label: 'Run report' })
746
- expect(validateProject(p).some((i) => /is empty/.test(i.message))).toBe(false)
747
- })
748
- })
749
-
750
- describe('block/screen efficiency ops', () => {
751
- it('insertBlock pastes a block into a screen with a fresh id', () => {
752
- let p = createProject([customers, orders])
753
- const src = p.screens.find((s) => s.entity === 'customers')!.blocks[0]! // the default grid
754
- const target = p.screens.find((s) => s.entity === 'orders')!.id
755
- const before = p.screens.find((s) => s.id === target)!.blocks.length
756
- p = insertBlock(p, target, src)
757
- const blocks = p.screens.find((s) => s.id === target)!.blocks
758
- expect(blocks).toHaveLength(before + 1)
759
- expect(blocks.at(-1)!.id).not.toBe(src.id) // fresh id, no collision
760
- expect(blocks.at(-1)!.config.kind).toBe(src.config.kind)
761
- })
762
-
763
- it('duplicateScreen clones a screen (fresh id/route/title + fresh block ids), inserted after', () => {
764
- let p = createProject([customers])
765
- const sid = p.screens[0]!.id
766
- p = duplicateScreen(p, sid)
767
- expect(p.screens).toHaveLength(2)
768
- const [a, b] = p.screens
769
- expect(b!.id).not.toBe(a!.id)
770
- expect(b!.route).not.toBe(a!.route)
771
- expect(b!.title).toMatch(/copy/)
772
- // Block ids in the clone are unique vs the source.
773
- const aIds = new Set(a!.blocks.map((x) => x.id))
774
- expect(b!.blocks.every((x) => !aIds.has(x.id))).toBe(true)
775
- })
776
-
777
- it('reorderScreen moves a screen to a new index', () => {
778
- let p = createProject([customers, orders])
779
- const [first, second] = p.screens
780
- p = reorderScreen(p, first!.id, 1)
781
- expect(p.screens.map((s) => s.id)).toEqual([second!.id, first!.id])
782
- })
783
- })
784
-
785
- describe('sanitizeProject (loaded-config hardening)', () => {
786
- it('is idempotent on a clean project', () => {
787
- const p = createProject([customers, orders])
788
- expect(sanitizeProject(p)).toEqual(p)
789
- })
790
-
791
- it('dedupes duplicate field names, screen ids, routes, and block ids', () => {
792
- const bad: StudioProject = {
793
- title: 'x', dataSource: 'memory',
794
- entities: [{ name: 'a', idField: 'id', fields: [{ field: 'id', type: 'text' }, { field: 'id', type: 'text' }] }],
795
- screens: [
796
- { id: 's', entity: 'a', title: 'S1', route: 'r', blocks: [
797
- { id: 'b', span: 1, config: { kind: 'form', presentation: 'modal' } },
798
- { id: 'b', span: 1, config: { kind: 'form', presentation: 'modal' } },
799
- ] },
800
- { id: 's', entity: 'a', title: 'S2', route: 'r', blocks: [] },
801
- ],
802
- }
803
- const clean = sanitizeProject(bad)
804
- expect(new Set(clean.entities[0]!.fields.map((f) => f.field)).size).toBe(2)
805
- expect(new Set(clean.screens.map((s) => s.id)).size).toBe(2)
806
- expect(new Set(clean.screens.map((s) => s.route)).size).toBe(2)
807
- expect(new Set(clean.screens[0]!.blocks.map((b) => b.id)).size).toBe(2)
808
- })
809
-
810
- it('drops a duplicate-named entity (the first survives, keeping the name)', () => {
811
- const clean = sanitizeProject({ ...createProject([customers]), entities: [customers, { ...customers, label: 'Dup' }] })
812
- expect(clean.entities).toHaveLength(1)
813
- expect(clean.entities[0]!.label).toBe('Customer')
814
- })
815
-
816
- it('parseProject sanitizes a dup-id config so keyed lists cannot collide', () => {
817
- const raw = JSON.stringify({
818
- entities: [customers],
819
- screens: [
820
- { id: 'x', entity: 'customers', title: 'A', route: 'c', blocks: [] },
821
- { id: 'x', entity: 'customers', title: 'B', route: 'c', blocks: [] },
822
- ],
823
- })
824
- const p = parseProject(raw)
825
- expect(new Set(p.screens.map((s) => s.id)).size).toBe(2)
826
- expect(new Set(p.screens.map((s) => s.route)).size).toBe(2)
827
- })
828
- })
829
-
830
- describe('per-entity data sources', () => {
831
- it('defaults to in-memory and resolves an explicit binding', () => {
832
- const p0 = createProject([customers])
833
- expect(entityDataSource(p0, 'customers')).toEqual({ kind: 'memory' })
834
- const p1 = setEntityDataSource(p0, 'customers', { kind: 'rest', baseUrl: 'https://api', path: 'customers', method: 'GET', params: [] })
835
- expect(entityDataSource(p1, 'customers').kind).toBe('rest')
836
- expect(p1.dataSources).toMatchObject({ customers: { kind: 'rest' } })
837
- })
838
-
839
- it('an unbound entity inherits the project default source (not always memory)', () => {
840
- const p0 = createProject([customers]) // dataSource: 'memory'
841
- expect(entityDataSource(p0, 'customers')).toEqual({ kind: 'memory' })
842
- // Flip the project default to SQL: unbound entities now resolve to the SQL skeleton.
843
- const pSql = { ...p0, dataSource: 'sql' as const }
844
- expect(entityDataSource(pSql, 'customers')).toEqual({ kind: 'sql', table: 'customers' })
845
- // An explicit per-entity binding still wins over the default.
846
- const pOverride = setEntityDataSource(pSql, 'customers', { kind: 'memory' })
847
- expect(entityDataSource(pOverride, 'customers')).toEqual({ kind: 'memory' })
848
- })
849
-
850
- it('defaultEntitySource seeds a skeleton per kind', () => {
851
- expect(defaultEntitySource('rest', 'customers')).toMatchObject({ kind: 'rest', path: 'customers', method: 'GET', params: [] })
852
- expect(defaultEntitySource('sql', 'orders')).toEqual({ kind: 'sql', table: 'orders' })
853
- expect(defaultEntitySource('supabase', 'orders')).toEqual({ kind: 'supabase', table: 'orders' })
854
- expect(defaultEntitySource('memory', 'x')).toEqual({ kind: 'memory' })
855
- })
856
-
857
- it('round-trips dataSources through serialize / parse', () => {
858
- const p = setEntityDataSource(createProject([customers, orders]), 'orders', { kind: 'sql', table: 'orders', dialect: 'postgres' })
859
- expect(parseProject(serializeProject(p)).dataSources).toEqual(p.dataSources)
860
- })
861
-
862
- it('sanitize prunes bindings for removed entities', () => {
863
- let p = setEntityDataSource(createProject([customers, orders]), 'orders', { kind: 'supabase', table: 'orders' })
864
- p = removeEntity(p, 'orders')
865
- expect(sanitizeProject(p).dataSources).toBeUndefined()
866
- })
867
- })
868
-
869
- describe('pages (nav) + shell', () => {
870
- it('updateScreen sets nav show / label / order', () => {
871
- const p = createProject([customers])
872
- const up = updateScreen(p, p.screens[0]!.id, { nav: { show: false, label: 'People', order: 3 } })
873
- expect(up.screens[0]!.nav).toEqual({ show: false, label: 'People', order: 3 })
874
- })
875
-
876
- it('the empty template makes a blank screen', () => {
877
- expect(screenFromTemplate(customers, 'empty').blocks).toEqual([])
878
- })
879
-
880
- it('setShell merges shell config into the theme', () => {
881
- const p = setShell(setShell(createProject([customers]), { style: 'top-nav' }), { brand: 'Acme' })
882
- expect(p.theme?.shell).toEqual({ style: 'top-nav', brand: 'Acme' })
883
- })
884
-
885
- it('round-trips nav + shell', () => {
886
- let p = updateScreen(createProject([customers]), 'customers', { nav: { order: 2 } })
887
- p = setShell(p, { style: 'top-nav', footer: '' })
888
- const back = parseProject(serializeProject(p))
889
- expect(back.screens[0]!.nav).toEqual({ order: 2 })
890
- expect(back.theme?.shell).toEqual({ style: 'top-nav', footer: '' })
891
- })
892
- })
893
-
894
- describe('screen templates', () => {
895
- it('crud -> grid; dashboard -> kpis + chart + grid; master-detail -> grid + md (editing is a grid property)', () => {
896
- expect(screenFromTemplate(customers, 'crud').blocks.map((b) => b.config.kind)).toEqual(['grid'])
897
- const dash = screenFromTemplate(customers, 'dashboard').blocks.map((b) => b.config.kind)
898
- expect(dash).toContain('kpi')
899
- expect(dash).toContain('chart')
900
- expect(dash).toContain('grid')
901
- const md = screenFromTemplate(customers, 'master-detail', { child: orders }).blocks
902
- expect(md.map((b) => b.config.kind)).toEqual(['grid', 'master-detail'])
903
- const mdBlock = md.find((b) => b.config.kind === 'master-detail')!
904
- expect(mdBlock.config).toMatchObject({ childEntity: 'orders' })
905
- })
906
-
907
- it('addScreenFromTemplate appends a screen with a unique id', () => {
908
- const p = addScreenFromTemplate(createProject([customers]), 'customers', 'dashboard')
909
- expect(p.screens).toHaveLength(2)
910
- expect(new Set(p.screens.map((s) => s.id)).size).toBe(2)
911
- expect(p.screens[1]!.blocks.some((b) => b.config.kind === 'chart')).toBe(true)
912
- })
913
- })
914
-
915
- describe('serialize / parse (studio.config round-trip)', () => {
916
- it('round-trips a project', () => {
917
- const p = setTheme(addBlock(createProject([customers, orders]), 'customers', 'chart'), { accent: '#e11d48' })
918
- const back = parseProject(serializeProject(p))
919
- expect(back).toEqual(p)
920
- })
921
-
922
- it('defaults missing fields and rejects invalid input', () => {
923
- const min = parseProject(JSON.stringify({ entities: [customers], screens: [] }))
924
- expect(min.title).toBe('My Studio App')
925
- expect(min.dataSource).toBe('memory')
926
- expect(() => parseProject('not json')).toThrow(/not valid JSON/)
927
- expect(() => parseProject(JSON.stringify({ screens: [] }))).toThrow(/entities/)
928
- })
929
- })