@rashidee/co2 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/index.js +25449 -0
  2. package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
  3. package/drizzle/0001_wise_wolf_cub.sql +13 -0
  4. package/drizzle/0002_secret_spyke.sql +16 -0
  5. package/drizzle/0003_workspace_config_singleton.sql +16 -0
  6. package/drizzle/0004_terminal_claim_v120.sql +11 -0
  7. package/drizzle/0005_brainstorm_v124.sql +10 -0
  8. package/drizzle/0006_project_v140.sql +6 -0
  9. package/drizzle/0007_prd_wizard_v124.sql +14 -0
  10. package/drizzle/0008_workflow_artifact_v126.sql +24 -0
  11. package/drizzle/0009_project_import_v150.sql +7 -0
  12. package/drizzle/0010_quality_v151.sql +3 -0
  13. package/drizzle/meta/0000_snapshot.json +1335 -0
  14. package/drizzle/meta/0001_snapshot.json +1416 -0
  15. package/drizzle/meta/0002_snapshot.json +1510 -0
  16. package/drizzle/meta/_journal.json +83 -0
  17. package/package.json +41 -0
  18. package/plugin/.claude-plugin/marketplace.json +36 -0
  19. package/plugin/.claude-plugin/plugin.json +22 -0
  20. package/plugin/README.md +625 -0
  21. package/plugin/SKILLS.md +350 -0
  22. package/plugin/co2.svg +5 -0
  23. package/plugin/commands/brainstorm-loop.md +129 -0
  24. package/plugin/skills/conductor-defect/SKILL.md +898 -0
  25. package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
  26. package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
  27. package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
  28. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
  29. package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
  30. package/plugin/skills/depgen-k8s/SKILL.md +468 -0
  31. package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
  32. package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
  33. package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
  34. package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
  35. package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
  36. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
  37. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
  38. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
  39. package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
  40. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
  41. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
  42. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
  43. package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
  44. package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
  45. package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
  46. package/plugin/skills/modelgen-relational/SKILL.md +707 -0
  47. package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
  48. package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
  49. package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
  50. package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
  51. package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
  52. package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
  53. package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
  54. package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
  55. package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
  56. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
  57. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
  58. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
  59. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
  60. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
  61. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
  62. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
  63. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
  64. package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
  65. package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
  66. package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
  67. package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
  68. package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
  69. package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
  70. package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
  71. package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
  72. package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
  73. package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
  74. package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
  75. package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
  76. package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
  77. package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
  78. package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
  79. package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
  80. package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
  81. package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
  82. package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
  83. package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
  84. package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
  85. package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
  86. package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
  87. package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
  88. package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
  89. package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
  90. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
  91. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
  92. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
  93. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
  94. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
  95. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
  96. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
  97. package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
  98. package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
  99. package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
  100. package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
  101. package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
  102. package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
  103. package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
  104. package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
  105. package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
  106. package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
  107. package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
  108. package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
  109. package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
  110. package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
  111. package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
  112. package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
  113. package/plugin/skills/testgen-functional/SKILL.md +1381 -0
  114. package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
  115. package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
  116. package/plugin/skills/util-projectinit/SKILL.md +307 -0
  117. package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
  118. package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
  119. package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
  120. package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
  121. package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
  122. package/plugin/skills/util-projectsync/SKILL.md +501 -0
  123. package/plugin/skills/util-updprd/SKILL.md +324 -0
  124. package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
  125. package/plugin/skills/util-ustagger/SKILL.md +154 -0
  126. package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
  127. package/static/assets/arc-DRKw7sjs.js +1 -0
  128. package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
  129. package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
  130. package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
  131. package/static/assets/channel-C2nVbuwv.js +1 -0
  132. package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
  133. package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
  134. package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
  135. package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
  136. package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
  137. package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
  138. package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
  139. package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
  140. package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
  141. package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
  142. package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
  143. package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
  144. package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
  145. package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
  146. package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
  147. package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  148. package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
  149. package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  150. package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
  151. package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
  152. package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
  153. package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
  154. package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
  155. package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
  156. package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
  157. package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
  158. package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
  159. package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
  160. package/static/assets/graph-C9eacEi8.js +1 -0
  161. package/static/assets/index-LZSQkLE2.css +41 -0
  162. package/static/assets/index-y7XDSgMG.js +495 -0
  163. package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
  164. package/static/assets/init-Gi6I4Gst.js +1 -0
  165. package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
  166. package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
  167. package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
  168. package/static/assets/katex-C5jXJg4s.js +257 -0
  169. package/static/assets/layout-DEXfKzaS.js +1 -0
  170. package/static/assets/linear-CLO--ryk.js +1 -0
  171. package/static/assets/map-Czzmt4hB.js +1 -0
  172. package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
  173. package/static/assets/ordinal-Cboi1Yqb.js +1 -0
  174. package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
  175. package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
  176. package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
  177. package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
  178. package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
  179. package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
  180. package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
  181. package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
  182. package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
  183. package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
  184. package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
  185. package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
  186. package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
  187. package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
  188. package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
  189. package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
  190. package/static/index.html +24 -0
@@ -0,0 +1,803 @@
1
+ # Reporting Patterns — Puppeteer (Browsershot) + Laravel Excel with DTO Data Source
2
+
3
+ This reference describes the reporting infrastructure for the spec. Include this content
4
+ in the Reporting section of the generated specification. All code samples must be reproduced
5
+ in the generated spec with full use statements and constructor injection.
6
+
7
+ The reporting feature provides:
8
+ - **Report Interface** for modules to define and register their own reports
9
+ - **Puppeteer via Browsershot** for PDF generation from Blade templates — uses headless Chrome for full CSS support including Tailwind CSS, flexbox, and grid
10
+ - **Laravel Excel** for XLSX and CSV export
11
+ - **DTO-based data source** — reports receive data via DTOs from services, never query DB directly
12
+ - **Multi-format export** — PDF, XLSX, CSV
13
+ - **Report registry** persisted in the database for discoverability
14
+ - **Report parameter forms** rendered via Blade for user input before generation
15
+ - **Blade templates with Tailwind CSS** — PDF templates use the same design system as the web application since Puppeteer renders full CSS
16
+
17
+ ---
18
+
19
+ ## Architecture Overview
20
+
21
+ ```
22
+ Module Shared Reporting Infrastructure
23
+ ┌─────────────────────┐ ┌─────────────────────────────────────────┐
24
+ │ StaffReport │ │ ReportService │
25
+ │ implements │────────►│ renderPdf(Browsershot + Blade + data)│
26
+ │ ReportDefinition │ │ exportExcel(Laravel Excel + data) │
27
+ │ │ │ exportCsv(Laravel Excel + data) │
28
+ │ getReportId() │ │ │
29
+ │ getName() │ │ ReportRegistry │
30
+ │ getDescription() │ │ discovers tagged ReportDefinition │
31
+ │ getParameters() │ │ persists to reports table │
32
+ │ generateData(...) │ │ provides report lookup │
33
+ │ │ │ │
34
+ │ --- Data Flow --- │ │ ReportPageController │
35
+ │ Service → DTO[] │ │ list all available reports │
36
+ │ (never Repository) │ │ render parameter form │
37
+ └─────────────────────┘ │ generate + download report │
38
+ └─────────────────────────────────────────┘
39
+ ```
40
+
41
+ **Key design rule:** Reports obtain data through module service interfaces (which return
42
+ DTOs), never by injecting repositories or querying the database directly. This preserves
43
+ module boundaries — reports are consumers of the module's public API.
44
+
45
+ **Rendering engine:** Browsershot renders Blade templates using headless Chrome (Puppeteer).
46
+ Templates are self-contained HTML documents that include Tailwind CSS via CDN, enabling
47
+ the same utility classes used in the web application. This eliminates the CSS limitations
48
+ of traditional PDF libraries (no flexbox, no grid, limited font support).
49
+
50
+ ---
51
+
52
+ ## Composer Dependencies
53
+
54
+ ```json
55
+ {
56
+ "require": {
57
+ "spatie/browsershot": "^4.0",
58
+ "maatwebsite/excel": "^3.1"
59
+ }
60
+ }
61
+ ```
62
+
63
+ Additionally, install the Puppeteer Node.js package on the server:
64
+
65
+ ```bash
66
+ npm install puppeteer
67
+ ```
68
+
69
+ This installs both the Puppeteer library and a compatible headless Chrome binary.
70
+
71
+ ---
72
+
73
+ ## Server Requirements
74
+
75
+ Browsershot requires Node.js and a Chrome/Chromium binary to render PDFs. The setup
76
+ depends on the deployment environment:
77
+
78
+ **Local development:**
79
+ - Install Node.js (v18+) and run `npm install puppeteer` in the project root.
80
+ - Puppeteer automatically downloads a compatible Chrome binary into `node_modules`.
81
+ - Browsershot auto-detects the Node.js binary and Chrome path from `node_modules`.
82
+
83
+ **Production / Docker:**
84
+ - The Dockerfile must include Node.js and either install Puppeteer (which bundles Chrome)
85
+ or install Chromium via the system package manager.
86
+ - When running Chrome inside Docker, the `--no-sandbox` flag is required (Browsershot
87
+ applies this automatically when it detects a non-TTY environment, but it can also be
88
+ set explicitly).
89
+ - Set the environment variable `PUPPETEER_EXECUTABLE_PATH` to use a system-installed
90
+ Chrome instead of the one bundled with Puppeteer.
91
+
92
+ **Environment variables (optional):**
93
+ - `BROWSERSHOT_NODE_BINARY` — absolute path to the Node.js binary
94
+ - `BROWSERSHOT_NPM_BINARY` — absolute path to the npm binary
95
+ - `BROWSERSHOT_CHROME_PATH` — absolute path to the Chrome/Chromium binary
96
+ - `BROWSERSHOT_NODE_MODULES_PATH` — absolute path to the `node_modules` directory
97
+
98
+ ---
99
+
100
+ ## Package Structure
101
+
102
+ ```
103
+ app/
104
+ ├── Contracts/
105
+ │ └── ReportDefinition.php # Interface for module reports
106
+ ├── Services/
107
+ │ └── Reporting/
108
+ │ ├── ReportService.php # Orchestrates render/export via Browsershot
109
+ │ └── ReportRegistry.php # Discovers and persists report definitions
110
+ ├── Models/
111
+ │ └── Report.php # Eloquent model for reports table
112
+ ├── Http/
113
+ │ └── Controllers/
114
+ │ └── ReportController.php # Report list, form, download endpoints
115
+ ├── config/
116
+ │ └── browsershot.php # Browsershot binary path configuration
117
+ └── resources/
118
+ └── views/
119
+ └── reports/
120
+ ├── index.blade.php # Report list page
121
+ ├── parameters.blade.php # Parameter form page
122
+ └── templates/ # PDF Blade templates (self-contained HTML)
123
+ └── sample-report.blade.php
124
+ ```
125
+
126
+ ---
127
+
128
+ ## ReportDefinition Interface
129
+
130
+ ```php
131
+ namespace App\Contracts;
132
+
133
+ interface ReportDefinition
134
+ {
135
+ /**
136
+ * Unique identifier for this report (e.g., 'staff-list-report').
137
+ */
138
+ public function getReportId(): string;
139
+
140
+ /**
141
+ * Human-readable display name.
142
+ */
143
+ public function getName(): string;
144
+
145
+ /**
146
+ * Description shown in the report list.
147
+ */
148
+ public function getDescription(): string;
149
+
150
+ /**
151
+ * Parameter definitions for the report form.
152
+ * Each parameter: ['name' => string, 'label' => string, 'type' => string, 'required' => bool]
153
+ */
154
+ public function getParameters(): array;
155
+
156
+ /**
157
+ * Generate report data as an array of DTOs.
158
+ * Parameters are the user-provided form inputs.
159
+ */
160
+ public function generateData(array $parameters): array;
161
+
162
+ /**
163
+ * Supported export formats: ['pdf', 'xlsx', 'csv']
164
+ */
165
+ public function getSupportedFormats(): array;
166
+
167
+ /**
168
+ * Blade template path for PDF rendering (e.g., 'reports.templates.staff-list').
169
+ */
170
+ public function getPdfTemplate(): string;
171
+
172
+ /**
173
+ * Column headers for XLSX/CSV export.
174
+ */
175
+ public function getExportHeaders(): array;
176
+
177
+ /**
178
+ * Whether the PDF should be rendered in landscape orientation.
179
+ */
180
+ public function isLandscape(): bool;
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Report Model (Eloquent)
187
+
188
+ ```php
189
+ namespace App\Models;
190
+
191
+ use Illuminate\Database\Eloquent\Model;
192
+ use Illuminate\Database\Eloquent\Concerns\HasUuids;
193
+
194
+ class Report extends Model
195
+ {
196
+ use HasUuids;
197
+
198
+ protected $fillable = [
199
+ 'report_id',
200
+ 'name',
201
+ 'description',
202
+ 'parameters',
203
+ 'supported_formats',
204
+ 'module',
205
+ ];
206
+
207
+ protected $casts = [
208
+ 'parameters' => 'array',
209
+ 'supported_formats' => 'array',
210
+ ];
211
+ }
212
+ ```
213
+
214
+ Migration:
215
+ ```php
216
+ Schema::create('reports', function (Blueprint $table) {
217
+ $table->uuid('id')->primary();
218
+ $table->string('report_id')->unique();
219
+ $table->string('name');
220
+ $table->text('description')->nullable();
221
+ $table->json('parameters')->nullable();
222
+ $table->json('supported_formats');
223
+ $table->string('module')->nullable();
224
+ $table->timestamps();
225
+ });
226
+ ```
227
+
228
+ ---
229
+
230
+ ## ReportRegistry
231
+
232
+ Auto-discovers all `ReportDefinition` implementations and syncs to the database:
233
+
234
+ ```php
235
+ namespace App\Services\Reporting;
236
+
237
+ use App\Contracts\ReportDefinition;
238
+ use App\Models\Report;
239
+
240
+ class ReportRegistry
241
+ {
242
+ /** @var array<string, ReportDefinition> */
243
+ private array $definitions = [];
244
+
245
+ /**
246
+ * Register a report definition (called from service providers).
247
+ */
248
+ public function register(ReportDefinition $definition): void
249
+ {
250
+ $this->definitions[$definition->getReportId()] = $definition;
251
+ }
252
+
253
+ /**
254
+ * Get a report definition by ID.
255
+ */
256
+ public function get(string $reportId): ?ReportDefinition
257
+ {
258
+ return $this->definitions[$reportId] ?? null;
259
+ }
260
+
261
+ /**
262
+ * Sync all registered definitions to the database.
263
+ */
264
+ public function sync(): void
265
+ {
266
+ foreach ($this->definitions as $definition) {
267
+ Report::updateOrCreate(
268
+ ['report_id' => $definition->getReportId()],
269
+ [
270
+ 'name' => $definition->getName(),
271
+ 'description' => $definition->getDescription(),
272
+ 'parameters' => $definition->getParameters(),
273
+ 'supported_formats' => $definition->getSupportedFormats(),
274
+ ]
275
+ );
276
+ }
277
+
278
+ // Remove reports no longer registered
279
+ Report::whereNotIn('report_id', array_keys($this->definitions))->delete();
280
+ }
281
+
282
+ /**
283
+ * Get all registered definitions.
284
+ */
285
+ public function all(): array
286
+ {
287
+ return $this->definitions;
288
+ }
289
+ }
290
+ ```
291
+
292
+ Register as singleton in `AppServiceProvider`:
293
+ ```php
294
+ $this->app->singleton(ReportRegistry::class);
295
+ ```
296
+
297
+ Modules register their reports in their service provider:
298
+ ```php
299
+ // In Modules/Staff/Providers/StaffServiceProvider.php
300
+ public function boot(): void
301
+ {
302
+ $registry = app(ReportRegistry::class);
303
+ $registry->register(app(StaffListReport::class));
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## ReportService
310
+
311
+ Orchestrates report generation across all formats. Uses Browsershot (Puppeteer) for PDF
312
+ rendering — the Blade template is rendered to HTML, then Browsershot converts it to PDF
313
+ using headless Chrome:
314
+
315
+ ```php
316
+ namespace App\Services\Reporting;
317
+
318
+ use App\Contracts\ReportDefinition;
319
+ use Illuminate\Http\Response;
320
+ use Maatwebsite\Excel\Facades\Excel;
321
+ use Spatie\Browsershot\Browsershot;
322
+ use Symfony\Component\HttpFoundation\BinaryFileResponse;
323
+
324
+ class ReportService
325
+ {
326
+ public function __construct(
327
+ private readonly ReportRegistry $registry,
328
+ ) {}
329
+
330
+ /**
331
+ * Generate and download a report in the specified format.
332
+ */
333
+ public function generate(string $reportId, array $parameters, string $format): Response|BinaryFileResponse
334
+ {
335
+ $definition = $this->registry->get($reportId)
336
+ ?? throw new \RuntimeException("Report not found: {$reportId}");
337
+
338
+ $data = $definition->generateData($parameters);
339
+
340
+ return match ($format) {
341
+ 'pdf' => $this->generatePdf($definition, $data, $parameters),
342
+ 'xlsx' => $this->generateExcel($definition, $data, 'xlsx'),
343
+ 'csv' => $this->generateExcel($definition, $data, 'csv'),
344
+ default => throw new \InvalidArgumentException("Unsupported format: {$format}"),
345
+ };
346
+ }
347
+
348
+ private function generatePdf(ReportDefinition $definition, array $data, array $parameters): Response
349
+ {
350
+ $html = view($definition->getPdfTemplate(), [
351
+ 'data' => $data,
352
+ 'parameters' => $parameters,
353
+ 'reportName' => $definition->getName(),
354
+ 'generatedAt' => now(),
355
+ ])->render();
356
+
357
+ $filename = $definition->getReportId() . '-' . now()->format('Y-m-d-His') . '.pdf';
358
+
359
+ $browsershot = Browsershot::html($html)
360
+ ->format('A4')
361
+ ->margins(10, 10, 10, 10)
362
+ ->showBackground()
363
+ ->waitUntilNetworkIdle();
364
+
365
+ if ($definition->isLandscape()) {
366
+ $browsershot->landscape();
367
+ }
368
+
369
+ $this->applyBrowsershotConfig($browsershot);
370
+
371
+ $pdfContent = $browsershot->pdf();
372
+
373
+ return response($pdfContent)
374
+ ->header('Content-Type', 'application/pdf')
375
+ ->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
376
+ }
377
+
378
+ private function generateExcel(ReportDefinition $definition, array $data, string $format): BinaryFileResponse
379
+ {
380
+ $export = new GenericReportExport($definition->getExportHeaders(), $data);
381
+ $extension = $format === 'csv' ? 'csv' : 'xlsx';
382
+ $filename = $definition->getReportId() . '-' . now()->format('Y-m-d-His') . '.' . $extension;
383
+
384
+ $writerType = $format === 'csv'
385
+ ? \Maatwebsite\Excel\Excel::CSV
386
+ : \Maatwebsite\Excel\Excel::XLSX;
387
+
388
+ return Excel::download($export, $filename, $writerType);
389
+ }
390
+
391
+ /**
392
+ * Apply optional Browsershot configuration from config/browsershot.php.
393
+ */
394
+ private function applyBrowsershotConfig(Browsershot $browsershot): void
395
+ {
396
+ $nodeBinary = config('browsershot.node_binary');
397
+ $npmBinary = config('browsershot.npm_binary');
398
+ $chromePath = config('browsershot.chrome_path');
399
+ $nodeModulesPath = config('browsershot.node_modules_path');
400
+
401
+ if ($nodeBinary) {
402
+ $browsershot->setNodeBinary($nodeBinary);
403
+ }
404
+
405
+ if ($npmBinary) {
406
+ $browsershot->setNpmBinary($npmBinary);
407
+ }
408
+
409
+ if ($chromePath) {
410
+ $browsershot->setChromePath($chromePath);
411
+ }
412
+
413
+ if ($nodeModulesPath) {
414
+ $browsershot->setNodeModulePath($nodeModulesPath);
415
+ }
416
+
417
+ if (config('browsershot.no_sandbox', false)) {
418
+ $browsershot->noSandbox();
419
+ }
420
+ }
421
+ }
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Generic Report Export (Laravel Excel)
427
+
428
+ ```php
429
+ namespace App\Services\Reporting;
430
+
431
+ use Maatwebsite\Excel\Concerns\FromArray;
432
+ use Maatwebsite\Excel\Concerns\WithHeadings;
433
+ use Maatwebsite\Excel\Concerns\WithStyles;
434
+ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
435
+
436
+ class GenericReportExport implements FromArray, WithHeadings, WithStyles
437
+ {
438
+ public function __construct(
439
+ private readonly array $headers,
440
+ private readonly array $data,
441
+ ) {}
442
+
443
+ public function headings(): array
444
+ {
445
+ return $this->headers;
446
+ }
447
+
448
+ public function array(): array
449
+ {
450
+ return array_map(function ($row) {
451
+ if (is_object($row) && method_exists($row, 'toArray')) {
452
+ return $row->toArray();
453
+ }
454
+ return (array) $row;
455
+ }, $this->data);
456
+ }
457
+
458
+ public function styles(Worksheet $sheet): array
459
+ {
460
+ return [
461
+ 1 => ['font' => ['bold' => true]],
462
+ ];
463
+ }
464
+ }
465
+ ```
466
+
467
+ ---
468
+
469
+ ## Report Controller
470
+
471
+ ```php
472
+ namespace App\Http\Controllers;
473
+
474
+ use App\Models\Report;
475
+ use App\Services\Reporting\ReportRegistry;
476
+ use App\Services\Reporting\ReportService;
477
+ use Illuminate\Http\Request;
478
+ use Illuminate\View\View;
479
+
480
+ class ReportController extends Controller
481
+ {
482
+ public function __construct(
483
+ private readonly ReportService $reportService,
484
+ private readonly ReportRegistry $registry,
485
+ ) {}
486
+
487
+ public function index(): View
488
+ {
489
+ $reports = Report::orderBy('name')->paginate(20);
490
+ return view('reports.index', compact('reports'));
491
+ }
492
+
493
+ public function parameters(string $reportId): View
494
+ {
495
+ $definition = $this->registry->get($reportId)
496
+ ?? abort(404, "Report not found: {$reportId}");
497
+
498
+ return view('reports.parameters', [
499
+ 'report' => $definition,
500
+ 'reportId' => $reportId,
501
+ ]);
502
+ }
503
+
504
+ public function generate(Request $request, string $reportId)
505
+ {
506
+ $definition = $this->registry->get($reportId)
507
+ ?? abort(404, "Report not found: {$reportId}");
508
+
509
+ $validated = $request->validate(
510
+ collect($definition->getParameters())
511
+ ->mapWithKeys(fn ($param) => [
512
+ $param['name'] => $param['required'] ? 'required' : 'nullable',
513
+ ])
514
+ ->toArray()
515
+ );
516
+
517
+ $format = $request->input('format', 'pdf');
518
+
519
+ return $this->reportService->generate($reportId, $validated, $format);
520
+ }
521
+ }
522
+ ```
523
+
524
+ ---
525
+
526
+ ## Sample Module Report Implementation
527
+
528
+ ```php
529
+ namespace Modules\Staff\Reports;
530
+
531
+ use App\Contracts\ReportDefinition;
532
+ use Modules\Staff\Contracts\StaffServiceInterface;
533
+
534
+ class StaffListReport implements ReportDefinition
535
+ {
536
+ public function __construct(
537
+ private readonly StaffServiceInterface $staffService,
538
+ ) {}
539
+
540
+ public function getReportId(): string
541
+ {
542
+ return 'staff-list-report';
543
+ }
544
+
545
+ public function getName(): string
546
+ {
547
+ return 'Staff List Report';
548
+ }
549
+
550
+ public function getDescription(): string
551
+ {
552
+ return 'List all staff members with their department and status.';
553
+ }
554
+
555
+ public function getParameters(): array
556
+ {
557
+ return [
558
+ ['name' => 'department', 'label' => 'Department', 'type' => 'select', 'required' => false],
559
+ ['name' => 'status', 'label' => 'Status', 'type' => 'select', 'required' => false],
560
+ ];
561
+ }
562
+
563
+ public function generateData(array $parameters): array
564
+ {
565
+ // Uses the service interface (public API), NOT the repository
566
+ return $this->staffService->getStaffForReport(
567
+ department: $parameters['department'] ?? null,
568
+ status: $parameters['status'] ?? null,
569
+ );
570
+ }
571
+
572
+ public function getSupportedFormats(): array
573
+ {
574
+ return ['pdf', 'xlsx', 'csv'];
575
+ }
576
+
577
+ public function getPdfTemplate(): string
578
+ {
579
+ return 'staff::reports.staff-list';
580
+ }
581
+
582
+ public function getExportHeaders(): array
583
+ {
584
+ return ['Name', 'Email', 'Department', 'Position', 'Status', 'Joined Date'];
585
+ }
586
+
587
+ public function isLandscape(): bool
588
+ {
589
+ return false;
590
+ }
591
+ }
592
+ ```
593
+
594
+ ---
595
+
596
+ ## PDF Blade Template
597
+
598
+ PDF templates are self-contained HTML documents. Because Browsershot uses headless Chrome,
599
+ templates have full CSS support — Tailwind CSS classes work exactly as they do in the
600
+ browser. Include the Tailwind CSS CDN in the `<head>` so the template renders correctly
601
+ when Browsershot processes it outside the web application context.
602
+
603
+ ```blade
604
+ {{-- Modules/Staff/resources/views/reports/staff-list.blade.php --}}
605
+ <!DOCTYPE html>
606
+ <html>
607
+ <head>
608
+ <meta charset="utf-8">
609
+ <title>{{ $reportName }}</title>
610
+ <script src="https://cdn.tailwindcss.com"></script>
611
+ <style>
612
+ @page { margin: 0; }
613
+ body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
614
+ </style>
615
+ </head>
616
+ <body class="bg-white text-gray-900 text-sm p-8">
617
+ {{-- Report Header --}}
618
+ <div class="text-center mb-6">
619
+ <h1 class="text-xl font-bold">{{ $reportName }}</h1>
620
+ <p class="text-xs text-gray-500">Generated: {{ $generatedAt->format('Y-m-d H:i:s') }}</p>
621
+ </div>
622
+
623
+ {{-- Report Parameters Summary --}}
624
+ @if (!empty(array_filter($parameters)))
625
+ <div class="mb-4 flex flex-wrap gap-3 text-xs text-gray-600">
626
+ @foreach ($parameters as $key => $value)
627
+ @if ($value)
628
+ <span class="bg-gray-100 rounded px-2 py-1">
629
+ <span class="font-medium">{{ ucfirst(str_replace('_', ' ', $key)) }}:</span>
630
+ {{ $value }}
631
+ </span>
632
+ @endif
633
+ @endforeach
634
+ </div>
635
+ @endif
636
+
637
+ {{-- Data Table --}}
638
+ <table class="w-full border-collapse">
639
+ <thead>
640
+ <tr class="bg-gray-100">
641
+ <th class="border border-gray-300 px-3 py-2 text-left font-semibold">Name</th>
642
+ <th class="border border-gray-300 px-3 py-2 text-left font-semibold">Email</th>
643
+ <th class="border border-gray-300 px-3 py-2 text-left font-semibold">Department</th>
644
+ <th class="border border-gray-300 px-3 py-2 text-left font-semibold">Position</th>
645
+ <th class="border border-gray-300 px-3 py-2 text-left font-semibold">Status</th>
646
+ <th class="border border-gray-300 px-3 py-2 text-left font-semibold">Joined Date</th>
647
+ </tr>
648
+ </thead>
649
+ <tbody>
650
+ @foreach ($data as $staff)
651
+ <tr class="even:bg-gray-50">
652
+ <td class="border border-gray-300 px-3 py-2">{{ $staff->name }}</td>
653
+ <td class="border border-gray-300 px-3 py-2">{{ $staff->email }}</td>
654
+ <td class="border border-gray-300 px-3 py-2">{{ $staff->department }}</td>
655
+ <td class="border border-gray-300 px-3 py-2">{{ $staff->position }}</td>
656
+ <td class="border border-gray-300 px-3 py-2">
657
+ <span class="inline-block rounded-full px-2 py-0.5 text-xs font-medium
658
+ {{ $staff->status === 'Active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
659
+ {{ $staff->status }}
660
+ </span>
661
+ </td>
662
+ <td class="border border-gray-300 px-3 py-2">{{ $staff->joined_date }}</td>
663
+ </tr>
664
+ @endforeach
665
+ </tbody>
666
+ </table>
667
+
668
+ {{-- Footer --}}
669
+ <div class="flex justify-between items-center mt-6 text-xs text-gray-500">
670
+ <p>Total records: {{ count($data) }}</p>
671
+ <p>Page 1</p>
672
+ </div>
673
+ </body>
674
+ </html>
675
+ ```
676
+
677
+ ---
678
+
679
+ ## Browsershot Configuration
680
+
681
+ Create a configuration file at `config/browsershot.php` to allow customizing binary paths
682
+ per environment. This is especially useful for Docker deployments where Node.js and Chrome
683
+ are installed at non-standard paths:
684
+
685
+ ```php
686
+ <?php
687
+
688
+ return [
689
+
690
+ /*
691
+ |--------------------------------------------------------------------------
692
+ | Node.js Binary Path
693
+ |--------------------------------------------------------------------------
694
+ |
695
+ | Absolute path to the Node.js binary. When null, Browsershot auto-detects
696
+ | the Node.js binary from the system PATH.
697
+ |
698
+ */
699
+ 'node_binary' => env('BROWSERSHOT_NODE_BINARY', null),
700
+
701
+ /*
702
+ |--------------------------------------------------------------------------
703
+ | npm Binary Path
704
+ |--------------------------------------------------------------------------
705
+ |
706
+ | Absolute path to the npm binary. When null, Browsershot auto-detects
707
+ | the npm binary from the system PATH.
708
+ |
709
+ */
710
+ 'npm_binary' => env('BROWSERSHOT_NPM_BINARY', null),
711
+
712
+ /*
713
+ |--------------------------------------------------------------------------
714
+ | Chrome / Chromium Binary Path
715
+ |--------------------------------------------------------------------------
716
+ |
717
+ | Absolute path to the Chrome or Chromium binary. When null, Browsershot
718
+ | uses the Chrome binary bundled with Puppeteer in node_modules.
719
+ |
720
+ */
721
+ 'chrome_path' => env('BROWSERSHOT_CHROME_PATH', null),
722
+
723
+ /*
724
+ |--------------------------------------------------------------------------
725
+ | node_modules Path
726
+ |--------------------------------------------------------------------------
727
+ |
728
+ | Absolute path to the node_modules directory containing Puppeteer.
729
+ | When null, Browsershot looks for node_modules in the project root.
730
+ |
731
+ */
732
+ 'node_modules_path' => env('BROWSERSHOT_NODE_MODULES_PATH', null),
733
+
734
+ /*
735
+ |--------------------------------------------------------------------------
736
+ | Disable Chrome Sandbox
737
+ |--------------------------------------------------------------------------
738
+ |
739
+ | Set to true when running inside Docker or other containerized environments
740
+ | where Chrome's sandbox cannot be used. This passes --no-sandbox to Chrome.
741
+ |
742
+ */
743
+ 'no_sandbox' => env('BROWSERSHOT_NO_SANDBOX', false),
744
+
745
+ ];
746
+ ```
747
+
748
+ ---
749
+
750
+ ## Key Design Decisions
751
+
752
+ **Why Puppeteer / Browsershot instead of DomPDF:**
753
+ - DomPDF has limited CSS support — no flexbox, no grid, incomplete Tailwind CSS compatibility.
754
+ - Puppeteer uses headless Chrome, which provides full CSS support and pixel-perfect rendering.
755
+ - Report Blade templates use Tailwind CSS classes, the same design system as the web application,
756
+ ensuring visual consistency between the UI and generated PDFs.
757
+ - Browsershot provides a clean PHP API around Puppeteer, handling process management and
758
+ binary detection automatically.
759
+
760
+ **Why DTO as data source:**
761
+ - Modules expose data through service interfaces that return DTOs.
762
+ - Reports consume the module's public API, never bypassing it to query the database directly.
763
+ - This preserves module boundaries — a report is a consumer of the module, not part of its internals.
764
+ - DTOs are easily testable (construct them in tests without database setup).
765
+
766
+ **Template management:**
767
+ - Each module owns its PDF Blade templates in its `resources/views/reports/` directory.
768
+ - Templates are self-contained HTML documents (not partials) — they include their own `<head>`,
769
+ Tailwind CDN, and print styles so Browsershot can render them independently.
770
+ - The AI coding agent writes these templates programmatically as part of module development.
771
+
772
+ ---
773
+
774
+ ## Operational Notes
775
+
776
+ **Chrome memory usage:**
777
+ - Each Browsershot PDF generation spawns a headless Chrome process. For reports with large
778
+ datasets (thousands of rows), Chrome may consume significant memory (200-500 MB per render).
779
+ - For very large reports, consider paginating the data and generating multiple pages rather
780
+ than rendering a single enormous table.
781
+
782
+ **Concurrent report generation:**
783
+ - Multiple simultaneous PDF requests each spawn their own Chrome process. On servers with
784
+ limited memory, consider using a queue (Laravel Queue) to serialize report generation
785
+ and prevent memory exhaustion.
786
+ - Example: dispatch a job to generate the report, then redirect the user to a download page
787
+ that polls for completion.
788
+
789
+ **Docker setup:**
790
+ - The Dockerfile must install Node.js and either Puppeteer (which bundles Chrome) or
791
+ a system Chromium package.
792
+ - Chrome inside Docker requires the `--no-sandbox` flag. Set `BROWSERSHOT_NO_SANDBOX=true`
793
+ in the container environment.
794
+ - Required system dependencies for Chrome in Debian/Ubuntu-based containers:
795
+ `libnss3`, `libatk1.0-0`, `libatk-bridge2.0-0`, `libcups2`, `libxcomposite1`,
796
+ `libxrandr2`, `libgbm1`, `libpango-1.0-0`, `libcairo2`, `libasound2`.
797
+
798
+ **Tailwind CDN in templates:**
799
+ - The `<script src="https://cdn.tailwindcss.com"></script>` tag in PDF templates loads
800
+ Tailwind CSS at render time. Browsershot's `waitUntilNetworkIdle()` ensures the script
801
+ is fully loaded before generating the PDF.
802
+ - For air-gapped environments without internet access, bundle a pre-built Tailwind CSS file
803
+ and reference it with an absolute `file://` path or inline the CSS directly in the template.