@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.
- package/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- 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
|