@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,503 @@
1
+ # Web Application Patterns — Layouts, Components, and Frontend
2
+
3
+ This reference describes the web-specific patterns for the server-rendered monolith.
4
+ Include this content in Sections 7-12 of the generated specification.
5
+
6
+ ---
7
+
8
+ ## Architecture Overview
9
+
10
+ The web application uses a layered composition model:
11
+
12
+ ```
13
+ Request → SecurityFilter → CspNonceFilter → @Controller
14
+ → Service (queries MongoDB via Repository)
15
+ → View Model (encapsulates all template data)
16
+ → JTE Template (renders HTML using Tailwind CSS)
17
+ → Response (full page or htmx fragment)
18
+ ```
19
+
20
+ JTE templates are organized to mirror Java package names:
21
+ - `src/main/jte/{{BASE_PACKAGE_PATH}}/shared/layout/` — Layouts
22
+ - `src/main/jte/{{BASE_PACKAGE_PATH}}/shared/fragment/` — Shared fragments
23
+ - `src/main/jte/{{BASE_PACKAGE_PATH}}/shared/component/` — Reusable components
24
+ - `src/main/jte/{{BASE_PACKAGE_PATH}}/{{module}}/page/` — Module page templates
25
+ - `src/main/jte/{{BASE_PACKAGE_PATH}}/{{module}}/fragment/` — Module fragment templates
26
+
27
+ ---
28
+
29
+ ## View Model Pattern
30
+
31
+ Every JTE template receives a strongly-typed view model. Never pass raw `Map` objects
32
+ or unstructured data to templates.
33
+
34
+ ```java
35
+ // View models are records or classes with all data the template needs
36
+ public record OrderListView(
37
+ List<OrderDTO> items,
38
+ Pagination pagination,
39
+ String theme,
40
+ UserProfile user
41
+ ) {}
42
+ ```
43
+
44
+ Layouts are also view models:
45
+
46
+ ```java
47
+ @Getter @Setter
48
+ public class MainLayout {
49
+ private final String title;
50
+ private final List<NavItem> navItems;
51
+ private final UserProfile user;
52
+ private final String theme;
53
+ private final String cspNonce;
54
+ private final ViteManifestService vite;
55
+
56
+ public static MainLayout defaultPage(String title, HttpServletRequest req,
57
+ ViteManifestService vite, List<NavItem> navItems) {
58
+ // Extract theme from cookie, user from JWT, nonce from request attribute
59
+ }
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Page Composition Flow
66
+
67
+ 1. Controller extracts pagination, theme, and user from request
68
+ 2. Controller calls service to get data
69
+ 3. Controller builds view model with all needed data
70
+ 4. Controller adds layout and view to Spring Model
71
+ 5. JTE layout wraps the page content
72
+ 6. JTE page renders view model data using shared components
73
+
74
+ ```
75
+ MainLayout.jte (@param Content content)
76
+ ├── HeaderFragment.jte (@template. call)
77
+ ├── SidebarFragment.jte (@template. call)
78
+ ├── <main> content area
79
+ │ └── ${content} ← page body injected via content = @`...`
80
+ │ └── OrderListPage.jte
81
+ │ └── Pagination.jte (@template. call)
82
+ │ └── OrderListFragment.jte (@template. call)
83
+ │ └── Pagination.jte (@template. call)
84
+ ├── FooterFragment.jte (@template. call, inside <main>)
85
+ └── ToastContainer.jte (@template. call)
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Tailwind CSS Component Strategy
91
+
92
+ All UI components use pure Tailwind CSS utility classes. No DaisyUI or other component
93
+ framework is used.
94
+
95
+ ### Design Tokens via CSS Custom Properties
96
+
97
+ Define a semantic color system using CSS custom properties that Tailwind references:
98
+
99
+ ```css
100
+ /* themes/light.css */
101
+ [data-theme="light"] {
102
+ --color-bg-primary: theme('colors.white');
103
+ --color-bg-secondary: theme('colors.gray.50');
104
+ --color-text-primary: theme('colors.gray.900');
105
+ --color-text-secondary: theme('colors.gray.500');
106
+ --color-border: theme('colors.gray.200');
107
+ --color-accent: theme('colors.blue.600');
108
+ }
109
+
110
+ /* themes/dark.css */
111
+ [data-theme="dark"] {
112
+ --color-bg-primary: theme('colors.gray.900');
113
+ --color-bg-secondary: theme('colors.gray.800');
114
+ --color-text-primary: theme('colors.gray.100');
115
+ --color-text-secondary: theme('colors.gray.400');
116
+ --color-border: theme('colors.gray.700');
117
+ --color-accent: theme('colors.blue.400');
118
+ }
119
+ ```
120
+
121
+ ### Component Convention
122
+
123
+ Each Tailwind component follows this pattern:
124
+ - Java view model (record or class) defining the component's data contract
125
+ - JTE template using Tailwind utility classes with `dark:` variants
126
+ - Conditional styling via JTE expressions (switch on type/tone/variant)
127
+
128
+ Example pattern:
129
+ ```java
130
+ // Java view model
131
+ public record Alert(String type, String message) {}
132
+ ```
133
+ ```jte
134
+ // JTE template — all styling is Tailwind utilities
135
+ @param Alert alert
136
+ !{var colors = switch(alert.type()) {
137
+ case "success" -> "bg-green-50 text-green-800 dark:bg-green-900/20 dark:text-green-400";
138
+ case "error" -> "bg-red-50 text-red-800 dark:bg-red-900/20 dark:text-red-400";
139
+ default -> "bg-blue-50 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400";
140
+ };}
141
+ <div class="p-4 rounded-lg border ${colors}" role="alert">
142
+ <span class="text-sm font-medium">${alert.message()}</span>
143
+ </div>
144
+ ```
145
+
146
+ ---
147
+
148
+ ## htmx Integration Pattern
149
+
150
+ For partial page updates, controllers return HTML fragments:
151
+
152
+ ```java
153
+ // Fragment controller
154
+ @GetMapping("/search")
155
+ String search(@RequestParam String q, Model model) {
156
+ var results = service.search(q);
157
+ model.addAttribute("items", results);
158
+ return "order/fragment/OrderSearchResults";
159
+ }
160
+ ```
161
+
162
+ ```jte
163
+ // In the page template — use @template. syntax, never @include()
164
+ <input type="search" name="q"
165
+ hx-get="/orders/fragments/search"
166
+ hx-target="#results"
167
+ hx-trigger="keyup changed delay:300ms" />
168
+ <div id="results">
169
+ @for(var item : view.items())
170
+ @template.{{BASE_PACKAGE_PATH_DOT}}.order.fragment.OrderRowFragment(item = item)
171
+ @endfor
172
+ </div>
173
+ ```
174
+
175
+ ### htmx Error Handling
176
+
177
+ Global error handler detects htmx requests and returns toast triggers instead of error pages:
178
+
179
+ ```java
180
+ if ("true".equals(req.getHeader("HX-Request"))) {
181
+ return ResponseEntity.status(ex.getStatus())
182
+ .header("HX-Trigger", "{\"showToast\":{\"type\":\"error\",\"message\":\"" + ex.getUserMessage() + "\"}}")
183
+ .body("");
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Asset Resolution
190
+
191
+ `ViteManifestService` reads Vite's `manifest.json` to resolve hashed asset filenames
192
+ for production. In development mode, assets are served from Vite's dev server.
193
+
194
+ Templates include assets via the service:
195
+ ```jte
196
+ <link rel="stylesheet" href="${layout.getVite().resolveAsset("css/app.css")}" />
197
+ <script src="${layout.getVite().resolveAsset("js/app.js")}" nonce="${layout.getCspNonce()}" defer></script>
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Pagination Contract
203
+
204
+ All list endpoints must be paginated. The pagination flow:
205
+
206
+ 1. Controller implements `PaginationAware` to parse `page` and `size` query params
207
+ 2. Service returns `PaginatedResult<T>` wrapping data + pagination metadata
208
+ 3. `PaginatedResult.toPagination(baseHref)` creates the `Pagination` view model
209
+ 4. JTE template renders `Pagination` component above and below the listing
210
+
211
+ Default page size: 20. Maximum page size: 100.
212
+
213
+ ---
214
+
215
+ ## Sidebar Fragment Pattern
216
+
217
+ The sidebar fragment renders navigation items using direct module URLs. **Never use
218
+ `/api/content/...` prefixed paths** — this pattern creates a dependency on a non-existent
219
+ content API and causes navigation failures.
220
+
221
+ Each sidebar link uses `hx-get` with the direct module URL and `hx-select="#content-area"`
222
+ to extract just the content area from the full page response. This allows HTMX to swap
223
+ the content without a full page reload.
224
+
225
+ ```jte
226
+ @import {{BASE_PACKAGE}}.shared.web.NavItem
227
+ @import java.util.List
228
+
229
+ @param List<NavItem> navItems
230
+
231
+ <aside class="fixed top-16 left-0 bottom-0 w-64 bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700 overflow-y-auto z-30">
232
+ <nav class="p-4 space-y-1">
233
+ @for(var item : navItems)
234
+ <%-- DO NOT use /api/content/ prefix — use direct module URL --%>
235
+ <a href="${item.getHref()}"
236
+ hx-get="${item.getHref()}"
237
+ hx-target="#content-area"
238
+ hx-select="#content-area"
239
+ hx-swap="innerHTML"
240
+ hx-push-url="true"
241
+ class="${item.isActive()
242
+ ? "flex items-center gap-3 px-3 py-2 rounded-lg bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 font-medium"
243
+ : "flex items-center gap-3 px-3 py-2 rounded-lg text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700"}">
244
+ <span class="text-sm">${item.getLabel()}</span>
245
+ </a>
246
+ @endfor
247
+ </nav>
248
+ </aside>
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Header Fragment Pattern
254
+
255
+ The header fragment renders the top bar with logo, notifications, and user menu. All
256
+ navigation links use direct URLs, NOT `/api/content/...` prefixed paths.
257
+
258
+ **Do NOT include a locale/language switcher** unless PRD.md has i18n requirements.
259
+ Non-functional UI elements confuse users and waste development effort.
260
+
261
+ ```jte
262
+ @import {{BASE_PACKAGE}}.shared.web.MainLayoutViewModel
263
+
264
+ @param MainLayoutViewModel layout
265
+
266
+ <header class="fixed top-0 left-0 right-0 z-40 h-16 bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 flex items-center justify-between px-6"
267
+ x-data="{ notifOpen: false, userOpen: false }">
268
+ <!-- Left: Logo & App Name -->
269
+ <div class="flex items-center gap-3">
270
+ <%-- DO NOT use /api/content/home — use direct URL --%>
271
+ <a href="/home"
272
+ hx-get="/home"
273
+ hx-target="#content-area"
274
+ hx-select="#content-area"
275
+ hx-swap="innerHTML"
276
+ hx-push-url="true"
277
+ class="flex items-center gap-3">
278
+ <div class="w-9 h-9 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center shadow-sm">
279
+ <!-- App icon SVG -->
280
+ </div>
281
+ <span class="text-lg font-semibold text-slate-800 dark:text-white tracking-tight">{{APP_NAME}}</span>
282
+ </a>
283
+ </div>
284
+
285
+ <!-- Right: Actions -->
286
+ <div class="flex items-center gap-2">
287
+ <!-- Dark Mode Toggle — MUST use $store.theme, NOT local variables -->
288
+ <button @click="$store.theme.toggle()"
289
+ class="p-2 rounded-lg text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">
290
+ <!-- Sun icon (shown in dark mode) -->
291
+ <svg x-show="$store.theme.dark" class="w-5 h-5" ...><!-- sun --></svg>
292
+ <!-- Moon icon (shown in light mode) -->
293
+ <svg x-show="!$store.theme.dark" class="w-5 h-5" ...><!-- moon --></svg>
294
+ </button>
295
+
296
+ <!-- Notification Bell -->
297
+ <div class="relative" @click.away="notifOpen = false">
298
+ <button @click="notifOpen = !notifOpen" class="relative p-2 rounded-lg ...">
299
+ <!-- Bell icon SVG -->
300
+ </button>
301
+ <div x-show="notifOpen" class="absolute right-0 mt-2 w-80 bg-white dark:bg-slate-800 rounded-xl shadow-lg ...">
302
+ <!-- Notification dropdown content -->
303
+ <div class="px-4 py-2.5 border-t">
304
+ <%-- DO NOT use /api/content/notification — use direct URL --%>
305
+ <a href="/notifications"
306
+ hx-get="/notifications"
307
+ hx-target="#content-area"
308
+ hx-select="#content-area"
309
+ hx-swap="innerHTML"
310
+ hx-push-url="true"
311
+ class="text-sm text-blue-600 hover:text-blue-700 font-medium">
312
+ View All Notifications
313
+ </a>
314
+ </div>
315
+ </div>
316
+ </div>
317
+
318
+ <!-- User Menu -->
319
+ <div class="relative" @click.away="userOpen = false">
320
+ <button @click="userOpen = !userOpen" class="flex items-center gap-2 ...">
321
+ <span class="text-sm font-medium">${layout.getUser().fullName()}</span>
322
+ </button>
323
+ <div x-show="userOpen" class="absolute right-0 mt-2 w-48 bg-white dark:bg-slate-800 rounded-xl shadow-lg ...">
324
+ <%-- DO NOT use /api/content/profile — use direct URL --%>
325
+ <a href="/profile"
326
+ hx-get="/profile"
327
+ hx-target="#content-area"
328
+ hx-select="#content-area"
329
+ hx-swap="innerHTML"
330
+ hx-push-url="true"
331
+ class="block px-4 py-2 text-sm ...">My Profile</a>
332
+ <a href="/logout" class="block px-4 py-2 text-sm ...">Logout</a>
333
+ </div>
334
+ </div>
335
+
336
+ <%-- NO locale dropdown — only include if PRD.md specifies i18n requirements --%>
337
+ </div>
338
+ </header>
339
+ ```
340
+
341
+ ---
342
+
343
+ ## Theme Switching Pattern
344
+
345
+ Theme switching uses an Alpine.js **global store** (`$store.theme`) that persists the
346
+ preference in a cookie and toggles the `dark` class on `<html>`.
347
+
348
+ **Critical:** Do NOT use local Alpine.js `x-data` variables (`darkMode`, `toggleDark`)
349
+ for theme state. Alpine.js v3 nested `x-data` components are separate scopes — a local
350
+ variable in `<html x-data="appShell()">` cannot be accessed from a nested
351
+ `<header x-data="{ ... }">`. This causes the dark mode toggle to silently fail.
352
+
353
+ ### Alpine Store (in app.js)
354
+
355
+ ```javascript
356
+ import Alpine from 'alpinejs';
357
+
358
+ Alpine.store('theme', {
359
+ dark: document.cookie.includes('theme=dark'),
360
+ toggle() {
361
+ this.dark = !this.dark;
362
+ document.documentElement.classList.toggle('dark', this.dark);
363
+ document.cookie = `theme=${this.dark ? 'dark' : 'light'};path=/;max-age=31536000`;
364
+ },
365
+ apply() {
366
+ document.documentElement.classList.toggle('dark', this.dark);
367
+ }
368
+ });
369
+
370
+ // Apply theme on load
371
+ document.addEventListener('alpine:init', () => {
372
+ Alpine.store('theme').apply();
373
+ });
374
+ ```
375
+
376
+ ### MainLayout.jte
377
+
378
+ ```html
379
+ <%-- Use $store.theme.dark, NOT a local darkMode variable --%>
380
+ <html lang="en" x-data :class="{ 'dark': $store.theme.dark }">
381
+ ```
382
+
383
+ ### Header Toggle Button
384
+
385
+ ```html
386
+ <%-- Use $store.theme.toggle(), NOT a local toggleDark() function --%>
387
+ <button @click="$store.theme.toggle()" ...>
388
+ <svg x-show="$store.theme.dark" ...><!-- sun icon --></svg>
389
+ <svg x-show="!$store.theme.dark" ...><!-- moon icon --></svg>
390
+ </button>
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Layout — Footer Positioning
396
+
397
+ When the sidebar uses `position: fixed`, the `<main>` element must have its own
398
+ `min-height` to ensure the footer stays at the bottom of the viewport even when content
399
+ is short. Without this, the footer floats up because the fixed sidebar is out of the
400
+ normal document flow.
401
+
402
+ ```html
403
+ <div class="flex pt-16 min-h-[calc(100vh-4rem)]">
404
+ <!-- Sidebar is position:fixed — it's out of flow -->
405
+ @template.fragment.Sidebar(layout = layout)
406
+
407
+ <!-- min-h required on <main> because sidebar is position:fixed (out of flow) -->
408
+ <main class="flex-1 ml-64 flex flex-col min-w-0 min-h-[calc(100vh-4rem)]">
409
+ <div id="content-area" class="flex-1 flex flex-col">
410
+ ${content}
411
+ </div>
412
+ @template.fragment.Footer()
413
+ </main>
414
+ </div>
415
+ ```
416
+
417
+ Key points:
418
+ - `min-h-[calc(100vh-4rem)]` on BOTH the wrapper div AND `<main>`
419
+ - `flex-col` on `<main>` with `flex-1` on `#content-area` pushes footer to bottom
420
+ - Footer is INSIDE `<main>`, not outside, so it scrolls with content
421
+
422
+ ---
423
+
424
+ ## Role-Based Navigation Filtering
425
+
426
+ The `LayoutService` (or equivalent) that builds sidebar navigation items MUST filter
427
+ by the current user's roles. The role-to-menu mapping is derived from the mockup
428
+ sidebar files — each role folder defines which menu items that role sees.
429
+
430
+ ```java
431
+ package {{BASE_PACKAGE}}.shared.web;
432
+
433
+ import {{BASE_PACKAGE}}.shared.security.Roles;
434
+ import {{BASE_PACKAGE}}.shared.security.SecurityContextUtil;
435
+ import lombok.RequiredArgsConstructor;
436
+ import org.springframework.stereotype.Service;
437
+
438
+ import java.util.ArrayList;
439
+ import java.util.List;
440
+ import java.util.Set;
441
+
442
+ @Service
443
+ @RequiredArgsConstructor
444
+ public class LayoutService {
445
+
446
+ private final SecurityContextUtil securityContextUtil;
447
+
448
+ public List<NavItem> buildNavItems(String currentPath) {
449
+ Set<String> userRoles = securityContextUtil.getCurrentRoles();
450
+ List<NavItem> items = new ArrayList<>();
451
+
452
+ // Home — visible to all authenticated users
453
+ items.add(NavItem.builder()
454
+ .label("Home").href("/home").section("")
455
+ .active("/home".equals(currentPath))
456
+ .build());
457
+
458
+ // Role-specific menu items
459
+ // Derive from mockup sidebar files: mockup/<role>/sidebar-<role>.html
460
+
461
+ if (userRoles.contains(Roles.HUB_ADMINISTRATOR)) {
462
+ // System / Configuration items for admin
463
+ // e.g., Audit Trail, Corridor, Recruitment Step
464
+ }
465
+
466
+ if (userRoles.contains(Roles.HUB_OPERATION_SUPPORT)) {
467
+ // Operations items for support
468
+ // e.g., Employer, Recruitment Agent, Job Demand, etc.
469
+ }
470
+
471
+ return items;
472
+ }
473
+ }
474
+ ```
475
+
476
+ **Never show all menu items to all users.** Even if the backend enforces access control
477
+ via `@PreAuthorize`, showing menu items the user cannot access creates a confusing UX
478
+ and may expose unauthorized functionality.
479
+
480
+ ---
481
+
482
+ ## JSON Display Pattern
483
+
484
+ When rendering raw JSON content (e.g., message payloads, audit trail changes, API
485
+ responses), use Alpine.js to pretty-print on initialization. This provides a readable,
486
+ well-formatted display without requiring server-side formatting.
487
+
488
+ ```html
489
+ <!-- For raw JSON display, use Alpine.js to pretty-print on init -->
490
+ <pre class="bg-slate-50 dark:bg-slate-800 p-4 rounded-lg overflow-auto text-sm font-mono max-h-96">
491
+ <code x-data x-init="try { $el.textContent = JSON.stringify(JSON.parse($el.textContent), null, 2) } catch(e) {}">
492
+ ${rawJsonContent}
493
+ </code>
494
+ </pre>
495
+ ```
496
+
497
+ Key points:
498
+ - `x-init` runs once when the element is initialized
499
+ - `JSON.parse` + `JSON.stringify(..., null, 2)` produces indented output
500
+ - `try/catch` gracefully handles non-JSON content (falls back to raw text)
501
+ - Use `$el.textContent` (not `innerHTML`) to avoid XSS and HTML entity issues
502
+ - For `<pre><code>` pattern: format the `<code>` element's textContent
503
+ - For `<pre>` only pattern: format the `<pre>` element's textContent directly