@svgrid/enterprise 2.3.2 → 2.5.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 (165) hide show
  1. package/dist/cdn/svgrid-enterprise.svelte-external.js +5917 -5545
  2. package/dist/designer/assets/index-oYok52zd.css +1 -0
  3. package/dist/designer/assets/{index-DVrv9dH6.js → index-qL7ir9C7.js} +89 -89
  4. package/dist/designer/assets/src-96d31O63.js +2873 -0
  5. package/dist/designer/assets/src-D-O2F805.css +1 -0
  6. package/dist/designer/index.html +6 -6
  7. package/dist/node/studio.js +681 -84
  8. package/package.json +3 -3
  9. package/src/SvGridEditPanel.dom.test.ts +174 -0
  10. package/src/SvGridEditPanel.svelte +209 -46
  11. package/src/edit-panel.test.ts +145 -36
  12. package/src/edit-panel.ts +167 -16
  13. package/src/expressions/evaluate.ts +1 -1
  14. package/src/expressions/parse.ts +3 -3
  15. package/src/index.ts +8 -0
  16. package/src/license-core.ts +77 -5
  17. package/src/license-expiry.test.ts +91 -0
  18. package/src/license.ts +31 -2
  19. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +1 -0
  20. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +178 -0
  21. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +3875 -0
  22. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +1 -0
  23. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +21 -0
  24. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +1 -0
  25. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +1938 -0
  26. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +1 -0
  27. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +205 -0
  28. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +1 -0
  29. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +18 -0
  30. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +1 -0
  31. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +264 -0
  32. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +1 -0
  33. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +697 -0
  34. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +1 -0
  35. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +3 -0
  36. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +134 -0
  37. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +1 -0
  38. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +55 -0
  39. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +1 -0
  40. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +2 -0
  41. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +46843 -0
  42. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +1 -0
  43. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +237 -0
  44. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +1 -0
  45. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +2 -0
  46. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +4 -0
  47. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +1 -0
  48. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +7 -0
  49. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +4 -0
  50. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
  51. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +5 -0
  52. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
  53. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +5 -0
  54. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
  55. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +5 -0
  56. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
  57. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +6 -0
  58. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +83 -0
  59. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +1 -0
  60. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +559 -0
  61. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +1 -0
  62. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +24 -0
  63. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +1 -0
  64. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +122 -0
  65. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +1 -0
  66. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +2 -0
  67. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +89 -0
  68. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +1 -0
  69. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +241 -0
  70. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +1 -0
  71. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +4938 -0
  72. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +1 -0
  73. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +495 -0
  74. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +1 -0
  75. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +190 -0
  76. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +1 -0
  77. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +8 -0
  78. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +1 -0
  79. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +138 -0
  80. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +1 -0
  81. package/src/schema.ts +63 -0
  82. package/src/studio/emit-project.test.ts +191 -0
  83. package/src/studio/emit-project.ts +237 -27
  84. package/src/studio/emit-schema.ts +4 -1
  85. package/src/studio/index.ts +2 -0
  86. package/src/studio/init-flow.test.ts +150 -0
  87. package/src/studio/init-flow.ts +169 -22
  88. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +1 -0
  89. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +178 -0
  90. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +3875 -0
  91. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +1 -0
  92. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +21 -0
  93. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +1 -0
  94. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +1938 -0
  95. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +1 -0
  96. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +205 -0
  97. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +1 -0
  98. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +18 -0
  99. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +1 -0
  100. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +264 -0
  101. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +1 -0
  102. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +697 -0
  103. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +1 -0
  104. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +3 -0
  105. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +134 -0
  106. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +1 -0
  107. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +55 -0
  108. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +1 -0
  109. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +2 -0
  110. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +46843 -0
  111. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +1 -0
  112. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +237 -0
  113. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +1 -0
  114. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +2 -0
  115. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +4 -0
  116. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +1 -0
  117. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +7 -0
  118. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +4 -0
  119. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
  120. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +5 -0
  121. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
  122. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +5 -0
  123. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
  124. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +5 -0
  125. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
  126. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +6 -0
  127. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +83 -0
  128. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +1 -0
  129. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +559 -0
  130. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +1 -0
  131. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +24 -0
  132. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +1 -0
  133. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +122 -0
  134. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +1 -0
  135. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +2 -0
  136. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +89 -0
  137. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +1 -0
  138. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +241 -0
  139. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +1 -0
  140. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +4938 -0
  141. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +1 -0
  142. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +495 -0
  143. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +1 -0
  144. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +190 -0
  145. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +1 -0
  146. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +8 -0
  147. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +1 -0
  148. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +138 -0
  149. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +1 -0
  150. package/src/studio/project.test.ts +30 -0
  151. package/src/studio/project.ts +72 -8
  152. package/src/studio/samples/crm.ts +11 -2
  153. package/src/studio/samples/insurance.ts +10 -4
  154. package/src/studio/samples/samples.test.ts +36 -0
  155. package/src/studio/samples/shared.ts +14 -0
  156. package/src/studio/samples/support.ts +12 -5
  157. package/src/studio/screen-suites.test.ts +43 -0
  158. package/src/studio/screen-suites.ts +5 -2
  159. package/src/studio/user-error.ts +31 -0
  160. package/src/upgrade-prompt.ts +23 -10
  161. package/src/version.test.ts +17 -0
  162. package/src/version.ts +13 -0
  163. package/dist/designer/assets/index-DjPp1mn7.css +0 -1
  164. package/dist/designer/assets/src-BRKu53tV.js +0 -2842
  165. package/dist/designer/assets/src-DseB1VRN.css +0 -1
@@ -1,6 +1,6 @@
1
1
  import type { EntitySchema } from '../../schema.js'
2
2
  import type { FormatRule } from '../project.js'
3
- import { screen, formScreen, boardScreen, calendarScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
3
+ import { screen, formScreen, boardScreen, calendarScreen, detailScreen, project, dashScreen, statusPills, pad, ids, whenIs, type SampleApp } from './shared.js'
4
4
 
5
5
  // SAP Fiori-style chrome: a dark shell bar across the top (the toolbar), a white
6
6
  // launchpad-style rail, and a blue left-bar on the active item (square corners).
@@ -88,8 +88,14 @@ const claims: EntitySchema = {
88
88
  { value: 'paid', label: 'Paid', color: '#10b981' },
89
89
  { value: 'denied', label: 'Denied', color: '#ef4444' },
90
90
  ] },
91
- { field: 'amount', type: 'number', label: 'Amount ($)', min: 0, required: true },
92
- { field: 'deductible', type: 'number', label: 'Deductible ($)', min: 0, defaultValue: 500 },
91
+ // Money is frozen once the claim has been paid out - the figures stay on
92
+ // screen for reference, but they can no longer be edited.
93
+ { field: 'amount', type: 'number', label: 'Amount ($)', min: 0, required: true, when: { disabled: whenIs('status', 'paid') } },
94
+ { field: 'deductible', type: 'number', label: 'Deductible ($)', min: 0, defaultValue: 500, when: { disabled: whenIs('status', 'paid') } },
95
+ // A denial has to be justified, and only a denial asks for it.
96
+ { field: 'denialReason', type: 'text', label: 'Reason for denial', hidden: { grid: true },
97
+ input: { editorType: 'textarea', span: 2, help: 'Required to deny a claim' },
98
+ when: { visible: whenIs('status', 'denied'), required: whenIs('status', 'denied') } },
93
99
  // Derived: net payout after the deductible (display-only, never stored).
94
100
  { field: 'net', type: 'number', label: 'Net payout ($)', readonly: true, formula: 'amount - deductible' },
95
101
  { field: 'severity', type: 'number', label: 'Severity', min: 0, max: 100, defaultValue: 40, input: { editorType: 'slider', suffix: '%', help: 'Loss severity 0-100' } },
@@ -123,7 +129,7 @@ const seed = {
123
129
  { id: 'cl1', claimNumber: 'CLM-500001', policyId: 'pl1', adjusterId: 'ad1', type: 'collision', status: 'paid', amount: 8400, deductible: 500, severity: 55, fraudRisk: 1, tags: ['rear-end', 'no-injury'], incidentDate: '2026-01-04', filedAt: '2026-01-06T09:30:00.000Z' },
124
130
  { id: 'cl2', claimNumber: 'CLM-500002', policyId: 'pl2', adjusterId: 'ad2', type: 'water', status: 'review', amount: 15200, deductible: 1000, severity: 70, fraudRisk: 2, tags: ['burst-pipe'], incidentDate: '2026-02-01', filedAt: '2026-02-03T14:15:00.000Z' },
125
131
  { id: 'cl3', claimNumber: 'CLM-500003', policyId: 'pl3', adjusterId: 'ad3', type: 'medical', status: 'approved', amount: 6200, deductible: 750, severity: 40, fraudRisk: 1, tags: ['surgery'], incidentDate: '2026-02-10', filedAt: '2026-02-12T11:00:00.000Z' },
126
- { id: 'cl4', claimNumber: 'CLM-500004', policyId: 'pl5', adjusterId: 'ad1', type: 'theft', status: 'denied', amount: 22000, deductible: 500, severity: 90, fraudRisk: 5, tags: ['suspicious', 'late-report'], incidentDate: '2026-01-20', filedAt: '2026-01-28T16:45:00.000Z' },
132
+ { id: 'cl4', claimNumber: 'CLM-500004', policyId: 'pl5', adjusterId: 'ad1', type: 'theft', status: 'denied', amount: 22000, deductible: 500, severity: 90, fraudRisk: 5, tags: ['suspicious', 'late-report'], incidentDate: '2026-01-20', filedAt: '2026-01-28T16:45:00.000Z', denialReason: 'Loss reported 8 days after the incident, outside the policy window.' },
127
133
  { id: 'cl5', claimNumber: 'CLM-500005', policyId: 'pl6', adjusterId: 'ad4', type: 'fire', status: 'paid', amount: 41000, deductible: 2000, severity: 95, fraudRisk: 2, tags: ['kitchen', 'total-loss'], incidentDate: '2026-03-01', filedAt: '2026-03-02T08:20:00.000Z' },
128
134
  { id: 'cl6', claimNumber: 'CLM-500006', policyId: 'pl7', adjusterId: 'ad3', type: 'medical', status: 'filed', amount: 3100, deductible: 750, severity: 25, fraudRisk: 1, tags: ['ER-visit'], incidentDate: '2026-03-11', filedAt: '2026-03-12T13:10:00.000Z' },
129
135
  { id: 'cl7', claimNumber: 'CLM-500007', policyId: 'pl8', adjusterId: 'ad6', type: 'collision', status: 'review', amount: 9700, deductible: 500, severity: 60, fraudRisk: 3, tags: ['intersection'], incidentDate: '2026-03-15', filedAt: '2026-03-17T10:05:00.000Z' },
@@ -214,6 +214,42 @@ describe('sample apps', () => {
214
214
  }
215
215
  })
216
216
  })
217
+
218
+ describe('forms that react to the answers', () => {
219
+ // Each of these asks a question only when the answer matters: a lost deal
220
+ // wants a reason, a closed ticket wants its resolution, a denied claim wants
221
+ // a justification.
222
+ const cases: Record<string, { field: string; drivenBy: string }> = {
223
+ crm: { field: 'lostReason', drivenBy: 'stage' },
224
+ support: { field: 'resolution', drivenBy: 'status' },
225
+ insurance: { field: 'denialReason', drivenBy: 'status' },
226
+ }
227
+
228
+ for (const [id, { field, drivenBy }] of Object.entries(cases)) {
229
+ it(`${id}: asks for "${field}" only when "${drivenBy}" calls for it`, () => {
230
+ const project = getSampleApp(id)!.build()
231
+ const conditional = project.entities.flatMap((e) => e.fields).find((f) => f.field === field)!
232
+ expect(conditional.when?.visible, `${field} should be conditional`).toBeTruthy()
233
+ expect(conditional.when?.required, `${field} should be demanded when shown`).toBeTruthy()
234
+ // The condition has to read a field that exists, or it would sit hidden
235
+ // forever - validateProject enforces this, so assert it stays clean.
236
+ expect(validateProject(project).filter((i) => i.level === 'error')).toEqual([])
237
+ // And it must survive into the generated app as plain data.
238
+ const schemas = emitStudioProject(project).find((f) => f.path === 'src/lib/schemas.ts')!.contents
239
+ expect(schemas).toContain(`"${field}"`)
240
+ expect(schemas).toMatch(new RegExp(`"column": "${drivenBy}"`))
241
+ })
242
+ }
243
+
244
+ it('seeds the conditional fields, so a loaded sample shows real answers', () => {
245
+ const rowsOf = (id: string, entity: string) =>
246
+ (getSampleApp(id)!.build().dataSources?.[entity] as { seed?: Record<string, unknown>[] })?.seed ?? []
247
+ expect(rowsOf('crm', 'deals').filter((r) => r.stage === 'lost').every((r) => !!r.lostReason)).toBe(true)
248
+ expect(rowsOf('support', 'tickets').filter((r) => r.status === 'resolved' || r.status === 'closed')
249
+ .every((r) => !!r.resolution)).toBe(true)
250
+ expect(rowsOf('insurance', 'claims').filter((r) => r.status === 'denied').every((r) => !!r.denialReason)).toBe(true)
251
+ })
252
+ })
217
253
  })
218
254
 
219
255
  describe('starter project (fresh `svgrid-studio designer` session)', () => {
@@ -8,6 +8,7 @@
8
8
  * parse/serialize and generates a runnable app with no special-casing.
9
9
  */
10
10
  import type { EntitySchema } from '../../schema.js'
11
+ import type { PredicateExpr } from '../../expressions/expression-types.js'
11
12
  import { generateValue } from '../sample-data.js'
12
13
  import { gridConfig, type GridOpts } from '../screen-suites.js'
13
14
  import { sanitizeProject, screenFromTemplate, buildDockLayout, type Block, type GridConfig, type Screen, type ScreenTemplate, type StudioProject, type EntityDataSource, type ShellStyle, type SchedulerViewConfig, type SchedulerViewMode, type AuthConfig, type AccessControl } from '../project.js'
@@ -61,6 +62,19 @@ export function pad(schema: EntitySchema, rows: Row[], target: number, fkPools:
61
62
  /** The `id` values of a seed array (for building foreign-key pools). */
62
63
  export const ids = (rows: Row[]): string[] => rows.map((r) => String(r.id))
63
64
 
65
+ /**
66
+ * `field = value` as a form condition (see `EntityField.when`). Helpers rather
67
+ * than hand-written literals because an operator the filter engine does not know
68
+ * silently matches everything, which would leave a conditional field permanently
69
+ * on-screen.
70
+ */
71
+ export const whenIs = (field: string, value: string | number): PredicateExpr =>
72
+ ({ kind: 'cmp', column: field, op: 'equals', value })
73
+
74
+ /** `field` is any one of `values`. */
75
+ export const whenIsAny = (field: string, values: ReadonlyArray<string | number>): PredicateExpr =>
76
+ ({ kind: 'or', parts: values.map((value) => ({ kind: 'cmp', column: field, op: 'equals', value })) })
77
+
64
78
  /** A ready-made app shown in the gallery. */
65
79
  export type SampleApp = {
66
80
  /** Stable id (also the `--template` value). */
@@ -1,5 +1,5 @@
1
1
  import type { EntitySchema } from '../../schema.js'
2
- import { screen, formScreen, boardScreen, workspaceScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
2
+ import { screen, formScreen, boardScreen, workspaceScreen, detailScreen, project, dashScreen, statusPills, pad, ids, whenIsAny, type SampleApp } from './shared.js'
3
3
 
4
4
  // Outlook-style chrome: a solid blue command ribbon (the toolbar), a light "folder"
5
5
  // rail with compact rows, and an accented left bar on the active folder.
@@ -86,7 +86,14 @@ const tickets: EntitySchema = {
86
86
  { field: 'tags', type: 'text', label: 'Tags', input: { editorType: 'chips', help: 'Topic labels for routing' } },
87
87
  { field: 'createdAt', type: 'datetime', label: 'Created', input: { editorType: 'datetime', help: 'When the ticket was opened' } },
88
88
  { field: 'firstResponseMins', type: 'number', label: 'First response (min)', min: 0, input: { help: 'Time to the first agent reply' } },
89
- { field: 'satisfaction', type: 'number', label: 'CSAT', min: 1, max: 5, input: { editorType: 'rating', help: 'Customer rating on close' } },
89
+ // The close notes appear the moment a ticket reaches a terminal status, and
90
+ // the ticket cannot be saved in that state without them.
91
+ { field: 'resolution', type: 'text', label: 'Resolution', hidden: { grid: true },
92
+ input: { editorType: 'textarea', span: 2, help: 'What fixed it - required to resolve or close' },
93
+ when: { visible: whenIsAny('status', ['resolved', 'closed']), required: whenIsAny('status', ['resolved', 'closed']) } },
94
+ { field: 'satisfaction', type: 'number', label: 'CSAT', min: 1, max: 5, input: { editorType: 'rating', help: 'Customer rating on close' },
95
+ // A rating only means something once the customer has had an outcome.
96
+ when: { visible: whenIsAny('status', ['resolved', 'closed']) } },
90
97
  { field: 'estimateHrs', type: 'number', label: 'Estimate (hrs)', min: 0, defaultValue: 1, input: { help: 'Estimated effort to resolve' } },
91
98
  { field: 'hourlyRate', type: 'number', label: 'Rate ($/hr)', min: 0, defaultValue: 90, input: { help: 'Billed support rate' } },
92
99
  // Derived: estimated support cost for the ticket (display-only, never seeded).
@@ -111,10 +118,10 @@ const seed = {
111
118
  { id: 'tk1', subject: 'Cannot reset password', customerId: 'c1', agentId: 'a1', priority: 'high', status: 'open', channel: 'email', tags: ['auth', 'login'], createdAt: '2026-07-10T08:30:00Z', firstResponseMins: 12, satisfaction: 4, estimateHrs: 2, hourlyRate: 90 },
112
119
  { id: 'tk2', subject: 'Invoice discrepancy', customerId: 'c2', agentId: 'a2', priority: 'normal', status: 'pending', channel: 'email', tags: ['billing'], createdAt: '2026-07-08T11:05:00Z', firstResponseMins: 45, satisfaction: 3, estimateHrs: 1, hourlyRate: 75 },
113
120
  { id: 'tk3', subject: 'API returns 500', customerId: 'c3', agentId: 'a1', priority: 'urgent', status: 'open', channel: 'chat', tags: ['api', 'outage'], createdAt: '2026-07-12T15:40:00Z', firstResponseMins: 4, satisfaction: 5, estimateHrs: 4, hourlyRate: 120 },
114
- { id: 'tk4', subject: 'How do I export data?', customerId: 'c4', agentId: 'a3', priority: 'low', status: 'resolved', channel: 'chat', tags: ['export'], createdAt: '2026-07-02T09:20:00Z', firstResponseMins: 20, satisfaction: 5, estimateHrs: 1, hourlyRate: 60 },
115
- { id: 'tk5', subject: 'Upgrade to Business', customerId: 'c5', agentId: 'a3', priority: 'normal', status: 'closed', channel: 'phone', tags: ['upgrade', 'billing'], createdAt: '2026-06-28T13:10:00Z', firstResponseMins: 8, satisfaction: 4, estimateHrs: 1, hourlyRate: 75 },
121
+ { id: 'tk4', subject: 'How do I export data?', customerId: 'c4', agentId: 'a3', priority: 'low', status: 'resolved', channel: 'chat', tags: ['export'], createdAt: '2026-07-02T09:20:00Z', firstResponseMins: 20, satisfaction: 5, estimateHrs: 1, hourlyRate: 60, resolution: 'Walked the customer through Export > CSV in the toolbar.' },
122
+ { id: 'tk5', subject: 'Upgrade to Business', customerId: 'c5', agentId: 'a3', priority: 'normal', status: 'closed', channel: 'phone', tags: ['upgrade', 'billing'], createdAt: '2026-06-28T13:10:00Z', firstResponseMins: 8, satisfaction: 4, estimateHrs: 1, hourlyRate: 75, resolution: 'Upgraded the account to Business and prorated the invoice.' },
116
123
  { id: 'tk6', subject: 'SSO configuration', customerId: 'c1', agentId: 'a1', priority: 'high', status: 'pending', channel: 'email', tags: ['sso', 'security'], createdAt: '2026-07-11T10:00:00Z', firstResponseMins: 60, satisfaction: 2, estimateHrs: 3, hourlyRate: 120 },
117
- { id: 'tk7', subject: 'Double charge refund', customerId: 'c2', agentId: 'a2', priority: 'urgent', status: 'resolved', channel: 'phone', tags: ['refund', 'billing'], createdAt: '2026-07-05T16:25:00Z', firstResponseMins: 15, satisfaction: 4, estimateHrs: 2, hourlyRate: 90 },
124
+ { id: 'tk7', subject: 'Double charge refund', customerId: 'c2', agentId: 'a2', priority: 'urgent', status: 'resolved', channel: 'phone', tags: ['refund', 'billing'], createdAt: '2026-07-05T16:25:00Z', firstResponseMins: 15, satisfaction: 4, estimateHrs: 2, hourlyRate: 90, resolution: 'Refunded the duplicate charge; payment retry guard added.' },
118
125
  { id: 'tk8', subject: 'Feature request: dark mode', customerId: 'c3', agentId: 'a3', priority: 'low', status: 'open', channel: 'chat', tags: ['feature'], createdAt: '2026-07-13T12:50:00Z', firstResponseMins: 90, satisfaction: 3, estimateHrs: 5, hourlyRate: 100 },
119
126
  ],
120
127
  }
@@ -5,7 +5,9 @@ import {
5
5
  createProject,
6
6
  parseProject,
7
7
  serializeProject,
8
+ ssrEligible,
8
9
  validateProject,
10
+ withSsrDefaults,
9
11
  type DetailConfig,
10
12
  type GridConfig,
11
13
  type Screen,
@@ -196,6 +198,47 @@ describe('crudAppFromSchemas', () => {
196
198
  expect(app.dataSource).toBe('sql')
197
199
  })
198
200
 
201
+ describe('server rendering defaults', () => {
202
+ const sqlApp = () => crudAppFromSchemas([customers, orders], {
203
+ dataSource: 'sql',
204
+ sources: {
205
+ customers: { kind: 'sql', table: 'customers', dialect: 'postgres' },
206
+ orders: { kind: 'sql', table: 'orders', dialect: 'postgres' },
207
+ },
208
+ })
209
+
210
+ it('renders database-backed screens on the server', () => {
211
+ const app = sqlApp()
212
+ const list = app.screens.find((s) => s.id === 'customers')!
213
+ expect(list.renderMode).toBe('ssr')
214
+ // Every screen that opted in has to actually be emittable that way.
215
+ for (const s of app.screens.filter((s) => s.renderMode === 'ssr')) {
216
+ expect(ssrEligible(app, s), `${s.id} claims ssr but is not eligible`).toBe(true)
217
+ }
218
+ })
219
+
220
+ it('leaves in-memory apps as a client SPA', () => {
221
+ // In-memory sources are per-process singletons: an SSR screen would mutate
222
+ // the server's rows while the app's SPA screens read the browser's.
223
+ const app = crudAppFromSchemas([customers, orders])
224
+ expect(app.screens.every((s) => s.renderMode === undefined)).toBe(true)
225
+ })
226
+
227
+ it('leaves PGlite alone even in a SQL project', () => {
228
+ const app = crudAppFromSchemas([customers], {
229
+ dataSource: 'sql',
230
+ sources: { customers: { kind: 'pglite', table: 'customers' } },
231
+ })
232
+ expect(app.screens.every((s) => s.renderMode === undefined)).toBe(true)
233
+ })
234
+
235
+ it('never overrides a mode that is already set', () => {
236
+ const app = sqlApp()
237
+ const pinned = { ...app, screens: app.screens.map((s) => ({ ...s, renderMode: 'spa' as const })) }
238
+ expect(withSsrDefaults(pinned).screens.every((s) => s.renderMode === 'spa')).toBe(true)
239
+ })
240
+ })
241
+
199
242
  it('applies per-entity overrides', () => {
200
243
  const app = crudAppFromSchemas([customers, orders], {
201
244
  overviewDashboard: false,
@@ -25,6 +25,7 @@ import {
25
25
  entityOf,
26
26
  pickFacetFields,
27
27
  sanitizeProject,
28
+ withSsrDefaults,
28
29
  type Block,
29
30
  type BlockConfig,
30
31
  type DataSourceKind,
@@ -434,12 +435,14 @@ export function crudAppFromSchemas(schemas: EntitySchema[], opts: CrudAppOptions
434
435
  : source
435
436
  }
436
437
 
437
- return sanitizeProject({
438
+ // Database-backed screens render on the server by default, so a generated app
439
+ // ships real SvelteKit (load + form actions) rather than a client SPA.
440
+ return withSsrDefaults(sanitizeProject({
438
441
  title: opts.title ?? 'My Studio App',
439
442
  entities: [...schemas],
440
443
  screens,
441
444
  dataSource: kind,
442
445
  dataSources,
443
446
  ...(opts.theme ? { theme: opts.theme } : {}),
444
- })
447
+ }))
445
448
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * UserError - a mistake the person at the keyboard can fix (a typo in a
3
+ * connection string, an empty table selection, a folder that already holds
4
+ * another app), as opposed to a defect in Studio.
5
+ *
6
+ * The CLI prints these plainly and stops. Everything else is treated as a crash
7
+ * and gets a sanitized bug report plus a prefilled issue link, so ordinary
8
+ * fat-fingering never looks like something worth reporting.
9
+ *
10
+ * `isUserError` is duck-typed rather than an `instanceof` check: the class can
11
+ * cross a bundle boundary (browser ESM source vs. the Node build), and a
12
+ * mis-detected user error would send someone to file an issue about their own
13
+ * typo.
14
+ */
15
+ export class UserError extends Error {
16
+ /** Marker read by `isUserError`; survives bundling and subclassing. */
17
+ readonly isUserError = true
18
+ /** Optional follow-up: what to try instead. */
19
+ readonly hint?: string
20
+
21
+ constructor(message: string, hint?: string) {
22
+ super(message)
23
+ this.name = 'UserError'
24
+ if (hint !== undefined) this.hint = hint
25
+ }
26
+ }
27
+
28
+ /** True for anything thrown as a `UserError`, across module instances. */
29
+ export function isUserError(err: unknown): err is UserError {
30
+ return !!err && typeof err === 'object' && (err as { isUserError?: unknown }).isUserError === true
31
+ }
@@ -39,13 +39,16 @@ export type EnterpriseFeatureLabel =
39
39
  * been shown this session, a card is already up, or we're on the server. Safe
40
40
  * to call on every Pro feature invocation - it self-throttles.
41
41
  */
42
- export function showUpgradePrompt(feature?: EnterpriseFeatureLabel | string): void {
42
+ export function showUpgradePrompt(
43
+ feature?: EnterpriseFeatureLabel | string,
44
+ opts?: { expired?: boolean },
45
+ ): void {
43
46
  if (shownThisSession) return
44
47
  if (typeof document === 'undefined' || typeof window === 'undefined') return
45
48
  if (document.querySelector(`[${CARD_ATTR}]`)) return
46
49
  shownThisSession = true
47
50
 
48
- const card = buildCard(feature)
51
+ const card = buildCard(feature, opts?.expired === true)
49
52
  document.body.appendChild(card)
50
53
  // Animate in on the next frame so the transition actually runs.
51
54
  requestAnimationFrame(() => {
@@ -73,11 +76,11 @@ function close(card: HTMLElement): void {
73
76
  window.setTimeout(() => card.remove(), 220)
74
77
  }
75
78
 
76
- function buildCard(feature?: EnterpriseFeatureLabel | string): HTMLElement {
79
+ function buildCard(feature?: EnterpriseFeatureLabel | string, expired = false): HTMLElement {
77
80
  const card = document.createElement('div')
78
81
  card.setAttribute(CARD_ATTR, '1')
79
82
  card.setAttribute('role', 'dialog')
80
- card.setAttribute('aria-label', 'Unlock SvGrid Pro')
83
+ card.setAttribute('aria-label', expired ? 'Your trial has ended' : 'Unlock SvGrid Pro')
81
84
  Object.assign(card.style, {
82
85
  position: 'fixed',
83
86
  bottom: '16px',
@@ -105,24 +108,34 @@ function buildCard(feature?: EnterpriseFeatureLabel | string): HTMLElement {
105
108
  ? `<strong style="color:#fff">${escapeHtml(featureName)}</strong> is a Pro feature.`
106
109
  : `You just used a <strong style="color:#fff">Pro</strong> feature.`
107
110
 
111
+ // An expired trial is a different message from "you never had a licence":
112
+ // they already evaluated, so the ask is to convert, not to start over.
113
+ const badge = expired ? 'TRIAL ENDED' : 'PRO'
114
+ const heading = expired ? 'Your trial has ended' : 'Unlock SvGrid Pro'
115
+ const body = expired
116
+ ? `${featurePhrase} Your evaluation period is over - everything still works,
117
+ but this notice stays until a licence key is set.`
118
+ : `${featurePhrase} It runs in evaluation with a watermark - start a free trial
119
+ to remove it and ship to production.`
120
+ const cta = expired ? 'Buy a licence' : 'Start free trial'
121
+
108
122
  card.innerHTML = `
109
123
  <button type="button" data-act="x" aria-label="Dismiss"
110
124
  style="position:absolute;top:8px;right:8px;width:24px;height:24px;display:flex;
111
125
  align-items:center;justify-content:center;background:transparent;border:0;
112
126
  color:#94a3b8;font-size:18px;line-height:1;cursor:pointer;border-radius:6px;">&times;</button>
113
127
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
114
- <span style="display:inline-block;background:#4f46e5;color:#fff;font-size:10px;
115
- font-weight:700;letter-spacing:0.06em;padding:2px 7px;border-radius:999px;">PRO</span>
116
- <span style="font-weight:700;color:#fff;">Unlock SvGrid Pro</span>
128
+ <span style="display:inline-block;background:${expired ? '#b45309' : '#4f46e5'};color:#fff;font-size:10px;
129
+ font-weight:700;letter-spacing:0.06em;padding:2px 7px;border-radius:999px;">${badge}</span>
130
+ <span style="font-weight:700;color:#fff;">${heading}</span>
117
131
  </div>
118
132
  <p style="margin:0 0 12px;color:#cbd5e1;">
119
- ${featurePhrase} It runs in evaluation with a watermark - start a free trial
120
- to remove it and ship to production.
133
+ ${body}
121
134
  </p>
122
135
  <div style="display:flex;gap:8px;align-items:center;">
123
136
  <a data-act="trial" href="${TRIAL_URL}" target="_blank" rel="noopener noreferrer"
124
137
  style="flex:1;text-align:center;background:#6366f1;color:#fff;font-weight:600;
125
- text-decoration:none;padding:8px 12px;border-radius:8px;">Start free trial</a>
138
+ text-decoration:none;padding:8px 12px;border-radius:8px;">${cta}</a>
126
139
  <a data-act="pricing" href="${PRICING_URL}" target="_blank" rel="noopener noreferrer"
127
140
  style="color:#a5b4fc;text-decoration:none;padding:8px 6px;font-weight:600;">Pricing</a>
128
141
  </div>
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { readFileSync } from 'node:fs'
3
+ import { resolve } from 'node:path'
4
+ import { SVGRID_VERSION } from './version'
5
+
6
+ describe('SVGRID_VERSION', () => {
7
+ it('matches package.json, so generated apps pin a version that exists', () => {
8
+ // Generated apps depend on `^SVGRID_VERSION`. If this drifts below the real
9
+ // version, every scaffolded app asks for a runtime older than the generator
10
+ // and can miss features the emitted code uses.
11
+ const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as { name: string; version: string }
12
+ // Guard the guard: if the runner's cwd ever moves, fail loudly here rather
13
+ // than quietly comparing against some other package's version.
14
+ expect(pkg.name).toBe('@svgrid/enterprise')
15
+ expect(SVGRID_VERSION).toBe(pkg.version)
16
+ })
17
+ })
package/src/version.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The version of this package, as a value the pure (browser-safe) generator can
3
+ * read - it cannot open `package.json` at runtime.
4
+ *
5
+ * Generated apps depend on `^SVGRID_VERSION` rather than `latest`, so an app
6
+ * always installs a runtime at least as new as the generator that wrote it.
7
+ * `latest` made that a coin toss: an app scaffolded today and the same app
8
+ * scaffolded tomorrow could resolve different runtimes, and an unrelated publish
9
+ * could break an app nobody had touched.
10
+ *
11
+ * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
12
+ */
13
+ export const SVGRID_VERSION = '2.4.0'