@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,1268 @@
|
|
|
1
|
+
# Specification Template — Laravel Web Monolith
|
|
2
|
+
|
|
3
|
+
This is the authoritative template for the generated specification. The specification is
|
|
4
|
+
split into **two types of files**:
|
|
5
|
+
|
|
6
|
+
1. **`SPECIFICATION.md`** (root) — Table of Contents, shared infrastructure, and all
|
|
7
|
+
application-level sections. Generated once per application.
|
|
8
|
+
2. **`<module>/SPEC.md`** (per-module) — Self-contained module blueprint.
|
|
9
|
+
Generated once per module from PRD.md.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
spec/
|
|
13
|
+
├── SPECIFICATION.md
|
|
14
|
+
├── location-information/
|
|
15
|
+
│ └── SPEC.md
|
|
16
|
+
├── corridor/
|
|
17
|
+
│ └── SPEC.md
|
|
18
|
+
└── ...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Placeholders use `{{VARIABLE}}` syntax and must be replaced with actual values gathered
|
|
22
|
+
from context files.
|
|
23
|
+
|
|
24
|
+
Sections marked **[CONDITIONAL]** should only be included when the corresponding
|
|
25
|
+
integration is selected. Within unconditional sections, some content blocks are
|
|
26
|
+
conditional — follow the inline guidance.
|
|
27
|
+
|
|
28
|
+
**CRITICAL: Sample code requirement.** Every component in the spec MUST include a complete,
|
|
29
|
+
continuous code sample in a fenced code block (PHP, Blade, JavaScript, YAML, or HTML).
|
|
30
|
+
The code must be self-explanatory and directly usable as a reference for a coding agent.
|
|
31
|
+
Do not describe components with bullet points alone — always accompany descriptions with
|
|
32
|
+
full code.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Part A: Root SPECIFICATION.md
|
|
37
|
+
|
|
38
|
+
Everything from this point until "Part B" goes into the root `SPECIFICATION.md` file.
|
|
39
|
+
A coding agent implements these shared sections **first**, before any module work.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Table of Contents
|
|
44
|
+
|
|
45
|
+
Generate a TOC with clickable Markdown anchor links to every H2 and H3 section in this
|
|
46
|
+
file. Additionally, include a **Modules** section in the TOC that links to each
|
|
47
|
+
module's `SPEC.md` file:
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
## Table of Contents
|
|
51
|
+
|
|
52
|
+
### Shared Infrastructure
|
|
53
|
+
- [1. Project Overview](#1-project-overview)
|
|
54
|
+
- [2. Composer & npm Configuration](#2-composer--npm-configuration)
|
|
55
|
+
- ...all shared sections...
|
|
56
|
+
|
|
57
|
+
### Modules
|
|
58
|
+
- [Location Information](location-information/SPEC.md)
|
|
59
|
+
- [Corridor](corridor/SPEC.md)
|
|
60
|
+
- [Employer](employer/SPEC.md)
|
|
61
|
+
- ...(one link per module)...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Only include conditional sections that apply based on user selections.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Section 1: Project Overview
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
# {{APPLICATION_NAME}} — Technical Specification
|
|
72
|
+
|
|
73
|
+
## 1. Project Overview
|
|
74
|
+
|
|
75
|
+
**Application Name**: {{APPLICATION_NAME}} (from CLAUDE.md Custom Applications section)
|
|
76
|
+
**Project Slug**: {{PROJECT_SLUG}} (kebab-case of application name)
|
|
77
|
+
**PHP Version**: 8.3+
|
|
78
|
+
**Laravel Version**: 12.x
|
|
79
|
+
**Description**: {{APP_DESCRIPTION}} (from CLAUDE.md application description)
|
|
80
|
+
**Versions Covered**: v1.0.0 — v{{LATEST_VERSION}}
|
|
81
|
+
|
|
82
|
+
### Optional Components (Auto-Determined)
|
|
83
|
+
**Database**: {{DATABASE}} (from CLAUDE.md dependencies)
|
|
84
|
+
**Authentication**: {{AUTH}} (from CLAUDE.md dependencies + PRD.md NFRs)
|
|
85
|
+
**Scheduling**: {{SCHEDULING}} (from PRD.md NFRs)
|
|
86
|
+
**Messaging**: {{MESSAGING}} (from CLAUDE.md dependencies)
|
|
87
|
+
**Internationalisation**: {{I18N}} (from PRD.md language requirements; e.g., `yes (en, ms)` or `no`)
|
|
88
|
+
|
|
89
|
+
### Technology Stack
|
|
90
|
+
(Render the core stack version table from SKILL.md, plus selected optional integration versions)
|
|
91
|
+
|
|
92
|
+
### User Roles
|
|
93
|
+
(List each user role extracted from mockup sidebar files, e.g., Hub Administrator, Hub Operation Support.
|
|
94
|
+
Include a role-to-module mapping table showing which modules each role can access.
|
|
95
|
+
This mapping drives middleware('role:...') on routes — NOT URL path prefixes.)
|
|
96
|
+
|
|
97
|
+
| Role | Modules | Permission Role |
|
|
98
|
+
|------|---------|----------------|
|
|
99
|
+
| Hub Administrator | Location Information, Corridor, Recruitment Step, ... | `HUB_ADMINISTRATOR` |
|
|
100
|
+
| Hub Operation Support | Employer, Quota, Quota Allocation, ... | `HUB_OPERATION_SUPPORT` |
|
|
101
|
+
|
|
102
|
+
### Sidebar Navigation Items
|
|
103
|
+
(For each role, list the navigation items with module-based hrefs — NOT role-prefixed hrefs.)
|
|
104
|
+
|
|
105
|
+
**Hub Administrator:**
|
|
106
|
+
| Label | Href | Section |
|
|
107
|
+
|-------|------|---------|
|
|
108
|
+
| Location Information | `/location-information` | Geography |
|
|
109
|
+
| Corridor | `/corridor` | Administration |
|
|
110
|
+
| ... | ... | ... |
|
|
111
|
+
|
|
112
|
+
### Modules
|
|
113
|
+
(List each module from PRD.md organized by System Module and Business Module,
|
|
114
|
+
with description from the module heading and user story count from MODEL.md summary table.
|
|
115
|
+
Each module links to its dedicated SPEC.md file.)
|
|
116
|
+
|
|
117
|
+
| Module | Type | Stories | Versions | Spec |
|
|
118
|
+
|--------|------|---------|----------|------|
|
|
119
|
+
| Location Information | Business | 12 | 1.0.0, 1.0.1, 1.0.3 | [SPEC](location-information/SPEC.md) |
|
|
120
|
+
| Corridor | Business | 8 | 1.0.0, 1.0.3 | [SPEC](corridor/SPEC.md) |
|
|
121
|
+
|
|
122
|
+
### Input Sources
|
|
123
|
+
(List the context files read to produce this specification)
|
|
124
|
+
- CLAUDE.md: {{path}}
|
|
125
|
+
- PRD.md: {{path}}
|
|
126
|
+
- Module Model: {{path to MODEL.md}}
|
|
127
|
+
- Mockup Screens: {{path to MOCKUP.html}}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Section 2: Composer & npm Configuration
|
|
133
|
+
|
|
134
|
+
### 2.1 composer.json
|
|
135
|
+
|
|
136
|
+
Generate a complete `composer.json` inside a code block. This section is critical — it must
|
|
137
|
+
be copy-pasteable and immediately functional.
|
|
138
|
+
|
|
139
|
+
Include:
|
|
140
|
+
- `"require"`:
|
|
141
|
+
- **Core**: `laravel/framework`, `nwidart/laravel-modules`, `spatie/laravel-data`, `spatie/laravel-view-models`
|
|
142
|
+
- **[If Database = MongoDB]** `mongodb/laravel-mongodb`
|
|
143
|
+
- **[If Auth = Keycloak]** `laravel/socialite`, `socialiteproviders/keycloak`
|
|
144
|
+
- **[If Auth = form]** `laravel/breeze`
|
|
145
|
+
- **RBAC**: `spatie/laravel-permission`
|
|
146
|
+
- **Audit**: `owen-it/laravel-auditing`
|
|
147
|
+
- **Health**: `spatie/laravel-health`
|
|
148
|
+
- **[If Messaging = yes]** `vladimir-yuldashev/laravel-queue-rabbitmq`, `php-amqplib/php-amqplib`
|
|
149
|
+
- **[If Reporting = yes]** `spatie/browsershot`, `maatwebsite/excel`
|
|
150
|
+
- `"require-dev"`:
|
|
151
|
+
- `phpunit/phpunit`, `phpat/phpat`, `laravel/pint`
|
|
152
|
+
|
|
153
|
+
### 2.2 package.json
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"private": true,
|
|
158
|
+
"type": "module",
|
|
159
|
+
"scripts": {
|
|
160
|
+
"dev": "vite",
|
|
161
|
+
"build": "vite build"
|
|
162
|
+
},
|
|
163
|
+
"devDependencies": {
|
|
164
|
+
"laravel-vite-plugin": "^1.2",
|
|
165
|
+
"vite": "^6.0",
|
|
166
|
+
"tailwindcss": "^4.0",
|
|
167
|
+
"@tailwindcss/vite": "^4.0",
|
|
168
|
+
"autoprefixer": "^10.4",
|
|
169
|
+
"postcss": "^8.4"
|
|
170
|
+
},
|
|
171
|
+
"dependencies": {
|
|
172
|
+
"alpinejs": "^3.14",
|
|
173
|
+
"htmx.org": "^2.0"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Section 3: Application Configuration
|
|
181
|
+
|
|
182
|
+
Generate complete configuration files.
|
|
183
|
+
|
|
184
|
+
**IMPORTANT: Environment variable externalization.** All configuration values that may
|
|
185
|
+
change between environments (ports, hostnames, credentials, URIs) MUST be externalized
|
|
186
|
+
to environment variables. Laravel's `env('VAR', 'default')` helper is used in config
|
|
187
|
+
files. The `.env` file provides local development defaults, while production deployments
|
|
188
|
+
override via system environment variables or `.env.production`.
|
|
189
|
+
|
|
190
|
+
### .env (default)
|
|
191
|
+
|
|
192
|
+
**Always include:**
|
|
193
|
+
```env
|
|
194
|
+
APP_NAME={{APPLICATION_NAME}}
|
|
195
|
+
APP_ENV=local
|
|
196
|
+
APP_KEY=
|
|
197
|
+
APP_DEBUG=true
|
|
198
|
+
APP_URL=http://localhost:8000
|
|
199
|
+
|
|
200
|
+
LOG_CHANNEL=stack
|
|
201
|
+
LOG_LEVEL=debug
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**[If Database = MongoDB] add:**
|
|
205
|
+
```env
|
|
206
|
+
DB_CONNECTION=mongodb
|
|
207
|
+
DB_URI=mongodb://localhost:27017/{{DB_NAME}}
|
|
208
|
+
DB_DATABASE={{DB_NAME}}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**[If Database = PostgreSQL] add:**
|
|
212
|
+
```env
|
|
213
|
+
DB_CONNECTION=pgsql
|
|
214
|
+
DB_HOST=localhost
|
|
215
|
+
DB_PORT=5432
|
|
216
|
+
DB_DATABASE={{DB_NAME}}
|
|
217
|
+
DB_USERNAME={{DB_USER}}
|
|
218
|
+
DB_PASSWORD={{DB_PASSWORD}}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**[If Database = MySQL] add:**
|
|
222
|
+
```env
|
|
223
|
+
DB_CONNECTION=mysql
|
|
224
|
+
DB_HOST=localhost
|
|
225
|
+
DB_PORT=3306
|
|
226
|
+
DB_DATABASE={{DB_NAME}}
|
|
227
|
+
DB_USERNAME={{DB_USER}}
|
|
228
|
+
DB_PASSWORD={{DB_PASSWORD}}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**[If Auth = Keycloak] add:**
|
|
232
|
+
```env
|
|
233
|
+
KEYCLOAK_CLIENT_ID={{KEYCLOAK_CLIENT_ID}}
|
|
234
|
+
KEYCLOAK_CLIENT_SECRET={{KEYCLOAK_CLIENT_SECRET}}
|
|
235
|
+
KEYCLOAK_REALM={{KEYCLOAK_REALM}}
|
|
236
|
+
KEYCLOAK_BASE_URL=http://localhost:8180
|
|
237
|
+
KEYCLOAK_REDIRECT_URI=${APP_URL}/auth/callback
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**[If Messaging = yes] add:**
|
|
241
|
+
```env
|
|
242
|
+
RABBITMQ_HOST=localhost
|
|
243
|
+
RABBITMQ_PORT=5672
|
|
244
|
+
RABBITMQ_USER=guest
|
|
245
|
+
RABBITMQ_PASSWORD=guest
|
|
246
|
+
RABBITMQ_VHOST=/
|
|
247
|
+
RABBITMQ_QUEUE=default
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### config/database.php
|
|
251
|
+
|
|
252
|
+
**[If Database = MongoDB] add connection:**
|
|
253
|
+
```php
|
|
254
|
+
'mongodb' => [
|
|
255
|
+
'driver' => 'mongodb',
|
|
256
|
+
'dsn' => env('DB_URI', 'mongodb://localhost:27017'),
|
|
257
|
+
'database' => env('DB_DATABASE', '{{DB_NAME}}'),
|
|
258
|
+
],
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**[If Database = PostgreSQL] add connection:**
|
|
262
|
+
```php
|
|
263
|
+
'pgsql' => [
|
|
264
|
+
'driver' => 'pgsql',
|
|
265
|
+
'host' => env('DB_HOST', 'localhost'),
|
|
266
|
+
'port' => env('DB_PORT', '5432'),
|
|
267
|
+
'database' => env('DB_DATABASE', '{{DB_NAME}}'),
|
|
268
|
+
'username' => env('DB_USERNAME', '{{DB_USER}}'),
|
|
269
|
+
'password' => env('DB_PASSWORD', ''),
|
|
270
|
+
'charset' => env('DB_CHARSET', 'utf8'),
|
|
271
|
+
'prefix' => '',
|
|
272
|
+
'schema' => 'public',
|
|
273
|
+
'sslmode' => env('DB_SSLMODE', 'prefer'),
|
|
274
|
+
],
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**[If Database = MySQL] add connection:**
|
|
278
|
+
```php
|
|
279
|
+
'mysql' => [
|
|
280
|
+
'driver' => 'mysql',
|
|
281
|
+
'host' => env('DB_HOST', 'localhost'),
|
|
282
|
+
'port' => env('DB_PORT', '3306'),
|
|
283
|
+
'database' => env('DB_DATABASE', '{{DB_NAME}}'),
|
|
284
|
+
'username' => env('DB_USERNAME', '{{DB_USER}}'),
|
|
285
|
+
'password' => env('DB_PASSWORD', ''),
|
|
286
|
+
'unix_socket' => env('DB_SOCKET', ''),
|
|
287
|
+
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
|
288
|
+
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
|
289
|
+
'prefix' => '',
|
|
290
|
+
'strict' => true,
|
|
291
|
+
'engine' => null,
|
|
292
|
+
],
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**[If Auth = Keycloak] add to config/services.php:**
|
|
296
|
+
```php
|
|
297
|
+
'keycloak' => [
|
|
298
|
+
'client_id' => env('KEYCLOAK_CLIENT_ID'),
|
|
299
|
+
'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
|
|
300
|
+
'realm' => env('KEYCLOAK_REALM'),
|
|
301
|
+
'base_url' => env('KEYCLOAK_BASE_URL', 'http://localhost:8180'),
|
|
302
|
+
'redirect' => env('KEYCLOAK_REDIRECT_URI', env('APP_URL') . '/auth/callback'),
|
|
303
|
+
],
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**[If Messaging = yes] add to config/messaging.php:**
|
|
307
|
+
```php
|
|
308
|
+
return [
|
|
309
|
+
'rabbitmq' => [
|
|
310
|
+
'host' => env('RABBITMQ_HOST', 'localhost'),
|
|
311
|
+
'port' => env('RABBITMQ_PORT', 5672),
|
|
312
|
+
'user' => env('RABBITMQ_USER', 'guest'),
|
|
313
|
+
'password' => env('RABBITMQ_PASSWORD', 'guest'),
|
|
314
|
+
'vhost' => env('RABBITMQ_VHOST', '/'),
|
|
315
|
+
'queue' => env('RABBITMQ_QUEUE', 'default'),
|
|
316
|
+
],
|
|
317
|
+
];
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### config/logging.php
|
|
321
|
+
|
|
322
|
+
```php
|
|
323
|
+
'channels' => [
|
|
324
|
+
'stack' => [
|
|
325
|
+
'driver' => 'stack',
|
|
326
|
+
'channels' => ['daily'],
|
|
327
|
+
],
|
|
328
|
+
'daily' => [
|
|
329
|
+
'driver' => 'daily',
|
|
330
|
+
'path' => storage_path('logs/laravel.log'),
|
|
331
|
+
'level' => env('LOG_LEVEL', 'debug'),
|
|
332
|
+
'days' => 14,
|
|
333
|
+
],
|
|
334
|
+
],
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Section 4: Build & Tooling
|
|
340
|
+
|
|
341
|
+
### 4.1 Resource Directory Structure
|
|
342
|
+
```
|
|
343
|
+
resources/
|
|
344
|
+
├── css/
|
|
345
|
+
│ ├── app.css # Main entry — imports Tailwind + partials
|
|
346
|
+
│ ├── base/
|
|
347
|
+
│ │ ├── reset.css # Tailwind @layer base
|
|
348
|
+
│ │ └── typography.css # Font imports, text defaults
|
|
349
|
+
│ ├── components/
|
|
350
|
+
│ │ ├── buttons.css # @layer components — button styles
|
|
351
|
+
│ │ ├── forms.css # Input, select, textarea styles
|
|
352
|
+
│ │ ├── cards.css # Card component styles
|
|
353
|
+
│ │ ├── tables.css # Table styles
|
|
354
|
+
│ │ ├── modals.css # Modal/dialog styles
|
|
355
|
+
│ │ └── navigation.css # Nav, breadcrumb, tabs styles
|
|
356
|
+
│ ├── layouts/
|
|
357
|
+
│ │ ├── sidebar.css # Sidebar responsive behavior
|
|
358
|
+
│ │ ├── navbar.css # Header/navbar styles
|
|
359
|
+
│ │ └── footer.css # Footer styles
|
|
360
|
+
│ └── themes/
|
|
361
|
+
│ ├── light.css # Light theme CSS custom properties
|
|
362
|
+
│ └── dark.css # Dark theme CSS custom properties
|
|
363
|
+
├── js/
|
|
364
|
+
│ ├── app.js # Entry — registers Alpine, htmx, global stores
|
|
365
|
+
│ ├── alpine/
|
|
366
|
+
│ │ ├── stores/
|
|
367
|
+
│ │ │ ├── toast.js # Toast notification store
|
|
368
|
+
│ │ │ ├── modal.js # Modal state store
|
|
369
|
+
│ │ │ ├── drawer.js # Sidebar drawer store
|
|
370
|
+
│ │ │ └── theme.js # Theme toggle store
|
|
371
|
+
│ │ └── components/
|
|
372
|
+
│ │ ├── dropdown.js # Dropdown behavior
|
|
373
|
+
│ │ ├── tabs.js # Tab switching
|
|
374
|
+
│ │ └── collapse.js # Accordion/collapse
|
|
375
|
+
│ └── htmx/
|
|
376
|
+
│ ├── index.js # Global error/loading handlers
|
|
377
|
+
│ └── extensions/
|
|
378
|
+
│ ├── toast-errors.js # Map htmx errors to toast notifications
|
|
379
|
+
│ └── loading-states.js # Toggle loading classes during requests
|
|
380
|
+
└── views/
|
|
381
|
+
├── layouts/
|
|
382
|
+
│ ├── app.blade.php # Main authenticated layout
|
|
383
|
+
│ ├── auth.blade.php # [If Auth != none] Login layout
|
|
384
|
+
│ └── error.blade.php # Error layout
|
|
385
|
+
├── partials/
|
|
386
|
+
│ ├── header.blade.php # Header bar
|
|
387
|
+
│ ├── sidebar.blade.php # Sidebar navigation
|
|
388
|
+
│ ├── footer.blade.php # Footer
|
|
389
|
+
│ └── theme-switcher.blade.php
|
|
390
|
+
└── components/
|
|
391
|
+
├── alert.blade.php
|
|
392
|
+
├── avatar.blade.php
|
|
393
|
+
├── badge.blade.php
|
|
394
|
+
├── breadcrumb.blade.php
|
|
395
|
+
├── button.blade.php
|
|
396
|
+
├── card.blade.php
|
|
397
|
+
├── form-control.blade.php
|
|
398
|
+
├── input.blade.php
|
|
399
|
+
├── select.blade.php
|
|
400
|
+
├── loading.blade.php
|
|
401
|
+
├── modal.blade.php
|
|
402
|
+
├── pagination.blade.php
|
|
403
|
+
├── table.blade.php
|
|
404
|
+
├── tabs.blade.php
|
|
405
|
+
└── toast-container.blade.php
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### 4.2 Vite Configuration
|
|
409
|
+
```js
|
|
410
|
+
// vite.config.js
|
|
411
|
+
import { defineConfig } from 'vite';
|
|
412
|
+
import laravel from 'laravel-vite-plugin';
|
|
413
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
414
|
+
|
|
415
|
+
export default defineConfig({
|
|
416
|
+
plugins: [
|
|
417
|
+
laravel({
|
|
418
|
+
input: ['resources/css/app.css', 'resources/js/app.js'],
|
|
419
|
+
refresh: true,
|
|
420
|
+
}),
|
|
421
|
+
tailwindcss(),
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### 4.3 Tailwind Configuration
|
|
427
|
+
```js
|
|
428
|
+
// tailwind.config.js
|
|
429
|
+
export default {
|
|
430
|
+
content: [
|
|
431
|
+
'./resources/**/*.blade.php',
|
|
432
|
+
'./resources/**/*.js',
|
|
433
|
+
'./Modules/*/resources/views/**/*.blade.php',
|
|
434
|
+
],
|
|
435
|
+
darkMode: ['class', '[data-theme="dark"]'],
|
|
436
|
+
theme: {
|
|
437
|
+
extend: {
|
|
438
|
+
colors: {
|
|
439
|
+
primary: { 50: '#eff6ff', /* ... full scale */ 900: '#1e3a5f' },
|
|
440
|
+
accent: { 50: '#f0fdf4', /* ... full scale */ 900: '#14532d' },
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
plugins: [],
|
|
445
|
+
};
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Section 4b: `.gitignore`
|
|
451
|
+
|
|
452
|
+
See SKILL.md Section 4b for the complete `.gitignore` content.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Section 5: Directory Structure
|
|
457
|
+
|
|
458
|
+
Generate the full directory tree. The structure must follow nwidart/laravel-modules
|
|
459
|
+
conventions adapted for server-rendered web applications.
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
{{PROJECT_SLUG}}/
|
|
463
|
+
├── app/
|
|
464
|
+
│ ├── Console/
|
|
465
|
+
│ │ └── Commands/
|
|
466
|
+
│ │ ├── [If Scheduling] CleanupExpiredRecordsCommand.php
|
|
467
|
+
│ │ ├── [If Batch] ProcessDataExportCommand.php
|
|
468
|
+
│ │ └── [If Messaging] SetupRabbitMQCommand.php
|
|
469
|
+
│ ├── Constants/
|
|
470
|
+
│ │ └── Roles.php
|
|
471
|
+
│ ├── Contracts/
|
|
472
|
+
│ │ └── [If Reporting] ReportDefinition.php
|
|
473
|
+
│ ├── Exceptions/
|
|
474
|
+
│ │ ├── Handler.php
|
|
475
|
+
│ │ └── WebApplicationException.php
|
|
476
|
+
│ ├── Http/
|
|
477
|
+
│ │ ├── Controllers/
|
|
478
|
+
│ │ │ ├── [If Auth = Keycloak] Auth/KeycloakAuthController.php
|
|
479
|
+
│ │ │ └── [If Reporting] ReportController.php
|
|
480
|
+
│ │ └── Middleware/
|
|
481
|
+
│ │ ├── CspNonceMiddleware.php
|
|
482
|
+
│ │ ├── ThemeMiddleware.php
|
|
483
|
+
│ │ └── CorrelationIdMiddleware.php
|
|
484
|
+
│ ├── Models/
|
|
485
|
+
│ │ ├── User.php
|
|
486
|
+
│ │ └── [If Reporting] Report.php
|
|
487
|
+
│ ├── Providers/
|
|
488
|
+
│ │ ├── AppServiceProvider.php
|
|
489
|
+
│ │ └── EventServiceProvider.php
|
|
490
|
+
│ ├── Services/
|
|
491
|
+
│ │ ├── [If Auth = Keycloak] KeycloakRoleMapper.php
|
|
492
|
+
│ │ ├── SecurityContext.php
|
|
493
|
+
│ │ ├── [If Messaging] Messaging/
|
|
494
|
+
│ │ │ ├── RabbitMQInfrastructure.php
|
|
495
|
+
│ │ │ └── MessagePublisher.php
|
|
496
|
+
│ │ └── [If Reporting] Reporting/
|
|
497
|
+
│ │ ├── ReportService.php
|
|
498
|
+
│ │ ├── ReportRegistry.php
|
|
499
|
+
│ │ └── GenericReportExport.php
|
|
500
|
+
│ ├── Traits/
|
|
501
|
+
│ │ └── Blameable.php
|
|
502
|
+
│ └── Jobs/
|
|
503
|
+
│ ├── [If Batch] ProcessExportChunkJob.php
|
|
504
|
+
│ └── [If Messaging] Consumers/
|
|
505
|
+
│ └── OrderEventConsumer.php
|
|
506
|
+
│
|
|
507
|
+
├── Modules/
|
|
508
|
+
│ ├── {{Module1}}/
|
|
509
|
+
│ │ ├── Contracts/
|
|
510
|
+
│ │ │ └── {{Module1}}ServiceInterface.php
|
|
511
|
+
│ │ ├── DTOs/
|
|
512
|
+
│ │ │ └── {{Module1}}Data.php
|
|
513
|
+
│ │ ├── Events/
|
|
514
|
+
│ │ │ └── {{Module1}}CreatedEvent.php
|
|
515
|
+
│ │ ├── Exceptions/
|
|
516
|
+
│ │ │ └── {{Module1}}Exception.php
|
|
517
|
+
│ │ ├── Services/
|
|
518
|
+
│ │ │ └── {{Module1}}Service.php
|
|
519
|
+
│ │ ├── Models/
|
|
520
|
+
│ │ │ └── {{Module1}}.php
|
|
521
|
+
│ │ ├── Http/
|
|
522
|
+
│ │ │ ├── Controllers/
|
|
523
|
+
│ │ │ │ ├── {{Module1}}PageController.php
|
|
524
|
+
│ │ │ │ └── {{Module1}}FragmentController.php
|
|
525
|
+
│ │ │ └── Requests/
|
|
526
|
+
│ │ │ ├── Store{{Module1}}Request.php
|
|
527
|
+
│ │ │ └── Update{{Module1}}Request.php
|
|
528
|
+
│ │ ├── ViewModels/
|
|
529
|
+
│ │ │ ├── {{Module1}}ListViewModel.php
|
|
530
|
+
│ │ │ └── {{Module1}}DetailViewModel.php
|
|
531
|
+
│ │ ├── database/
|
|
532
|
+
│ │ │ └── migrations/
|
|
533
|
+
│ │ ├── resources/
|
|
534
|
+
│ │ │ └── views/
|
|
535
|
+
│ │ │ ├── pages/
|
|
536
|
+
│ │ │ │ ├── index.blade.php
|
|
537
|
+
│ │ │ │ ├── show.blade.php
|
|
538
|
+
│ │ │ │ ├── create.blade.php
|
|
539
|
+
│ │ │ │ └── edit.blade.php
|
|
540
|
+
│ │ │ └── fragments/
|
|
541
|
+
│ │ │ └── {{module1}}-row.blade.php
|
|
542
|
+
│ │ ├── routes/
|
|
543
|
+
│ │ │ └── web.php
|
|
544
|
+
│ │ ├── Providers/
|
|
545
|
+
│ │ │ └── {{Module1}}ServiceProvider.php
|
|
546
|
+
│ │ └── Tests/
|
|
547
|
+
│ │
|
|
548
|
+
│ └── {{Module2}}/
|
|
549
|
+
│ └── (same structure)
|
|
550
|
+
│
|
|
551
|
+
├── resources/
|
|
552
|
+
│ ├── css/ # (see Section 4.1)
|
|
553
|
+
│ ├── js/ # (see Section 4.1)
|
|
554
|
+
│ └── views/ # Shared views (see Section 4.1)
|
|
555
|
+
│
|
|
556
|
+
├── routes/
|
|
557
|
+
│ ├── web.php # Core routes (auth, reports)
|
|
558
|
+
│ └── console.php # Scheduled commands
|
|
559
|
+
│
|
|
560
|
+
├── config/
|
|
561
|
+
│ ├── app.php
|
|
562
|
+
│ ├── auth.php
|
|
563
|
+
│ ├── database.php
|
|
564
|
+
│ ├── logging.php
|
|
565
|
+
│ ├── queue.php
|
|
566
|
+
│ ├── services.php
|
|
567
|
+
│ ├── [If Messaging] messaging.php
|
|
568
|
+
│ └── modules.php
|
|
569
|
+
│
|
|
570
|
+
├── database/
|
|
571
|
+
│ └── migrations/ # Core migrations (users, roles, etc.)
|
|
572
|
+
│
|
|
573
|
+
├── bootstrap/
|
|
574
|
+
│ └── app.php # Middleware registration
|
|
575
|
+
│
|
|
576
|
+
├── public/
|
|
577
|
+
│ └── build/ # Vite build output (gitignored)
|
|
578
|
+
│
|
|
579
|
+
├── storage/
|
|
580
|
+
├── tests/
|
|
581
|
+
│ ├── Architecture/
|
|
582
|
+
│ │ └── ModuleBoundaryTest.php
|
|
583
|
+
│ ├── Feature/
|
|
584
|
+
│ └── Unit/
|
|
585
|
+
│
|
|
586
|
+
├── composer.json
|
|
587
|
+
├── package.json
|
|
588
|
+
├── vite.config.js
|
|
589
|
+
├── tailwind.config.js
|
|
590
|
+
├── .env.example
|
|
591
|
+
└── .gitignore
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## Sections 6-23
|
|
597
|
+
|
|
598
|
+
For Sections 6 through 23, follow the same structure as described in `SKILL.md` under
|
|
599
|
+
"What Goes in SPECIFICATION.md (Root)". Each section must include complete code samples
|
|
600
|
+
in the appropriate language (PHP, Blade, JavaScript, CSS, YAML).
|
|
601
|
+
|
|
602
|
+
Reference the corresponding `references/` files for detailed patterns:
|
|
603
|
+
- Section 6 (Security): `references/security-patterns.md`
|
|
604
|
+
- Section 7-12 (Web/UI): `references/web-patterns.md`
|
|
605
|
+
- Section 18 (Scheduling): `references/batch-patterns.md`
|
|
606
|
+
- Section 19 (Events): `references/modulith-patterns.md`
|
|
607
|
+
- Section 20 (Messaging): `references/messaging-patterns.md`
|
|
608
|
+
- Section 23 (Reporting): `references/reporting-patterns.md`
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Section 24: Internationalisation (Localization) [CONDITIONAL — Include only if i18n = yes]
|
|
613
|
+
|
|
614
|
+
> Omit this entire section when i18n = no. When omitted, do NOT render a locale/language
|
|
615
|
+
> switcher anywhere — Blade templates use literal copy.
|
|
616
|
+
|
|
617
|
+
Uses Laravel's **built-in localization** — no third-party package. The active locale is
|
|
618
|
+
resolved per request, persisted across requests, and exposed to every Blade view.
|
|
619
|
+
|
|
620
|
+
### 24.1 Configuration (`config/app.php`)
|
|
621
|
+
|
|
622
|
+
```php
|
|
623
|
+
// config/app.php
|
|
624
|
+
'locale' => env('APP_LOCALE', 'en'),
|
|
625
|
+
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
|
626
|
+
|
|
627
|
+
// Application-owned: the locales this app actually ships translations for.
|
|
628
|
+
'supported_locales' => [
|
|
629
|
+
'en' => 'English',
|
|
630
|
+
'ms' => 'Bahasa Malaysia',
|
|
631
|
+
],
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
`.env` additions:
|
|
635
|
+
|
|
636
|
+
```properties
|
|
637
|
+
APP_LOCALE=en
|
|
638
|
+
APP_FALLBACK_LOCALE=en
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### 24.2 Locale Resolution Middleware
|
|
642
|
+
|
|
643
|
+
```php
|
|
644
|
+
// app/Http/Middleware/SetLocale.php
|
|
645
|
+
namespace App\Http\Middleware;
|
|
646
|
+
|
|
647
|
+
use Closure;
|
|
648
|
+
use Illuminate\Http\Request;
|
|
649
|
+
|
|
650
|
+
class SetLocale
|
|
651
|
+
{
|
|
652
|
+
public function handle(Request $request, Closure $next)
|
|
653
|
+
{
|
|
654
|
+
$supported = array_keys(config('app.supported_locales'));
|
|
655
|
+
|
|
656
|
+
$locale = $request->session()->get('locale')
|
|
657
|
+
?? $request->cookie('locale')
|
|
658
|
+
?? $request->getPreferredLanguage($supported) // Accept-Language negotiation
|
|
659
|
+
?? config('app.locale');
|
|
660
|
+
|
|
661
|
+
if (! in_array($locale, $supported, true)) {
|
|
662
|
+
$locale = config('app.locale');
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
app()->setLocale($locale);
|
|
666
|
+
|
|
667
|
+
return $next($request);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Register on the `web` middleware group in `bootstrap/app.php`:
|
|
673
|
+
|
|
674
|
+
```php
|
|
675
|
+
->withMiddleware(function (Middleware $middleware) {
|
|
676
|
+
$middleware->web(append: [\App\Http\Middleware\SetLocale::class]);
|
|
677
|
+
})
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### 24.3 Locale Switch Route + Controller
|
|
681
|
+
|
|
682
|
+
```php
|
|
683
|
+
// routes/web.php
|
|
684
|
+
Route::post('/locale/{locale}', [\App\Http\Controllers\LocaleController::class, 'switch'])
|
|
685
|
+
->name('locale.switch');
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
```php
|
|
689
|
+
// app/Http/Controllers/LocaleController.php
|
|
690
|
+
namespace App\Http\Controllers;
|
|
691
|
+
|
|
692
|
+
use Illuminate\Http\Request;
|
|
693
|
+
|
|
694
|
+
class LocaleController extends Controller
|
|
695
|
+
{
|
|
696
|
+
public function switch(Request $request, string $locale)
|
|
697
|
+
{
|
|
698
|
+
abort_unless(array_key_exists($locale, config('app.supported_locales')), 404);
|
|
699
|
+
|
|
700
|
+
$request->session()->put('locale', $locale);
|
|
701
|
+
cookie()->queue(cookie('locale', $locale, 60 * 24 * 365)); // 1 year
|
|
702
|
+
|
|
703
|
+
// htmx requests: ask the client to do a full refresh so all copy re-renders.
|
|
704
|
+
if ($request->header('HX-Request')) {
|
|
705
|
+
return response()->noContent()->header('HX-Refresh', 'true');
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return redirect()->back();
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### 24.4 Language Switcher Partial (header)
|
|
714
|
+
|
|
715
|
+
```blade
|
|
716
|
+
{{-- resources/views/partials/locale-switcher.blade.php --}}
|
|
717
|
+
<div x-data="{ open: false }" class="relative">
|
|
718
|
+
<button @click="open = !open" type="button"
|
|
719
|
+
class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
|
720
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
721
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
722
|
+
d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"/>
|
|
723
|
+
</svg>
|
|
724
|
+
<span>{{ config('app.supported_locales')[app()->getLocale()] }}</span>
|
|
725
|
+
</button>
|
|
726
|
+
<div x-show="open" @click.outside="open = false" x-cloak
|
|
727
|
+
class="absolute right-0 mt-1 w-40 rounded-md border border-gray-200 bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800">
|
|
728
|
+
@foreach (config('app.supported_locales') as $code => $label)
|
|
729
|
+
<form method="POST" action="{{ route('locale.switch', $code) }}"
|
|
730
|
+
hx-post="{{ route('locale.switch', $code) }}" hx-swap="none">
|
|
731
|
+
@csrf
|
|
732
|
+
<button type="submit"
|
|
733
|
+
class="block w-full px-3 py-1.5 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700
|
|
734
|
+
{{ app()->getLocale() === $code ? 'font-semibold text-primary-600' : 'text-gray-700 dark:text-gray-200' }}">
|
|
735
|
+
{{ $label }}
|
|
736
|
+
</button>
|
|
737
|
+
</form>
|
|
738
|
+
@endforeach
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Include it in `resources/views/partials/header.blade.php` next to the theme switcher.
|
|
744
|
+
|
|
745
|
+
### 24.5 Translation Files
|
|
746
|
+
|
|
747
|
+
Application-level strings:
|
|
748
|
+
|
|
749
|
+
```php
|
|
750
|
+
// lang/en/messages.php
|
|
751
|
+
return [
|
|
752
|
+
'welcome' => 'Welcome',
|
|
753
|
+
'items_count' => '{0} No items|{1} :count item|[2,*] :count items', // trans_choice()
|
|
754
|
+
];
|
|
755
|
+
|
|
756
|
+
// lang/ms/messages.php
|
|
757
|
+
return [
|
|
758
|
+
'welcome' => 'Selamat datang',
|
|
759
|
+
'items_count' => '{0} Tiada item|{1} :count item|[2,*] :count item',
|
|
760
|
+
];
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Localized validation messages live in `lang/<locale>/validation.php` (publish Laravel's
|
|
764
|
+
defaults, then add per-locale overrides).
|
|
765
|
+
|
|
766
|
+
### 24.6 Blade Usage Conventions
|
|
767
|
+
|
|
768
|
+
```blade
|
|
769
|
+
<h1>{{ __('messages.welcome') }}</h1>
|
|
770
|
+
<p>{{ trans_choice('messages.items_count', $count, ['count' => $count]) }}</p>
|
|
771
|
+
|
|
772
|
+
{{-- Module-namespaced key (registered by the module service provider) --}}
|
|
773
|
+
<h2>{{ __('location-information::messages.title') }}</h2>
|
|
774
|
+
|
|
775
|
+
{{-- Locale-aware date --}}
|
|
776
|
+
<span>{{ $record->created_at->locale(app()->getLocale())->translatedFormat('d F Y') }}</span>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
> **Per-module translations.** Each module registers its own namespace in its service
|
|
780
|
+
> provider — `$this->loadTranslationsFrom(module_path('LocationInformation', 'lang'), 'location-information');`
|
|
781
|
+
> — so module copy resolves via `__('location-information::messages.<key>')`. See the
|
|
782
|
+
> per-module SPEC.md template for the module-level translation file.
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
# Part B: Per-Module SPEC.md Template
|
|
787
|
+
|
|
788
|
+
Each module from PRD.md gets its own `<module-name>/SPEC.md` file.
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## Module SPEC.md Template
|
|
793
|
+
|
|
794
|
+
```markdown
|
|
795
|
+
# {{MODULE_NAME}} — Module Specification
|
|
796
|
+
|
|
797
|
+
> Back to [SPECIFICATION.md](../SPECIFICATION.md)
|
|
798
|
+
|
|
799
|
+
## 1. Traceability
|
|
800
|
+
|
|
801
|
+
### User Stories
|
|
802
|
+
| ID | Description | Version |
|
|
803
|
+
|----|-------------|---------|
|
|
804
|
+
| USHM00108 | As a user, I want to view... | v1.0.0 |
|
|
805
|
+
|
|
806
|
+
### Non-Functional Requirements
|
|
807
|
+
| ID | Description | Version |
|
|
808
|
+
|----|-------------|---------|
|
|
809
|
+
| NFRHM0120 | Data must be stored in... | v1.0.0 |
|
|
810
|
+
|
|
811
|
+
### Constraints
|
|
812
|
+
| ID | Description | Version |
|
|
813
|
+
|----|-------------|---------|
|
|
814
|
+
| CONSHM042 | Can only change name and... | v1.0.0 |
|
|
815
|
+
|
|
816
|
+
### Removed / Replaced
|
|
817
|
+
| ID | Removed In | Replaced By | Reason |
|
|
818
|
+
|----|-----------|-------------|--------|
|
|
819
|
+
| USHM00015 | v1.0.3 | USHM00222 | Consolidated into... |
|
|
820
|
+
_(or "None." if no removals)_
|
|
821
|
+
|
|
822
|
+
### Data Sources
|
|
823
|
+
| Artifact | Reference |
|
|
824
|
+
|----------|-----------|
|
|
825
|
+
| Collection/Table | `{{collection_or_table_name}}` |
|
|
826
|
+
| Mockup Screen | `mockup/{{role}}/content/{{screen}}.html` |
|
|
827
|
+
|
|
828
|
+
## 2. Service Contract
|
|
829
|
+
|
|
830
|
+
```php
|
|
831
|
+
namespace Modules\{{Module}}\Contracts;
|
|
832
|
+
|
|
833
|
+
interface {{Module}}ServiceInterface
|
|
834
|
+
{
|
|
835
|
+
public function list(int $page = 1, int $perPage = 20): LengthAwarePaginator;
|
|
836
|
+
public function get(string $id): {{Module}}Data;
|
|
837
|
+
public function create(array $data): {{Module}}Data;
|
|
838
|
+
public function update(string $id, array $data): {{Module}}Data;
|
|
839
|
+
public function delete(string $id): void;
|
|
840
|
+
// Additional methods from user stories...
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
## 3. DTO (spatie/laravel-data)
|
|
845
|
+
|
|
846
|
+
```php
|
|
847
|
+
namespace Modules\{{Module}}\DTOs;
|
|
848
|
+
|
|
849
|
+
use Spatie\LaravelData\Data;
|
|
850
|
+
|
|
851
|
+
class {{Module}}Data extends Data
|
|
852
|
+
{
|
|
853
|
+
public function __construct(
|
|
854
|
+
public readonly ?string $id,
|
|
855
|
+
// Fields from model/<module>/model.md...
|
|
856
|
+
public readonly ?string $created_by,
|
|
857
|
+
public readonly ?string $updated_by,
|
|
858
|
+
public readonly ?\DateTimeImmutable $created_at,
|
|
859
|
+
public readonly ?\DateTimeImmutable $updated_at,
|
|
860
|
+
) {}
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
## 4. Exception
|
|
865
|
+
|
|
866
|
+
```php
|
|
867
|
+
namespace Modules\{{Module}}\Exceptions;
|
|
868
|
+
|
|
869
|
+
use App\Exceptions\WebApplicationException;
|
|
870
|
+
|
|
871
|
+
class {{Module}}Exception extends WebApplicationException
|
|
872
|
+
{
|
|
873
|
+
public static function notFound(string $id): self
|
|
874
|
+
{
|
|
875
|
+
return new self("{{Module}} not found: {$id}", 404);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
## 5. Eloquent Model
|
|
881
|
+
|
|
882
|
+
```php
|
|
883
|
+
namespace Modules\{{Module}}\Models;
|
|
884
|
+
|
|
885
|
+
use App\Traits\Blameable;
|
|
886
|
+
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
|
887
|
+
use Illuminate\Database\Eloquent\Model;
|
|
888
|
+
|
|
889
|
+
class {{Module}} extends Model
|
|
890
|
+
{
|
|
891
|
+
use HasUuids, Blameable;
|
|
892
|
+
|
|
893
|
+
protected $table = '{{table_name}}';
|
|
894
|
+
|
|
895
|
+
protected $fillable = [
|
|
896
|
+
// Fields from model/<module>/model.md...
|
|
897
|
+
];
|
|
898
|
+
|
|
899
|
+
protected $casts = [
|
|
900
|
+
// Type casts...
|
|
901
|
+
];
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
## 6. Migration [If Database = PostgreSQL/MySQL]
|
|
906
|
+
|
|
907
|
+
```php
|
|
908
|
+
use Illuminate\Database\Migrations\Migration;
|
|
909
|
+
use Illuminate\Database\Schema\Blueprint;
|
|
910
|
+
use Illuminate\Support\Facades\Schema;
|
|
911
|
+
|
|
912
|
+
return new class extends Migration
|
|
913
|
+
{
|
|
914
|
+
public function up(): void
|
|
915
|
+
{
|
|
916
|
+
Schema::create('{{table_name}}', function (Blueprint $table) {
|
|
917
|
+
$table->uuid('id')->primary();
|
|
918
|
+
// Fields from model/<module>/model.md...
|
|
919
|
+
$table->string('created_by', 100)->nullable();
|
|
920
|
+
$table->string('updated_by', 100)->nullable();
|
|
921
|
+
$table->timestamps();
|
|
922
|
+
// Indexes from model/<module>/model.md...
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
public function down(): void
|
|
927
|
+
{
|
|
928
|
+
Schema::dropIfExists('{{table_name}}');
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
## 7. Service Implementation
|
|
934
|
+
|
|
935
|
+
```php
|
|
936
|
+
namespace Modules\{{Module}}\Services;
|
|
937
|
+
|
|
938
|
+
use Modules\{{Module}}\Contracts\{{Module}}ServiceInterface;
|
|
939
|
+
use Modules\{{Module}}\DTOs\{{Module}}Data;
|
|
940
|
+
use Modules\{{Module}}\Events\{{Module}}CreatedEvent;
|
|
941
|
+
use Modules\{{Module}}\Exceptions\{{Module}}Exception;
|
|
942
|
+
use Modules\{{Module}}\Models\{{Module}};
|
|
943
|
+
|
|
944
|
+
class {{Module}}Service implements {{Module}}ServiceInterface
|
|
945
|
+
{
|
|
946
|
+
public function list(int $page = 1, int $perPage = 20): LengthAwarePaginator
|
|
947
|
+
{
|
|
948
|
+
return {{Module}}::orderByDesc('created_at')->paginate($perPage);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
public function get(string $id): {{Module}}Data
|
|
952
|
+
{
|
|
953
|
+
$model = {{Module}}::findOrFail($id);
|
|
954
|
+
return {{Module}}Data::from($model);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
public function create(array $data): {{Module}}Data
|
|
958
|
+
{
|
|
959
|
+
$model = {{Module}}::create($data);
|
|
960
|
+
|
|
961
|
+
event(new {{Module}}CreatedEvent(
|
|
962
|
+
id: $model->id,
|
|
963
|
+
occurredAt: new \DateTimeImmutable(),
|
|
964
|
+
));
|
|
965
|
+
|
|
966
|
+
return {{Module}}Data::from($model);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
public function update(string $id, array $data): {{Module}}Data
|
|
970
|
+
{
|
|
971
|
+
$model = {{Module}}::findOrFail($id);
|
|
972
|
+
$model->update($data);
|
|
973
|
+
return {{Module}}Data::from($model->fresh());
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
public function delete(string $id): void
|
|
977
|
+
{
|
|
978
|
+
$model = {{Module}}::findOrFail($id);
|
|
979
|
+
$model->delete();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
## 8. Form Requests
|
|
985
|
+
|
|
986
|
+
```php
|
|
987
|
+
namespace Modules\{{Module}}\Http\Requests;
|
|
988
|
+
|
|
989
|
+
use Illuminate\Foundation\Http\FormRequest;
|
|
990
|
+
|
|
991
|
+
class Store{{Module}}Request extends FormRequest
|
|
992
|
+
{
|
|
993
|
+
public function authorize(): bool { return true; }
|
|
994
|
+
|
|
995
|
+
public function rules(): array
|
|
996
|
+
{
|
|
997
|
+
return [
|
|
998
|
+
// Validation rules from model + mockup screens...
|
|
999
|
+
];
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
## 9. Page Controller
|
|
1005
|
+
|
|
1006
|
+
```php
|
|
1007
|
+
namespace Modules\{{Module}}\Http\Controllers;
|
|
1008
|
+
|
|
1009
|
+
use Illuminate\Http\RedirectResponse;
|
|
1010
|
+
use Illuminate\Http\Request;
|
|
1011
|
+
use Illuminate\View\View;
|
|
1012
|
+
use Modules\{{Module}}\Contracts\{{Module}}ServiceInterface;
|
|
1013
|
+
use Modules\{{Module}}\Http\Requests\Store{{Module}}Request;
|
|
1014
|
+
use Modules\{{Module}}\Http\Requests\Update{{Module}}Request;
|
|
1015
|
+
use Modules\{{Module}}\ViewModels\{{Module}}ListViewModel;
|
|
1016
|
+
use Modules\{{Module}}\ViewModels\{{Module}}DetailViewModel;
|
|
1017
|
+
|
|
1018
|
+
class {{Module}}PageController extends Controller
|
|
1019
|
+
{
|
|
1020
|
+
public function __construct(
|
|
1021
|
+
private readonly {{Module}}ServiceInterface $service,
|
|
1022
|
+
) {}
|
|
1023
|
+
|
|
1024
|
+
public function index(Request $request): View
|
|
1025
|
+
{
|
|
1026
|
+
$items = $this->service->list(
|
|
1027
|
+
page: $request->integer('page', 1),
|
|
1028
|
+
);
|
|
1029
|
+
return (new {{Module}}ListViewModel(items: $items))
|
|
1030
|
+
->view('{{module}}::pages.index');
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
public function show(string $id): View
|
|
1034
|
+
{
|
|
1035
|
+
$item = $this->service->get($id);
|
|
1036
|
+
return (new {{Module}}DetailViewModel(item: $item))
|
|
1037
|
+
->view('{{module}}::pages.show');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
public function create(): View
|
|
1041
|
+
{
|
|
1042
|
+
return view('{{module}}::pages.create');
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
public function store(Store{{Module}}Request $request): RedirectResponse
|
|
1046
|
+
{
|
|
1047
|
+
$this->service->create($request->validated());
|
|
1048
|
+
return redirect()->route('{{module}}.index')
|
|
1049
|
+
->with('success', '{{Module}} created successfully.');
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
public function edit(string $id): View
|
|
1053
|
+
{
|
|
1054
|
+
$item = $this->service->get($id);
|
|
1055
|
+
return view('{{module}}::pages.edit', compact('item'));
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
public function update(Update{{Module}}Request $request, string $id): RedirectResponse
|
|
1059
|
+
{
|
|
1060
|
+
$this->service->update($id, $request->validated());
|
|
1061
|
+
return redirect()->route('{{module}}.show', $id)
|
|
1062
|
+
->with('success', '{{Module}} updated successfully.');
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
public function destroy(string $id): RedirectResponse
|
|
1066
|
+
{
|
|
1067
|
+
$this->service->delete($id);
|
|
1068
|
+
return redirect()->route('{{module}}.index')
|
|
1069
|
+
->with('success', '{{Module}} deleted successfully.');
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
## 10. Fragment Controller
|
|
1075
|
+
|
|
1076
|
+
```php
|
|
1077
|
+
namespace Modules\{{Module}}\Http\Controllers;
|
|
1078
|
+
|
|
1079
|
+
use Illuminate\Http\Request;
|
|
1080
|
+
use Illuminate\Http\Response;
|
|
1081
|
+
use Illuminate\View\View;
|
|
1082
|
+
use Modules\{{Module}}\Contracts\{{Module}}ServiceInterface;
|
|
1083
|
+
|
|
1084
|
+
class {{Module}}FragmentController extends Controller
|
|
1085
|
+
{
|
|
1086
|
+
public function __construct(
|
|
1087
|
+
private readonly {{Module}}ServiceInterface $service,
|
|
1088
|
+
) {}
|
|
1089
|
+
|
|
1090
|
+
public function row(string $id): View
|
|
1091
|
+
{
|
|
1092
|
+
$item = $this->service->get($id);
|
|
1093
|
+
return view('{{module}}::fragments.{{module}}-row', compact('item'));
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
public function search(Request $request): View
|
|
1097
|
+
{
|
|
1098
|
+
$items = $this->service->search($request->input('q', ''));
|
|
1099
|
+
return view('{{module}}::fragments.search-results', compact('items'));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
public function destroy(string $id): Response
|
|
1103
|
+
{
|
|
1104
|
+
$this->service->delete($id);
|
|
1105
|
+
return response('', 200);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
## 11. View Models
|
|
1111
|
+
|
|
1112
|
+
```php
|
|
1113
|
+
namespace Modules\{{Module}}\ViewModels;
|
|
1114
|
+
|
|
1115
|
+
use Illuminate\Pagination\LengthAwarePaginator;
|
|
1116
|
+
use Spatie\ViewModels\ViewModel;
|
|
1117
|
+
|
|
1118
|
+
class {{Module}}ListViewModel extends ViewModel
|
|
1119
|
+
{
|
|
1120
|
+
public function __construct(
|
|
1121
|
+
public readonly LengthAwarePaginator $items,
|
|
1122
|
+
) {}
|
|
1123
|
+
}
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
## 12. Blade Templates
|
|
1127
|
+
|
|
1128
|
+
### pages/index.blade.php
|
|
1129
|
+
```blade
|
|
1130
|
+
<x-app-layout title="{{Module}} List">
|
|
1131
|
+
<div class="space-y-6">
|
|
1132
|
+
<div class="flex items-center justify-between">
|
|
1133
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{Module}}</h1>
|
|
1134
|
+
<x-button label="Create" href="{{ route('{{module}}.create') }}" tone="primary" />
|
|
1135
|
+
</div>
|
|
1136
|
+
|
|
1137
|
+
<div class="flex gap-4">
|
|
1138
|
+
<input type="search" name="q" placeholder="Search..."
|
|
1139
|
+
hx-get="{{ route('{{module}}.fragments.search') }}"
|
|
1140
|
+
hx-target="#{{module}}-list"
|
|
1141
|
+
hx-trigger="keyup changed delay:300ms"
|
|
1142
|
+
class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm" />
|
|
1143
|
+
</div>
|
|
1144
|
+
|
|
1145
|
+
<div id="{{module}}-list">
|
|
1146
|
+
@include('{{module}}::fragments.{{module}}-table', ['items' => $items])
|
|
1147
|
+
</div>
|
|
1148
|
+
|
|
1149
|
+
<x-pagination :paginator="$items" />
|
|
1150
|
+
</div>
|
|
1151
|
+
</x-app-layout>
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
### fragments/{{module}}-row.blade.php
|
|
1155
|
+
```blade
|
|
1156
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
|
|
1157
|
+
id="{{module}}-row-{{ $item->id }}">
|
|
1158
|
+
<td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-300">{{ $item->field_one }}</td>
|
|
1159
|
+
{{-- Additional fields from mockup... --}}
|
|
1160
|
+
<td class="px-6 py-4 text-sm">
|
|
1161
|
+
<a href="{{ route('{{module}}.show', $item->id) }}"
|
|
1162
|
+
class="text-blue-600 hover:text-blue-800 dark:text-blue-400">View</a>
|
|
1163
|
+
<button hx-delete="{{ route('{{module}}.fragments.destroy', $item->id) }}"
|
|
1164
|
+
hx-target="#{{module}}-row-{{ $item->id }}"
|
|
1165
|
+
hx-swap="outerHTML"
|
|
1166
|
+
hx-confirm="Are you sure?"
|
|
1167
|
+
class="text-red-600 hover:text-red-800 dark:text-red-400 ml-3">Delete</button>
|
|
1168
|
+
</td>
|
|
1169
|
+
</tr>
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
## 13. Module Routes
|
|
1173
|
+
|
|
1174
|
+
```php
|
|
1175
|
+
// Modules/{{Module}}/routes/web.php
|
|
1176
|
+
use Illuminate\Support\Facades\Route;
|
|
1177
|
+
use Modules\{{Module}}\Http\Controllers\{{Module}}PageController;
|
|
1178
|
+
use Modules\{{Module}}\Http\Controllers\{{Module}}FragmentController;
|
|
1179
|
+
|
|
1180
|
+
Route::middleware(['auth', 'role:{{ROLE}}'])->group(function () {
|
|
1181
|
+
Route::resource('{{route-prefix}}', {{Module}}PageController::class);
|
|
1182
|
+
|
|
1183
|
+
Route::prefix('{{route-prefix}}/fragments')->name('{{module}}.fragments.')->group(function () {
|
|
1184
|
+
Route::get('/search', [{{Module}}FragmentController::class, 'search'])->name('search');
|
|
1185
|
+
Route::get('/row/{id}', [{{Module}}FragmentController::class, 'row'])->name('row');
|
|
1186
|
+
Route::delete('/{id}', [{{Module}}FragmentController::class, 'destroy'])->name('destroy');
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
## 14. Module Service Provider
|
|
1192
|
+
|
|
1193
|
+
```php
|
|
1194
|
+
namespace Modules\{{Module}}\Providers;
|
|
1195
|
+
|
|
1196
|
+
use Illuminate\Support\ServiceProvider;
|
|
1197
|
+
use Modules\{{Module}}\Contracts\{{Module}}ServiceInterface;
|
|
1198
|
+
use Modules\{{Module}}\Services\{{Module}}Service;
|
|
1199
|
+
|
|
1200
|
+
class {{Module}}ServiceProvider extends ServiceProvider
|
|
1201
|
+
{
|
|
1202
|
+
public function register(): void
|
|
1203
|
+
{
|
|
1204
|
+
$this->app->bind({{Module}}ServiceInterface::class, {{Module}}Service::class);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
public function boot(): void
|
|
1208
|
+
{
|
|
1209
|
+
$this->loadMigrationsFrom(module_path('{{Module}}', 'database/migrations'));
|
|
1210
|
+
$this->loadViewsFrom(module_path('{{Module}}', 'resources/views'), '{{module}}');
|
|
1211
|
+
$this->loadRoutesFrom(module_path('{{Module}}', 'routes/web.php'));
|
|
1212
|
+
// [If i18n = yes] register this module's translation namespace
|
|
1213
|
+
$this->loadTranslationsFrom(module_path('{{Module}}', 'lang'), '{{module-slug}}');
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
## 15. [If i18n] Module Translations
|
|
1219
|
+
|
|
1220
|
+
(Only include if i18n = yes. One file per supported locale; keys are referenced from this
|
|
1221
|
+
module's Blade templates via the `{{module-slug}}::` namespace registered in the service
|
|
1222
|
+
provider above.)
|
|
1223
|
+
|
|
1224
|
+
```php
|
|
1225
|
+
// Modules/{{Module}}/lang/en/messages.php
|
|
1226
|
+
return [
|
|
1227
|
+
'title' => '{{Module Title}}',
|
|
1228
|
+
'create' => 'Create {{Module Title}}',
|
|
1229
|
+
'empty_state' => 'No records yet',
|
|
1230
|
+
];
|
|
1231
|
+
|
|
1232
|
+
// Modules/{{Module}}/lang/ms/messages.php
|
|
1233
|
+
return [
|
|
1234
|
+
'title' => '{{Module Title (ms)}}',
|
|
1235
|
+
'create' => 'Cipta {{Module Title (ms)}}',
|
|
1236
|
+
'empty_state' => 'Tiada rekod lagi',
|
|
1237
|
+
];
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
Blade templates in this module reference keys as `{{ __('{{module-slug}}::messages.title') }}`.
|
|
1241
|
+
|
|
1242
|
+
## 16. [If Messaging] Messaging Pipeline
|
|
1243
|
+
|
|
1244
|
+
(Only include if the module has async processing NFRs)
|
|
1245
|
+
|
|
1246
|
+
### Consumer
|
|
1247
|
+
```php
|
|
1248
|
+
// Queue job consuming from RabbitMQ...
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
### Publisher
|
|
1252
|
+
```php
|
|
1253
|
+
// Publishing domain events via MessagePublisher...
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
**IMPORTANT — Separating UI Layer from Messaging/Async Pipeline:**
|
|
1259
|
+
|
|
1260
|
+
When a module has BOTH user-facing screens AND async processing NFRs, the SPEC.md
|
|
1261
|
+
MUST clearly separate these into distinct sections as shown above (Sections 9-12 for
|
|
1262
|
+
UI, Section 16 for messaging). This enables the implementation orchestrator to track
|
|
1263
|
+
and implement each concern independently.
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
---
|
|
1267
|
+
|
|
1268
|
+
End of template.
|