create-flowdular 0.2.3 → 0.2.5

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 (271) hide show
  1. package/README.md +11 -0
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
  4. package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
  5. package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
  6. package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
  7. package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
  8. package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
  9. package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
  10. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  11. package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
  12. package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
  13. package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
  14. package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
  15. package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
  16. package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
  17. package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
  18. package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
  19. package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
  20. package/agent-template/.agents/skills/variables/SKILL.md +156 -0
  21. package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
  22. package/agent-template/.ai/README.md +62 -0
  23. package/agent-template/.ai/agents/README.md +27 -0
  24. package/agent-template/.ai/agents/module-executor.md +36 -0
  25. package/agent-template/.ai/agents/reviewer.md +23 -0
  26. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
  27. package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
  28. package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
  29. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
  30. package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
  31. package/agent-template/.ai/agents/spec-author.md +29 -0
  32. package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
  33. package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  34. package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
  35. package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  36. package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  37. package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  38. package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  39. package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
  40. package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
  41. package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
  42. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
  43. package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  44. package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
  45. package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
  46. package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  47. package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
  48. package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  49. package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  50. package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
  51. package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
  52. package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
  53. package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
  54. package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  55. package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
  56. package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  57. package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
  58. package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  59. package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
  60. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  61. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  62. package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  63. package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  64. package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
  65. package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
  66. package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  67. package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  68. package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  69. package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
  70. package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
  71. package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  72. package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
  73. package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  74. package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  75. package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  76. package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  77. package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
  78. package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
  79. package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
  80. package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
  81. package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  82. package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
  83. package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
  84. package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  85. package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
  86. package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  87. package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  88. package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  89. package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  90. package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
  91. package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
  92. package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
  93. package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
  94. package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  95. package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
  96. package/agent-template/.ai/blueprints/new-module/README.md +7 -0
  97. package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  98. package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
  99. package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  100. package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  101. package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  102. package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  103. package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
  104. package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
  105. package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
  106. package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
  107. package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  108. package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
  109. package/agent-template/.ai/blueprints/release/README.md +5 -0
  110. package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
  111. package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
  112. package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  113. package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  114. package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
  115. package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
  116. package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
  117. package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
  118. package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
  119. package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
  120. package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
  121. package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
  122. package/agent-template/.ai/blueprints/security-review/README.md +5 -0
  123. package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  124. package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
  125. package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  126. package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  127. package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  128. package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  129. package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
  130. package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
  131. package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
  132. package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
  133. package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  134. package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
  135. package/agent-template/.ai/examples/README.md +8 -0
  136. package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
  137. package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
  138. package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
  139. package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
  140. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
  141. package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
  142. package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
  143. package/agent-template/.ai/examples/client-contribution/README.md +11 -0
  144. package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
  145. package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
  146. package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
  147. package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
  148. package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
  149. package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
  150. package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
  151. package/agent-template/.ai/guides/application-development.md +97 -0
  152. package/agent-template/.ai/policies/capabilities.yaml +164 -0
  153. package/agent-template/.ai/policies/model-routing.yaml +72 -0
  154. package/agent-template/.ai/policies/path-ownership.yaml +65 -0
  155. package/agent-template/.ai/policies/task-budgets.yaml +37 -0
  156. package/agent-template/.ai/references/catalog/LICENSE +21 -0
  157. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  158. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  159. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  160. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  161. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  162. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  163. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  164. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  165. package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
  166. package/agent-template/.ai/references/catalog/module.json +27 -0
  167. package/agent-template/.ai/references/catalog/package.json +49 -0
  168. package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
  169. package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
  170. package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
  171. package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
  172. package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  173. package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  174. package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  175. package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
  176. package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  177. package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
  178. package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  179. package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
  180. package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
  181. package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
  182. package/agent-template/.ai/references/catalog/src/index.ts +31 -0
  183. package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
  184. package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
  185. package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
  186. package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  187. package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
  188. package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
  189. package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
  190. package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
  191. package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  192. package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  193. package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  194. package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  195. package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
  196. package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
  197. package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
  198. package/agent-template/.ai/references/catalog/translations/en.json +101 -0
  199. package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
  200. package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
  201. package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
  202. package/agent-template/.ai/references/catalog.provenance.json +55 -0
  203. package/agent-template/.ai/rules/flowdular.md +86 -0
  204. package/agent-template/.ai/skills/README.md +36 -0
  205. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
  206. package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
  207. package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
  208. package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
  209. package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
  210. package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
  211. package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
  212. package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
  213. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  214. package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
  215. package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
  216. package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
  217. package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
  218. package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
  219. package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
  220. package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
  221. package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
  222. package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
  223. package/agent-template/.ai/skills/variables/SKILL.md +164 -0
  224. package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
  225. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
  226. package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
  227. package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
  228. package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
  229. package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
  230. package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
  231. package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
  232. package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
  233. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  234. package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
  235. package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
  236. package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
  237. package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
  238. package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
  239. package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
  240. package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
  241. package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
  242. package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
  243. package/agent-template/.claude/skills/variables/SKILL.md +156 -0
  244. package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
  245. package/agent-template/AGENTS.md +77 -0
  246. package/agent-template/CLAUDE.md +77 -0
  247. package/agent-template/docs/adr/0001-development-reload.md +16 -0
  248. package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
  249. package/agent-template/docs/adr/0003-module-settings.md +22 -0
  250. package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
  251. package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
  252. package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
  253. package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
  254. package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
  255. package/agent-template/docs/agent-contract.md +45 -0
  256. package/agent-template/docs/configuration.md +122 -0
  257. package/agent-template/docs/database-adapters.md +346 -0
  258. package/agent-template/docs/design-system.md +217 -0
  259. package/agent-template/docs/modules.md +146 -0
  260. package/agent-template/platform/scripts/build.mjs +38 -0
  261. package/agent-template/rulesync.jsonc +11 -0
  262. package/dist/bin.js +3 -1
  263. package/package.json +3 -2
  264. package/template/default/.prettierignore +9 -0
  265. package/template/default/README.md +12 -0
  266. package/template/default/flowdular.json +3 -3
  267. package/template/default/modules/example/package.json +1 -1
  268. package/template/default/package.json +6 -2
  269. package/template/default/platform/octane.config.ts +17 -6
  270. package/template/default/platform/package.json +3 -2
  271. package/template/default/platform/scripts/dev.mjs +39 -6
@@ -0,0 +1,473 @@
1
+ import { useEffect, useMemo } from 'octane';
2
+ import {
3
+ Alert,
4
+ Button,
5
+ ConfirmDialog,
6
+ Drawer,
7
+ Filters,
8
+ Icon,
9
+ Kpi,
10
+ PageHeader,
11
+ SearchField,
12
+ TableCard,
13
+ Tag,
14
+ type TableColumn,
15
+ } from '@flowdular/sdk/ui';
16
+ import { activeLocale, t } from '@flowdular/sdk/client';
17
+ import { useValue } from 'segment-state';
18
+ import type { CatalogItem, CatalogItemKind } from '../domain/types.ts';
19
+ import {
20
+ archiveCatalogItem,
21
+ createCatalogItem,
22
+ deleteCatalogItem,
23
+ loadCatalogItemHistory,
24
+ loadCatalogItems,
25
+ restoreCatalogItem,
26
+ updateCatalogItem,
27
+ } from './api.ts';
28
+ import { createCatalogClientState } from './state.ts';
29
+ import { CatalogItemForm } from './CatalogItemForm.tsrx';
30
+ import { CatalogHistoryDrawer } from './CatalogHistoryDrawer.tsrx';
31
+
32
+ export interface CatalogViewProps {
33
+ readonly csrfToken: string;
34
+ readonly canManage: boolean;
35
+ }
36
+
37
+ function formValue(form: FormData, key: string): string {
38
+ const value = form.get(key);
39
+ return typeof value === 'string' ? value : '';
40
+ }
41
+
42
+ function price(minor: number, currency: string): string {
43
+ try {
44
+ return new Intl.NumberFormat(activeLocale(), {
45
+ style: 'currency',
46
+ currency,
47
+ }).format(minor / 100);
48
+ } catch {
49
+ return t('catalog.price.fallback', { amount: minor, currency });
50
+ }
51
+ }
52
+
53
+ function columns(): readonly TableColumn<CatalogItem>[] {
54
+ return [
55
+ {
56
+ key: 'item',
57
+ header: t('catalog.table.column.item'),
58
+ width: '36%',
59
+ cell: (item) => <span class="ui-cell">
60
+ <b>{item.name}</b>
61
+ <small class="ui-mono">{item.sku + ' · ' + item.unit}</small>
62
+ </span>,
63
+ },
64
+ {
65
+ key: 'kind',
66
+ header: t('catalog.table.column.kind'),
67
+ width: '16%',
68
+ cell: (item) =>
69
+ <Tag tone={item.kind === 'product' ? 'info' : 'neutral'}>{t(
70
+ 'catalog.kind.' + item.kind,
71
+ )}</Tag>,
72
+ },
73
+ {
74
+ key: 'price',
75
+ header: t('catalog.table.column.price'),
76
+ width: '22%',
77
+ numeric: true,
78
+ cell: (item) => price(item.basePriceMinor, item.currency),
79
+ },
80
+ {
81
+ key: 'status',
82
+ header: t('catalog.table.column.status'),
83
+ width: '16%',
84
+ cell: (item) =>
85
+ <Tag tone={item.status === 'active' ? 'success' : 'neutral'} dot>{t(
86
+ 'catalog.status.' + item.status,
87
+ )}</Tag>,
88
+ },
89
+ ];
90
+ }
91
+
92
+ export function CatalogView(props: CatalogViewProps) @{
93
+ const catalogState = useMemo(() => createCatalogClientState(), []);
94
+ const [items] = useValue(catalogState.state.items);
95
+ const [status, setStatus] = useValue(catalogState.state.status);
96
+ const [error, setError] = useValue(catalogState.state.error);
97
+ const [query, setQuery] = useValue(catalogState.state.query);
98
+ const [includeArchived, setIncludeArchived] = useValue(
99
+ catalogState.state.includeArchived,
100
+ );
101
+ const [filtersOpen, setFiltersOpen] = useValue(
102
+ catalogState.state.filtersOpen,
103
+ );
104
+ const [formOpen, setFormOpen] = useValue(catalogState.state.formOpen);
105
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = useValue(
106
+ catalogState.state.deleteConfirmOpen,
107
+ );
108
+ const [editingItem, setEditingItem] = useValue(
109
+ catalogState.state.editingItem,
110
+ );
111
+ const [formSession, setFormSession] = useValue(
112
+ catalogState.state.formSession,
113
+ );
114
+ const [historyOpen, setHistoryOpen] = useValue(
115
+ catalogState.state.historyOpen,
116
+ );
117
+ const [historyItem, setHistoryItem] = useValue(
118
+ catalogState.state.historyItem,
119
+ );
120
+ const [historyEntries] = useValue(catalogState.state.historyEntries);
121
+ const [historyLoading, setHistoryLoading] = useValue(
122
+ catalogState.state.historyLoading,
123
+ );
124
+ const [historyError, setHistoryError] = useValue(
125
+ catalogState.state.historyError,
126
+ );
127
+ const locale = activeLocale();
128
+ const term = query.trim().toLocaleLowerCase(locale);
129
+ const visible = items.filter(
130
+ (item) =>
131
+ (includeArchived || item.status === 'active') &&
132
+ (term === '' || item.name.toLocaleLowerCase(locale).includes(term) ||
133
+ item.sku.toLocaleLowerCase(locale).includes(term)),
134
+ );
135
+ const filtered = term !== '' || includeArchived;
136
+
137
+ const refresh = async () => {
138
+ setStatus('loading');
139
+ setError('');
140
+ try {
141
+ const records = await loadCatalogItems();
142
+ catalogState.store.act(
143
+ (transaction) => transaction.set(catalogState.state.items, records),
144
+ 'catalog/loaded',
145
+ );
146
+ } catch (loadError) {
147
+ setError(
148
+ loadError instanceof Error
149
+ ? loadError.message
150
+ : t('catalog.error.load'),
151
+ );
152
+ } finally {
153
+ setStatus('idle');
154
+ }
155
+ };
156
+
157
+ useEffect(() => {
158
+ void refresh();
159
+ }, []);
160
+
161
+ const openForm = (item: CatalogItem | null) => {
162
+ setEditingItem(item);
163
+ setError('');
164
+ setFormSession((current) => current + 1);
165
+ setFormOpen(true);
166
+ };
167
+
168
+ const closeForm = () => {
169
+ setFormOpen(false);
170
+ setEditingItem(null);
171
+ setError('');
172
+ };
173
+
174
+ const replace = (item: CatalogItem) => {
175
+ catalogState.store.act(
176
+ (transaction) => transaction.set(
177
+ catalogState.state.items,
178
+ items.map((current) => current.id === item.id ? item : current),
179
+ ),
180
+ 'catalog/lifecycle',
181
+ );
182
+ setEditingItem(item);
183
+ };
184
+
185
+ const submit = async (event: SubmitEvent) => {
186
+ event.preventDefault();
187
+ const element = event.currentTarget as HTMLFormElement;
188
+ const form = new FormData(element);
189
+ setStatus('submitting');
190
+ setError('');
191
+ try {
192
+ const mutable = {
193
+ name: formValue(form, 'name'),
194
+ kind: formValue(form, 'kind') as CatalogItemKind,
195
+ unit: formValue(form, 'unit'),
196
+ basePriceMinor: Number(formValue(form, 'basePriceMinor')),
197
+ currency: formValue(form, 'currency'),
198
+ };
199
+ const item = editingItem
200
+ ? await updateCatalogItem(
201
+ { id: editingItem.id, ...mutable },
202
+ props.csrfToken,
203
+ )
204
+ : await createCatalogItem(
205
+ { sku: formValue(form, 'sku'), ...mutable },
206
+ props.csrfToken,
207
+ );
208
+ const next =
209
+ editingItem
210
+ ? items.map((current) => current.id === item.id ? item : current)
211
+ : [...items, item];
212
+ catalogState.store.act(
213
+ (transaction) => transaction.set(
214
+ catalogState.state.items,
215
+ [...next].sort((left, right) => left.sku.localeCompare(right.sku)),
216
+ ),
217
+ editingItem ? 'catalog/updated' : 'catalog/created',
218
+ );
219
+ closeForm();
220
+ } catch (saveError) {
221
+ setError(
222
+ saveError instanceof Error
223
+ ? saveError.message
224
+ : t(editingItem ? 'catalog.error.update' : 'catalog.error.create'),
225
+ );
226
+ } finally {
227
+ setStatus('idle');
228
+ }
229
+ };
230
+
231
+ const changeStatus = async (action: 'archive' | 'restore') => {
232
+ if (!editingItem) return;
233
+ setStatus('submitting');
234
+ setError('');
235
+ try {
236
+ const item =
237
+ action === 'archive'
238
+ ? await archiveCatalogItem(editingItem.id, props.csrfToken)
239
+ : await restoreCatalogItem(editingItem.id, props.csrfToken);
240
+ replace(item);
241
+ } catch (lifecycleError) {
242
+ setError(
243
+ lifecycleError instanceof Error
244
+ ? lifecycleError.message
245
+ : t('catalog.error.lifecycle'),
246
+ );
247
+ } finally {
248
+ setStatus('idle');
249
+ }
250
+ };
251
+
252
+ const remove = async () => {
253
+ if (!editingItem) return;
254
+ setStatus('submitting');
255
+ setError('');
256
+ try {
257
+ await deleteCatalogItem(editingItem.id, props.csrfToken);
258
+ catalogState.store.act(
259
+ (transaction) => transaction.set(
260
+ catalogState.state.items,
261
+ items.filter((item) => item.id !== editingItem.id),
262
+ ),
263
+ 'catalog/deleted',
264
+ );
265
+ setDeleteConfirmOpen(false);
266
+ closeForm();
267
+ } catch (deleteError) {
268
+ setError(
269
+ deleteError instanceof Error
270
+ ? deleteError.message
271
+ : t('catalog.error.delete'),
272
+ );
273
+ } finally {
274
+ setStatus('idle');
275
+ }
276
+ };
277
+
278
+ const openHistory = async (item: CatalogItem) => {
279
+ setHistoryItem(item);
280
+ setHistoryOpen(true);
281
+ setHistoryLoading(true);
282
+ setHistoryError('');
283
+ catalogState.store.act(
284
+ (transaction) => transaction.set(catalogState.state.historyEntries, []),
285
+ 'catalog/history-loading',
286
+ );
287
+ try {
288
+ const page = await loadCatalogItemHistory(item.id);
289
+ catalogState.store.act(
290
+ (transaction) => transaction.set(
291
+ catalogState.state.historyEntries,
292
+ page.entries,
293
+ ),
294
+ 'catalog/history-loaded',
295
+ );
296
+ } catch (historyFailure) {
297
+ setHistoryError(
298
+ historyFailure instanceof Error
299
+ ? historyFailure.message
300
+ : t('catalog.error.history'),
301
+ );
302
+ } finally {
303
+ setHistoryLoading(false);
304
+ }
305
+ };
306
+
307
+ <div class="ui-view">
308
+ <PageHeader
309
+ eyebrow={t('catalog.page.eyebrow')}
310
+ title={t('catalog.page.title')}
311
+ description={t('catalog.page.description')}
312
+ >
313
+ <Button size="sm" onClick={() => void refresh()}>
314
+ <Icon name="refresh" size={14} />
315
+ {t('catalog.page.action.refresh')}
316
+ </Button>
317
+ @if (props.canManage) {
318
+ <Button size="sm" variant="primary" onClick={() => openForm(null)}>
319
+ <Icon name="plus" size={14} />
320
+ {t('catalog.page.action.new')}
321
+ </Button>
322
+ }
323
+ </PageHeader>
324
+ @if (error && !formOpen) {
325
+ <Alert>{error}</Alert>
326
+ }
327
+ <TableCard
328
+ title={t('catalog.table.title')}
329
+ count={t(
330
+ items.length === 1
331
+ ? 'catalog.table.count.one'
332
+ : 'catalog.table.count.other',
333
+ { count: items.length },
334
+ )}
335
+ search={<SearchField
336
+ value={query}
337
+ placeholder={t('catalog.table.search.placeholder')}
338
+ label={t('catalog.table.search.label')}
339
+ onInput={setQuery}
340
+ />}
341
+ filters={<Filters
342
+ open={filtersOpen}
343
+ onToggle={() => setFiltersOpen(!filtersOpen)}
344
+ activeCount={includeArchived ? 1 : 0}
345
+ label={t('catalog.filters.label')}
346
+ >
347
+ <label class="ui-checkbox">
348
+ <input
349
+ type="checkbox"
350
+ checked={includeArchived}
351
+ onChange={(event: Event) => setIncludeArchived(
352
+ (event.currentTarget as HTMLInputElement).checked,
353
+ )}
354
+ />
355
+ <span>{t('catalog.filters.includeArchived')}</span>
356
+ </label>
357
+ </Filters>}
358
+ caption={t('catalog.table.caption')}
359
+ columns={columns()}
360
+ rows={visible}
361
+ rowKey={(item) => item.id}
362
+ status={status === 'loading' && items.length === 0 ? 'loading' : 'idle'}
363
+ loadingLabel={t('catalog.table.loading')}
364
+ filtered={filtered}
365
+ empty={{
366
+ icon: 'catalog',
367
+ title: t('catalog.table.empty.title'),
368
+ hint: t('catalog.table.empty.hint'),
369
+ }}
370
+ emptyFiltered={{
371
+ icon: 'catalog',
372
+ title: t('catalog.table.emptyFiltered.title'),
373
+ hint: t('catalog.table.emptyFiltered.hint'),
374
+ }}
375
+ actionsLabel={t('catalog.table.column.actions')}
376
+ actionsWidth="280px"
377
+ actions={(item) => [
378
+ {
379
+ id: 'history',
380
+ label: t('catalog.action.history'),
381
+ icon: 'activity',
382
+ onSelect: () => void openHistory(item),
383
+ },
384
+ ...(props.canManage
385
+ ? [
386
+ {
387
+ id: 'edit',
388
+ label: t('catalog.action.edit'),
389
+ icon: 'file-text',
390
+ onSelect: () => openForm(item),
391
+ },
392
+ ]
393
+ : []),
394
+ ]}
395
+ />
396
+ <Drawer
397
+ open={formOpen}
398
+ title={t(
399
+ editingItem ? 'catalog.drawer.editTitle' : 'catalog.drawer.title',
400
+ )}
401
+ subtitle={t('catalog.drawer.subtitle')}
402
+ width="lg"
403
+ onClose={closeForm}
404
+ >
405
+ <CatalogItemForm
406
+ key={'catalog-form-' + formSession}
407
+ item={editingItem}
408
+ busy={status === 'submitting'}
409
+ error={error}
410
+ onCancel={closeForm}
411
+ onArchive={editingItem?.status === 'active'
412
+ ? () => void changeStatus('archive')
413
+ : undefined}
414
+ onRestore={editingItem?.status === 'archived'
415
+ ? () => void changeStatus('restore')
416
+ : undefined}
417
+ onDelete={editingItem?.status === 'archived'
418
+ ? () => setDeleteConfirmOpen(true)
419
+ : undefined}
420
+ onHistory={editingItem
421
+ ? () => void openHistory(editingItem)
422
+ : undefined}
423
+ onSubmit={(event) => void submit(event)}
424
+ />
425
+ </Drawer>
426
+ <CatalogHistoryDrawer
427
+ open={historyOpen}
428
+ item={historyItem}
429
+ entries={historyEntries}
430
+ loading={historyLoading}
431
+ error={historyError}
432
+ onClose={() => {
433
+ setHistoryOpen(false);
434
+ setHistoryItem(null);
435
+ }}
436
+ />
437
+ <ConfirmDialog
438
+ open={deleteConfirmOpen}
439
+ title={t('catalog.delete.title')}
440
+ confirmLabel={t('catalog.delete.confirm')}
441
+ cancelLabel={t('catalog.form.cancel')}
442
+ busy={status === 'submitting'}
443
+ onConfirm={() => void remove()}
444
+ onCancel={() => setDeleteConfirmOpen(false)}
445
+ >{t('catalog.delete.description', {
446
+ name: editingItem?.name ?? '',
447
+ })}</ConfirmDialog>
448
+ </div>
449
+ }
450
+
451
+ export function CatalogDashboardWidget() @{
452
+ const widgetState = useMemo(() => createCatalogClientState(), []);
453
+ const [items] = useValue(widgetState.state.items);
454
+ const [status, setStatus] = useValue(widgetState.state.status);
455
+ useEffect(() => {
456
+ setStatus('loading');
457
+ void loadCatalogItems().then((records) => {
458
+ widgetState.store.act(
459
+ (transaction) => transaction.set(widgetState.state.items, records),
460
+ 'catalog/widget-loaded',
461
+ );
462
+ }).catch(() => undefined).finally(() => setStatus('idle'));
463
+ }, []);
464
+ <Kpi
465
+ label={t('catalog.widget.label')}
466
+ value={status === 'loading'
467
+ ? '…'
468
+ : new Intl.NumberFormat(activeLocale()).format(items.length)}
469
+ note={t('catalog.widget.note')}
470
+ href="/catalog"
471
+ linkLabel={t('catalog.widget.link')}
472
+ />
473
+ }
@@ -0,0 +1,111 @@
1
+ import { t } from '@flowdular/sdk/client/i18n';
2
+ import type { HistoryPage } from '@flowdular/sdk/kernel';
3
+ import type {
4
+ CatalogItem,
5
+ CreateCatalogItemInput,
6
+ UpdateCatalogItemInput,
7
+ } from '../domain/types.ts';
8
+
9
+ interface ErrorEnvelope {
10
+ readonly error?: { readonly message?: string };
11
+ }
12
+
13
+ async function payload<T>(response: Response): Promise<T> {
14
+ const value = (await response.json()) as T & ErrorEnvelope;
15
+ if (!response.ok) {
16
+ throw new Error(value.error?.message ?? t('catalog.error.request'));
17
+ }
18
+ return value;
19
+ }
20
+
21
+ export async function loadCatalogItems(): Promise<readonly CatalogItem[]> {
22
+ const response = await fetch('/api/catalog/items', {
23
+ headers: { accept: 'application/json' },
24
+ credentials: 'same-origin',
25
+ });
26
+ return (await payload<{ readonly items: readonly CatalogItem[] }>(response))
27
+ .items;
28
+ }
29
+
30
+ export async function createCatalogItem(
31
+ input: CreateCatalogItemInput,
32
+ csrfToken: string,
33
+ ): Promise<CatalogItem> {
34
+ const response = await fetch('/api/catalog/items', {
35
+ method: 'POST',
36
+ headers: {
37
+ 'content-type': 'application/json',
38
+ 'x-csrf-token': csrfToken,
39
+ },
40
+ credentials: 'same-origin',
41
+ body: JSON.stringify(input),
42
+ });
43
+ return (await payload<{ readonly item: CatalogItem }>(response)).item;
44
+ }
45
+
46
+ async function mutateItem(
47
+ path: string,
48
+ body: unknown,
49
+ csrfToken: string,
50
+ ): Promise<CatalogItem> {
51
+ const response = await fetch(path, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'content-type': 'application/json',
55
+ 'x-csrf-token': csrfToken,
56
+ },
57
+ credentials: 'same-origin',
58
+ body: JSON.stringify(body),
59
+ });
60
+ return (await payload<{ readonly item: CatalogItem }>(response)).item;
61
+ }
62
+
63
+ export function updateCatalogItem(
64
+ input: UpdateCatalogItemInput,
65
+ csrfToken: string,
66
+ ): Promise<CatalogItem> {
67
+ return mutateItem('/api/catalog/items/update', input, csrfToken);
68
+ }
69
+
70
+ export function archiveCatalogItem(
71
+ id: string,
72
+ csrfToken: string,
73
+ ): Promise<CatalogItem> {
74
+ return mutateItem('/api/catalog/items/archive', { id }, csrfToken);
75
+ }
76
+
77
+ export function restoreCatalogItem(
78
+ id: string,
79
+ csrfToken: string,
80
+ ): Promise<CatalogItem> {
81
+ return mutateItem('/api/catalog/items/restore', { id }, csrfToken);
82
+ }
83
+
84
+ export async function deleteCatalogItem(
85
+ id: string,
86
+ csrfToken: string,
87
+ ): Promise<void> {
88
+ const response = await fetch('/api/catalog/items/delete', {
89
+ method: 'POST',
90
+ headers: {
91
+ 'content-type': 'application/json',
92
+ 'x-csrf-token': csrfToken,
93
+ },
94
+ credentials: 'same-origin',
95
+ body: JSON.stringify({ id }),
96
+ });
97
+ await payload<{ readonly deleted: true }>(response);
98
+ }
99
+
100
+ export async function loadCatalogItemHistory(
101
+ recordId: string,
102
+ ): Promise<HistoryPage> {
103
+ const response = await fetch(
104
+ `/api/catalog/items/history?recordId=${encodeURIComponent(recordId)}&limit=100`,
105
+ {
106
+ headers: { accept: 'application/json' },
107
+ credentials: 'same-origin',
108
+ },
109
+ );
110
+ return payload<HistoryPage>(response);
111
+ }
@@ -0,0 +1,61 @@
1
+ import type { ModuleClientContribution } from '@flowdular/sdk/client';
2
+ import { CATALOG_PERMISSIONS } from '../acl/permissions.ts';
3
+ import translationsEn from '../../translations/en.json';
4
+ import translationsPl from '../../translations/pl.json';
5
+ import { CatalogDashboardWidget, CatalogView } from './CatalogView.tsrx';
6
+ import {
7
+ catalogNavigationDescription,
8
+ catalogNavigationLabel,
9
+ } from './navigation-copy.ts';
10
+
11
+ export interface CatalogClientContributionOptions {
12
+ readonly csrfToken: string;
13
+ readonly scopes: readonly string[];
14
+ }
15
+
16
+ export function createCatalogClientContribution(
17
+ options: CatalogClientContributionOptions,
18
+ ): ModuleClientContribution {
19
+ return {
20
+ moduleId: 'catalog.core',
21
+ translations: { en: translationsEn, pl: translationsPl },
22
+ navigation: [
23
+ {
24
+ id: 'catalog.navigation',
25
+ viewId: 'catalog',
26
+ group: 'Operations',
27
+ /* The shell registers this contribution's bundles after the factory
28
+ returns. Getters resolve copy only when the shell reads navigation,
29
+ after registration, and again when the active locale changes. */
30
+ get label() {
31
+ return catalogNavigationLabel();
32
+ },
33
+ glyph: 'catalog',
34
+ get description() {
35
+ return catalogNavigationDescription();
36
+ },
37
+ scope: CATALOG_PERMISSIONS.read,
38
+ order: 20,
39
+ },
40
+ ],
41
+ views: [
42
+ {
43
+ id: 'catalog',
44
+ render: () =>
45
+ <CatalogView
46
+ csrfToken={options.csrfToken}
47
+ canManage={options.scopes.includes(CATALOG_PERMISSIONS.manage)}
48
+ />,
49
+ },
50
+ ],
51
+ widgets: [
52
+ {
53
+ id: 'catalog.dashboard.master-data',
54
+ slot: 'dashboard.metrics',
55
+ scope: CATALOG_PERMISSIONS.read,
56
+ order: 20,
57
+ render: () => <CatalogDashboardWidget />,
58
+ },
59
+ ],
60
+ };
61
+ }
@@ -0,0 +1,18 @@
1
+ export { createCatalogClientContribution } from './contribution.tsrx';
2
+ export type { CatalogClientContributionOptions } from './contribution.tsrx';
3
+ export { CatalogDashboardWidget, CatalogView } from './CatalogView.tsrx';
4
+ import type {
5
+ ModuleClientContext,
6
+ ModuleClientContribution,
7
+ } from '@flowdular/sdk/client';
8
+ import { createCatalogClientContribution as canonicalContribution } from './contribution.tsrx';
9
+
10
+ /* Canonical entry used by the generated platform composition. */
11
+ export function createClientContribution(
12
+ context: ModuleClientContext,
13
+ ): ModuleClientContribution {
14
+ return canonicalContribution({
15
+ csrfToken: context.csrfToken,
16
+ scopes: context.scopes,
17
+ });
18
+ }
@@ -0,0 +1,9 @@
1
+ import { t } from '@flowdular/sdk/client/i18n';
2
+
3
+ export function catalogNavigationLabel(): string {
4
+ return t('catalog.nav.label');
5
+ }
6
+
7
+ export function catalogNavigationDescription(): string {
8
+ return t('catalog.nav.description');
9
+ }
@@ -0,0 +1,24 @@
1
+ import { cell, createStore } from 'segment-state';
2
+ import type { HistoryEntry } from '@flowdular/sdk/kernel';
3
+ import type { CatalogItem } from '../domain/types.ts';
4
+
5
+ export function createCatalogClientState() {
6
+ const store = createStore({
7
+ items: cell<readonly CatalogItem[]>([]),
8
+ query: '',
9
+ includeArchived: false,
10
+ filtersOpen: false,
11
+ formOpen: false,
12
+ deleteConfirmOpen: false,
13
+ editingItem: cell<CatalogItem | null>(null),
14
+ historyOpen: false,
15
+ historyItem: cell<CatalogItem | null>(null),
16
+ historyEntries: cell<readonly HistoryEntry[]>([]),
17
+ historyLoading: false,
18
+ historyError: '',
19
+ status: cell<'idle' | 'loading' | 'submitting'>('idle'),
20
+ error: '',
21
+ formSession: 0,
22
+ });
23
+ return { store, state: store.state };
24
+ }