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