@rashidee/co2 1.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 (190) hide show
  1. package/dist/index.js +25449 -0
  2. package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
  3. package/drizzle/0001_wise_wolf_cub.sql +13 -0
  4. package/drizzle/0002_secret_spyke.sql +16 -0
  5. package/drizzle/0003_workspace_config_singleton.sql +16 -0
  6. package/drizzle/0004_terminal_claim_v120.sql +11 -0
  7. package/drizzle/0005_brainstorm_v124.sql +10 -0
  8. package/drizzle/0006_project_v140.sql +6 -0
  9. package/drizzle/0007_prd_wizard_v124.sql +14 -0
  10. package/drizzle/0008_workflow_artifact_v126.sql +24 -0
  11. package/drizzle/0009_project_import_v150.sql +7 -0
  12. package/drizzle/0010_quality_v151.sql +3 -0
  13. package/drizzle/meta/0000_snapshot.json +1335 -0
  14. package/drizzle/meta/0001_snapshot.json +1416 -0
  15. package/drizzle/meta/0002_snapshot.json +1510 -0
  16. package/drizzle/meta/_journal.json +83 -0
  17. package/package.json +41 -0
  18. package/plugin/.claude-plugin/marketplace.json +36 -0
  19. package/plugin/.claude-plugin/plugin.json +22 -0
  20. package/plugin/README.md +625 -0
  21. package/plugin/SKILLS.md +350 -0
  22. package/plugin/co2.svg +5 -0
  23. package/plugin/commands/brainstorm-loop.md +129 -0
  24. package/plugin/skills/conductor-defect/SKILL.md +898 -0
  25. package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
  26. package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
  27. package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
  28. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
  29. package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
  30. package/plugin/skills/depgen-k8s/SKILL.md +468 -0
  31. package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
  32. package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
  33. package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
  34. package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
  35. package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
  36. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
  37. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
  38. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
  39. package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
  40. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
  41. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
  42. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
  43. package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
  44. package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
  45. package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
  46. package/plugin/skills/modelgen-relational/SKILL.md +707 -0
  47. package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
  48. package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
  49. package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
  50. package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
  51. package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
  52. package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
  53. package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
  54. package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
  55. package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
  56. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
  57. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
  58. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
  59. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
  60. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
  61. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
  62. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
  63. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
  64. package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
  65. package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
  66. package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
  67. package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
  68. package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
  69. package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
  70. package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
  71. package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
  72. package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
  73. package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
  74. package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
  75. package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
  76. package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
  77. package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
  78. package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
  79. package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
  80. package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
  81. package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
  82. package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
  83. package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
  84. package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
  85. package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
  86. package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
  87. package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
  88. package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
  89. package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
  90. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
  91. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
  92. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
  93. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
  94. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
  95. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
  96. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
  97. package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
  98. package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
  99. package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
  100. package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
  101. package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
  102. package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
  103. package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
  104. package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
  105. package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
  106. package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
  107. package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
  108. package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
  109. package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
  110. package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
  111. package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
  112. package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
  113. package/plugin/skills/testgen-functional/SKILL.md +1381 -0
  114. package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
  115. package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
  116. package/plugin/skills/util-projectinit/SKILL.md +307 -0
  117. package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
  118. package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
  119. package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
  120. package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
  121. package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
  122. package/plugin/skills/util-projectsync/SKILL.md +501 -0
  123. package/plugin/skills/util-updprd/SKILL.md +324 -0
  124. package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
  125. package/plugin/skills/util-ustagger/SKILL.md +154 -0
  126. package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
  127. package/static/assets/arc-DRKw7sjs.js +1 -0
  128. package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
  129. package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
  130. package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
  131. package/static/assets/channel-C2nVbuwv.js +1 -0
  132. package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
  133. package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
  134. package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
  135. package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
  136. package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
  137. package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
  138. package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
  139. package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
  140. package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
  141. package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
  142. package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
  143. package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
  144. package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
  145. package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
  146. package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
  147. package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  148. package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
  149. package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  150. package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
  151. package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
  152. package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
  153. package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
  154. package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
  155. package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
  156. package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
  157. package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
  158. package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
  159. package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
  160. package/static/assets/graph-C9eacEi8.js +1 -0
  161. package/static/assets/index-LZSQkLE2.css +41 -0
  162. package/static/assets/index-y7XDSgMG.js +495 -0
  163. package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
  164. package/static/assets/init-Gi6I4Gst.js +1 -0
  165. package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
  166. package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
  167. package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
  168. package/static/assets/katex-C5jXJg4s.js +257 -0
  169. package/static/assets/layout-DEXfKzaS.js +1 -0
  170. package/static/assets/linear-CLO--ryk.js +1 -0
  171. package/static/assets/map-Czzmt4hB.js +1 -0
  172. package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
  173. package/static/assets/ordinal-Cboi1Yqb.js +1 -0
  174. package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
  175. package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
  176. package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
  177. package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
  178. package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
  179. package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
  180. package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
  181. package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
  182. package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
  183. package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
  184. package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
  185. package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
  186. package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
  187. package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
  188. package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
  189. package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
  190. package/static/index.html +24 -0
@@ -0,0 +1,669 @@
1
+ # Routing Patterns — React Router v7 + Feature-Based Architecture (Tailwind)
2
+
3
+ This reference describes the React Router v7 conventions, the feature-based directory
4
+ structure, and the Tailwind/Headless UI layout shell used in the spec. Include this content
5
+ in Sections 4, 7, 11, 12, and 13 of the generated specification.
6
+
7
+ ---
8
+
9
+ ## Feature-Based Directory Structure
10
+
11
+ Each PRD module maps to a `src/features/<module>/` folder. This co-locates everything
12
+ related to a feature (types, API, hooks, components, pages) so the coding agent can
13
+ implement one feature folder at a time.
14
+
15
+ ```
16
+ src/
17
+ ├── main.tsx # App entry — providers, ReactDOM.render
18
+ ├── App.tsx # Root component — router, dark-mode sync, notifications
19
+ ├── index.css # Tailwind directives + design-token CSS variables
20
+ ├── router/
21
+ │ └── index.tsx # Route tree definition
22
+ ├── lib/
23
+ │ ├── api/
24
+ │ │ ├── axiosInstance.ts # Axios instance with interceptors
25
+ │ │ └── authBridge.ts # [If Auth = Keycloak] Auth bridge for interceptors
26
+ │ ├── auth/
27
+ │ │ ├── oidcConfig.ts # [If Auth = Keycloak] oidc-client-ts config
28
+ │ │ ├── keycloakRoles.ts # [If Auth = Keycloak] Role extraction utilities
29
+ │ │ ├── useAuthUser.ts # [If Auth = Keycloak] Auth hook wrapper
30
+ │ │ └── roles.ts # Role constants
31
+ │ ├── utils/
32
+ │ │ ├── cn.ts # twMerge(clsx(...)) helper
33
+ │ │ └── zodErrors.ts # Zod error extraction utilities
34
+ │ └── queryClient.ts # TanStack Query QueryClient instance
35
+ ├── store/
36
+ │ ├── auth.store.ts # [If Auth = Local] Zustand auth store
37
+ │ ├── notification.store.ts # Zustand toast queue
38
+ │ └── ui.store.ts # Zustand UI store (sidebar, theme mode)
39
+ ├── shared/
40
+ │ ├── layouts/
41
+ │ │ ├── DashboardLayout.tsx # Authenticated layout (sidebar + topbar + outlet)
42
+ │ │ ├── PublicLayout.tsx # Public layout (header/footer + outlet)
43
+ │ │ ├── AuthLayout.tsx # Minimal centered layout for login/callback
44
+ │ │ └── Footer.tsx # Footer with version string
45
+ │ ├── components/
46
+ │ │ ├── ProtectedRoute.tsx # Auth + role guard wrapper
47
+ │ │ ├── Button.tsx # cva button
48
+ │ │ ├── Card.tsx # Surface container
49
+ │ │ ├── Spinner.tsx # Inline spinner
50
+ │ │ ├── AppTopBar.tsx # Sticky topbar (toggle, user menu, theme toggle)
51
+ │ │ ├── AppSidebar.tsx # Role-filtered navigation sidebar
52
+ │ │ ├── navigationConfig.ts # Navigation item definitions
53
+ │ │ ├── PageHeader.tsx # Page title + breadcrumbs + action buttons
54
+ │ │ ├── DataTable.tsx # Generic TanStack Table wrapper (sorting/selection)
55
+ │ │ ├── Pagination.tsx # Page navigation control
56
+ │ │ ├── ConfirmDialog.tsx # Headless UI Dialog confirm
57
+ │ │ ├── StatusBadge.tsx # cva status badge
58
+ │ │ ├── Modal.tsx # Headless UI Dialog wrapper for forms
59
+ │ │ ├── ImageUpload.tsx # Drag-and-drop image upload with preview
60
+ │ │ ├── LoadingOverlay.tsx # Full-page spinner overlay
61
+ │ │ ├── EmptyState.tsx # Empty list placeholder with icon + message
62
+ │ │ ├── ErrorBoundary.tsx # React ErrorBoundary with Tailwind fallback UI
63
+ │ │ ├── NotificationProvider.tsx # Headless UI Transition toast stack
64
+ │ │ └── SearchInput.tsx # Debounced search text field
65
+ │ ├── hooks/
66
+ │ │ ├── useDebounce.ts # Generic debounce hook
67
+ │ │ ├── useNotification.ts # Toast notification Zustand hook
68
+ │ │ ├── useColorMode.ts # Dark-mode toggle hook
69
+ │ │ └── useConfirm.ts # Confirm dialog imperative hook
70
+ │ └── form/
71
+ │ ├── TextFieldController.tsx # RHF Controller wrapping a Tailwind <input>
72
+ │ ├── TextAreaController.tsx # RHF Controller wrapping a Tailwind <textarea>
73
+ │ ├── SelectController.tsx # RHF Controller wrapping Headless UI Listbox
74
+ │ ├── DatePickerController.tsx # RHF Controller wrapping react-day-picker
75
+ │ ├── SwitchController.tsx # RHF Controller wrapping Headless UI Switch
76
+ │ ├── ComboboxController.tsx # RHF Controller wrapping Headless UI Combobox
77
+ │ └── RichTextController.tsx # [If RichText = yes] RHF Controller for Tiptap
78
+ ├── features/
79
+ │ ├── auth/ # [If Auth != none] Auth feature
80
+ │ │ ├── pages/
81
+ │ │ │ ├── LoginPage.tsx # [If Auth = Local] Login form page
82
+ │ │ │ └── AuthCallbackPage.tsx # [If Auth = Keycloak] OIDC callback handler
83
+ │ │ └── index.ts
84
+ │ ├── {{module1}}/ # PRD module 1 (e.g., heroSection)
85
+ │ │ ├── api/
86
+ │ │ │ └── {{module1}}Api.ts # Axios API functions
87
+ │ │ ├── components/
88
+ │ │ │ ├── {{Module1}}Form.tsx # Create/edit form
89
+ │ │ │ └── {{Module1}}Card.tsx # Card component (if applicable)
90
+ │ │ ├── hooks/
91
+ │ │ │ ├── use{{Module1}}s.ts # useQuery for list
92
+ │ │ │ ├── use{{Module1}}.ts # useQuery for single item
93
+ │ │ │ └── use{{Module1}}Mutations.ts # useMutation for CUD operations
94
+ │ │ ├── pages/
95
+ │ │ │ ├── {{Module1}}ListPage.tsx # List page component (lazy-loaded)
96
+ │ │ │ └── {{Module1}}FormPage.tsx # Create/edit page (lazy-loaded)
97
+ │ │ ├── store/
98
+ │ │ │ └── {{module1}}.store.ts # [If needed] Feature-level Zustand store
99
+ │ │ ├── {{module1}}.types.ts # TypeScript interfaces
100
+ │ │ ├── {{module1}}.schema.ts # Zod validation schemas
101
+ │ │ └── index.ts # Public API re-exports
102
+ │ └── {{module2}}/
103
+ │ └── (same structure)
104
+ └── types/
105
+ └── global.d.ts # Global type augmentations (e.g., import.meta.env)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Route Tree Definition
111
+
112
+ ```tsx
113
+ // src/router/index.tsx
114
+ import { lazy, Suspense } from 'react';
115
+ import { createBrowserRouter, Navigate } from 'react-router-dom';
116
+ import { DashboardLayout } from '../shared/layouts/DashboardLayout';
117
+ import { PublicLayout } from '../shared/layouts/PublicLayout';
118
+ import { AuthLayout } from '../shared/layouts/AuthLayout';
119
+ import { ProtectedRoute } from '../shared/components/ProtectedRoute';
120
+ import { LoadingOverlay } from '../shared/components/LoadingOverlay';
121
+ import { Roles } from '../lib/auth/roles';
122
+
123
+ // Lazy-load all page components for code splitting
124
+ // [If Auth = Keycloak]
125
+ const AuthCallbackPage = lazy(() =>
126
+ import('../features/auth/pages/AuthCallbackPage').then((m) => ({ default: m.AuthCallbackPage })),
127
+ );
128
+
129
+ // [If Auth = Local]
130
+ const LoginPage = lazy(() =>
131
+ import('../features/auth/pages/LoginPage').then((m) => ({ default: m.LoginPage })),
132
+ );
133
+
134
+ const DashboardPage = lazy(() =>
135
+ import('../features/dashboard/pages/DashboardPage').then((m) => ({ default: m.DashboardPage })),
136
+ );
137
+
138
+ // Hero Section (example — replace with actual modules from PRD)
139
+ const HeroSectionListPage = lazy(() =>
140
+ import('../features/heroSection/pages/HeroSectionListPage').then((m) => ({
141
+ default: m.HeroSectionListPage,
142
+ })),
143
+ );
144
+ const HeroSectionFormPage = lazy(() =>
145
+ import('../features/heroSection/pages/HeroSectionFormPage').then((m) => ({
146
+ default: m.HeroSectionFormPage,
147
+ })),
148
+ );
149
+
150
+ function SuspenseWrapper({ children }: { children: React.ReactNode }) {
151
+ return <Suspense fallback={<LoadingOverlay />}>{children}</Suspense>;
152
+ }
153
+
154
+ export const router = createBrowserRouter([
155
+ // ── Auth routes (public) ────────────────────────────────────────────
156
+ {
157
+ element: <AuthLayout />,
158
+ children: [
159
+ // [If Auth = Keycloak]
160
+ {
161
+ path: '/auth/callback',
162
+ element: (
163
+ <SuspenseWrapper>
164
+ <AuthCallbackPage />
165
+ </SuspenseWrapper>
166
+ ),
167
+ },
168
+ // [If Auth = Local]
169
+ {
170
+ path: '/login',
171
+ element: (
172
+ <SuspenseWrapper>
173
+ <LoginPage />
174
+ </SuspenseWrapper>
175
+ ),
176
+ },
177
+ ],
178
+ },
179
+
180
+ // ── Protected / dashboard routes ────────────────────────────────────
181
+ {
182
+ element: (
183
+ <ProtectedRoute>
184
+ <DashboardLayout />
185
+ </ProtectedRoute>
186
+ ),
187
+ children: [
188
+ { index: true, element: <Navigate to="/dashboard" replace /> },
189
+ {
190
+ path: '/dashboard',
191
+ element: (
192
+ <SuspenseWrapper>
193
+ <DashboardPage />
194
+ </SuspenseWrapper>
195
+ ),
196
+ },
197
+
198
+ // Hero Section — accessible by ADMIN and EDITOR (module-based path, NOT role-prefixed)
199
+ {
200
+ path: '/hero-section',
201
+ element: (
202
+ <ProtectedRoute requiredRoles={[Roles.ADMIN, Roles.EDITOR]}>
203
+ <SuspenseWrapper>
204
+ <HeroSectionListPage />
205
+ </SuspenseWrapper>
206
+ </ProtectedRoute>
207
+ ),
208
+ },
209
+ {
210
+ path: '/hero-section/create',
211
+ element: (
212
+ <ProtectedRoute requiredRoles={[Roles.ADMIN, Roles.EDITOR]}>
213
+ <SuspenseWrapper>
214
+ <HeroSectionFormPage />
215
+ </SuspenseWrapper>
216
+ </ProtectedRoute>
217
+ ),
218
+ },
219
+ {
220
+ path: '/hero-section/:id/edit',
221
+ element: (
222
+ <ProtectedRoute requiredRoles={[Roles.ADMIN, Roles.EDITOR]}>
223
+ <SuspenseWrapper>
224
+ <HeroSectionFormPage />
225
+ </SuspenseWrapper>
226
+ </ProtectedRoute>
227
+ ),
228
+ },
229
+
230
+ // Add additional module routes following the same pattern...
231
+ ],
232
+ },
233
+
234
+ // ── Error routes ────────────────────────────────────────────────────
235
+ {
236
+ path: '/unauthorized',
237
+ element: <div className="p-8 text-slate-700 dark:text-slate-200">You do not have permission to access this page.</div>,
238
+ },
239
+ {
240
+ path: '*',
241
+ element: <div className="p-8 text-slate-700 dark:text-slate-200">404 — Page not found.</div>,
242
+ },
243
+ ]);
244
+ ```
245
+
246
+ ---
247
+
248
+ ## DashboardLayout
249
+
250
+ ```tsx
251
+ // src/shared/layouts/DashboardLayout.tsx
252
+ import { Outlet } from 'react-router-dom';
253
+ import { AppTopBar } from '../components/AppTopBar';
254
+ import { AppSidebar } from '../components/AppSidebar';
255
+ import { Footer } from './Footer';
256
+ import { useUiStore } from '../../store/ui.store';
257
+ import { cn } from '../../lib/utils/cn';
258
+
259
+ export function DashboardLayout() {
260
+ const sidebarOpen = useUiStore((s) => s.sidebarOpen);
261
+
262
+ return (
263
+ <div className="min-h-screen bg-canvas">
264
+ <AppTopBar />
265
+ <AppSidebar />
266
+ <div
267
+ className={cn(
268
+ 'flex min-h-screen flex-col pt-16 transition-[padding] duration-200',
269
+ sidebarOpen ? 'lg:pl-60' : 'lg:pl-0',
270
+ )}
271
+ >
272
+ <main className="flex-1 p-4 sm:p-6">
273
+ <Outlet />
274
+ </main>
275
+ <Footer />
276
+ </div>
277
+ </div>
278
+ );
279
+ }
280
+ ```
281
+
282
+ ```tsx
283
+ // src/shared/layouts/Footer.tsx
284
+ export function Footer() {
285
+ const version = import.meta.env.VITE_APP_VERSION;
286
+ return (
287
+ <footer className="border-t border-slate-200 px-6 py-3 text-center text-xs text-slate-500 dark:border-slate-700">
288
+ © {new Date().getFullYear()} {import.meta.env.VITE_APP_TITLE} — v{version}
289
+ </footer>
290
+ );
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## AppTopBar (Headless UI Menu user dropdown + theme toggle)
297
+
298
+ ```tsx
299
+ // src/shared/components/AppTopBar.tsx
300
+ import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
301
+ import { Bars3Icon, UserCircleIcon, ArrowRightStartOnRectangleIcon } from '@heroicons/react/24/outline';
302
+ import { useUiStore } from '../../store/ui.store';
303
+ import { useAuthUser } from '../../lib/auth/useAuthUser';
304
+ import { ThemeToggle } from './ThemeToggle';
305
+ import { cn } from '../../lib/utils/cn';
306
+
307
+ export function AppTopBar() {
308
+ const toggleSidebar = useUiStore((s) => s.toggleSidebar);
309
+ const { name, logout } = useAuthUser();
310
+
311
+ return (
312
+ <header className="fixed inset-x-0 top-0 z-30 flex h-16 items-center justify-between border-b border-slate-200 bg-surface px-4 dark:border-slate-700">
313
+ <div className="flex items-center gap-3">
314
+ <button
315
+ type="button"
316
+ onClick={toggleSidebar}
317
+ aria-label="Toggle navigation"
318
+ className="rounded p-2 text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700"
319
+ >
320
+ <Bars3Icon className="h-5 w-5" />
321
+ </button>
322
+ <span className="text-lg font-semibold text-slate-900 dark:text-slate-100">
323
+ {import.meta.env.VITE_APP_TITLE}
324
+ </span>
325
+ </div>
326
+
327
+ <div className="flex items-center gap-2">
328
+ <ThemeToggle />
329
+ <Menu>
330
+ <MenuButton className="flex items-center gap-2 rounded p-1.5 text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-700">
331
+ <UserCircleIcon className="h-6 w-6" />
332
+ <span className="hidden text-sm sm:inline">{name}</span>
333
+ </MenuButton>
334
+ <MenuItems
335
+ anchor="bottom end"
336
+ className="z-50 mt-1 w-48 rounded border border-slate-200 bg-surface py-1 text-sm shadow-lg focus:outline-none dark:border-slate-700"
337
+ >
338
+ <MenuItem>
339
+ <button
340
+ onClick={logout}
341
+ className={cn(
342
+ 'flex w-full items-center gap-2 px-3 py-2 text-left text-slate-700 dark:text-slate-200',
343
+ 'data-[focus]:bg-slate-100 dark:data-[focus]:bg-slate-700',
344
+ )}
345
+ >
346
+ <ArrowRightStartOnRectangleIcon className="h-4 w-4" />
347
+ Sign out
348
+ </button>
349
+ </MenuItem>
350
+ </MenuItems>
351
+ </Menu>
352
+ </div>
353
+ </header>
354
+ );
355
+ }
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Sidebar Navigation
361
+
362
+ ```typescript
363
+ // src/shared/components/navigationConfig.ts
364
+ import { HomeIcon, PhotoIcon, UsersIcon } from '@heroicons/react/24/outline';
365
+ import type { ComponentType, SVGProps } from 'react';
366
+ import { Roles } from '../../lib/auth/roles';
367
+
368
+ export interface NavItem {
369
+ label: string;
370
+ path: string;
371
+ icon: ComponentType<SVGProps<SVGSVGElement>>;
372
+ /** If provided, user must have at least one of these roles to see this item */
373
+ requiredRoles?: string[];
374
+ }
375
+
376
+ /**
377
+ * All sidebar navigation items.
378
+ * Items with requiredRoles are hidden for users who lack those roles.
379
+ * Replace with actual modules from the PRD.md (module-based paths only).
380
+ */
381
+ export const navigationItems: NavItem[] = [
382
+ { label: 'Dashboard', path: '/dashboard', icon: HomeIcon },
383
+ { label: 'Hero Section', path: '/hero-section', icon: PhotoIcon, requiredRoles: [Roles.ADMIN, Roles.EDITOR] },
384
+ { label: 'Users', path: '/users', icon: UsersIcon, requiredRoles: [Roles.ADMIN] },
385
+ // Add additional nav items matching the mockup sidebar files...
386
+ ];
387
+ ```
388
+
389
+ ```tsx
390
+ // src/shared/components/AppSidebar.tsx
391
+ import { NavLink } from 'react-router-dom';
392
+ import { useAuthUser } from '../../lib/auth/useAuthUser';
393
+ import { useUiStore } from '../../store/ui.store';
394
+ import { navigationItems } from './navigationConfig';
395
+ import { cn } from '../../lib/utils/cn';
396
+
397
+ export function AppSidebar() {
398
+ const { hasAnyRole } = useAuthUser();
399
+ const sidebarOpen = useUiStore((s) => s.sidebarOpen);
400
+
401
+ const visibleItems = navigationItems.filter(
402
+ (item) => !item.requiredRoles || hasAnyRole(item.requiredRoles),
403
+ );
404
+
405
+ return (
406
+ <aside
407
+ className={cn(
408
+ 'fixed inset-y-0 left-0 z-20 w-60 border-r border-slate-200 bg-surface pt-16 transition-transform duration-200 dark:border-slate-700',
409
+ sidebarOpen ? 'translate-x-0' : '-translate-x-full',
410
+ )}
411
+ >
412
+ <nav className="flex flex-col gap-1 p-3">
413
+ {visibleItems.map((item) => {
414
+ const Icon = item.icon;
415
+ return (
416
+ <NavLink
417
+ key={item.path}
418
+ to={item.path}
419
+ className={({ isActive }) =>
420
+ cn(
421
+ 'flex items-center gap-3 rounded px-3 py-2 text-sm font-medium transition-colors',
422
+ isActive
423
+ ? 'bg-primary/10 text-primary'
424
+ : 'text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-700',
425
+ )
426
+ }
427
+ >
428
+ <Icon className="h-5 w-5 shrink-0" />
429
+ {item.label}
430
+ </NavLink>
431
+ );
432
+ })}
433
+ </nav>
434
+ </aside>
435
+ );
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ## PageHeader (breadcrumbs)
442
+
443
+ ```tsx
444
+ // src/shared/components/PageHeader.tsx
445
+ import { Link } from 'react-router-dom';
446
+ import { ChevronRightIcon } from '@heroicons/react/20/solid';
447
+
448
+ export interface BreadcrumbItem {
449
+ label: string;
450
+ path?: string;
451
+ }
452
+
453
+ interface PageHeaderProps {
454
+ title: string;
455
+ breadcrumbs?: BreadcrumbItem[];
456
+ actions?: React.ReactNode;
457
+ }
458
+
459
+ export function PageHeader({ title, breadcrumbs, actions }: PageHeaderProps) {
460
+ return (
461
+ <div className="mb-6">
462
+ {breadcrumbs && (
463
+ <nav className="mb-1 flex items-center gap-1 text-xs text-slate-500" aria-label="Breadcrumb">
464
+ {breadcrumbs.map((crumb, index) => (
465
+ <span key={index} className="flex items-center gap-1">
466
+ {index > 0 && <ChevronRightIcon className="h-3.5 w-3.5 text-slate-400" />}
467
+ {crumb.path ? (
468
+ <Link to={crumb.path} className="hover:text-primary hover:underline">
469
+ {crumb.label}
470
+ </Link>
471
+ ) : (
472
+ <span className="text-slate-700 dark:text-slate-300">{crumb.label}</span>
473
+ )}
474
+ </span>
475
+ ))}
476
+ </nav>
477
+ )}
478
+ <div className="flex items-center justify-between gap-3">
479
+ <h1 className="text-xl font-semibold text-slate-900 dark:text-slate-100">{title}</h1>
480
+ {actions && <div className="flex items-center gap-2">{actions}</div>}
481
+ </div>
482
+ </div>
483
+ );
484
+ }
485
+ ```
486
+
487
+ ---
488
+
489
+ ## Pagination
490
+
491
+ ```tsx
492
+ // src/shared/components/Pagination.tsx
493
+ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/20/solid';
494
+ import { Button } from './Button';
495
+
496
+ interface PaginationProps {
497
+ /** Zero-based current page index */
498
+ page: number;
499
+ size: number;
500
+ totalElements: number;
501
+ onPageChange: (page: number) => void;
502
+ onSizeChange?: (size: number) => void;
503
+ }
504
+
505
+ export function Pagination({ page, size, totalElements, onPageChange, onSizeChange }: PaginationProps) {
506
+ const totalPages = Math.max(1, Math.ceil(totalElements / size));
507
+ const from = totalElements === 0 ? 0 : page * size + 1;
508
+ const to = Math.min((page + 1) * size, totalElements);
509
+
510
+ return (
511
+ <div className="flex flex-wrap items-center justify-between gap-3 border-t border-slate-200 px-4 py-3 text-sm dark:border-slate-700">
512
+ <p className="text-slate-600 dark:text-slate-300">
513
+ Showing <span className="font-medium">{from}</span>–<span className="font-medium">{to}</span> of{' '}
514
+ <span className="font-medium">{totalElements}</span>
515
+ </p>
516
+ <div className="flex items-center gap-3">
517
+ {onSizeChange && (
518
+ <select
519
+ value={size}
520
+ onChange={(e) => onSizeChange(Number(e.target.value))}
521
+ className="rounded border border-slate-300 bg-surface px-2 py-1 text-sm focus:border-primary focus:ring-1 focus:ring-primary dark:border-slate-600"
522
+ aria-label="Rows per page"
523
+ >
524
+ {[10, 25, 50].map((n) => (
525
+ <option key={n} value={n}>
526
+ {n} / page
527
+ </option>
528
+ ))}
529
+ </select>
530
+ )}
531
+ <div className="flex items-center gap-1">
532
+ <Button variant="outline" size="icon" disabled={page <= 0} onClick={() => onPageChange(page - 1)} aria-label="Previous page">
533
+ <ChevronLeftIcon className="h-4 w-4" />
534
+ </Button>
535
+ <span className="px-2 text-slate-600 dark:text-slate-300">
536
+ {page + 1} / {totalPages}
537
+ </span>
538
+ <Button variant="outline" size="icon" disabled={page + 1 >= totalPages} onClick={() => onPageChange(page + 1)} aria-label="Next page">
539
+ <ChevronRightIcon className="h-4 w-4" />
540
+ </Button>
541
+ </div>
542
+ </div>
543
+ </div>
544
+ );
545
+ }
546
+ ```
547
+
548
+ ---
549
+
550
+ ## DataTable (TanStack Table wrapper) [Include when DataGrid = yes]
551
+
552
+ ```tsx
553
+ // src/shared/components/DataTable.tsx
554
+ import {
555
+ flexRender,
556
+ getCoreRowModel,
557
+ getSortedRowModel,
558
+ useReactTable,
559
+ type ColumnDef,
560
+ type SortingState,
561
+ } from '@tanstack/react-table';
562
+ import { useState } from 'react';
563
+ import { ChevronUpDownIcon } from '@heroicons/react/20/solid';
564
+ import { cn } from '../../lib/utils/cn';
565
+
566
+ interface DataTableProps<TData> {
567
+ columns: ColumnDef<TData, unknown>[];
568
+ data: TData[];
569
+ /** Optional empty-state message */
570
+ emptyMessage?: string;
571
+ }
572
+
573
+ export function DataTable<TData>({ columns, data, emptyMessage = 'No records found.' }: DataTableProps<TData>) {
574
+ const [sorting, setSorting] = useState<SortingState>([]);
575
+
576
+ const table = useReactTable({
577
+ data,
578
+ columns,
579
+ state: { sorting },
580
+ onSortingChange: setSorting,
581
+ getCoreRowModel: getCoreRowModel(),
582
+ getSortedRowModel: getSortedRowModel(),
583
+ });
584
+
585
+ return (
586
+ <div className="overflow-x-auto">
587
+ <table className="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
588
+ <thead className="bg-canvas">
589
+ {table.getHeaderGroups().map((headerGroup) => (
590
+ <tr key={headerGroup.id}>
591
+ {headerGroup.headers.map((header) => {
592
+ const canSort = header.column.getCanSort();
593
+ return (
594
+ <th
595
+ key={header.id}
596
+ className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-600 dark:text-slate-300"
597
+ >
598
+ <button
599
+ type="button"
600
+ disabled={!canSort}
601
+ onClick={header.column.getToggleSortingHandler()}
602
+ className={cn('flex items-center gap-1', canSort && 'cursor-pointer select-none')}
603
+ >
604
+ {flexRender(header.column.columnDef.header, header.getContext())}
605
+ {canSort && <ChevronUpDownIcon className="h-3.5 w-3.5 text-slate-400" />}
606
+ </button>
607
+ </th>
608
+ );
609
+ })}
610
+ </tr>
611
+ ))}
612
+ </thead>
613
+ <tbody className="divide-y divide-slate-100 dark:divide-slate-800">
614
+ {table.getRowModel().rows.length === 0 ? (
615
+ <tr>
616
+ <td colSpan={columns.length} className="px-4 py-8 text-center text-sm text-slate-500">
617
+ {emptyMessage}
618
+ </td>
619
+ </tr>
620
+ ) : (
621
+ table.getRowModel().rows.map((row) => (
622
+ <tr key={row.id} className="hover:bg-canvas/60">
623
+ {row.getVisibleCells().map((cell) => (
624
+ <td key={cell.id} className="px-4 py-3 text-sm text-slate-700 dark:text-slate-200">
625
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
626
+ </td>
627
+ ))}
628
+ </tr>
629
+ ))
630
+ )}
631
+ </tbody>
632
+ </table>
633
+ </div>
634
+ );
635
+ }
636
+ ```
637
+
638
+ ---
639
+
640
+ ## LoadingOverlay & EmptyState
641
+
642
+ ```tsx
643
+ // src/shared/components/LoadingOverlay.tsx
644
+ import { Spinner } from './Spinner';
645
+
646
+ export function LoadingOverlay({ message }: { message?: string }) {
647
+ return (
648
+ <div className="flex min-h-[40vh] flex-col items-center justify-center gap-3 text-slate-500">
649
+ <Spinner className="h-8 w-8 text-primary" />
650
+ {message && <p className="text-sm">{message}</p>}
651
+ </div>
652
+ );
653
+ }
654
+ ```
655
+
656
+ ```tsx
657
+ // src/shared/components/EmptyState.tsx
658
+ import { InboxIcon } from '@heroicons/react/24/outline';
659
+
660
+ export function EmptyState({ message, action }: { message: string; action?: React.ReactNode }) {
661
+ return (
662
+ <div className="flex flex-col items-center justify-center gap-3 py-12 text-center text-slate-500">
663
+ <InboxIcon className="h-10 w-10 text-slate-300" />
664
+ <p className="text-sm">{message}</p>
665
+ {action}
666
+ </div>
667
+ );
668
+ }
669
+ ```