@contractspec/example.saas-boilerplate 3.7.6 → 3.8.2

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 (143) hide show
  1. package/.turbo/turbo-build.log +39 -27
  2. package/AGENTS.md +50 -27
  3. package/CHANGELOG.md +36 -0
  4. package/README.md +65 -144
  5. package/dist/billing/billing.event.js +1 -1
  6. package/dist/billing/index.d.ts +6 -6
  7. package/dist/billing/index.js +1 -1
  8. package/dist/browser/billing/billing.event.js +1 -1
  9. package/dist/browser/billing/index.js +1 -1
  10. package/dist/browser/index.js +1147 -869
  11. package/dist/browser/project/index.js +209 -209
  12. package/dist/browser/project/project.event.js +1 -1
  13. package/dist/browser/saas-boilerplate.feature.js +208 -0
  14. package/dist/browser/ui/SaasDashboard.js +356 -105
  15. package/dist/browser/ui/SaasDashboard.visualizations.js +249 -0
  16. package/dist/browser/ui/SaasProjectList.js +7 -7
  17. package/dist/browser/ui/SaasSettingsPanel.js +12 -12
  18. package/dist/browser/ui/hooks/index.js +2 -2
  19. package/dist/browser/ui/hooks/useProjectList.js +1 -1
  20. package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
  21. package/dist/browser/ui/index.js +790 -521
  22. package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
  23. package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
  24. package/dist/browser/ui/modals/index.js +23 -23
  25. package/dist/browser/ui/renderers/index.js +341 -115
  26. package/dist/browser/ui/renderers/project-list.markdown.js +229 -3
  27. package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
  28. package/dist/browser/visualizations/catalog.js +155 -0
  29. package/dist/browser/visualizations/index.js +217 -0
  30. package/dist/browser/visualizations/selectors.js +210 -0
  31. package/dist/handlers/index.d.ts +2 -2
  32. package/dist/index.d.ts +5 -4
  33. package/dist/index.js +1147 -869
  34. package/dist/node/billing/billing.event.js +1 -1
  35. package/dist/node/billing/index.js +1 -1
  36. package/dist/node/index.js +1147 -869
  37. package/dist/node/project/index.js +209 -209
  38. package/dist/node/project/project.event.js +1 -1
  39. package/dist/node/saas-boilerplate.feature.js +208 -0
  40. package/dist/node/ui/SaasDashboard.js +356 -105
  41. package/dist/node/ui/SaasDashboard.visualizations.js +249 -0
  42. package/dist/node/ui/SaasProjectList.js +7 -7
  43. package/dist/node/ui/SaasSettingsPanel.js +12 -12
  44. package/dist/node/ui/hooks/index.js +2 -2
  45. package/dist/node/ui/hooks/useProjectList.js +1 -1
  46. package/dist/node/ui/hooks/useProjectMutations.js +1 -1
  47. package/dist/node/ui/index.js +790 -521
  48. package/dist/node/ui/modals/CreateProjectModal.js +10 -10
  49. package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
  50. package/dist/node/ui/modals/index.js +23 -23
  51. package/dist/node/ui/renderers/index.js +341 -115
  52. package/dist/node/ui/renderers/project-list.markdown.js +229 -3
  53. package/dist/node/ui/renderers/project-list.renderer.js +7 -7
  54. package/dist/node/visualizations/catalog.js +155 -0
  55. package/dist/node/visualizations/index.js +217 -0
  56. package/dist/node/visualizations/selectors.js +210 -0
  57. package/dist/presentations/index.d.ts +1 -1
  58. package/dist/project/index.d.ts +7 -7
  59. package/dist/project/index.js +209 -209
  60. package/dist/project/project.event.js +1 -1
  61. package/dist/saas-boilerplate.feature.js +208 -0
  62. package/dist/settings/index.d.ts +1 -1
  63. package/dist/ui/SaasDashboard.js +356 -105
  64. package/dist/ui/SaasDashboard.visualizations.d.ts +5 -0
  65. package/dist/ui/SaasDashboard.visualizations.js +250 -0
  66. package/dist/ui/SaasProjectList.js +7 -7
  67. package/dist/ui/SaasSettingsPanel.js +12 -12
  68. package/dist/ui/hooks/index.d.ts +2 -2
  69. package/dist/ui/hooks/index.js +2 -2
  70. package/dist/ui/hooks/useProjectList.d.ts +5 -0
  71. package/dist/ui/hooks/useProjectList.js +1 -1
  72. package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
  73. package/dist/ui/hooks/useProjectMutations.js +1 -1
  74. package/dist/ui/index.d.ts +4 -4
  75. package/dist/ui/index.js +790 -521
  76. package/dist/ui/modals/CreateProjectModal.js +10 -10
  77. package/dist/ui/modals/ProjectActionsModal.js +13 -13
  78. package/dist/ui/modals/index.js +23 -23
  79. package/dist/ui/renderers/index.d.ts +1 -1
  80. package/dist/ui/renderers/index.js +341 -115
  81. package/dist/ui/renderers/project-list.markdown.js +229 -3
  82. package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
  83. package/dist/ui/renderers/project-list.renderer.js +7 -7
  84. package/dist/visualizations/catalog.d.ts +11 -0
  85. package/dist/visualizations/catalog.js +156 -0
  86. package/dist/visualizations/index.d.ts +2 -0
  87. package/dist/visualizations/index.js +218 -0
  88. package/dist/visualizations/selectors.d.ts +8 -0
  89. package/dist/visualizations/selectors.js +211 -0
  90. package/dist/visualizations/selectors.test.d.ts +1 -0
  91. package/package.json +70 -14
  92. package/src/billing/billing.entity.ts +132 -132
  93. package/src/billing/billing.enum.ts +9 -9
  94. package/src/billing/billing.event.ts +71 -71
  95. package/src/billing/billing.handler.ts +87 -87
  96. package/src/billing/billing.operations.ts +158 -158
  97. package/src/billing/billing.presentation.ts +45 -45
  98. package/src/billing/billing.schema.ts +76 -76
  99. package/src/billing/index.ts +43 -48
  100. package/src/dashboard/dashboard.presentation.ts +45 -45
  101. package/src/dashboard/index.ts +2 -2
  102. package/src/docs/saas-boilerplate.docblock.ts +43 -43
  103. package/src/example.ts +32 -32
  104. package/src/handlers/index.ts +9 -9
  105. package/src/handlers/saas.handlers.ts +250 -249
  106. package/src/index.ts +41 -41
  107. package/src/presentations/index.ts +18 -20
  108. package/src/project/index.ts +45 -50
  109. package/src/project/project.entity.ts +68 -68
  110. package/src/project/project.enum.ts +8 -8
  111. package/src/project/project.event.ts +79 -79
  112. package/src/project/project.handler.ts +103 -103
  113. package/src/project/project.operations.ts +236 -236
  114. package/src/project/project.presentation.ts +46 -46
  115. package/src/project/project.schema.ts +90 -90
  116. package/src/saas-boilerplate.feature.ts +103 -100
  117. package/src/seeders/index.ts +20 -20
  118. package/src/settings/index.ts +2 -3
  119. package/src/settings/settings.entity.ts +65 -65
  120. package/src/settings/settings.enum.ts +4 -4
  121. package/src/shared/mock-data.ts +92 -92
  122. package/src/shared/overlay-types.ts +23 -23
  123. package/src/tests/operations.test-spec.ts +96 -96
  124. package/src/ui/SaasDashboard.tsx +278 -270
  125. package/src/ui/SaasDashboard.visualizations.tsx +41 -0
  126. package/src/ui/SaasProjectList.tsx +90 -90
  127. package/src/ui/SaasSettingsPanel.tsx +84 -84
  128. package/src/ui/hooks/index.ts +3 -3
  129. package/src/ui/hooks/useProjectList.ts +69 -68
  130. package/src/ui/hooks/useProjectMutations.ts +144 -143
  131. package/src/ui/index.ts +8 -12
  132. package/src/ui/modals/CreateProjectModal.tsx +154 -154
  133. package/src/ui/modals/ProjectActionsModal.tsx +321 -321
  134. package/src/ui/overlays/demo-overlays.ts +49 -49
  135. package/src/ui/renderers/index.ts +5 -4
  136. package/src/ui/renderers/project-list.markdown.ts +229 -205
  137. package/src/ui/renderers/project-list.renderer.tsx +14 -13
  138. package/src/visualizations/catalog.ts +153 -0
  139. package/src/visualizations/index.ts +2 -0
  140. package/src/visualizations/selectors.test.ts +25 -0
  141. package/src/visualizations/selectors.ts +85 -0
  142. package/tsconfig.json +7 -8
  143. package/tsdown.config.js +7 -3
package/dist/index.js CHANGED
@@ -124,8 +124,8 @@ var FeatureAccessReasonEnum = defineEnum("FeatureAccessReason", [
124
124
  ]);
125
125
 
126
126
  // src/billing/billing.event.ts
127
- import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
128
127
  import { defineEvent } from "@contractspec/lib.contracts-spec";
128
+ import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
129
129
  var UsageRecordedPayload = defineSchemaModel({
130
130
  name: "UsageRecordedPayload",
131
131
  description: "Payload when feature usage is recorded",
@@ -1044,6 +1044,79 @@ function createSaasHandlers(db) {
1044
1044
  getSubscription
1045
1045
  };
1046
1046
  }
1047
+ // src/project/project.entity.ts
1048
+ import {
1049
+ defineEntity as defineEntity2,
1050
+ defineEntityEnum as defineEntityEnum2,
1051
+ field as field2,
1052
+ index as index2
1053
+ } from "@contractspec/lib.schema";
1054
+ var ProjectStatusEnum = defineEntityEnum2({
1055
+ name: "ProjectStatus",
1056
+ values: ["DRAFT", "ACTIVE", "ARCHIVED", "DELETED"],
1057
+ schema: "saas_app",
1058
+ description: "Status of a project."
1059
+ });
1060
+ var ProjectEntity = defineEntity2({
1061
+ name: "Project",
1062
+ description: "A project belonging to an organization.",
1063
+ schema: "saas_app",
1064
+ map: "project",
1065
+ fields: {
1066
+ id: field2.id({ description: "Unique project ID" }),
1067
+ name: field2.string({ description: "Project name" }),
1068
+ description: field2.string({
1069
+ isOptional: true,
1070
+ description: "Project description"
1071
+ }),
1072
+ slug: field2.string({
1073
+ isOptional: true,
1074
+ description: "URL-friendly identifier"
1075
+ }),
1076
+ organizationId: field2.foreignKey({ description: "Owning organization" }),
1077
+ createdBy: field2.foreignKey({
1078
+ description: "User who created the project"
1079
+ }),
1080
+ status: field2.enum("ProjectStatus", { default: "DRAFT" }),
1081
+ isPublic: field2.boolean({
1082
+ default: false,
1083
+ description: "Whether project is publicly visible"
1084
+ }),
1085
+ settings: field2.json({
1086
+ isOptional: true,
1087
+ description: "Project-specific settings"
1088
+ }),
1089
+ tags: field2.string({ isArray: true, description: "Project tags" }),
1090
+ metadata: field2.json({ isOptional: true }),
1091
+ createdAt: field2.createdAt(),
1092
+ updatedAt: field2.updatedAt(),
1093
+ archivedAt: field2.dateTime({ isOptional: true })
1094
+ },
1095
+ indexes: [
1096
+ index2.on(["organizationId", "status"]),
1097
+ index2.on(["organizationId", "createdAt"]),
1098
+ index2.unique(["organizationId", "slug"])
1099
+ ],
1100
+ enums: [ProjectStatusEnum]
1101
+ });
1102
+ var ProjectMemberEntity = defineEntity2({
1103
+ name: "ProjectMember",
1104
+ description: "User access to a specific project.",
1105
+ schema: "saas_app",
1106
+ map: "project_member",
1107
+ fields: {
1108
+ id: field2.id(),
1109
+ projectId: field2.foreignKey(),
1110
+ userId: field2.foreignKey(),
1111
+ role: field2.string({
1112
+ description: "Role in project (owner, editor, viewer)"
1113
+ }),
1114
+ addedBy: field2.string({ isOptional: true }),
1115
+ createdAt: field2.createdAt()
1116
+ },
1117
+ indexes: [index2.unique(["projectId", "userId"])]
1118
+ });
1119
+
1047
1120
  // src/project/project.enum.ts
1048
1121
  import { defineEnum as defineEnum2 } from "@contractspec/lib.schema";
1049
1122
  var ProjectStatusSchemaEnum = defineEnum2("ProjectStatus", [
@@ -1059,112 +1132,210 @@ var ProjectStatusFilterEnum = defineEnum2("ProjectStatusFilter", [
1059
1132
  "all"
1060
1133
  ]);
1061
1134
 
1062
- // src/project/project.schema.ts
1135
+ // src/project/project.event.ts
1136
+ import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts-spec";
1063
1137
  import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
1064
- var ProjectModel = defineSchemaModel3({
1065
- name: "Project",
1066
- description: "A project within an organization",
1138
+ var ProjectCreatedPayload = defineSchemaModel3({
1139
+ name: "ProjectCreatedPayload",
1140
+ description: "Payload when a project is created",
1067
1141
  fields: {
1068
- id: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1142
+ projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1069
1143
  name: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1070
- description: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1071
- slug: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1072
1144
  organizationId: {
1073
1145
  type: ScalarTypeEnum3.String_unsecure(),
1074
1146
  isOptional: false
1075
1147
  },
1076
1148
  createdBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1077
- status: { type: ProjectStatusSchemaEnum, isOptional: false },
1078
- isPublic: { type: ScalarTypeEnum3.Boolean(), isOptional: false },
1079
- tags: {
1149
+ createdAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
1150
+ }
1151
+ });
1152
+ var ProjectUpdatedPayload = defineSchemaModel3({
1153
+ name: "ProjectUpdatedPayload",
1154
+ description: "Payload when a project is updated",
1155
+ fields: {
1156
+ projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1157
+ updatedFields: {
1080
1158
  type: ScalarTypeEnum3.String_unsecure(),
1081
1159
  isArray: true,
1082
1160
  isOptional: false
1083
1161
  },
1084
- createdAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false },
1162
+ updatedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1085
1163
  updatedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
1086
1164
  }
1087
1165
  });
1088
- var CreateProjectInputModel = defineSchemaModel3({
1166
+ var ProjectDeletedPayload = defineSchemaModel3({
1167
+ name: "ProjectDeletedPayload",
1168
+ description: "Payload when a project is deleted",
1169
+ fields: {
1170
+ projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1171
+ organizationId: {
1172
+ type: ScalarTypeEnum3.String_unsecure(),
1173
+ isOptional: false
1174
+ },
1175
+ deletedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1176
+ deletedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
1177
+ }
1178
+ });
1179
+ var ProjectArchivedPayload = defineSchemaModel3({
1180
+ name: "ProjectArchivedPayload",
1181
+ description: "Payload when a project is archived",
1182
+ fields: {
1183
+ projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1184
+ archivedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1185
+ archivedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
1186
+ }
1187
+ });
1188
+ var ProjectCreatedEvent = defineEvent2({
1189
+ meta: {
1190
+ key: "project.created",
1191
+ version: "1.0.0",
1192
+ description: "A new project has been created.",
1193
+ stability: "stable",
1194
+ owners: ["@saas-team"],
1195
+ tags: ["project", "created"]
1196
+ },
1197
+ payload: ProjectCreatedPayload
1198
+ });
1199
+ var ProjectUpdatedEvent = defineEvent2({
1200
+ meta: {
1201
+ key: "project.updated",
1202
+ version: "1.0.0",
1203
+ description: "A project has been updated.",
1204
+ stability: "stable",
1205
+ owners: ["@saas-team"],
1206
+ tags: ["project", "updated"]
1207
+ },
1208
+ payload: ProjectUpdatedPayload
1209
+ });
1210
+ var ProjectDeletedEvent = defineEvent2({
1211
+ meta: {
1212
+ key: "project.deleted",
1213
+ version: "1.0.0",
1214
+ description: "A project has been deleted.",
1215
+ stability: "stable",
1216
+ owners: ["@saas-team"],
1217
+ tags: ["project", "deleted"]
1218
+ },
1219
+ payload: ProjectDeletedPayload
1220
+ });
1221
+ var ProjectArchivedEvent = defineEvent2({
1222
+ meta: {
1223
+ key: "project.archived",
1224
+ version: "1.0.0",
1225
+ description: "A project has been archived.",
1226
+ stability: "stable",
1227
+ owners: ["@saas-team"],
1228
+ tags: ["project", "archived"]
1229
+ },
1230
+ payload: ProjectArchivedPayload
1231
+ });
1232
+
1233
+ // src/project/project.schema.ts
1234
+ import { defineSchemaModel as defineSchemaModel4, ScalarTypeEnum as ScalarTypeEnum4 } from "@contractspec/lib.schema";
1235
+ var ProjectModel = defineSchemaModel4({
1236
+ name: "Project",
1237
+ description: "A project within an organization",
1238
+ fields: {
1239
+ id: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1240
+ name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1241
+ description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1242
+ slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1243
+ organizationId: {
1244
+ type: ScalarTypeEnum4.String_unsecure(),
1245
+ isOptional: false
1246
+ },
1247
+ createdBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1248
+ status: { type: ProjectStatusSchemaEnum, isOptional: false },
1249
+ isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: false },
1250
+ tags: {
1251
+ type: ScalarTypeEnum4.String_unsecure(),
1252
+ isArray: true,
1253
+ isOptional: false
1254
+ },
1255
+ createdAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false },
1256
+ updatedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
1257
+ }
1258
+ });
1259
+ var CreateProjectInputModel = defineSchemaModel4({
1089
1260
  name: "CreateProjectInput",
1090
1261
  description: "Input for creating a project",
1091
1262
  fields: {
1092
- name: { type: ScalarTypeEnum3.NonEmptyString(), isOptional: false },
1093
- description: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1094
- slug: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1095
- isPublic: { type: ScalarTypeEnum3.Boolean(), isOptional: true },
1263
+ name: { type: ScalarTypeEnum4.NonEmptyString(), isOptional: false },
1264
+ description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1265
+ slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1266
+ isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: true },
1096
1267
  tags: {
1097
- type: ScalarTypeEnum3.String_unsecure(),
1268
+ type: ScalarTypeEnum4.String_unsecure(),
1098
1269
  isArray: true,
1099
1270
  isOptional: true
1100
1271
  }
1101
1272
  }
1102
1273
  });
1103
- var UpdateProjectInputModel = defineSchemaModel3({
1274
+ var UpdateProjectInputModel = defineSchemaModel4({
1104
1275
  name: "UpdateProjectInput",
1105
1276
  description: "Input for updating a project",
1106
1277
  fields: {
1107
- projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1108
- name: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1109
- description: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1110
- slug: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1111
- isPublic: { type: ScalarTypeEnum3.Boolean(), isOptional: true },
1278
+ projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1279
+ name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1280
+ description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1281
+ slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1282
+ isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: true },
1112
1283
  tags: {
1113
- type: ScalarTypeEnum3.String_unsecure(),
1284
+ type: ScalarTypeEnum4.String_unsecure(),
1114
1285
  isArray: true,
1115
1286
  isOptional: true
1116
1287
  },
1117
1288
  status: { type: ProjectStatusSchemaEnum, isOptional: true }
1118
1289
  }
1119
1290
  });
1120
- var GetProjectInputModel = defineSchemaModel3({
1291
+ var GetProjectInputModel = defineSchemaModel4({
1121
1292
  name: "GetProjectInput",
1122
1293
  fields: {
1123
- projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false }
1294
+ projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
1124
1295
  }
1125
1296
  });
1126
- var DeleteProjectInputModel = defineSchemaModel3({
1297
+ var DeleteProjectInputModel = defineSchemaModel4({
1127
1298
  name: "DeleteProjectInput",
1128
1299
  fields: {
1129
- projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false }
1300
+ projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
1130
1301
  }
1131
1302
  });
1132
- var DeleteProjectOutputModel = defineSchemaModel3({
1303
+ var DeleteProjectOutputModel = defineSchemaModel4({
1133
1304
  name: "DeleteProjectOutput",
1134
1305
  fields: {
1135
- success: { type: ScalarTypeEnum3.Boolean(), isOptional: false }
1306
+ success: { type: ScalarTypeEnum4.Boolean(), isOptional: false }
1136
1307
  }
1137
1308
  });
1138
- var ProjectDeletedPayloadModel = defineSchemaModel3({
1309
+ var ProjectDeletedPayloadModel = defineSchemaModel4({
1139
1310
  name: "ProjectDeletedPayload",
1140
1311
  fields: {
1141
- projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false }
1312
+ projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
1142
1313
  }
1143
1314
  });
1144
- var ListProjectsInputModel = defineSchemaModel3({
1315
+ var ListProjectsInputModel = defineSchemaModel4({
1145
1316
  name: "ListProjectsInput",
1146
1317
  description: "Input for listing projects",
1147
1318
  fields: {
1148
1319
  status: { type: ProjectStatusFilterEnum, isOptional: true },
1149
- search: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1320
+ search: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
1150
1321
  limit: {
1151
- type: ScalarTypeEnum3.Int_unsecure(),
1322
+ type: ScalarTypeEnum4.Int_unsecure(),
1152
1323
  isOptional: true,
1153
1324
  defaultValue: 20
1154
1325
  },
1155
1326
  offset: {
1156
- type: ScalarTypeEnum3.Int_unsecure(),
1327
+ type: ScalarTypeEnum4.Int_unsecure(),
1157
1328
  isOptional: true,
1158
1329
  defaultValue: 0
1159
1330
  }
1160
1331
  }
1161
1332
  });
1162
- var ListProjectsOutputModel = defineSchemaModel3({
1333
+ var ListProjectsOutputModel = defineSchemaModel4({
1163
1334
  name: "ListProjectsOutput",
1164
1335
  description: "Output for listing projects",
1165
1336
  fields: {
1166
1337
  projects: { type: ProjectModel, isArray: true, isOptional: false },
1167
- total: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false }
1338
+ total: { type: ScalarTypeEnum4.Int_unsecure(), isOptional: false }
1168
1339
  }
1169
1340
  });
1170
1341
 
@@ -1410,209 +1581,38 @@ var ListProjectsContract = defineQuery2({
1410
1581
  }
1411
1582
  });
1412
1583
 
1413
- // src/project/project.event.ts
1414
- import { ScalarTypeEnum as ScalarTypeEnum4, defineSchemaModel as defineSchemaModel4 } from "@contractspec/lib.schema";
1415
- import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts-spec";
1416
- var ProjectCreatedPayload = defineSchemaModel4({
1417
- name: "ProjectCreatedPayload",
1418
- description: "Payload when a project is created",
1419
- fields: {
1420
- projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1421
- name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1422
- organizationId: {
1423
- type: ScalarTypeEnum4.String_unsecure(),
1424
- isOptional: false
1425
- },
1426
- createdBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1427
- createdAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
1428
- }
1429
- });
1430
- var ProjectUpdatedPayload = defineSchemaModel4({
1431
- name: "ProjectUpdatedPayload",
1432
- description: "Payload when a project is updated",
1433
- fields: {
1434
- projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1435
- updatedFields: {
1436
- type: ScalarTypeEnum4.String_unsecure(),
1437
- isArray: true,
1438
- isOptional: false
1439
- },
1440
- updatedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1441
- updatedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
1442
- }
1443
- });
1444
- var ProjectDeletedPayload = defineSchemaModel4({
1445
- name: "ProjectDeletedPayload",
1446
- description: "Payload when a project is deleted",
1447
- fields: {
1448
- projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1449
- organizationId: {
1450
- type: ScalarTypeEnum4.String_unsecure(),
1451
- isOptional: false
1452
- },
1453
- deletedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1454
- deletedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
1455
- }
1456
- });
1457
- var ProjectArchivedPayload = defineSchemaModel4({
1458
- name: "ProjectArchivedPayload",
1459
- description: "Payload when a project is archived",
1460
- fields: {
1461
- projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1462
- archivedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1463
- archivedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
1464
- }
1465
- });
1466
- var ProjectCreatedEvent = defineEvent2({
1584
+ // src/project/project.presentation.ts
1585
+ import {
1586
+ definePresentation as definePresentation3,
1587
+ StabilityEnum as StabilityEnum3
1588
+ } from "@contractspec/lib.contracts-spec";
1589
+ var ProjectListPresentation = definePresentation3({
1467
1590
  meta: {
1468
- key: "project.created",
1591
+ key: "saas.project.list",
1469
1592
  version: "1.0.0",
1470
- description: "A new project has been created.",
1471
- stability: "stable",
1593
+ title: "Project List",
1594
+ description: "List view of projects with status, tags, and last updated info",
1595
+ domain: "saas-boilerplate",
1472
1596
  owners: ["@saas-team"],
1473
- tags: ["project", "created"]
1597
+ tags: ["project", "list", "dashboard"],
1598
+ stability: StabilityEnum3.Beta,
1599
+ goal: "Browse and manage projects",
1600
+ context: "Project list page"
1474
1601
  },
1475
- payload: ProjectCreatedPayload
1602
+ source: {
1603
+ type: "component",
1604
+ framework: "react",
1605
+ componentKey: "ProjectListView",
1606
+ props: ProjectModel
1607
+ },
1608
+ targets: ["react", "markdown", "application/json"],
1609
+ policy: {
1610
+ flags: ["saas.projects.enabled"]
1611
+ }
1476
1612
  });
1477
- var ProjectUpdatedEvent = defineEvent2({
1613
+ var ProjectDetailPresentation = definePresentation3({
1478
1614
  meta: {
1479
- key: "project.updated",
1480
- version: "1.0.0",
1481
- description: "A project has been updated.",
1482
- stability: "stable",
1483
- owners: ["@saas-team"],
1484
- tags: ["project", "updated"]
1485
- },
1486
- payload: ProjectUpdatedPayload
1487
- });
1488
- var ProjectDeletedEvent = defineEvent2({
1489
- meta: {
1490
- key: "project.deleted",
1491
- version: "1.0.0",
1492
- description: "A project has been deleted.",
1493
- stability: "stable",
1494
- owners: ["@saas-team"],
1495
- tags: ["project", "deleted"]
1496
- },
1497
- payload: ProjectDeletedPayload
1498
- });
1499
- var ProjectArchivedEvent = defineEvent2({
1500
- meta: {
1501
- key: "project.archived",
1502
- version: "1.0.0",
1503
- description: "A project has been archived.",
1504
- stability: "stable",
1505
- owners: ["@saas-team"],
1506
- tags: ["project", "archived"]
1507
- },
1508
- payload: ProjectArchivedPayload
1509
- });
1510
-
1511
- // src/project/project.entity.ts
1512
- import {
1513
- defineEntity as defineEntity2,
1514
- defineEntityEnum as defineEntityEnum2,
1515
- field as field2,
1516
- index as index2
1517
- } from "@contractspec/lib.schema";
1518
- var ProjectStatusEnum = defineEntityEnum2({
1519
- name: "ProjectStatus",
1520
- values: ["DRAFT", "ACTIVE", "ARCHIVED", "DELETED"],
1521
- schema: "saas_app",
1522
- description: "Status of a project."
1523
- });
1524
- var ProjectEntity = defineEntity2({
1525
- name: "Project",
1526
- description: "A project belonging to an organization.",
1527
- schema: "saas_app",
1528
- map: "project",
1529
- fields: {
1530
- id: field2.id({ description: "Unique project ID" }),
1531
- name: field2.string({ description: "Project name" }),
1532
- description: field2.string({
1533
- isOptional: true,
1534
- description: "Project description"
1535
- }),
1536
- slug: field2.string({
1537
- isOptional: true,
1538
- description: "URL-friendly identifier"
1539
- }),
1540
- organizationId: field2.foreignKey({ description: "Owning organization" }),
1541
- createdBy: field2.foreignKey({
1542
- description: "User who created the project"
1543
- }),
1544
- status: field2.enum("ProjectStatus", { default: "DRAFT" }),
1545
- isPublic: field2.boolean({
1546
- default: false,
1547
- description: "Whether project is publicly visible"
1548
- }),
1549
- settings: field2.json({
1550
- isOptional: true,
1551
- description: "Project-specific settings"
1552
- }),
1553
- tags: field2.string({ isArray: true, description: "Project tags" }),
1554
- metadata: field2.json({ isOptional: true }),
1555
- createdAt: field2.createdAt(),
1556
- updatedAt: field2.updatedAt(),
1557
- archivedAt: field2.dateTime({ isOptional: true })
1558
- },
1559
- indexes: [
1560
- index2.on(["organizationId", "status"]),
1561
- index2.on(["organizationId", "createdAt"]),
1562
- index2.unique(["organizationId", "slug"])
1563
- ],
1564
- enums: [ProjectStatusEnum]
1565
- });
1566
- var ProjectMemberEntity = defineEntity2({
1567
- name: "ProjectMember",
1568
- description: "User access to a specific project.",
1569
- schema: "saas_app",
1570
- map: "project_member",
1571
- fields: {
1572
- id: field2.id(),
1573
- projectId: field2.foreignKey(),
1574
- userId: field2.foreignKey(),
1575
- role: field2.string({
1576
- description: "Role in project (owner, editor, viewer)"
1577
- }),
1578
- addedBy: field2.string({ isOptional: true }),
1579
- createdAt: field2.createdAt()
1580
- },
1581
- indexes: [index2.unique(["projectId", "userId"])]
1582
- });
1583
-
1584
- // src/project/project.presentation.ts
1585
- import {
1586
- definePresentation as definePresentation3,
1587
- StabilityEnum as StabilityEnum3
1588
- } from "@contractspec/lib.contracts-spec";
1589
- var ProjectListPresentation = definePresentation3({
1590
- meta: {
1591
- key: "saas.project.list",
1592
- version: "1.0.0",
1593
- title: "Project List",
1594
- description: "List view of projects with status, tags, and last updated info",
1595
- domain: "saas-boilerplate",
1596
- owners: ["@saas-team"],
1597
- tags: ["project", "list", "dashboard"],
1598
- stability: StabilityEnum3.Beta,
1599
- goal: "Browse and manage projects",
1600
- context: "Project list page"
1601
- },
1602
- source: {
1603
- type: "component",
1604
- framework: "react",
1605
- componentKey: "ProjectListView",
1606
- props: ProjectModel
1607
- },
1608
- targets: ["react", "markdown", "application/json"],
1609
- policy: {
1610
- flags: ["saas.projects.enabled"]
1611
- }
1612
- });
1613
- var ProjectDetailPresentation = definePresentation3({
1614
- meta: {
1615
- key: "saas.project.detail",
1615
+ key: "saas.project.detail",
1616
1616
  version: "1.0.0",
1617
1617
  title: "Project Details",
1618
1618
  description: "Detailed view of a project with settings and activity",
@@ -1633,76 +1633,213 @@ var ProjectDetailPresentation = definePresentation3({
1633
1633
  flags: ["saas.projects.enabled"]
1634
1634
  }
1635
1635
  });
1636
- // src/settings/settings.enum.ts
1637
- import { defineEntityEnum as defineEntityEnum3 } from "@contractspec/lib.schema";
1638
- var SettingsScopeEnum = defineEntityEnum3({
1639
- name: "SettingsScope",
1640
- values: ["APP", "ORG", "USER", "PROJECT"],
1641
- schema: "saas_app",
1642
- description: "Scope of a setting."
1636
+ // src/visualizations/catalog.ts
1637
+ import {
1638
+ defineVisualization,
1639
+ VisualizationRegistry
1640
+ } from "@contractspec/lib.contracts-spec/visualizations";
1641
+ var PROJECT_LIST_REF = {
1642
+ key: "saas.project.list",
1643
+ version: "1.0.0"
1644
+ };
1645
+ var META = {
1646
+ version: "1.0.0",
1647
+ domain: "saas",
1648
+ stability: "experimental",
1649
+ owners: ["@example.saas-boilerplate"],
1650
+ tags: ["saas", "visualization", "projects"]
1651
+ };
1652
+ var SaasProjectUsageVisualization = defineVisualization({
1653
+ meta: {
1654
+ ...META,
1655
+ key: "saas-boilerplate.visualization.project-usage",
1656
+ title: "Project Capacity",
1657
+ description: "Current project count against the current plan limit.",
1658
+ goal: "Show usage against the active plan allowance.",
1659
+ context: "SaaS account overview."
1660
+ },
1661
+ source: { primary: PROJECT_LIST_REF, resultPath: "data" },
1662
+ visualization: {
1663
+ kind: "metric",
1664
+ measure: "totalProjects",
1665
+ comparisonMeasure: "projectLimit",
1666
+ measures: [
1667
+ {
1668
+ key: "totalProjects",
1669
+ label: "Projects",
1670
+ dataPath: "totalProjects",
1671
+ format: "number"
1672
+ },
1673
+ {
1674
+ key: "projectLimit",
1675
+ label: "Plan Limit",
1676
+ dataPath: "projectLimit",
1677
+ format: "number"
1678
+ }
1679
+ ],
1680
+ table: { caption: "Current project count and plan limit." }
1681
+ }
1643
1682
  });
1644
-
1645
- // src/settings/settings.entity.ts
1646
- import { defineEntity as defineEntity3, field as field3, index as index3 } from "@contractspec/lib.schema";
1647
- var SettingsEntity = defineEntity3({
1648
- name: "Settings",
1649
- description: "Application, organization, or user settings.",
1650
- schema: "saas_app",
1651
- map: "settings",
1652
- fields: {
1653
- id: field3.id(),
1654
- key: field3.string({
1655
- description: 'Setting key (e.g., "theme", "notifications.email")'
1656
- }),
1657
- scope: field3.enum("SettingsScope"),
1658
- scopeId: field3.string({
1659
- isOptional: true,
1660
- description: "ID of scoped entity (org, user, project)"
1661
- }),
1662
- value: field3.json({ description: "Setting value" }),
1663
- valueType: field3.string({
1664
- default: '"string"',
1665
- description: "Type hint for value"
1666
- }),
1667
- schema: field3.json({
1668
- isOptional: true,
1669
- description: "JSON schema for validation"
1670
- }),
1671
- description: field3.string({ isOptional: true }),
1672
- isSecret: field3.boolean({
1673
- default: false,
1674
- description: "Whether value should be encrypted"
1675
- }),
1676
- createdAt: field3.createdAt(),
1677
- updatedAt: field3.updatedAt()
1678
- },
1679
- indexes: [
1680
- index3.unique(["scope", "scopeId", "key"]),
1681
- index3.on(["scope", "key"])
1682
- ],
1683
- enums: [SettingsScopeEnum]
1683
+ var SaasProjectStatusVisualization = defineVisualization({
1684
+ meta: {
1685
+ ...META,
1686
+ key: "saas-boilerplate.visualization.project-status",
1687
+ title: "Project Status",
1688
+ description: "Distribution of project states.",
1689
+ goal: "Show the mix of active, draft, and archived projects.",
1690
+ context: "Project portfolio overview."
1691
+ },
1692
+ source: { primary: PROJECT_LIST_REF, resultPath: "data" },
1693
+ visualization: {
1694
+ kind: "pie",
1695
+ nameDimension: "status",
1696
+ valueMeasure: "projects",
1697
+ dimensions: [
1698
+ { key: "status", label: "Status", dataPath: "status", type: "category" }
1699
+ ],
1700
+ measures: [
1701
+ {
1702
+ key: "projects",
1703
+ label: "Projects",
1704
+ dataPath: "projects",
1705
+ format: "number"
1706
+ }
1707
+ ],
1708
+ table: { caption: "Project counts by status." }
1709
+ }
1684
1710
  });
1685
- var FeatureFlagEntity = defineEntity3({
1686
- name: "FeatureFlag",
1687
- description: "Feature flags for progressive rollout.",
1688
- schema: "saas_app",
1689
- map: "feature_flag",
1690
- fields: {
1691
- id: field3.id(),
1692
- key: field3.string({ isUnique: true, description: "Feature flag key" }),
1693
- name: field3.string({ description: "Human-readable name" }),
1694
- description: field3.string({ isOptional: true }),
1695
- enabled: field3.boolean({ default: false }),
1696
- defaultValue: field3.boolean({ default: false }),
1697
- rules: field3.json({ isOptional: true, description: "Targeting rules" }),
1698
- rolloutPercentage: field3.int({
1699
- default: 0,
1700
- description: "Percentage rollout (0-100)"
1701
- }),
1702
- createdAt: field3.createdAt(),
1703
- updatedAt: field3.updatedAt()
1711
+ var SaasProjectTierVisualization = defineVisualization({
1712
+ meta: {
1713
+ ...META,
1714
+ key: "saas-boilerplate.visualization.project-tiers",
1715
+ title: "Tier Comparison",
1716
+ description: "Distribution of projects across tiers.",
1717
+ goal: "Compare how the current portfolio is distributed by tier.",
1718
+ context: "Plan and packaging overview."
1719
+ },
1720
+ source: { primary: PROJECT_LIST_REF, resultPath: "data" },
1721
+ visualization: {
1722
+ kind: "cartesian",
1723
+ variant: "bar",
1724
+ xDimension: "tier",
1725
+ yMeasures: ["projects"],
1726
+ dimensions: [
1727
+ { key: "tier", label: "Tier", dataPath: "tier", type: "category" }
1728
+ ],
1729
+ measures: [
1730
+ {
1731
+ key: "projects",
1732
+ label: "Projects",
1733
+ dataPath: "projects",
1734
+ format: "number",
1735
+ color: "#1d4ed8"
1736
+ }
1737
+ ],
1738
+ table: { caption: "Project counts by tier." }
1739
+ }
1740
+ });
1741
+ var SaasProjectActivityVisualization = defineVisualization({
1742
+ meta: {
1743
+ ...META,
1744
+ key: "saas-boilerplate.visualization.project-activity",
1745
+ title: "Recent Project Activity",
1746
+ description: "Daily project creation activity.",
1747
+ goal: "Show recent project activity over time.",
1748
+ context: "Project portfolio trend view."
1749
+ },
1750
+ source: { primary: PROJECT_LIST_REF, resultPath: "data" },
1751
+ visualization: {
1752
+ kind: "cartesian",
1753
+ variant: "line",
1754
+ xDimension: "day",
1755
+ yMeasures: ["projects"],
1756
+ dimensions: [{ key: "day", label: "Day", dataPath: "day", type: "time" }],
1757
+ measures: [
1758
+ {
1759
+ key: "projects",
1760
+ label: "Projects",
1761
+ dataPath: "projects",
1762
+ format: "number",
1763
+ color: "#0f766e"
1764
+ }
1765
+ ],
1766
+ table: { caption: "Daily project creation counts." }
1704
1767
  }
1705
1768
  });
1769
+ var SaasVisualizationSpecs = [
1770
+ SaasProjectUsageVisualization,
1771
+ SaasProjectStatusVisualization,
1772
+ SaasProjectTierVisualization,
1773
+ SaasProjectActivityVisualization
1774
+ ];
1775
+ var SaasVisualizationRegistry = new VisualizationRegistry([
1776
+ ...SaasVisualizationSpecs
1777
+ ]);
1778
+ var SaasVisualizationRefs = SaasVisualizationSpecs.map((spec) => ({
1779
+ key: spec.meta.key,
1780
+ version: spec.meta.version
1781
+ }));
1782
+
1783
+ // src/visualizations/selectors.ts
1784
+ function toDayKey(value) {
1785
+ const date = value instanceof Date ? value : new Date(value);
1786
+ return date.toISOString().slice(0, 10);
1787
+ }
1788
+ function createSaasVisualizationItems(projects, projectLimit = 10) {
1789
+ const statusCounts = new Map;
1790
+ const tierCounts = new Map;
1791
+ const activityCounts = new Map;
1792
+ for (const project of projects) {
1793
+ statusCounts.set(project.status, (statusCounts.get(project.status) ?? 0) + 1);
1794
+ tierCounts.set(project.tier, (tierCounts.get(project.tier) ?? 0) + 1);
1795
+ const day = toDayKey(project.createdAt);
1796
+ activityCounts.set(day, (activityCounts.get(day) ?? 0) + 1);
1797
+ }
1798
+ return [
1799
+ {
1800
+ key: "saas-capacity",
1801
+ spec: SaasProjectUsageVisualization,
1802
+ data: { data: [{ totalProjects: projects.length, projectLimit }] },
1803
+ title: "Project Capacity",
1804
+ description: "Current project count compared to the active limit.",
1805
+ height: 220
1806
+ },
1807
+ {
1808
+ key: "saas-status",
1809
+ spec: SaasProjectStatusVisualization,
1810
+ data: {
1811
+ data: Array.from(statusCounts.entries()).map(([status, count]) => ({
1812
+ status,
1813
+ projects: count
1814
+ }))
1815
+ },
1816
+ title: "Project Status",
1817
+ description: "Status mix across the current project portfolio.",
1818
+ height: 260
1819
+ },
1820
+ {
1821
+ key: "saas-tier",
1822
+ spec: SaasProjectTierVisualization,
1823
+ data: {
1824
+ data: Array.from(tierCounts.entries()).map(([tier, count]) => ({
1825
+ tier,
1826
+ projects: count
1827
+ }))
1828
+ },
1829
+ title: "Tier Comparison",
1830
+ description: "How projects are distributed across tiers."
1831
+ },
1832
+ {
1833
+ key: "saas-activity",
1834
+ spec: SaasProjectActivityVisualization,
1835
+ data: {
1836
+ data: Array.from(activityCounts.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([day, count]) => ({ day, projects: count }))
1837
+ },
1838
+ title: "Recent Project Activity",
1839
+ description: "Daily project creation activity."
1840
+ }
1841
+ ];
1842
+ }
1706
1843
  // src/saas-boilerplate.feature.ts
1707
1844
  import { defineFeature } from "@contractspec/lib.contracts-spec";
1708
1845
  var SaasBoilerplateFeature = defineFeature({
@@ -1780,6 +1917,7 @@ var SaasBoilerplateFeature = defineFeature({
1780
1917
  targets: ["react", "markdown"]
1781
1918
  }
1782
1919
  ],
1920
+ visualizations: SaasVisualizationRefs,
1783
1921
  capabilities: {
1784
1922
  requires: [
1785
1923
  { key: "identity", version: "1.0.0" },
@@ -1797,19 +1935,89 @@ var SaasBoilerplateFeature = defineFeature({
1797
1935
  ]
1798
1936
  });
1799
1937
 
1800
- // src/ui/hooks/useProjectList.ts
1801
- import { useCallback, useEffect, useMemo, useState } from "react";
1802
- import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
1803
- function useProjectList(options = {}) {
1804
- const { handlers, projectId } = useTemplateRuntime();
1805
- const { saas: saas2 } = handlers;
1806
- const [data, setData] = useState(null);
1807
- const [subscription, setSubscription] = useState(null);
1808
- const [loading, setLoading] = useState(true);
1809
- const [error, setError] = useState(null);
1810
- const [page, setPage] = useState(1);
1811
- const fetchData = useCallback(async () => {
1812
- setLoading(true);
1938
+ // src/settings/settings.enum.ts
1939
+ import { defineEntityEnum as defineEntityEnum3 } from "@contractspec/lib.schema";
1940
+ var SettingsScopeEnum = defineEntityEnum3({
1941
+ name: "SettingsScope",
1942
+ values: ["APP", "ORG", "USER", "PROJECT"],
1943
+ schema: "saas_app",
1944
+ description: "Scope of a setting."
1945
+ });
1946
+
1947
+ // src/settings/settings.entity.ts
1948
+ import { defineEntity as defineEntity3, field as field3, index as index3 } from "@contractspec/lib.schema";
1949
+ var SettingsEntity = defineEntity3({
1950
+ name: "Settings",
1951
+ description: "Application, organization, or user settings.",
1952
+ schema: "saas_app",
1953
+ map: "settings",
1954
+ fields: {
1955
+ id: field3.id(),
1956
+ key: field3.string({
1957
+ description: 'Setting key (e.g., "theme", "notifications.email")'
1958
+ }),
1959
+ scope: field3.enum("SettingsScope"),
1960
+ scopeId: field3.string({
1961
+ isOptional: true,
1962
+ description: "ID of scoped entity (org, user, project)"
1963
+ }),
1964
+ value: field3.json({ description: "Setting value" }),
1965
+ valueType: field3.string({
1966
+ default: '"string"',
1967
+ description: "Type hint for value"
1968
+ }),
1969
+ schema: field3.json({
1970
+ isOptional: true,
1971
+ description: "JSON schema for validation"
1972
+ }),
1973
+ description: field3.string({ isOptional: true }),
1974
+ isSecret: field3.boolean({
1975
+ default: false,
1976
+ description: "Whether value should be encrypted"
1977
+ }),
1978
+ createdAt: field3.createdAt(),
1979
+ updatedAt: field3.updatedAt()
1980
+ },
1981
+ indexes: [
1982
+ index3.unique(["scope", "scopeId", "key"]),
1983
+ index3.on(["scope", "key"])
1984
+ ],
1985
+ enums: [SettingsScopeEnum]
1986
+ });
1987
+ var FeatureFlagEntity = defineEntity3({
1988
+ name: "FeatureFlag",
1989
+ description: "Feature flags for progressive rollout.",
1990
+ schema: "saas_app",
1991
+ map: "feature_flag",
1992
+ fields: {
1993
+ id: field3.id(),
1994
+ key: field3.string({ isUnique: true, description: "Feature flag key" }),
1995
+ name: field3.string({ description: "Human-readable name" }),
1996
+ description: field3.string({ isOptional: true }),
1997
+ enabled: field3.boolean({ default: false }),
1998
+ defaultValue: field3.boolean({ default: false }),
1999
+ rules: field3.json({ isOptional: true, description: "Targeting rules" }),
2000
+ rolloutPercentage: field3.int({
2001
+ default: 0,
2002
+ description: "Percentage rollout (0-100)"
2003
+ }),
2004
+ createdAt: field3.createdAt(),
2005
+ updatedAt: field3.updatedAt()
2006
+ }
2007
+ });
2008
+ // src/ui/hooks/useProjectList.ts
2009
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
2010
+ import { useCallback, useEffect, useMemo, useState } from "react";
2011
+ function useProjectList(options = {}) {
2012
+ const { handlers, projectId } = useTemplateRuntime();
2013
+ const { saas: saas2 } = handlers;
2014
+ const [data, setData] = useState(null);
2015
+ const [subscription, setSubscription] = useState(null);
2016
+ const [loading, setLoading] = useState(true);
2017
+ const [error, setError] = useState(null);
2018
+ const [page, setPage] = useState(1);
2019
+ const fetchData = useCallback(async () => {
2020
+ setLoading(true);
1813
2021
  setError(null);
1814
2022
  try {
1815
2023
  const [projectsResult, subscriptionResult] = await Promise.all([
@@ -1862,8 +2070,8 @@ function useProjectList(options = {}) {
1862
2070
  }
1863
2071
 
1864
2072
  // src/ui/hooks/useProjectMutations.ts
1865
- import { useCallback as useCallback2, useState as useState2 } from "react";
1866
2073
  import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
2074
+ import { useCallback as useCallback2, useState as useState2 } from "react";
1867
2075
  function useProjectMutations(options = {}) {
1868
2076
  const { handlers, projectId } = useTemplateRuntime2();
1869
2077
  const { saas: saas2 } = handlers;
@@ -1950,9 +2158,12 @@ function useProjectMutations(options = {}) {
1950
2158
  };
1951
2159
  }
1952
2160
 
2161
+ // src/ui/hooks/index.ts
2162
+ "use client";
2163
+
1953
2164
  // src/ui/modals/CreateProjectModal.tsx
1954
- import { useState as useState3 } from "react";
1955
2165
  import { Button, Input } from "@contractspec/lib.design-system";
2166
+ import { useState as useState3 } from "react";
1956
2167
  import { jsxDEV } from "react/jsx-dev-runtime";
1957
2168
  "use client";
1958
2169
  var TIERS = [
@@ -1997,7 +2208,7 @@ function CreateProjectModal({
1997
2208
  className: "fixed inset-0 z-50 flex items-center justify-center",
1998
2209
  children: [
1999
2210
  /* @__PURE__ */ jsxDEV("div", {
2000
- className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
2211
+ className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
2001
2212
  onClick: onClose,
2002
2213
  role: "button",
2003
2214
  tabIndex: 0,
@@ -2008,10 +2219,10 @@ function CreateProjectModal({
2008
2219
  "aria-label": "Close modal"
2009
2220
  }, undefined, false, undefined, this),
2010
2221
  /* @__PURE__ */ jsxDEV("div", {
2011
- className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
2222
+ className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
2012
2223
  children: [
2013
2224
  /* @__PURE__ */ jsxDEV("h2", {
2014
- className: "mb-4 text-xl font-semibold",
2225
+ className: "mb-4 font-semibold text-xl",
2015
2226
  children: "Create New Project"
2016
2227
  }, undefined, false, undefined, this),
2017
2228
  /* @__PURE__ */ jsxDEV("form", {
@@ -2022,7 +2233,7 @@ function CreateProjectModal({
2022
2233
  children: [
2023
2234
  /* @__PURE__ */ jsxDEV("label", {
2024
2235
  htmlFor: "project-name",
2025
- className: "text-muted-foreground mb-1 block text-sm font-medium",
2236
+ className: "mb-1 block font-medium text-muted-foreground text-sm",
2026
2237
  children: "Project Name *"
2027
2238
  }, undefined, false, undefined, this),
2028
2239
  /* @__PURE__ */ jsxDEV(Input, {
@@ -2038,7 +2249,7 @@ function CreateProjectModal({
2038
2249
  children: [
2039
2250
  /* @__PURE__ */ jsxDEV("label", {
2040
2251
  htmlFor: "project-description",
2041
- className: "text-muted-foreground mb-1 block text-sm font-medium",
2252
+ className: "mb-1 block font-medium text-muted-foreground text-sm",
2042
2253
  children: "Description"
2043
2254
  }, undefined, false, undefined, this),
2044
2255
  /* @__PURE__ */ jsxDEV("textarea", {
@@ -2048,7 +2259,7 @@ function CreateProjectModal({
2048
2259
  placeholder: "Describe what this project is about...",
2049
2260
  rows: 3,
2050
2261
  disabled: isLoading,
2051
- className: "border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
2262
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
2052
2263
  }, undefined, false, undefined, this)
2053
2264
  ]
2054
2265
  }, undefined, true, undefined, this),
@@ -2056,7 +2267,7 @@ function CreateProjectModal({
2056
2267
  children: [
2057
2268
  /* @__PURE__ */ jsxDEV("label", {
2058
2269
  htmlFor: "project-tier",
2059
- className: "text-muted-foreground mb-1 block text-sm font-medium",
2270
+ className: "mb-1 block font-medium text-muted-foreground text-sm",
2060
2271
  children: "Tier"
2061
2272
  }, undefined, false, undefined, this),
2062
2273
  /* @__PURE__ */ jsxDEV("select", {
@@ -2064,7 +2275,7 @@ function CreateProjectModal({
2064
2275
  value: tier,
2065
2276
  onChange: (e) => setTier(e.target.value),
2066
2277
  disabled: isLoading,
2067
- className: "border-input bg-background focus:ring-ring h-10 w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50",
2278
+ className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
2068
2279
  children: TIERS.map((t) => /* @__PURE__ */ jsxDEV("option", {
2069
2280
  value: t.value,
2070
2281
  children: t.label
@@ -2073,7 +2284,7 @@ function CreateProjectModal({
2073
2284
  ]
2074
2285
  }, undefined, true, undefined, this),
2075
2286
  error && /* @__PURE__ */ jsxDEV("div", {
2076
- className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2287
+ className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
2077
2288
  children: error
2078
2289
  }, undefined, false, undefined, this),
2079
2290
  /* @__PURE__ */ jsxDEV("div", {
@@ -2102,8 +2313,8 @@ function CreateProjectModal({
2102
2313
  }
2103
2314
 
2104
2315
  // src/ui/modals/ProjectActionsModal.tsx
2105
- import { useEffect as useEffect2, useState as useState4 } from "react";
2106
2316
  import { Button as Button2, Input as Input2 } from "@contractspec/lib.design-system";
2317
+ import { useEffect as useEffect2, useState as useState4 } from "react";
2107
2318
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
2108
2319
  "use client";
2109
2320
  function ProjectActionsModal({
@@ -2196,7 +2407,7 @@ function ProjectActionsModal({
2196
2407
  className: "fixed inset-0 z-50 flex items-center justify-center",
2197
2408
  children: [
2198
2409
  /* @__PURE__ */ jsxDEV2("div", {
2199
- className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
2410
+ className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
2200
2411
  onClick: handleClose,
2201
2412
  role: "button",
2202
2413
  tabIndex: 0,
@@ -2207,13 +2418,13 @@ function ProjectActionsModal({
2207
2418
  "aria-label": "Close modal"
2208
2419
  }, undefined, false, undefined, this),
2209
2420
  /* @__PURE__ */ jsxDEV2("div", {
2210
- className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
2421
+ className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
2211
2422
  children: [
2212
2423
  /* @__PURE__ */ jsxDEV2("div", {
2213
- className: "border-border mb-4 border-b pb-4",
2424
+ className: "mb-4 border-border border-b pb-4",
2214
2425
  children: [
2215
2426
  /* @__PURE__ */ jsxDEV2("h2", {
2216
- className: "text-xl font-semibold",
2427
+ className: "font-semibold text-xl",
2217
2428
  children: project.name
2218
2429
  }, undefined, false, undefined, this),
2219
2430
  /* @__PURE__ */ jsxDEV2("p", {
@@ -2295,7 +2506,7 @@ function ProjectActionsModal({
2295
2506
  children: [
2296
2507
  /* @__PURE__ */ jsxDEV2("label", {
2297
2508
  htmlFor: "edit-name",
2298
- className: "text-muted-foreground mb-1 block text-sm font-medium",
2509
+ className: "mb-1 block font-medium text-muted-foreground text-sm",
2299
2510
  children: "Project Name *"
2300
2511
  }, undefined, false, undefined, this),
2301
2512
  /* @__PURE__ */ jsxDEV2(Input2, {
@@ -2310,7 +2521,7 @@ function ProjectActionsModal({
2310
2521
  children: [
2311
2522
  /* @__PURE__ */ jsxDEV2("label", {
2312
2523
  htmlFor: "edit-description",
2313
- className: "text-muted-foreground mb-1 block text-sm font-medium",
2524
+ className: "mb-1 block font-medium text-muted-foreground text-sm",
2314
2525
  children: "Description"
2315
2526
  }, undefined, false, undefined, this),
2316
2527
  /* @__PURE__ */ jsxDEV2("textarea", {
@@ -2319,12 +2530,12 @@ function ProjectActionsModal({
2319
2530
  onChange: (e) => setDescription(e.target.value),
2320
2531
  rows: 3,
2321
2532
  disabled: isLoading,
2322
- className: "border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
2533
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
2323
2534
  }, undefined, false, undefined, this)
2324
2535
  ]
2325
2536
  }, undefined, true, undefined, this),
2326
2537
  error && /* @__PURE__ */ jsxDEV2("div", {
2327
- className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2538
+ className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
2328
2539
  children: error
2329
2540
  }, undefined, false, undefined, this),
2330
2541
  /* @__PURE__ */ jsxDEV2("div", {
@@ -2354,7 +2565,7 @@ function ProjectActionsModal({
2354
2565
  "Are you sure you want to archive",
2355
2566
  " ",
2356
2567
  /* @__PURE__ */ jsxDEV2("span", {
2357
- className: "text-foreground font-medium",
2568
+ className: "font-medium text-foreground",
2358
2569
  children: project.name
2359
2570
  }, undefined, false, undefined, this),
2360
2571
  "?"
@@ -2365,7 +2576,7 @@ function ProjectActionsModal({
2365
2576
  children: "Archived projects can be restored later."
2366
2577
  }, undefined, false, undefined, this),
2367
2578
  error && /* @__PURE__ */ jsxDEV2("div", {
2368
- className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2579
+ className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
2369
2580
  children: error
2370
2581
  }, undefined, false, undefined, this),
2371
2582
  /* @__PURE__ */ jsxDEV2("div", {
@@ -2395,7 +2606,7 @@ function ProjectActionsModal({
2395
2606
  "Are you sure you want to delete",
2396
2607
  " ",
2397
2608
  /* @__PURE__ */ jsxDEV2("span", {
2398
- className: "text-foreground font-medium",
2609
+ className: "font-medium text-foreground",
2399
2610
  children: project.name
2400
2611
  }, undefined, false, undefined, this),
2401
2612
  "?"
@@ -2406,7 +2617,7 @@ function ProjectActionsModal({
2406
2617
  children: "This action cannot be undone."
2407
2618
  }, undefined, false, undefined, this),
2408
2619
  error && /* @__PURE__ */ jsxDEV2("div", {
2409
- className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2620
+ className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
2410
2621
  children: error
2411
2622
  }, undefined, false, undefined, this),
2412
2623
  /* @__PURE__ */ jsxDEV2("div", {
@@ -2433,92 +2644,290 @@ function ProjectActionsModal({
2433
2644
  ]
2434
2645
  }, undefined, true, undefined, this);
2435
2646
  }
2436
-
2437
- // src/ui/SaasDashboard.tsx
2438
- import { useState as useState5, useCallback as useCallback3 } from "react";
2439
- import {
2440
- StatCard,
2441
- StatCardGroup,
2442
- StatusChip,
2443
- EntityCard,
2444
- EmptyState,
2445
- LoaderBlock,
2446
- ErrorState,
2447
- Button as Button3
2448
- } from "@contractspec/lib.design-system";
2449
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
2450
- "use client";
2451
- function getStatusTone(status) {
2452
- switch (status) {
2453
- case "ACTIVE":
2454
- return "success";
2455
- case "DRAFT":
2456
- return "neutral";
2457
- case "ARCHIVED":
2458
- return "warning";
2459
- default:
2460
- return "neutral";
2461
- }
2647
+ // src/ui/overlays/demo-overlays.ts
2648
+ var saasFreeUserOverlay = {
2649
+ overlayId: "saas-boilerplate.free-tier",
2650
+ version: "1.0.0",
2651
+ description: "Shows limitations for free tier users",
2652
+ appliesTo: {
2653
+ feature: "saas-boilerplate",
2654
+ tier: "free"
2655
+ },
2656
+ modifications: [
2657
+ {
2658
+ type: "setLimit",
2659
+ field: "projects",
2660
+ max: 3,
2661
+ message: "Upgrade to create more projects"
2662
+ },
2663
+ { type: "hideField", field: "advancedSettings", reason: "Pro feature" },
2664
+ {
2665
+ type: "addBadge",
2666
+ position: "header",
2667
+ label: "Free Plan",
2668
+ variant: "default"
2669
+ }
2670
+ ]
2671
+ };
2672
+ var saasDemoOverlay = {
2673
+ overlayId: "saas-boilerplate.demo-user",
2674
+ version: "1.0.0",
2675
+ description: "Demo mode for SaaS boilerplate",
2676
+ appliesTo: {
2677
+ feature: "saas-boilerplate",
2678
+ role: "demo"
2679
+ },
2680
+ modifications: [
2681
+ {
2682
+ type: "hideField",
2683
+ field: "billingSection",
2684
+ reason: "Demo users cannot access billing"
2685
+ },
2686
+ {
2687
+ type: "hideField",
2688
+ field: "deleteAccount",
2689
+ reason: "Not available in demo"
2690
+ },
2691
+ {
2692
+ type: "addBadge",
2693
+ position: "header",
2694
+ label: "Demo Mode",
2695
+ variant: "warning"
2696
+ }
2697
+ ]
2698
+ };
2699
+ var saasOverlays = [
2700
+ saasFreeUserOverlay,
2701
+ saasDemoOverlay
2702
+ ];
2703
+ // src/ui/renderers/project-list.markdown.ts
2704
+ var PROJECT_TIERS = [
2705
+ "FREE",
2706
+ "PRO",
2707
+ "ENTERPRISE"
2708
+ ];
2709
+ function toVisualizationProject(project, index4) {
2710
+ return {
2711
+ status: project.status === "DELETED" ? "ARCHIVED" : project.status,
2712
+ tier: PROJECT_TIERS[index4 % PROJECT_TIERS.length] ?? "FREE",
2713
+ createdAt: project.createdAt
2714
+ };
2462
2715
  }
2463
- function SaasDashboard() {
2464
- const [activeTab, setActiveTab] = useState5("projects");
2465
- const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
2466
- const [selectedProject, setSelectedProject] = useState5(null);
2467
- const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
2468
- const { data, subscription, loading, error, stats, refetch } = useProjectList();
2469
- const mutations = useProjectMutations({
2470
- onSuccess: () => {
2471
- refetch();
2716
+ var projectListMarkdownRenderer = {
2717
+ target: "markdown",
2718
+ render: async (desc, _ctx) => {
2719
+ if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
2720
+ throw new Error("projectListMarkdownRenderer: not ProjectListView");
2472
2721
  }
2473
- });
2474
- const handleProjectClick = useCallback3((project) => {
2475
- setSelectedProject(project);
2476
- setIsProjectActionsOpen(true);
2477
- }, []);
2478
- const tabs = [
2479
- { id: "projects", label: "Projects", icon: "\uD83D\uDCC1" },
2480
- { id: "billing", label: "Billing", icon: "\uD83D\uDCB3" },
2481
- { id: "settings", label: "Settings", icon: "\u2699\uFE0F" }
2482
- ];
2483
- if (loading && !data) {
2484
- return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
2485
- label: "Loading dashboard..."
2486
- }, undefined, false, undefined, this);
2487
- }
2488
- if (error) {
2489
- return /* @__PURE__ */ jsxDEV3(ErrorState, {
2490
- title: "Failed to load dashboard",
2491
- description: error.message,
2492
- onRetry: refetch,
2493
- retryLabel: "Retry"
2494
- }, undefined, false, undefined, this);
2722
+ const data = await mockListProjectsHandler({
2723
+ limit: 20,
2724
+ offset: 0
2725
+ });
2726
+ const items = data.projects ?? [];
2727
+ const lines = [
2728
+ "# Projects",
2729
+ "",
2730
+ `**Total**: ${data.total} projects`,
2731
+ ""
2732
+ ];
2733
+ if (items.length === 0) {
2734
+ lines.push("_No projects found._");
2735
+ } else {
2736
+ lines.push("| Status | Project | Description |");
2737
+ lines.push("|--------|---------|-------------|");
2738
+ for (const project of items) {
2739
+ const status = project.status === "ACTIVE" ? "\u2705" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "\u23F8\uFE0F";
2740
+ lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
2741
+ }
2742
+ }
2743
+ return {
2744
+ mimeType: "text/markdown",
2745
+ body: lines.join(`
2746
+ `)
2747
+ };
2495
2748
  }
2496
- return /* @__PURE__ */ jsxDEV3("div", {
2497
- className: "space-y-6",
2498
- children: [
2499
- /* @__PURE__ */ jsxDEV3("div", {
2500
- className: "flex items-center justify-between",
2501
- children: [
2502
- /* @__PURE__ */ jsxDEV3("h2", {
2503
- className: "text-2xl font-bold",
2504
- children: "SaaS Dashboard"
2505
- }, undefined, false, undefined, this),
2506
- activeTab === "projects" && /* @__PURE__ */ jsxDEV3(Button3, {
2507
- onPress: () => setIsCreateModalOpen(true),
2508
- children: [
2509
- /* @__PURE__ */ jsxDEV3("span", {
2510
- className: "mr-2",
2511
- children: "+"
2512
- }, undefined, false, undefined, this),
2513
- " New Project"
2514
- ]
2515
- }, undefined, true, undefined, this)
2516
- ]
2517
- }, undefined, true, undefined, this),
2518
- stats && subscription && /* @__PURE__ */ jsxDEV3(StatCardGroup, {
2519
- children: [
2520
- /* @__PURE__ */ jsxDEV3(StatCard, {
2521
- label: "Projects",
2749
+ };
2750
+ var saasDashboardMarkdownRenderer = {
2751
+ target: "markdown",
2752
+ render: async (desc, _ctx) => {
2753
+ if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
2754
+ throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
2755
+ }
2756
+ const [projectsData, subscription] = await Promise.all([
2757
+ mockListProjectsHandler({ limit: 50 }),
2758
+ mockGetSubscriptionHandler()
2759
+ ]);
2760
+ const projects = projectsData.projects ?? [];
2761
+ const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
2762
+ const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
2763
+ const visualizations = createSaasVisualizationItems(projects.map(toVisualizationProject), 10);
2764
+ const lines = [
2765
+ "# SaaS Dashboard",
2766
+ "",
2767
+ "> Organization overview and usage summary",
2768
+ "",
2769
+ "## Summary",
2770
+ "",
2771
+ "| Metric | Value |",
2772
+ "|--------|-------|",
2773
+ `| Total Projects | ${projectsData.total} |`,
2774
+ `| Active Projects | ${activeProjects} |`,
2775
+ `| Archived Projects | ${archivedProjects} |`,
2776
+ `| Subscription Plan | ${subscription.planName} |`,
2777
+ `| Subscription Status | ${subscription.status} |`,
2778
+ ""
2779
+ ];
2780
+ lines.push("## Visualization Overview");
2781
+ lines.push("");
2782
+ for (const item of visualizations) {
2783
+ lines.push(`- **${item.title}** via \`${item.spec.meta.key}\``);
2784
+ }
2785
+ lines.push("");
2786
+ lines.push("## Projects");
2787
+ lines.push("");
2788
+ if (projects.length === 0) {
2789
+ lines.push("_No projects yet._");
2790
+ } else {
2791
+ lines.push("| Status | Project | Description |");
2792
+ lines.push("|--------|---------|-------------|");
2793
+ for (const project of projects.slice(0, 10)) {
2794
+ const status = project.status === "ACTIVE" ? "\u2705" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "\u23F8\uFE0F";
2795
+ lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
2796
+ }
2797
+ if (projects.length > 10) {
2798
+ lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
2799
+ }
2800
+ }
2801
+ lines.push("");
2802
+ lines.push("## Subscription");
2803
+ lines.push("");
2804
+ lines.push(`- **Plan**: ${subscription.planName}`);
2805
+ lines.push(`- **Status**: ${subscription.status}`);
2806
+ if (subscription.currentPeriodEnd) {
2807
+ lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
2808
+ }
2809
+ return {
2810
+ mimeType: "text/markdown",
2811
+ body: lines.join(`
2812
+ `)
2813
+ };
2814
+ }
2815
+ };
2816
+ var saasBillingMarkdownRenderer = {
2817
+ target: "markdown",
2818
+ render: async (desc, _ctx) => {
2819
+ if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
2820
+ throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
2821
+ }
2822
+ const subscription = await mockGetSubscriptionHandler();
2823
+ const lines = [
2824
+ "# Billing & Subscription",
2825
+ "",
2826
+ "> Current subscription details and billing information",
2827
+ "",
2828
+ "## Subscription Details",
2829
+ "",
2830
+ "| Property | Value |",
2831
+ "|----------|-------|",
2832
+ `| Plan | ${subscription.planName} |`,
2833
+ `| Status | ${subscription.status} |`,
2834
+ `| ID | ${subscription.id} |`,
2835
+ `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
2836
+ `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
2837
+ ];
2838
+ lines.push("");
2839
+ lines.push("## Plan Limits");
2840
+ lines.push("");
2841
+ lines.push(`- **Projects**: ${subscription.limits.projects}`);
2842
+ lines.push(`- **Users**: ${subscription.limits.users}`);
2843
+ lines.push("");
2844
+ lines.push("## Plan Features");
2845
+ lines.push("");
2846
+ if (subscription.planName.toLowerCase().includes("free")) {
2847
+ lines.push("- \u2705 Up to 3 projects");
2848
+ lines.push("- \u2705 Basic support");
2849
+ lines.push("- \u274C Priority support");
2850
+ lines.push("- \u274C Advanced analytics");
2851
+ } else if (subscription.planName.toLowerCase().includes("pro")) {
2852
+ lines.push("- \u2705 Unlimited projects");
2853
+ lines.push("- \u2705 Priority support");
2854
+ lines.push("- \u2705 Advanced analytics");
2855
+ lines.push("- \u274C Custom integrations");
2856
+ } else {
2857
+ lines.push("- \u2705 Unlimited projects");
2858
+ lines.push("- \u2705 Priority support");
2859
+ lines.push("- \u2705 Advanced analytics");
2860
+ lines.push("- \u2705 Custom integrations");
2861
+ lines.push("- \u2705 Dedicated support");
2862
+ }
2863
+ return {
2864
+ mimeType: "text/markdown",
2865
+ body: lines.join(`
2866
+ `)
2867
+ };
2868
+ }
2869
+ };
2870
+
2871
+ // src/ui/SaasProjectList.tsx
2872
+ import {
2873
+ Button as Button3,
2874
+ EmptyState,
2875
+ EntityCard,
2876
+ ErrorState,
2877
+ LoaderBlock,
2878
+ StatCard,
2879
+ StatCardGroup,
2880
+ StatusChip
2881
+ } from "@contractspec/lib.design-system";
2882
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
2883
+ "use client";
2884
+ function getStatusTone(status) {
2885
+ switch (status) {
2886
+ case "ACTIVE":
2887
+ return "success";
2888
+ case "DRAFT":
2889
+ return "neutral";
2890
+ case "ARCHIVED":
2891
+ return "danger";
2892
+ default:
2893
+ return "neutral";
2894
+ }
2895
+ }
2896
+ function SaasProjectList({
2897
+ onProjectClick,
2898
+ onCreateProject
2899
+ }) {
2900
+ const { data, loading, error, stats, refetch } = useProjectList();
2901
+ if (loading && !data) {
2902
+ return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
2903
+ label: "Loading projects..."
2904
+ }, undefined, false, undefined, this);
2905
+ }
2906
+ if (error) {
2907
+ return /* @__PURE__ */ jsxDEV3(ErrorState, {
2908
+ title: "Failed to load projects",
2909
+ description: error.message,
2910
+ onRetry: refetch,
2911
+ retryLabel: "Retry"
2912
+ }, undefined, false, undefined, this);
2913
+ }
2914
+ if (!data?.items.length) {
2915
+ return /* @__PURE__ */ jsxDEV3(EmptyState, {
2916
+ title: "No projects found",
2917
+ description: "Create your first project to get started.",
2918
+ primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV3(Button3, {
2919
+ onPress: onCreateProject,
2920
+ children: "Create Project"
2921
+ }, undefined, false, undefined, this) : undefined
2922
+ }, undefined, false, undefined, this);
2923
+ }
2924
+ return /* @__PURE__ */ jsxDEV3("div", {
2925
+ className: "space-y-6",
2926
+ children: [
2927
+ stats && /* @__PURE__ */ jsxDEV3(StatCardGroup, {
2928
+ children: [
2929
+ /* @__PURE__ */ jsxDEV3(StatCard, {
2930
+ label: "Total Projects",
2522
2931
  value: stats.total.toString()
2523
2932
  }, undefined, false, undefined, this),
2524
2933
  /* @__PURE__ */ jsxDEV3(StatCard, {
@@ -2528,46 +2937,225 @@ function SaasDashboard() {
2528
2937
  /* @__PURE__ */ jsxDEV3(StatCard, {
2529
2938
  label: "Draft",
2530
2939
  value: stats.draftCount.toString()
2940
+ }, undefined, false, undefined, this)
2941
+ ]
2942
+ }, undefined, true, undefined, this),
2943
+ /* @__PURE__ */ jsxDEV3("div", {
2944
+ className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
2945
+ children: data.items.map((project) => /* @__PURE__ */ jsxDEV3(EntityCard, {
2946
+ cardTitle: project.name,
2947
+ cardSubtitle: project.tier,
2948
+ meta: /* @__PURE__ */ jsxDEV3("p", {
2949
+ className: "text-muted-foreground text-sm",
2950
+ children: project.description
2531
2951
  }, undefined, false, undefined, this),
2532
- /* @__PURE__ */ jsxDEV3(StatCard, {
2952
+ chips: /* @__PURE__ */ jsxDEV3(StatusChip, {
2953
+ tone: getStatusTone(project.status),
2954
+ label: project.status
2955
+ }, undefined, false, undefined, this),
2956
+ footer: /* @__PURE__ */ jsxDEV3("span", {
2957
+ className: "text-muted-foreground text-xs",
2958
+ children: project.updatedAt.toLocaleDateString()
2959
+ }, undefined, false, undefined, this),
2960
+ onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
2961
+ }, project.id, false, undefined, this))
2962
+ }, undefined, false, undefined, this)
2963
+ ]
2964
+ }, undefined, true, undefined, this);
2965
+ }
2966
+
2967
+ // src/ui/renderers/project-list.renderer.tsx
2968
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
2969
+ var projectListReactRenderer = {
2970
+ target: "react",
2971
+ render: async (desc, _ctx) => {
2972
+ if (desc.source.type !== "component") {
2973
+ throw new Error("Invalid source type");
2974
+ }
2975
+ if (desc.source.componentKey !== "SaasProjectListView") {
2976
+ throw new Error(`Unknown component: ${desc.source.componentKey}`);
2977
+ }
2978
+ return /* @__PURE__ */ jsxDEV4(SaasProjectList, {}, undefined, false, undefined, this);
2979
+ }
2980
+ };
2981
+ // src/ui/SaasDashboard.visualizations.tsx
2982
+ import {
2983
+ VisualizationCard,
2984
+ VisualizationGrid
2985
+ } from "@contractspec/lib.design-system";
2986
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
2987
+ "use client";
2988
+ function SaasVisualizationOverview({
2989
+ projects,
2990
+ projectLimit
2991
+ }) {
2992
+ const items = createSaasVisualizationItems(projects, projectLimit);
2993
+ return /* @__PURE__ */ jsxDEV5("section", {
2994
+ className: "space-y-3",
2995
+ children: [
2996
+ /* @__PURE__ */ jsxDEV5("div", {
2997
+ children: [
2998
+ /* @__PURE__ */ jsxDEV5("h3", {
2999
+ className: "font-semibold text-lg",
3000
+ children: "Portfolio Visualizations"
3001
+ }, undefined, false, undefined, this),
3002
+ /* @__PURE__ */ jsxDEV5("p", {
3003
+ className: "text-muted-foreground text-sm",
3004
+ children: "Contract-backed charts for project mix, capacity, and activity."
3005
+ }, undefined, false, undefined, this)
3006
+ ]
3007
+ }, undefined, true, undefined, this),
3008
+ /* @__PURE__ */ jsxDEV5(VisualizationGrid, {
3009
+ children: items.map((item) => /* @__PURE__ */ jsxDEV5(VisualizationCard, {
3010
+ data: item.data,
3011
+ description: item.description,
3012
+ height: item.height,
3013
+ spec: item.spec,
3014
+ title: item.title
3015
+ }, item.key, false, undefined, this))
3016
+ }, undefined, false, undefined, this)
3017
+ ]
3018
+ }, undefined, true, undefined, this);
3019
+ }
3020
+
3021
+ // src/ui/SaasDashboard.tsx
3022
+ import {
3023
+ Button as Button4,
3024
+ EmptyState as EmptyState2,
3025
+ EntityCard as EntityCard2,
3026
+ ErrorState as ErrorState2,
3027
+ LoaderBlock as LoaderBlock2,
3028
+ StatCard as StatCard2,
3029
+ StatCardGroup as StatCardGroup2,
3030
+ StatusChip as StatusChip2
3031
+ } from "@contractspec/lib.design-system";
3032
+ import { useCallback as useCallback3, useState as useState5 } from "react";
3033
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
3034
+ "use client";
3035
+ function getStatusTone2(status) {
3036
+ switch (status) {
3037
+ case "ACTIVE":
3038
+ return "success";
3039
+ case "DRAFT":
3040
+ return "neutral";
3041
+ case "ARCHIVED":
3042
+ return "warning";
3043
+ default:
3044
+ return "neutral";
3045
+ }
3046
+ }
3047
+ function SaasDashboard() {
3048
+ const [activeTab, setActiveTab] = useState5("projects");
3049
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
3050
+ const [selectedProject, setSelectedProject] = useState5(null);
3051
+ const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
3052
+ const { data, subscription, loading, error, stats, refetch } = useProjectList();
3053
+ const mutations = useProjectMutations({
3054
+ onSuccess: () => {
3055
+ refetch();
3056
+ }
3057
+ });
3058
+ const handleProjectClick = useCallback3((project) => {
3059
+ setSelectedProject(project);
3060
+ setIsProjectActionsOpen(true);
3061
+ }, []);
3062
+ const tabs = [
3063
+ { id: "projects", label: "Projects", icon: "\uD83D\uDCC1" },
3064
+ { id: "billing", label: "Billing", icon: "\uD83D\uDCB3" },
3065
+ { id: "settings", label: "Settings", icon: "\u2699\uFE0F" }
3066
+ ];
3067
+ if (loading && !data) {
3068
+ return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
3069
+ label: "Loading dashboard..."
3070
+ }, undefined, false, undefined, this);
3071
+ }
3072
+ if (error) {
3073
+ return /* @__PURE__ */ jsxDEV6(ErrorState2, {
3074
+ title: "Failed to load dashboard",
3075
+ description: error.message,
3076
+ onRetry: refetch,
3077
+ retryLabel: "Retry"
3078
+ }, undefined, false, undefined, this);
3079
+ }
3080
+ return /* @__PURE__ */ jsxDEV6("div", {
3081
+ className: "space-y-6",
3082
+ children: [
3083
+ /* @__PURE__ */ jsxDEV6("div", {
3084
+ className: "flex items-center justify-between",
3085
+ children: [
3086
+ /* @__PURE__ */ jsxDEV6("h2", {
3087
+ className: "font-bold text-2xl",
3088
+ children: "SaaS Dashboard"
3089
+ }, undefined, false, undefined, this),
3090
+ activeTab === "projects" && /* @__PURE__ */ jsxDEV6(Button4, {
3091
+ onPress: () => setIsCreateModalOpen(true),
3092
+ children: [
3093
+ /* @__PURE__ */ jsxDEV6("span", {
3094
+ className: "mr-2",
3095
+ children: "+"
3096
+ }, undefined, false, undefined, this),
3097
+ " New Project"
3098
+ ]
3099
+ }, undefined, true, undefined, this)
3100
+ ]
3101
+ }, undefined, true, undefined, this),
3102
+ stats && subscription && /* @__PURE__ */ jsxDEV6(StatCardGroup2, {
3103
+ children: [
3104
+ /* @__PURE__ */ jsxDEV6(StatCard2, {
3105
+ label: "Projects",
3106
+ value: stats.total.toString()
3107
+ }, undefined, false, undefined, this),
3108
+ /* @__PURE__ */ jsxDEV6(StatCard2, {
3109
+ label: "Active",
3110
+ value: stats.activeCount.toString()
3111
+ }, undefined, false, undefined, this),
3112
+ /* @__PURE__ */ jsxDEV6(StatCard2, {
3113
+ label: "Draft",
3114
+ value: stats.draftCount.toString()
3115
+ }, undefined, false, undefined, this),
3116
+ /* @__PURE__ */ jsxDEV6(StatCard2, {
2533
3117
  label: "Plan",
2534
3118
  value: subscription.plan,
2535
3119
  hint: subscription.status
2536
3120
  }, undefined, false, undefined, this)
2537
3121
  ]
2538
3122
  }, undefined, true, undefined, this),
2539
- /* @__PURE__ */ jsxDEV3("nav", {
2540
- className: "bg-muted flex gap-1 rounded-lg p-1",
3123
+ data && stats && /* @__PURE__ */ jsxDEV6(SaasVisualizationOverview, {
3124
+ projectLimit: stats.projectLimit,
3125
+ projects: data.items
3126
+ }, undefined, false, undefined, this),
3127
+ /* @__PURE__ */ jsxDEV6("nav", {
3128
+ className: "flex gap-1 rounded-lg bg-muted p-1",
2541
3129
  role: "tablist",
2542
- children: tabs.map((tab) => /* @__PURE__ */ jsxDEV3("button", {
3130
+ children: tabs.map((tab) => /* @__PURE__ */ jsxDEV6("button", {
2543
3131
  type: "button",
2544
3132
  role: "tab",
2545
3133
  "aria-selected": activeTab === tab.id,
2546
3134
  onClick: () => setActiveTab(tab.id),
2547
- className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
3135
+ className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 font-medium text-sm transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
2548
3136
  children: [
2549
- /* @__PURE__ */ jsxDEV3("span", {
3137
+ /* @__PURE__ */ jsxDEV6("span", {
2550
3138
  children: tab.icon
2551
3139
  }, undefined, false, undefined, this),
2552
3140
  tab.label
2553
3141
  ]
2554
3142
  }, tab.id, true, undefined, this))
2555
3143
  }, undefined, false, undefined, this),
2556
- /* @__PURE__ */ jsxDEV3("div", {
3144
+ /* @__PURE__ */ jsxDEV6("div", {
2557
3145
  className: "min-h-[400px]",
2558
3146
  role: "tabpanel",
2559
3147
  children: [
2560
- activeTab === "projects" && /* @__PURE__ */ jsxDEV3(ProjectsTab, {
3148
+ activeTab === "projects" && /* @__PURE__ */ jsxDEV6(ProjectsTab, {
2561
3149
  data,
2562
3150
  onProjectClick: handleProjectClick
2563
3151
  }, undefined, false, undefined, this),
2564
- activeTab === "billing" && /* @__PURE__ */ jsxDEV3(BillingTab, {
3152
+ activeTab === "billing" && /* @__PURE__ */ jsxDEV6(BillingTab, {
2565
3153
  subscription
2566
3154
  }, undefined, false, undefined, this),
2567
- activeTab === "settings" && /* @__PURE__ */ jsxDEV3(SettingsTab, {}, undefined, false, undefined, this)
3155
+ activeTab === "settings" && /* @__PURE__ */ jsxDEV6(SettingsTab, {}, undefined, false, undefined, this)
2568
3156
  ]
2569
3157
  }, undefined, true, undefined, this),
2570
- /* @__PURE__ */ jsxDEV3(CreateProjectModal, {
3158
+ /* @__PURE__ */ jsxDEV6(CreateProjectModal, {
2571
3159
  isOpen: isCreateModalOpen,
2572
3160
  onClose: () => setIsCreateModalOpen(false),
2573
3161
  onSubmit: async (input) => {
@@ -2575,7 +3163,7 @@ function SaasDashboard() {
2575
3163
  },
2576
3164
  isLoading: mutations.createState.loading
2577
3165
  }, undefined, false, undefined, this),
2578
- /* @__PURE__ */ jsxDEV3(ProjectActionsModal, {
3166
+ /* @__PURE__ */ jsxDEV6(ProjectActionsModal, {
2579
3167
  isOpen: isProjectActionsOpen,
2580
3168
  project: selectedProject,
2581
3169
  onClose: () => {
@@ -2601,34 +3189,34 @@ function SaasDashboard() {
2601
3189
  }
2602
3190
  function ProjectsTab({ data, onProjectClick }) {
2603
3191
  if (!data?.items.length) {
2604
- return /* @__PURE__ */ jsxDEV3(EmptyState, {
3192
+ return /* @__PURE__ */ jsxDEV6(EmptyState2, {
2605
3193
  title: "No projects yet",
2606
3194
  description: "Create your first project to get started."
2607
3195
  }, undefined, false, undefined, this);
2608
3196
  }
2609
- return /* @__PURE__ */ jsxDEV3("div", {
3197
+ return /* @__PURE__ */ jsxDEV6("div", {
2610
3198
  className: "space-y-4",
2611
- children: /* @__PURE__ */ jsxDEV3("div", {
3199
+ children: /* @__PURE__ */ jsxDEV6("div", {
2612
3200
  className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
2613
- children: data.items.map((project) => /* @__PURE__ */ jsxDEV3(EntityCard, {
3201
+ children: data.items.map((project) => /* @__PURE__ */ jsxDEV6(EntityCard2, {
2614
3202
  cardTitle: project.name,
2615
3203
  cardSubtitle: project.tier,
2616
- meta: /* @__PURE__ */ jsxDEV3("p", {
3204
+ meta: /* @__PURE__ */ jsxDEV6("p", {
2617
3205
  className: "text-muted-foreground text-sm",
2618
3206
  children: project.description
2619
3207
  }, undefined, false, undefined, this),
2620
- chips: /* @__PURE__ */ jsxDEV3(StatusChip, {
2621
- tone: getStatusTone(project.status),
3208
+ chips: /* @__PURE__ */ jsxDEV6(StatusChip2, {
3209
+ tone: getStatusTone2(project.status),
2622
3210
  label: project.status
2623
3211
  }, undefined, false, undefined, this),
2624
- footer: /* @__PURE__ */ jsxDEV3("div", {
3212
+ footer: /* @__PURE__ */ jsxDEV6("div", {
2625
3213
  className: "flex w-full items-center justify-between",
2626
3214
  children: [
2627
- /* @__PURE__ */ jsxDEV3("span", {
3215
+ /* @__PURE__ */ jsxDEV6("span", {
2628
3216
  className: "text-muted-foreground text-xs",
2629
3217
  children: project.updatedAt.toLocaleDateString()
2630
3218
  }, undefined, false, undefined, this),
2631
- /* @__PURE__ */ jsxDEV3(Button3, {
3219
+ /* @__PURE__ */ jsxDEV6(Button4, {
2632
3220
  variant: "ghost",
2633
3221
  size: "sm",
2634
3222
  onPress: () => onProjectClick?.(project),
@@ -2643,25 +3231,25 @@ function ProjectsTab({ data, onProjectClick }) {
2643
3231
  function BillingTab({ subscription }) {
2644
3232
  if (!subscription)
2645
3233
  return null;
2646
- return /* @__PURE__ */ jsxDEV3("div", {
3234
+ return /* @__PURE__ */ jsxDEV6("div", {
2647
3235
  className: "space-y-6",
2648
3236
  children: [
2649
- /* @__PURE__ */ jsxDEV3("div", {
2650
- className: "border-border bg-card rounded-xl border p-6",
3237
+ /* @__PURE__ */ jsxDEV6("div", {
3238
+ className: "rounded-xl border border-border bg-card p-6",
2651
3239
  children: [
2652
- /* @__PURE__ */ jsxDEV3("div", {
3240
+ /* @__PURE__ */ jsxDEV6("div", {
2653
3241
  className: "flex items-start justify-between",
2654
3242
  children: [
2655
- /* @__PURE__ */ jsxDEV3("div", {
3243
+ /* @__PURE__ */ jsxDEV6("div", {
2656
3244
  children: [
2657
- /* @__PURE__ */ jsxDEV3("h3", {
2658
- className: "text-lg font-semibold",
3245
+ /* @__PURE__ */ jsxDEV6("h3", {
3246
+ className: "font-semibold text-lg",
2659
3247
  children: [
2660
3248
  subscription.plan,
2661
3249
  " Plan"
2662
3250
  ]
2663
3251
  }, undefined, true, undefined, this),
2664
- /* @__PURE__ */ jsxDEV3("p", {
3252
+ /* @__PURE__ */ jsxDEV6("p", {
2665
3253
  className: "text-muted-foreground text-sm",
2666
3254
  children: [
2667
3255
  "Current period:",
@@ -2672,7 +3260,7 @@ function BillingTab({ subscription }) {
2672
3260
  subscription.currentPeriodEnd.toLocaleDateString()
2673
3261
  ]
2674
3262
  }, undefined, true, undefined, this),
2675
- /* @__PURE__ */ jsxDEV3("p", {
3263
+ /* @__PURE__ */ jsxDEV6("p", {
2676
3264
  className: "text-muted-foreground text-sm",
2677
3265
  children: [
2678
3266
  "Billing cycle: ",
@@ -2681,21 +3269,21 @@ function BillingTab({ subscription }) {
2681
3269
  }, undefined, true, undefined, this)
2682
3270
  ]
2683
3271
  }, undefined, true, undefined, this),
2684
- /* @__PURE__ */ jsxDEV3(StatusChip, {
3272
+ /* @__PURE__ */ jsxDEV6(StatusChip2, {
2685
3273
  tone: "success",
2686
3274
  label: subscription.status
2687
3275
  }, undefined, false, undefined, this)
2688
3276
  ]
2689
3277
  }, undefined, true, undefined, this),
2690
- /* @__PURE__ */ jsxDEV3("div", {
3278
+ /* @__PURE__ */ jsxDEV6("div", {
2691
3279
  className: "mt-4 flex gap-3",
2692
3280
  children: [
2693
- /* @__PURE__ */ jsxDEV3(Button3, {
3281
+ /* @__PURE__ */ jsxDEV6(Button4, {
2694
3282
  variant: "outline",
2695
3283
  onPress: () => alert("Upgrade clicked!"),
2696
3284
  children: "Upgrade Plan"
2697
3285
  }, undefined, false, undefined, this),
2698
- /* @__PURE__ */ jsxDEV3(Button3, {
3286
+ /* @__PURE__ */ jsxDEV6(Button4, {
2699
3287
  variant: "ghost",
2700
3288
  onPress: () => alert("Manage Billing clicked!"),
2701
3289
  children: "Manage Billing"
@@ -2704,10 +3292,10 @@ function BillingTab({ subscription }) {
2704
3292
  }, undefined, true, undefined, this)
2705
3293
  ]
2706
3294
  }, undefined, true, undefined, this),
2707
- subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsxDEV3("div", {
2708
- className: "border-border bg-destructive/10 text-destructive rounded-xl border p-4",
2709
- children: /* @__PURE__ */ jsxDEV3("p", {
2710
- className: "text-sm font-medium",
3295
+ subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsxDEV6("div", {
3296
+ className: "rounded-xl border border-border bg-destructive/10 p-4 text-destructive",
3297
+ children: /* @__PURE__ */ jsxDEV6("p", {
3298
+ className: "font-medium text-sm",
2711
3299
  children: "\u26A0\uFE0F Your subscription will be cancelled at the end of the current period."
2712
3300
  }, undefined, false, undefined, this)
2713
3301
  }, undefined, false, undefined, this)
@@ -2715,233 +3303,137 @@ function BillingTab({ subscription }) {
2715
3303
  }, undefined, true, undefined, this);
2716
3304
  }
2717
3305
  function SettingsTab() {
2718
- return /* @__PURE__ */ jsxDEV3("div", {
3306
+ return /* @__PURE__ */ jsxDEV6("div", {
2719
3307
  className: "space-y-6",
2720
- children: /* @__PURE__ */ jsxDEV3("div", {
2721
- className: "border-border bg-card rounded-xl border p-6",
3308
+ children: /* @__PURE__ */ jsxDEV6("div", {
3309
+ className: "rounded-xl border border-border bg-card p-6",
2722
3310
  children: [
2723
- /* @__PURE__ */ jsxDEV3("h3", {
2724
- className: "mb-4 text-lg font-semibold",
3311
+ /* @__PURE__ */ jsxDEV6("h3", {
3312
+ className: "mb-4 font-semibold text-lg",
2725
3313
  children: "Organization Settings"
2726
3314
  }, undefined, false, undefined, this),
2727
- /* @__PURE__ */ jsxDEV3("div", {
3315
+ /* @__PURE__ */ jsxDEV6("div", {
2728
3316
  className: "space-y-4",
2729
3317
  children: [
2730
- /* @__PURE__ */ jsxDEV3("div", {
3318
+ /* @__PURE__ */ jsxDEV6("div", {
2731
3319
  children: [
2732
- /* @__PURE__ */ jsxDEV3("label", {
3320
+ /* @__PURE__ */ jsxDEV6("label", {
2733
3321
  htmlFor: "org-name",
2734
- className: "text-sm font-medium",
3322
+ className: "font-medium text-sm",
2735
3323
  children: "Organization Name"
2736
3324
  }, undefined, false, undefined, this),
2737
- /* @__PURE__ */ jsxDEV3("input", {
3325
+ /* @__PURE__ */ jsxDEV6("input", {
2738
3326
  id: "org-name",
2739
3327
  type: "text",
2740
3328
  defaultValue: "Demo Organization",
2741
- className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2"
3329
+ className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
2742
3330
  }, undefined, false, undefined, this)
2743
3331
  ]
2744
3332
  }, undefined, true, undefined, this),
2745
- /* @__PURE__ */ jsxDEV3("div", {
3333
+ /* @__PURE__ */ jsxDEV6("div", {
2746
3334
  children: [
2747
- /* @__PURE__ */ jsxDEV3("label", {
2748
- htmlFor: "timezone",
2749
- className: "text-sm font-medium",
2750
- children: "Default Timezone"
2751
- }, undefined, false, undefined, this),
2752
- /* @__PURE__ */ jsxDEV3("select", {
2753
- id: "timezone",
2754
- className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2",
2755
- children: [
2756
- /* @__PURE__ */ jsxDEV3("option", {
2757
- children: "UTC"
2758
- }, undefined, false, undefined, this),
2759
- /* @__PURE__ */ jsxDEV3("option", {
2760
- children: "America/New_York"
2761
- }, undefined, false, undefined, this),
2762
- /* @__PURE__ */ jsxDEV3("option", {
2763
- children: "Europe/London"
2764
- }, undefined, false, undefined, this),
2765
- /* @__PURE__ */ jsxDEV3("option", {
2766
- children: "Asia/Tokyo"
2767
- }, undefined, false, undefined, this)
2768
- ]
2769
- }, undefined, true, undefined, this)
2770
- ]
2771
- }, undefined, true, undefined, this),
2772
- /* @__PURE__ */ jsxDEV3("div", {
2773
- className: "pt-2",
2774
- children: /* @__PURE__ */ jsxDEV3(Button3, {
2775
- onPress: () => alert("Settings saved!"),
2776
- children: "Save Settings"
2777
- }, undefined, false, undefined, this)
2778
- }, undefined, false, undefined, this)
2779
- ]
2780
- }, undefined, true, undefined, this)
2781
- ]
2782
- }, undefined, true, undefined, this)
2783
- }, undefined, false, undefined, this);
2784
- }
2785
-
2786
- // src/ui/SaasProjectList.tsx
2787
- import {
2788
- StatCard as StatCard2,
2789
- StatCardGroup as StatCardGroup2,
2790
- StatusChip as StatusChip2,
2791
- EntityCard as EntityCard2,
2792
- EmptyState as EmptyState2,
2793
- LoaderBlock as LoaderBlock2,
2794
- ErrorState as ErrorState2,
2795
- Button as Button4
2796
- } from "@contractspec/lib.design-system";
2797
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
2798
- "use client";
2799
- function getStatusTone2(status) {
2800
- switch (status) {
2801
- case "ACTIVE":
2802
- return "success";
2803
- case "DRAFT":
2804
- return "neutral";
2805
- case "ARCHIVED":
2806
- return "danger";
2807
- default:
2808
- return "neutral";
2809
- }
2810
- }
2811
- function SaasProjectList({
2812
- onProjectClick,
2813
- onCreateProject
2814
- }) {
2815
- const { data, loading, error, stats, refetch } = useProjectList();
2816
- if (loading && !data) {
2817
- return /* @__PURE__ */ jsxDEV4(LoaderBlock2, {
2818
- label: "Loading projects..."
2819
- }, undefined, false, undefined, this);
2820
- }
2821
- if (error) {
2822
- return /* @__PURE__ */ jsxDEV4(ErrorState2, {
2823
- title: "Failed to load projects",
2824
- description: error.message,
2825
- onRetry: refetch,
2826
- retryLabel: "Retry"
2827
- }, undefined, false, undefined, this);
2828
- }
2829
- if (!data?.items.length) {
2830
- return /* @__PURE__ */ jsxDEV4(EmptyState2, {
2831
- title: "No projects found",
2832
- description: "Create your first project to get started.",
2833
- primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV4(Button4, {
2834
- onPress: onCreateProject,
2835
- children: "Create Project"
2836
- }, undefined, false, undefined, this) : undefined
2837
- }, undefined, false, undefined, this);
2838
- }
2839
- return /* @__PURE__ */ jsxDEV4("div", {
2840
- className: "space-y-6",
2841
- children: [
2842
- stats && /* @__PURE__ */ jsxDEV4(StatCardGroup2, {
2843
- children: [
2844
- /* @__PURE__ */ jsxDEV4(StatCard2, {
2845
- label: "Total Projects",
2846
- value: stats.total.toString()
2847
- }, undefined, false, undefined, this),
2848
- /* @__PURE__ */ jsxDEV4(StatCard2, {
2849
- label: "Active",
2850
- value: stats.activeCount.toString()
2851
- }, undefined, false, undefined, this),
2852
- /* @__PURE__ */ jsxDEV4(StatCard2, {
2853
- label: "Draft",
2854
- value: stats.draftCount.toString()
2855
- }, undefined, false, undefined, this)
2856
- ]
2857
- }, undefined, true, undefined, this),
2858
- /* @__PURE__ */ jsxDEV4("div", {
2859
- className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
2860
- children: data.items.map((project) => /* @__PURE__ */ jsxDEV4(EntityCard2, {
2861
- cardTitle: project.name,
2862
- cardSubtitle: project.tier,
2863
- meta: /* @__PURE__ */ jsxDEV4("p", {
2864
- className: "text-muted-foreground text-sm",
2865
- children: project.description
2866
- }, undefined, false, undefined, this),
2867
- chips: /* @__PURE__ */ jsxDEV4(StatusChip2, {
2868
- tone: getStatusTone2(project.status),
2869
- label: project.status
2870
- }, undefined, false, undefined, this),
2871
- footer: /* @__PURE__ */ jsxDEV4("span", {
2872
- className: "text-muted-foreground text-xs",
2873
- children: project.updatedAt.toLocaleDateString()
2874
- }, undefined, false, undefined, this),
2875
- onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
2876
- }, project.id, false, undefined, this))
2877
- }, undefined, false, undefined, this)
2878
- ]
2879
- }, undefined, true, undefined, this);
3335
+ /* @__PURE__ */ jsxDEV6("label", {
3336
+ htmlFor: "timezone",
3337
+ className: "font-medium text-sm",
3338
+ children: "Default Timezone"
3339
+ }, undefined, false, undefined, this),
3340
+ /* @__PURE__ */ jsxDEV6("select", {
3341
+ id: "timezone",
3342
+ className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
3343
+ children: [
3344
+ /* @__PURE__ */ jsxDEV6("option", {
3345
+ children: "UTC"
3346
+ }, undefined, false, undefined, this),
3347
+ /* @__PURE__ */ jsxDEV6("option", {
3348
+ children: "America/New_York"
3349
+ }, undefined, false, undefined, this),
3350
+ /* @__PURE__ */ jsxDEV6("option", {
3351
+ children: "Europe/London"
3352
+ }, undefined, false, undefined, this),
3353
+ /* @__PURE__ */ jsxDEV6("option", {
3354
+ children: "Asia/Tokyo"
3355
+ }, undefined, false, undefined, this)
3356
+ ]
3357
+ }, undefined, true, undefined, this)
3358
+ ]
3359
+ }, undefined, true, undefined, this),
3360
+ /* @__PURE__ */ jsxDEV6("div", {
3361
+ className: "pt-2",
3362
+ children: /* @__PURE__ */ jsxDEV6(Button4, {
3363
+ onPress: () => alert("Settings saved!"),
3364
+ children: "Save Settings"
3365
+ }, undefined, false, undefined, this)
3366
+ }, undefined, false, undefined, this)
3367
+ ]
3368
+ }, undefined, true, undefined, this)
3369
+ ]
3370
+ }, undefined, true, undefined, this)
3371
+ }, undefined, false, undefined, this);
2880
3372
  }
2881
3373
 
2882
3374
  // src/ui/SaasSettingsPanel.tsx
2883
- import { useState as useState6 } from "react";
2884
3375
  import { Button as Button5 } from "@contractspec/lib.design-system";
2885
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
3376
+ import { useState as useState6 } from "react";
3377
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
2886
3378
  "use client";
2887
3379
  function SaasSettingsPanel() {
2888
3380
  const [orgName, setOrgName] = useState6("Demo Organization");
2889
3381
  const [timezone, setTimezone] = useState6("UTC");
2890
- return /* @__PURE__ */ jsxDEV5("div", {
3382
+ return /* @__PURE__ */ jsxDEV7("div", {
2891
3383
  className: "space-y-6",
2892
3384
  children: [
2893
- /* @__PURE__ */ jsxDEV5("div", {
2894
- className: "border-border bg-card rounded-xl border p-6",
3385
+ /* @__PURE__ */ jsxDEV7("div", {
3386
+ className: "rounded-xl border border-border bg-card p-6",
2895
3387
  children: [
2896
- /* @__PURE__ */ jsxDEV5("h3", {
2897
- className: "mb-4 text-lg font-semibold",
3388
+ /* @__PURE__ */ jsxDEV7("h3", {
3389
+ className: "mb-4 font-semibold text-lg",
2898
3390
  children: "Organization Settings"
2899
3391
  }, undefined, false, undefined, this),
2900
- /* @__PURE__ */ jsxDEV5("div", {
3392
+ /* @__PURE__ */ jsxDEV7("div", {
2901
3393
  className: "space-y-4",
2902
3394
  children: [
2903
- /* @__PURE__ */ jsxDEV5("div", {
3395
+ /* @__PURE__ */ jsxDEV7("div", {
2904
3396
  children: [
2905
- /* @__PURE__ */ jsxDEV5("label", {
3397
+ /* @__PURE__ */ jsxDEV7("label", {
2906
3398
  htmlFor: "setting-org-name",
2907
- className: "block text-sm font-medium",
3399
+ className: "block font-medium text-sm",
2908
3400
  children: "Organization Name"
2909
3401
  }, undefined, false, undefined, this),
2910
- /* @__PURE__ */ jsxDEV5("input", {
3402
+ /* @__PURE__ */ jsxDEV7("input", {
2911
3403
  id: "setting-org-name",
2912
3404
  type: "text",
2913
3405
  value: orgName,
2914
3406
  onChange: (e) => setOrgName(e.target.value),
2915
- className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2"
3407
+ className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
2916
3408
  }, undefined, false, undefined, this)
2917
3409
  ]
2918
3410
  }, undefined, true, undefined, this),
2919
- /* @__PURE__ */ jsxDEV5("div", {
3411
+ /* @__PURE__ */ jsxDEV7("div", {
2920
3412
  children: [
2921
- /* @__PURE__ */ jsxDEV5("label", {
3413
+ /* @__PURE__ */ jsxDEV7("label", {
2922
3414
  htmlFor: "setting-timezone",
2923
- className: "block text-sm font-medium",
3415
+ className: "block font-medium text-sm",
2924
3416
  children: "Default Timezone"
2925
3417
  }, undefined, false, undefined, this),
2926
- /* @__PURE__ */ jsxDEV5("select", {
3418
+ /* @__PURE__ */ jsxDEV7("select", {
2927
3419
  id: "setting-timezone",
2928
3420
  value: timezone,
2929
3421
  onChange: (e) => setTimezone(e.target.value),
2930
- className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2",
3422
+ className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
2931
3423
  children: [
2932
- /* @__PURE__ */ jsxDEV5("option", {
3424
+ /* @__PURE__ */ jsxDEV7("option", {
2933
3425
  value: "UTC",
2934
3426
  children: "UTC"
2935
3427
  }, undefined, false, undefined, this),
2936
- /* @__PURE__ */ jsxDEV5("option", {
3428
+ /* @__PURE__ */ jsxDEV7("option", {
2937
3429
  value: "America/New_York",
2938
3430
  children: "America/New_York"
2939
3431
  }, undefined, false, undefined, this),
2940
- /* @__PURE__ */ jsxDEV5("option", {
3432
+ /* @__PURE__ */ jsxDEV7("option", {
2941
3433
  value: "Europe/London",
2942
3434
  children: "Europe/London"
2943
3435
  }, undefined, false, undefined, this),
2944
- /* @__PURE__ */ jsxDEV5("option", {
3436
+ /* @__PURE__ */ jsxDEV7("option", {
2945
3437
  value: "Asia/Tokyo",
2946
3438
  children: "Asia/Tokyo"
2947
3439
  }, undefined, false, undefined, this)
@@ -2951,37 +3443,37 @@ function SaasSettingsPanel() {
2951
3443
  }, undefined, true, undefined, this)
2952
3444
  ]
2953
3445
  }, undefined, true, undefined, this),
2954
- /* @__PURE__ */ jsxDEV5("div", {
3446
+ /* @__PURE__ */ jsxDEV7("div", {
2955
3447
  className: "mt-6",
2956
- children: /* @__PURE__ */ jsxDEV5(Button5, {
3448
+ children: /* @__PURE__ */ jsxDEV7(Button5, {
2957
3449
  variant: "default",
2958
3450
  children: "Save Changes"
2959
3451
  }, undefined, false, undefined, this)
2960
3452
  }, undefined, false, undefined, this)
2961
3453
  ]
2962
3454
  }, undefined, true, undefined, this),
2963
- /* @__PURE__ */ jsxDEV5("div", {
2964
- className: "border-border bg-card rounded-xl border p-6",
3455
+ /* @__PURE__ */ jsxDEV7("div", {
3456
+ className: "rounded-xl border border-border bg-card p-6",
2965
3457
  children: [
2966
- /* @__PURE__ */ jsxDEV5("h3", {
2967
- className: "mb-4 text-lg font-semibold",
3458
+ /* @__PURE__ */ jsxDEV7("h3", {
3459
+ className: "mb-4 font-semibold text-lg",
2968
3460
  children: "Notifications"
2969
3461
  }, undefined, false, undefined, this),
2970
- /* @__PURE__ */ jsxDEV5("div", {
3462
+ /* @__PURE__ */ jsxDEV7("div", {
2971
3463
  className: "space-y-3",
2972
3464
  children: [
2973
3465
  { label: "Email notifications", defaultChecked: true },
2974
3466
  { label: "Usage alerts", defaultChecked: true },
2975
3467
  { label: "Weekly digest", defaultChecked: false }
2976
- ].map((item) => /* @__PURE__ */ jsxDEV5("label", {
3468
+ ].map((item) => /* @__PURE__ */ jsxDEV7("label", {
2977
3469
  className: "flex items-center gap-3",
2978
3470
  children: [
2979
- /* @__PURE__ */ jsxDEV5("input", {
3471
+ /* @__PURE__ */ jsxDEV7("input", {
2980
3472
  type: "checkbox",
2981
3473
  defaultChecked: item.defaultChecked,
2982
- className: "border-input h-4 w-4 rounded"
3474
+ className: "h-4 w-4 rounded border-input"
2983
3475
  }, undefined, false, undefined, this),
2984
- /* @__PURE__ */ jsxDEV5("span", {
3476
+ /* @__PURE__ */ jsxDEV7("span", {
2985
3477
  className: "text-sm",
2986
3478
  children: item.label
2987
3479
  }, undefined, false, undefined, this)
@@ -2990,26 +3482,26 @@ function SaasSettingsPanel() {
2990
3482
  }, undefined, false, undefined, this)
2991
3483
  ]
2992
3484
  }, undefined, true, undefined, this),
2993
- /* @__PURE__ */ jsxDEV5("div", {
3485
+ /* @__PURE__ */ jsxDEV7("div", {
2994
3486
  className: "rounded-xl border border-red-200 bg-red-50 p-6 dark:border-red-900 dark:bg-red-950/20",
2995
3487
  children: [
2996
- /* @__PURE__ */ jsxDEV5("h3", {
2997
- className: "mb-2 text-lg font-semibold text-red-700 dark:text-red-400",
3488
+ /* @__PURE__ */ jsxDEV7("h3", {
3489
+ className: "mb-2 font-semibold text-lg text-red-700 dark:text-red-400",
2998
3490
  children: "Danger Zone"
2999
3491
  }, undefined, false, undefined, this),
3000
- /* @__PURE__ */ jsxDEV5("p", {
3001
- className: "mb-4 text-sm text-red-600 dark:text-red-300",
3492
+ /* @__PURE__ */ jsxDEV7("p", {
3493
+ className: "mb-4 text-red-600 text-sm dark:text-red-300",
3002
3494
  children: "These actions are irreversible. Please proceed with caution."
3003
3495
  }, undefined, false, undefined, this),
3004
- /* @__PURE__ */ jsxDEV5("div", {
3496
+ /* @__PURE__ */ jsxDEV7("div", {
3005
3497
  className: "flex gap-3",
3006
3498
  children: [
3007
- /* @__PURE__ */ jsxDEV5(Button5, {
3499
+ /* @__PURE__ */ jsxDEV7(Button5, {
3008
3500
  variant: "secondary",
3009
3501
  size: "sm",
3010
3502
  children: "Export Data"
3011
3503
  }, undefined, false, undefined, this),
3012
- /* @__PURE__ */ jsxDEV5(Button5, {
3504
+ /* @__PURE__ */ jsxDEV7(Button5, {
3013
3505
  variant: "secondary",
3014
3506
  size: "sm",
3015
3507
  children: "Delete Organization"
@@ -3021,228 +3513,6 @@ function SaasSettingsPanel() {
3021
3513
  ]
3022
3514
  }, undefined, true, undefined, this);
3023
3515
  }
3024
- // src/ui/hooks/index.ts
3025
- "use client";
3026
-
3027
- // src/ui/renderers/project-list.renderer.tsx
3028
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
3029
- var projectListReactRenderer = {
3030
- target: "react",
3031
- render: async (desc, _ctx) => {
3032
- if (desc.source.type !== "component") {
3033
- throw new Error("Invalid source type");
3034
- }
3035
- if (desc.source.componentKey !== "SaasProjectListView") {
3036
- throw new Error(`Unknown component: ${desc.source.componentKey}`);
3037
- }
3038
- return /* @__PURE__ */ jsxDEV6(SaasProjectList, {}, undefined, false, undefined, this);
3039
- }
3040
- };
3041
-
3042
- // src/ui/renderers/project-list.markdown.ts
3043
- var projectListMarkdownRenderer = {
3044
- target: "markdown",
3045
- render: async (desc, _ctx) => {
3046
- if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
3047
- throw new Error("projectListMarkdownRenderer: not ProjectListView");
3048
- }
3049
- const data = await mockListProjectsHandler({
3050
- limit: 20,
3051
- offset: 0
3052
- });
3053
- const items = data.projects ?? data.items ?? [];
3054
- const lines = [
3055
- "# Projects",
3056
- "",
3057
- `**Total**: ${data.total} projects`,
3058
- ""
3059
- ];
3060
- if (items.length === 0) {
3061
- lines.push("_No projects found._");
3062
- } else {
3063
- lines.push("| Status | Project | Description |");
3064
- lines.push("|--------|---------|-------------|");
3065
- for (const project of items) {
3066
- const status = project.status === "ACTIVE" ? "\u2705" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "\u23F8\uFE0F";
3067
- lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
3068
- }
3069
- }
3070
- return {
3071
- mimeType: "text/markdown",
3072
- body: lines.join(`
3073
- `)
3074
- };
3075
- }
3076
- };
3077
- var saasDashboardMarkdownRenderer = {
3078
- target: "markdown",
3079
- render: async (desc, _ctx) => {
3080
- if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
3081
- throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
3082
- }
3083
- const [projectsData, subscription] = await Promise.all([
3084
- mockListProjectsHandler({ limit: 50 }),
3085
- mockGetSubscriptionHandler()
3086
- ]);
3087
- const projects = projectsData.projects ?? [];
3088
- const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
3089
- const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
3090
- const lines = [
3091
- "# SaaS Dashboard",
3092
- "",
3093
- "> Organization overview and usage summary",
3094
- "",
3095
- "## Summary",
3096
- "",
3097
- "| Metric | Value |",
3098
- "|--------|-------|",
3099
- `| Total Projects | ${projectsData.total} |`,
3100
- `| Active Projects | ${activeProjects} |`,
3101
- `| Archived Projects | ${archivedProjects} |`,
3102
- `| Subscription Plan | ${subscription.planName} |`,
3103
- `| Subscription Status | ${subscription.status} |`,
3104
- "",
3105
- "## Projects",
3106
- ""
3107
- ];
3108
- if (projects.length === 0) {
3109
- lines.push("_No projects yet._");
3110
- } else {
3111
- lines.push("| Status | Project | Description |");
3112
- lines.push("|--------|---------|-------------|");
3113
- for (const project of projects.slice(0, 10)) {
3114
- const status = project.status === "ACTIVE" ? "\u2705" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "\u23F8\uFE0F";
3115
- lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
3116
- }
3117
- if (projects.length > 10) {
3118
- lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
3119
- }
3120
- }
3121
- lines.push("");
3122
- lines.push("## Subscription");
3123
- lines.push("");
3124
- lines.push(`- **Plan**: ${subscription.planName}`);
3125
- lines.push(`- **Status**: ${subscription.status}`);
3126
- if (subscription.currentPeriodEnd) {
3127
- lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
3128
- }
3129
- return {
3130
- mimeType: "text/markdown",
3131
- body: lines.join(`
3132
- `)
3133
- };
3134
- }
3135
- };
3136
- var saasBillingMarkdownRenderer = {
3137
- target: "markdown",
3138
- render: async (desc, _ctx) => {
3139
- if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
3140
- throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
3141
- }
3142
- const subscription = await mockGetSubscriptionHandler();
3143
- const lines = [
3144
- "# Billing & Subscription",
3145
- "",
3146
- "> Current subscription details and billing information",
3147
- "",
3148
- "## Subscription Details",
3149
- "",
3150
- "| Property | Value |",
3151
- "|----------|-------|",
3152
- `| Plan | ${subscription.planName} |`,
3153
- `| Status | ${subscription.status} |`,
3154
- `| ID | ${subscription.id} |`,
3155
- `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
3156
- `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
3157
- ];
3158
- lines.push("");
3159
- lines.push("## Plan Limits");
3160
- lines.push("");
3161
- lines.push(`- **Projects**: ${subscription.limits.projects}`);
3162
- lines.push(`- **Users**: ${subscription.limits.users}`);
3163
- lines.push("");
3164
- lines.push("## Plan Features");
3165
- lines.push("");
3166
- if (subscription.planName.toLowerCase().includes("free")) {
3167
- lines.push("- \u2705 Up to 3 projects");
3168
- lines.push("- \u2705 Basic support");
3169
- lines.push("- \u274C Priority support");
3170
- lines.push("- \u274C Advanced analytics");
3171
- } else if (subscription.planName.toLowerCase().includes("pro")) {
3172
- lines.push("- \u2705 Unlimited projects");
3173
- lines.push("- \u2705 Priority support");
3174
- lines.push("- \u2705 Advanced analytics");
3175
- lines.push("- \u274C Custom integrations");
3176
- } else {
3177
- lines.push("- \u2705 Unlimited projects");
3178
- lines.push("- \u2705 Priority support");
3179
- lines.push("- \u2705 Advanced analytics");
3180
- lines.push("- \u2705 Custom integrations");
3181
- lines.push("- \u2705 Dedicated support");
3182
- }
3183
- return {
3184
- mimeType: "text/markdown",
3185
- body: lines.join(`
3186
- `)
3187
- };
3188
- }
3189
- };
3190
- // src/ui/overlays/demo-overlays.ts
3191
- var saasFreeUserOverlay = {
3192
- overlayId: "saas-boilerplate.free-tier",
3193
- version: "1.0.0",
3194
- description: "Shows limitations for free tier users",
3195
- appliesTo: {
3196
- feature: "saas-boilerplate",
3197
- tier: "free"
3198
- },
3199
- modifications: [
3200
- {
3201
- type: "setLimit",
3202
- field: "projects",
3203
- max: 3,
3204
- message: "Upgrade to create more projects"
3205
- },
3206
- { type: "hideField", field: "advancedSettings", reason: "Pro feature" },
3207
- {
3208
- type: "addBadge",
3209
- position: "header",
3210
- label: "Free Plan",
3211
- variant: "default"
3212
- }
3213
- ]
3214
- };
3215
- var saasDemoOverlay = {
3216
- overlayId: "saas-boilerplate.demo-user",
3217
- version: "1.0.0",
3218
- description: "Demo mode for SaaS boilerplate",
3219
- appliesTo: {
3220
- feature: "saas-boilerplate",
3221
- role: "demo"
3222
- },
3223
- modifications: [
3224
- {
3225
- type: "hideField",
3226
- field: "billingSection",
3227
- reason: "Demo users cannot access billing"
3228
- },
3229
- {
3230
- type: "hideField",
3231
- field: "deleteAccount",
3232
- reason: "Not available in demo"
3233
- },
3234
- {
3235
- type: "addBadge",
3236
- position: "header",
3237
- label: "Demo Mode",
3238
- variant: "warning"
3239
- }
3240
- ]
3241
- };
3242
- var saasOverlays = [
3243
- saasFreeUserOverlay,
3244
- saasDemoOverlay
3245
- ];
3246
3516
  // src/index.ts
3247
3517
  import { identityRbacSchemaContribution } from "@contractspec/lib.identity-rbac";
3248
3518
  import { jobsSchemaContribution } from "@contractspec/lib.jobs";
@@ -3294,6 +3564,7 @@ export {
3294
3564
  mockCreateProjectHandler,
3295
3565
  mockCheckFeatureAccessHandler,
3296
3566
  example_default as example,
3567
+ createSaasVisualizationItems,
3297
3568
  createSaasHandlers,
3298
3569
  UsageSummaryModel,
3299
3570
  UsageRecordedPayloadModel,
@@ -3312,8 +3583,15 @@ export {
3312
3583
  SettingsScopeEnum,
3313
3584
  SettingsPanelPresentation,
3314
3585
  SettingsEntity,
3586
+ SaasVisualizationSpecs,
3587
+ SaasVisualizationRegistry,
3588
+ SaasVisualizationRefs,
3315
3589
  SaasSettingsPanel,
3590
+ SaasProjectUsageVisualization,
3591
+ SaasProjectTierVisualization,
3592
+ SaasProjectStatusVisualization,
3316
3593
  SaasProjectList,
3594
+ SaasProjectActivityVisualization,
3317
3595
  SaasDashboardPresentation,
3318
3596
  SaasDashboard,
3319
3597
  SaasBoilerplateFeature,