@svgrid/enterprise 2.5.3 → 2.6.1

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 (624) 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/{src → dist}/SvExpressionEditor.svelte +4 -1
  8. package/dist/SvExpressionEditor.svelte.d.ts +12 -0
  9. package/dist/SvFileInput.svelte.d.ts +20 -0
  10. package/dist/SvGridAlerts.svelte.d.ts +77 -0
  11. package/dist/SvGridBoard.svelte.d.ts +30 -0
  12. package/{src → dist}/SvGridEditPanel.svelte +173 -28
  13. package/dist/SvGridEditPanel.svelte.d.ts +75 -0
  14. package/dist/SvGridMasterDetail.svelte.d.ts +49 -0
  15. package/dist/SvGridScheduler.svelte.d.ts +30 -0
  16. package/dist/SvImportDialog.svelte.d.ts +88 -0
  17. package/dist/SvLookupInput.svelte.d.ts +21 -0
  18. package/dist/SvPivotDesigner.svelte.d.ts +176 -0
  19. package/dist/SvRecordDetail.svelte.d.ts +58 -0
  20. package/dist/SvSchedule.svelte.d.ts +29 -0
  21. package/dist/SvSchemaChart.svelte.d.ts +61 -0
  22. package/dist/SvSchemaDashboard.svelte.d.ts +46 -0
  23. package/dist/alerts/alert-engine-attach.d.ts +79 -0
  24. package/dist/alerts/alert-engine-attach.js +106 -0
  25. package/dist/alerts/alert-engine.d.ts +42 -0
  26. package/dist/alerts/alert-engine.js +196 -0
  27. package/dist/alerts/alert-formats.d.ts +26 -0
  28. package/dist/alerts/alert-formats.js +58 -0
  29. package/dist/alerts/alert-observer.d.ts +54 -0
  30. package/dist/alerts/alert-observer.js +156 -0
  31. package/dist/alerts/alert-scheduler.d.ts +30 -0
  32. package/dist/alerts/alert-scheduler.js +54 -0
  33. package/dist/alerts/alert-storage.d.ts +31 -0
  34. package/dist/alerts/alert-storage.js +92 -0
  35. package/dist/alerts/alert-store.svelte.d.ts +24 -0
  36. package/dist/alerts/alert-store.svelte.js +69 -0
  37. package/dist/alerts/alert-types.d.ts +86 -0
  38. package/dist/alerts/alert-types.js +1 -0
  39. package/dist/alerts.d.ts +2 -0
  40. package/{src/alerts.ts → dist/alerts.js} +9 -9
  41. package/dist/board.d.ts +7 -0
  42. package/{src/board.ts → dist/board.js} +13 -14
  43. package/dist/cdn/svgrid-enterprise.svelte-external.js +3220 -2607
  44. package/dist/designer/assets/index-BfzoL904.css +1 -0
  45. package/dist/designer/assets/{index-CBYU-KNn.js → index-CKdwDseq.js} +139 -139
  46. package/dist/designer/assets/src-DCKhP5Xy.css +1 -0
  47. package/dist/designer/assets/{src-i309BeJb.js → src-DLsSdh3J.js} +213 -193
  48. package/dist/designer/index.html +4 -4
  49. package/dist/edit-panel.d.ts +105 -0
  50. package/dist/edit-panel.js +350 -0
  51. package/dist/export-conditional.d.ts +31 -0
  52. package/dist/export-conditional.js +65 -0
  53. package/dist/export-ooxml.d.ts +93 -0
  54. package/dist/export-ooxml.js +385 -0
  55. package/dist/export-pdf.d.ts +107 -0
  56. package/dist/export-pdf.js +144 -0
  57. package/dist/export-print.d.ts +55 -0
  58. package/dist/export-print.js +91 -0
  59. package/{src/export-serialize.ts → dist/export-serialize.d.ts} +11 -24
  60. package/dist/export-serialize.js +11 -0
  61. package/dist/export-xls.d.ts +26 -0
  62. package/dist/export-xls.js +157 -0
  63. package/dist/export.d.ts +323 -0
  64. package/dist/export.js +1021 -0
  65. package/dist/expressions/evaluate.d.ts +17 -0
  66. package/dist/expressions/evaluate.js +214 -0
  67. package/dist/expressions/expression-columns.d.ts +61 -0
  68. package/dist/expressions/expression-columns.js +72 -0
  69. package/dist/expressions/expression-types.d.ts +125 -0
  70. package/dist/expressions/expression-types.js +1 -0
  71. package/dist/expressions/parse.d.ts +25 -0
  72. package/dist/expressions/parse.js +537 -0
  73. package/dist/import.d.ts +232 -0
  74. package/dist/import.js +846 -0
  75. package/dist/index.d.ts +66 -0
  76. package/dist/index.js +91 -0
  77. package/dist/install.d.ts +70 -0
  78. package/dist/install.js +61 -0
  79. package/dist/license-core.d.ts +37 -0
  80. package/dist/license-core.js +81 -0
  81. package/dist/license.d.ts +30 -0
  82. package/dist/license.js +139 -0
  83. package/dist/master-detail.d.ts +23 -0
  84. package/dist/master-detail.js +35 -0
  85. package/dist/node/studio.js +683 -13
  86. package/dist/pivot-chart.d.ts +28 -0
  87. package/dist/pivot-chart.js +71 -0
  88. package/dist/pivot-designer.d.ts +69 -0
  89. package/dist/pivot-designer.js +38 -0
  90. package/dist/pivot-enable.d.ts +6 -0
  91. package/{src/pivot-enable.ts → dist/pivot-enable.js} +13 -18
  92. package/dist/pivot.d.ts +111 -0
  93. package/dist/pivot.js +353 -0
  94. package/dist/print.d.ts +33 -0
  95. package/dist/print.js +61 -0
  96. package/dist/revoked.d.ts +1 -0
  97. package/{src/revoked.ts → dist/revoked.js} +5 -6
  98. package/dist/scheduler-assignments.d.ts +64 -0
  99. package/dist/scheduler-assignments.js +92 -0
  100. package/dist/scheduler-axis.d.ts +81 -0
  101. package/dist/scheduler-axis.js +180 -0
  102. package/{src/scheduler-booking.ts → dist/scheduler-booking.d.ts} +10 -43
  103. package/dist/scheduler-booking.js +50 -0
  104. package/dist/scheduler-config.d.ts +177 -0
  105. package/dist/scheduler-config.js +1 -0
  106. package/dist/scheduler-dependencies.d.ts +89 -0
  107. package/dist/scheduler-dependencies.js +179 -0
  108. package/dist/scheduler-freebusy.d.ts +19 -0
  109. package/{src/scheduler-freebusy.ts → dist/scheduler-freebusy.js} +9 -19
  110. package/{src/scheduler-heatmap.ts → dist/scheduler-heatmap.d.ts} +12 -35
  111. package/dist/scheduler-heatmap.js +41 -0
  112. package/dist/scheduler-resource-tree.d.ts +48 -0
  113. package/dist/scheduler-resource-tree.js +78 -0
  114. package/dist/scheduler-slots.d.ts +37 -0
  115. package/dist/scheduler-slots.js +80 -0
  116. package/dist/scheduler-summary.d.ts +41 -0
  117. package/dist/scheduler-summary.js +54 -0
  118. package/dist/scheduler.d.ts +7 -0
  119. package/{src/scheduler.ts → dist/scheduler.js} +13 -14
  120. package/dist/scheduling.d.ts +132 -0
  121. package/dist/scheduling.js +228 -0
  122. package/dist/schema-designer.d.ts +42 -0
  123. package/dist/schema-designer.js +117 -0
  124. package/dist/schema.d.ts +408 -0
  125. package/dist/schema.js +225 -0
  126. package/dist/smart-shim.d.ts +62 -0
  127. package/dist/smart-shim.js +34 -0
  128. package/dist/sources/aggregate.d.ts +43 -0
  129. package/dist/sources/aggregate.js +60 -0
  130. package/dist/sources/auth-supabase.d.ts +69 -0
  131. package/dist/sources/auth-supabase.js +39 -0
  132. package/dist/sources/dashboard.d.ts +65 -0
  133. package/dist/sources/dashboard.js +95 -0
  134. package/dist/sources/field-inference.d.ts +23 -0
  135. package/dist/sources/field-inference.js +52 -0
  136. package/dist/sources/filters.d.ts +35 -0
  137. package/dist/sources/filters.js +52 -0
  138. package/dist/sources/index.d.ts +18 -0
  139. package/dist/sources/index.js +18 -0
  140. package/dist/sources/introspect-supabase.d.ts +30 -0
  141. package/dist/sources/introspect-supabase.js +134 -0
  142. package/dist/sources/realtime-supabase.d.ts +37 -0
  143. package/dist/sources/realtime-supabase.js +52 -0
  144. package/dist/sources/relation-lookup.d.ts +50 -0
  145. package/dist/sources/relation-lookup.js +77 -0
  146. package/dist/sources/rest-adapters.d.ts +48 -0
  147. package/dist/sources/rest-adapters.js +85 -0
  148. package/dist/sources/rest.d.ts +38 -0
  149. package/dist/sources/rest.js +86 -0
  150. package/dist/sources/schema-from-columns.d.ts +37 -0
  151. package/dist/sources/schema-from-columns.js +48 -0
  152. package/dist/sources/supabase.d.ts +33 -0
  153. package/dist/sources/supabase.js +74 -0
  154. package/dist/sources/with-entity-rules.d.ts +25 -0
  155. package/dist/sources/with-entity-rules.js +47 -0
  156. package/dist/sources/with-relation-labels.d.ts +26 -0
  157. package/dist/sources/with-relation-labels.js +38 -0
  158. package/dist/staged-editing.d.ts +85 -0
  159. package/dist/staged-editing.js +0 -0
  160. package/dist/studio/bug-report.d.ts +61 -0
  161. package/dist/studio/bug-report.js +126 -0
  162. package/dist/studio/cli.d.ts +56 -0
  163. package/dist/studio/cli.js +70 -0
  164. package/dist/studio/copilot-core.d.ts +22 -0
  165. package/{src/studio/copilot-core.ts → dist/studio/copilot-core.js} +34 -38
  166. package/dist/studio/csv.d.ts +36 -0
  167. package/dist/studio/csv.js +173 -0
  168. package/dist/studio/db-connect-string.d.ts +45 -0
  169. package/dist/studio/db-connect-string.js +113 -0
  170. package/dist/studio/deploy-cli.d.ts +35 -0
  171. package/dist/studio/deploy-cli.js +81 -0
  172. package/dist/studio/emit-project.d.ts +77 -0
  173. package/dist/studio/emit-project.js +5463 -0
  174. package/dist/studio/emit-schema.d.ts +110 -0
  175. package/dist/studio/emit-schema.js +1128 -0
  176. package/dist/studio/index.d.ts +35 -0
  177. package/dist/studio/index.js +41 -0
  178. package/dist/studio/init-flow.d.ts +58 -0
  179. package/dist/studio/init-flow.js +451 -0
  180. package/dist/studio/introspect-db.d.ts +47 -0
  181. package/dist/studio/introspect-db.js +255 -0
  182. package/dist/studio/introspect-openapi.d.ts +19 -0
  183. package/dist/studio/introspect-openapi.js +244 -0
  184. package/dist/studio/introspect-prisma.d.ts +25 -0
  185. package/dist/studio/introspect-prisma.js +164 -0
  186. package/dist/studio/introspect.d.ts +35 -0
  187. package/dist/studio/introspect.js +306 -0
  188. package/dist/studio/project.d.ts +1448 -0
  189. package/dist/studio/project.js +2187 -0
  190. package/dist/studio/sample-data.d.ts +19 -0
  191. package/dist/studio/sample-data.js +207 -0
  192. package/dist/studio/samples/ats.d.ts +2 -0
  193. package/dist/studio/samples/ats.js +194 -0
  194. package/dist/studio/samples/clinic.d.ts +2 -0
  195. package/dist/studio/samples/clinic.js +171 -0
  196. package/dist/studio/samples/crm.d.ts +2 -0
  197. package/dist/studio/samples/crm.js +280 -0
  198. package/dist/studio/samples/datasets.d.ts +38 -0
  199. package/dist/studio/samples/datasets.js +296 -0
  200. package/dist/studio/samples/ecommerce.d.ts +2 -0
  201. package/dist/studio/samples/ecommerce.js +178 -0
  202. package/dist/studio/samples/events.d.ts +2 -0
  203. package/dist/studio/samples/events.js +192 -0
  204. package/dist/studio/samples/fleet.d.ts +2 -0
  205. package/dist/studio/samples/fleet.js +203 -0
  206. package/dist/studio/samples/gym.d.ts +2 -0
  207. package/dist/studio/samples/gym.js +205 -0
  208. package/dist/studio/samples/hr.d.ts +2 -0
  209. package/dist/studio/samples/hr.js +186 -0
  210. package/dist/studio/samples/index.d.ts +13 -0
  211. package/dist/studio/samples/index.js +44 -0
  212. package/dist/studio/samples/insurance.d.ts +2 -0
  213. package/dist/studio/samples/insurance.js +220 -0
  214. package/dist/studio/samples/inventory.d.ts +2 -0
  215. package/dist/studio/samples/inventory.js +204 -0
  216. package/dist/studio/samples/invoicing.d.ts +2 -0
  217. package/dist/studio/samples/invoicing.js +154 -0
  218. package/dist/studio/samples/library.d.ts +2 -0
  219. package/dist/studio/samples/library.js +171 -0
  220. package/dist/studio/samples/live-data.d.ts +5 -0
  221. package/dist/studio/samples/live-data.js +261 -0
  222. package/dist/studio/samples/projects.d.ts +2 -0
  223. package/dist/studio/samples/projects.js +203 -0
  224. package/dist/studio/samples/realestate.d.ts +2 -0
  225. package/dist/studio/samples/realestate.js +187 -0
  226. package/dist/studio/samples/restaurant.d.ts +2 -0
  227. package/dist/studio/samples/restaurant.js +180 -0
  228. package/dist/studio/samples/school.d.ts +2 -0
  229. package/dist/studio/samples/school.js +188 -0
  230. package/dist/studio/samples/shared.d.ts +142 -0
  231. package/dist/studio/samples/shared.js +164 -0
  232. package/dist/studio/samples/starter.d.ts +4 -0
  233. package/dist/studio/samples/starter.js +237 -0
  234. package/dist/studio/samples/subscriptions.d.ts +2 -0
  235. package/dist/studio/samples/subscriptions.js +175 -0
  236. package/dist/studio/samples/support.d.ts +2 -0
  237. package/dist/studio/samples/support.js +209 -0
  238. package/dist/studio/scaffold-app.d.ts +22 -0
  239. package/{src/studio/scaffold-app.ts → dist/studio/scaffold-app.js} +45 -63
  240. package/dist/studio/scaffold.d.ts +63 -0
  241. package/dist/studio/scaffold.js +346 -0
  242. package/dist/studio/screen-suites.d.ts +193 -0
  243. package/dist/studio/screen-suites.js +297 -0
  244. package/dist/studio/themes.d.ts +34 -0
  245. package/dist/studio/themes.js +41 -0
  246. package/dist/studio/ui-components.d.ts +89 -0
  247. package/dist/studio/ui-components.generated.d.ts +29 -0
  248. package/dist/studio/ui-components.generated.js +7319 -0
  249. package/dist/studio/ui-components.js +673 -0
  250. package/dist/studio/user-error.d.ts +23 -0
  251. package/{src/studio/user-error.ts → dist/studio/user-error.js} +10 -13
  252. package/dist/studio/verify.d.ts +34 -0
  253. package/dist/studio/verify.js +47 -0
  254. package/dist/sveltekit/in-memory.d.ts +14 -0
  255. package/dist/sveltekit/in-memory.js +113 -0
  256. package/dist/sveltekit/index.d.ts +6 -0
  257. package/dist/sveltekit/index.js +5 -0
  258. package/dist/sveltekit/query-plan.d.ts +44 -0
  259. package/dist/sveltekit/query-plan.js +76 -0
  260. package/dist/sveltekit/sql-source.d.ts +47 -0
  261. package/dist/sveltekit/sql-source.js +106 -0
  262. package/dist/sveltekit/sql.d.ts +48 -0
  263. package/dist/sveltekit/sql.js +73 -0
  264. package/dist/sveltekit/transport.d.ts +209 -0
  265. package/dist/sveltekit/transport.js +219 -0
  266. package/{src/sveltekit/types.ts → dist/sveltekit/types.d.ts} +8 -11
  267. package/dist/sveltekit/types.js +1 -0
  268. package/dist/upgrade-prompt.d.ts +16 -0
  269. package/dist/upgrade-prompt.js +141 -0
  270. package/dist/version.d.ts +13 -0
  271. package/{src/version.ts → dist/version.js} +1 -1
  272. package/dist/watermark.d.ts +2 -0
  273. package/dist/watermark.js +146 -0
  274. package/package.json +28 -26
  275. package/dist/designer/assets/index-oYok52zd.css +0 -1
  276. package/dist/designer/assets/src-D-O2F805.css +0 -1
  277. package/src/SvBoard.dom.test.ts +0 -67
  278. package/src/SvGridAlerts.dom.test.ts +0 -113
  279. package/src/SvGridEditPanel.dom.test.ts +0 -320
  280. package/src/SvRecordDetail.dom.test.ts +0 -137
  281. package/src/SvSchedule.dom.test.ts +0 -57
  282. package/src/ai-export-pdf.dom.test.ts +0 -77
  283. package/src/ai-export-xlsx.dom.test.ts +0 -90
  284. package/src/ai-export.dom.test.ts +0 -114
  285. package/src/alerts/alert-engine-attach.ts +0 -165
  286. package/src/alerts/alert-engine.test.ts +0 -135
  287. package/src/alerts/alert-engine.ts +0 -260
  288. package/src/alerts/alert-formats.test.ts +0 -87
  289. package/src/alerts/alert-formats.ts +0 -77
  290. package/src/alerts/alert-observer.test.ts +0 -189
  291. package/src/alerts/alert-observer.ts +0 -208
  292. package/src/alerts/alert-scheduler.ts +0 -96
  293. package/src/alerts/alert-storage.test.ts +0 -54
  294. package/src/alerts/alert-storage.ts +0 -116
  295. package/src/alerts/alert-store.svelte.ts +0 -80
  296. package/src/alerts/alert-types.ts +0 -94
  297. package/src/board.dom.test.ts +0 -941
  298. package/src/edit-panel.test.ts +0 -322
  299. package/src/edit-panel.ts +0 -379
  300. package/src/export-conditional.test.ts +0 -60
  301. package/src/export-conditional.ts +0 -94
  302. package/src/export-ooxml.test.ts +0 -152
  303. package/src/export-ooxml.ts +0 -489
  304. package/src/export-pdf.test.ts +0 -138
  305. package/src/export-pdf.ts +0 -245
  306. package/src/export-print.test.ts +0 -66
  307. package/src/export-print.ts +0 -132
  308. package/src/export-serialize.test.ts +0 -56
  309. package/src/export-xls.ts +0 -199
  310. package/src/export-xlsx-roundtrip.test.ts +0 -120
  311. package/src/export-xlsx.test.ts +0 -150
  312. package/src/export.test.ts +0 -349
  313. package/src/export.ts +0 -1464
  314. package/src/expressions/evaluate.test.ts +0 -111
  315. package/src/expressions/evaluate.ts +0 -237
  316. package/src/expressions/expression-columns.ts +0 -133
  317. package/src/expressions/expression-types.ts +0 -84
  318. package/src/expressions/parse.test.ts +0 -106
  319. package/src/expressions/parse.ts +0 -610
  320. package/src/import-automap.test.ts +0 -131
  321. package/src/import-hardening.test.ts +0 -158
  322. package/src/import.test.ts +0 -415
  323. package/src/import.ts +0 -1044
  324. package/src/index.ts +0 -569
  325. package/src/install.ts +0 -154
  326. package/src/license-core.test.ts +0 -23
  327. package/src/license-core.ts +0 -107
  328. package/src/license-expiry.test.ts +0 -144
  329. package/src/license.ts +0 -157
  330. package/src/master-detail.test.ts +0 -61
  331. package/src/master-detail.ts +0 -42
  332. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -178
  333. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +0 -3875
  334. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +0 -1
  335. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +0 -21
  336. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +0 -1
  337. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +0 -1938
  338. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +0 -1
  339. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +0 -205
  340. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +0 -1
  341. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +0 -18
  342. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +0 -1
  343. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +0 -264
  344. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +0 -1
  345. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +0 -697
  346. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +0 -1
  347. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -134
  348. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -1
  349. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -55
  350. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -1
  351. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -2
  352. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -46843
  353. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -1
  354. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -237
  355. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -1
  356. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -2
  357. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -4
  358. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -1
  359. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -7
  360. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -4
  361. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -1
  362. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -5
  363. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -1
  364. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -5
  365. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -1
  366. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -5
  367. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -1
  368. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -6
  369. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -83
  370. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -1
  371. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -559
  372. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -1
  373. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -24
  374. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -1
  375. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -122
  376. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -1
  377. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -2
  378. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -89
  379. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -1
  380. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -241
  381. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -1
  382. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +0 -4938
  383. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +0 -1
  384. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +0 -495
  385. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +0 -1
  386. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +0 -190
  387. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +0 -1
  388. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +0 -8
  389. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +0 -1
  390. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +0 -138
  391. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +0 -1
  392. package/src/pivot-chart.test.ts +0 -86
  393. package/src/pivot-chart.ts +0 -112
  394. package/src/pivot-designer.ts +0 -101
  395. package/src/pivot.test.ts +0 -335
  396. package/src/pivot.ts +0 -578
  397. package/src/print.ts +0 -107
  398. package/src/scheduler-assignments.test.ts +0 -97
  399. package/src/scheduler-assignments.ts +0 -134
  400. package/src/scheduler-axis.test.ts +0 -108
  401. package/src/scheduler-axis.ts +0 -238
  402. package/src/scheduler-booking.test.ts +0 -57
  403. package/src/scheduler-config.ts +0 -179
  404. package/src/scheduler-dependencies.test.ts +0 -155
  405. package/src/scheduler-dependencies.ts +0 -223
  406. package/src/scheduler-freebusy.test.ts +0 -41
  407. package/src/scheduler-heatmap.test.ts +0 -39
  408. package/src/scheduler-resource-tree.test.ts +0 -84
  409. package/src/scheduler-resource-tree.ts +0 -107
  410. package/src/scheduler-slots.test.ts +0 -53
  411. package/src/scheduler-slots.ts +0 -94
  412. package/src/scheduler-summary.test.ts +0 -47
  413. package/src/scheduler-summary.ts +0 -81
  414. package/src/scheduling.test.ts +0 -194
  415. package/src/scheduling.ts +0 -293
  416. package/src/schema-designer.test.ts +0 -121
  417. package/src/schema-designer.ts +0 -142
  418. package/src/schema.test.ts +0 -268
  419. package/src/schema.ts +0 -562
  420. package/src/smart-shim.ts +0 -107
  421. package/src/sources/aggregate.test.ts +0 -79
  422. package/src/sources/aggregate.ts +0 -102
  423. package/src/sources/auth-supabase.test.ts +0 -80
  424. package/src/sources/auth-supabase.ts +0 -87
  425. package/src/sources/dashboard.kpi.test.ts +0 -50
  426. package/src/sources/dashboard.test.ts +0 -61
  427. package/src/sources/dashboard.ts +0 -136
  428. package/src/sources/field-inference.test.ts +0 -60
  429. package/src/sources/field-inference.ts +0 -63
  430. package/src/sources/filters.test.ts +0 -58
  431. package/src/sources/filters.ts +0 -70
  432. package/src/sources/index.ts +0 -67
  433. package/src/sources/introspect-supabase.test.ts +0 -112
  434. package/src/sources/introspect-supabase.ts +0 -139
  435. package/src/sources/realtime-supabase.test.ts +0 -93
  436. package/src/sources/realtime-supabase.ts +0 -99
  437. package/src/sources/relation-lookup.test.ts +0 -99
  438. package/src/sources/relation-lookup.ts +0 -128
  439. package/src/sources/rest-adapters.test.ts +0 -95
  440. package/src/sources/rest-adapters.ts +0 -120
  441. package/src/sources/rest.test.ts +0 -104
  442. package/src/sources/rest.ts +0 -129
  443. package/src/sources/schema-from-columns.test.ts +0 -106
  444. package/src/sources/schema-from-columns.ts +0 -88
  445. package/src/sources/supabase.test.ts +0 -110
  446. package/src/sources/supabase.ts +0 -99
  447. package/src/sources/with-entity-rules.test.ts +0 -104
  448. package/src/sources/with-entity-rules.ts +0 -78
  449. package/src/sources/with-relation-labels.test.ts +0 -75
  450. package/src/sources/with-relation-labels.ts +0 -73
  451. package/src/staged-editing.ts +0 -0
  452. package/src/studio/bug-report.test.ts +0 -101
  453. package/src/studio/bug-report.ts +0 -165
  454. package/src/studio/cli.test.ts +0 -187
  455. package/src/studio/cli.ts +0 -117
  456. package/src/studio/copilot-core.test.ts +0 -45
  457. package/src/studio/csv.test.ts +0 -90
  458. package/src/studio/csv.ts +0 -165
  459. package/src/studio/db-connect-string.test.ts +0 -94
  460. package/src/studio/db-connect-string.ts +0 -128
  461. package/src/studio/deploy-cli.test.ts +0 -56
  462. package/src/studio/deploy-cli.ts +0 -100
  463. package/src/studio/emit-project.test.ts +0 -3324
  464. package/src/studio/emit-project.ts +0 -5377
  465. package/src/studio/emit-schema.test.ts +0 -111
  466. package/src/studio/emit-schema.ts +0 -1181
  467. package/src/studio/index.ts +0 -380
  468. package/src/studio/init-flow.test.ts +0 -514
  469. package/src/studio/init-flow.ts +0 -556
  470. package/src/studio/introspect-db.test.ts +0 -186
  471. package/src/studio/introspect-db.ts +0 -312
  472. package/src/studio/introspect-openapi.test.ts +0 -84
  473. package/src/studio/introspect-openapi.ts +0 -252
  474. package/src/studio/introspect-prisma.test.ts +0 -99
  475. package/src/studio/introspect-prisma.ts +0 -175
  476. package/src/studio/introspect.test.ts +0 -172
  477. package/src/studio/introspect.ts +0 -310
  478. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -1
  479. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -3
  480. package/src/studio/pipeline.test.ts +0 -42
  481. package/src/studio/project-robust.test.ts +0 -157
  482. package/src/studio/project.test.ts +0 -929
  483. package/src/studio/project.ts +0 -2394
  484. package/src/studio/sample-data.test.ts +0 -58
  485. package/src/studio/sample-data.ts +0 -203
  486. package/src/studio/samples/ats.ts +0 -202
  487. package/src/studio/samples/clinic.ts +0 -179
  488. package/src/studio/samples/crm.ts +0 -291
  489. package/src/studio/samples/datasets.test.ts +0 -84
  490. package/src/studio/samples/datasets.ts +0 -340
  491. package/src/studio/samples/ecommerce.ts +0 -187
  492. package/src/studio/samples/events.ts +0 -201
  493. package/src/studio/samples/fleet.ts +0 -213
  494. package/src/studio/samples/gym.ts +0 -215
  495. package/src/studio/samples/hr.ts +0 -195
  496. package/src/studio/samples/index.ts +0 -55
  497. package/src/studio/samples/insurance.ts +0 -229
  498. package/src/studio/samples/inventory.ts +0 -213
  499. package/src/studio/samples/invoicing.ts +0 -162
  500. package/src/studio/samples/library.ts +0 -180
  501. package/src/studio/samples/live-data.test.ts +0 -99
  502. package/src/studio/samples/live-data.ts +0 -306
  503. package/src/studio/samples/projects.ts +0 -212
  504. package/src/studio/samples/realestate.ts +0 -196
  505. package/src/studio/samples/restaurant.ts +0 -189
  506. package/src/studio/samples/samples.test.ts +0 -304
  507. package/src/studio/samples/school.ts +0 -197
  508. package/src/studio/samples/seed-floor.test.ts +0 -25
  509. package/src/studio/samples/shared.ts +0 -237
  510. package/src/studio/samples/starter.ts +0 -251
  511. package/src/studio/samples/subscriptions.ts +0 -183
  512. package/src/studio/samples/support.ts +0 -216
  513. package/src/studio/scaffold-app.test.ts +0 -91
  514. package/src/studio/scaffold.test.ts +0 -192
  515. package/src/studio/scaffold.ts +0 -390
  516. package/src/studio/screen-suites.test.ts +0 -269
  517. package/src/studio/screen-suites.ts +0 -448
  518. package/src/studio/themes.ts +0 -62
  519. package/src/studio/ui-components-surface.test.ts +0 -185
  520. package/src/studio/ui-components.generated.ts +0 -7154
  521. package/src/studio/ui-components.ts +0 -742
  522. package/src/studio/verify.test.ts +0 -47
  523. package/src/studio/verify.ts +0 -79
  524. package/src/sveltekit/in-memory.test.ts +0 -104
  525. package/src/sveltekit/in-memory.ts +0 -129
  526. package/src/sveltekit/index.ts +0 -24
  527. package/src/sveltekit/query-plan.test.ts +0 -109
  528. package/src/sveltekit/query-plan.ts +0 -125
  529. package/src/sveltekit/sql-source.test.ts +0 -198
  530. package/src/sveltekit/sql-source.ts +0 -171
  531. package/src/sveltekit/sql.test.ts +0 -80
  532. package/src/sveltekit/sql.ts +0 -131
  533. package/src/sveltekit/transport-scope.test.ts +0 -125
  534. package/src/sveltekit/transport.test.ts +0 -201
  535. package/src/sveltekit/transport.ts +0 -373
  536. package/src/upgrade-prompt.test.ts +0 -71
  537. package/src/upgrade-prompt.ts +0 -162
  538. package/src/version.test.ts +0 -17
  539. package/src/watermark.test.ts +0 -97
  540. package/src/watermark.ts +0 -156
  541. /package/{src → dist}/SvAlertRuleEditor.svelte +0 -0
  542. /package/{src → dist}/SvAlertsManager.svelte +0 -0
  543. /package/{src → dist}/SvAlertsPanel.svelte +0 -0
  544. /package/{src → dist}/SvAuthGate.svelte +0 -0
  545. /package/{src → dist}/SvBoard.svelte +0 -0
  546. /package/{src → dist}/SvExportMenu.svelte +0 -0
  547. /package/{src → dist}/SvFileInput.svelte +0 -0
  548. /package/{src → dist}/SvGridAlerts.svelte +0 -0
  549. /package/{src → dist}/SvGridBoard.svelte +0 -0
  550. /package/{src → dist}/SvGridMasterDetail.svelte +0 -0
  551. /package/{src → dist}/SvGridScheduler.svelte +0 -0
  552. /package/{src → dist}/SvImportDialog.svelte +0 -0
  553. /package/{src → dist}/SvLookupInput.svelte +0 -0
  554. /package/{src → dist}/SvPivotDesigner.svelte +0 -0
  555. /package/{src → dist}/SvRecordDetail.svelte +0 -0
  556. /package/{src → dist}/SvSchedule.svelte +0 -0
  557. /package/{src → dist}/SvSchemaChart.svelte +0 -0
  558. /package/{src → dist}/SvSchemaDashboard.svelte +0 -0
  559. /package/{src → dist}/pdfmake-shims.d.ts +0 -0
  560. /package/{src → dist}/smart.export.d.ts +0 -0
  561. /package/{src → dist}/smart.export.js +0 -0
  562. /package/{src → dist}/studio/HANDLERS-DESIGN.md +0 -0
  563. /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -0
  564. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -0
  565. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +0 -0
  566. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +0 -0
  567. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +0 -0
  568. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +0 -0
  569. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +0 -0
  570. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +0 -0
  571. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +0 -0
  572. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +0 -0
  573. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +0 -0
  574. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +0 -0
  575. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +0 -0
  576. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +0 -0
  577. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +0 -0
  578. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +0 -0
  579. /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -0
  580. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -0
  581. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -0
  582. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -0
  583. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -0
  584. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -0
  585. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -0
  586. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -0
  587. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -0
  588. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -0
  589. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -0
  590. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -0
  591. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -0
  592. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -0
  593. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -0
  594. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -0
  595. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -0
  596. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -0
  597. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -0
  598. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -0
  599. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -0
  600. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -0
  601. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -0
  602. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -0
  603. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -0
  604. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -0
  605. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -0
  606. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -0
  607. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -0
  608. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -0
  609. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -0
  610. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -0
  611. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -0
  612. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -0
  613. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -0
  614. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -0
  615. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +0 -0
  616. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +0 -0
  617. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +0 -0
  618. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +0 -0
  619. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +0 -0
  620. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +0 -0
  621. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +0 -0
  622. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +0 -0
  623. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +0 -0
  624. /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
- })