@sienklogic/plan-build-run 2.0.0

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 (221) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/CLAUDE.md +149 -0
  3. package/LICENSE +21 -0
  4. package/README.md +247 -0
  5. package/dashboard/bin/cli.js +25 -0
  6. package/dashboard/package.json +34 -0
  7. package/dashboard/public/.gitkeep +0 -0
  8. package/dashboard/public/css/layout.css +406 -0
  9. package/dashboard/public/css/status-colors.css +98 -0
  10. package/dashboard/public/js/htmx-title.js +5 -0
  11. package/dashboard/public/js/sidebar-toggle.js +20 -0
  12. package/dashboard/src/app.js +78 -0
  13. package/dashboard/src/middleware/errorHandler.js +52 -0
  14. package/dashboard/src/middleware/notFoundHandler.js +9 -0
  15. package/dashboard/src/repositories/planning.repository.js +128 -0
  16. package/dashboard/src/routes/events.routes.js +40 -0
  17. package/dashboard/src/routes/index.routes.js +31 -0
  18. package/dashboard/src/routes/pages.routes.js +195 -0
  19. package/dashboard/src/server.js +42 -0
  20. package/dashboard/src/services/dashboard.service.js +222 -0
  21. package/dashboard/src/services/phase.service.js +167 -0
  22. package/dashboard/src/services/project.service.js +57 -0
  23. package/dashboard/src/services/roadmap.service.js +171 -0
  24. package/dashboard/src/services/sse.service.js +58 -0
  25. package/dashboard/src/services/todo.service.js +254 -0
  26. package/dashboard/src/services/watcher.service.js +48 -0
  27. package/dashboard/src/views/coming-soon.ejs +11 -0
  28. package/dashboard/src/views/error.ejs +13 -0
  29. package/dashboard/src/views/index.ejs +5 -0
  30. package/dashboard/src/views/layout.ejs +1 -0
  31. package/dashboard/src/views/partials/dashboard-content.ejs +77 -0
  32. package/dashboard/src/views/partials/footer.ejs +3 -0
  33. package/dashboard/src/views/partials/head.ejs +21 -0
  34. package/dashboard/src/views/partials/header.ejs +12 -0
  35. package/dashboard/src/views/partials/layout-bottom.ejs +15 -0
  36. package/dashboard/src/views/partials/layout-top.ejs +8 -0
  37. package/dashboard/src/views/partials/phase-content.ejs +181 -0
  38. package/dashboard/src/views/partials/phases-content.ejs +117 -0
  39. package/dashboard/src/views/partials/roadmap-content.ejs +142 -0
  40. package/dashboard/src/views/partials/sidebar.ejs +38 -0
  41. package/dashboard/src/views/partials/todo-create-content.ejs +53 -0
  42. package/dashboard/src/views/partials/todo-detail-content.ejs +38 -0
  43. package/dashboard/src/views/partials/todos-content.ejs +53 -0
  44. package/dashboard/src/views/phase-detail.ejs +5 -0
  45. package/dashboard/src/views/phases.ejs +5 -0
  46. package/dashboard/src/views/roadmap.ejs +5 -0
  47. package/dashboard/src/views/todo-create.ejs +5 -0
  48. package/dashboard/src/views/todo-detail.ejs +5 -0
  49. package/dashboard/src/views/todos.ejs +5 -0
  50. package/package.json +57 -0
  51. package/plugins/pbr/.claude-plugin/plugin.json +13 -0
  52. package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -0
  53. package/plugins/pbr/agents/codebase-mapper.md +271 -0
  54. package/plugins/pbr/agents/debugger.md +281 -0
  55. package/plugins/pbr/agents/executor.md +407 -0
  56. package/plugins/pbr/agents/general.md +164 -0
  57. package/plugins/pbr/agents/integration-checker.md +141 -0
  58. package/plugins/pbr/agents/plan-checker.md +280 -0
  59. package/plugins/pbr/agents/planner.md +358 -0
  60. package/plugins/pbr/agents/researcher.md +363 -0
  61. package/plugins/pbr/agents/synthesizer.md +230 -0
  62. package/plugins/pbr/agents/verifier.md +454 -0
  63. package/plugins/pbr/commands/begin.md +5 -0
  64. package/plugins/pbr/commands/build.md +5 -0
  65. package/plugins/pbr/commands/config.md +5 -0
  66. package/plugins/pbr/commands/continue.md +5 -0
  67. package/plugins/pbr/commands/debug.md +5 -0
  68. package/plugins/pbr/commands/discuss.md +5 -0
  69. package/plugins/pbr/commands/explore.md +5 -0
  70. package/plugins/pbr/commands/health.md +5 -0
  71. package/plugins/pbr/commands/help.md +5 -0
  72. package/plugins/pbr/commands/import.md +5 -0
  73. package/plugins/pbr/commands/milestone.md +5 -0
  74. package/plugins/pbr/commands/note.md +5 -0
  75. package/plugins/pbr/commands/pause.md +5 -0
  76. package/plugins/pbr/commands/plan.md +5 -0
  77. package/plugins/pbr/commands/quick.md +5 -0
  78. package/plugins/pbr/commands/resume.md +5 -0
  79. package/plugins/pbr/commands/review.md +5 -0
  80. package/plugins/pbr/commands/scan.md +5 -0
  81. package/plugins/pbr/commands/setup.md +5 -0
  82. package/plugins/pbr/commands/status.md +5 -0
  83. package/plugins/pbr/commands/todo.md +5 -0
  84. package/plugins/pbr/contexts/dev.md +27 -0
  85. package/plugins/pbr/contexts/research.md +28 -0
  86. package/plugins/pbr/contexts/review.md +36 -0
  87. package/plugins/pbr/hooks/hooks.json +183 -0
  88. package/plugins/pbr/references/agent-anti-patterns.md +24 -0
  89. package/plugins/pbr/references/agent-interactions.md +134 -0
  90. package/plugins/pbr/references/agent-teams.md +54 -0
  91. package/plugins/pbr/references/checkpoints.md +157 -0
  92. package/plugins/pbr/references/common-bug-patterns.md +13 -0
  93. package/plugins/pbr/references/continuation-format.md +212 -0
  94. package/plugins/pbr/references/deviation-rules.md +112 -0
  95. package/plugins/pbr/references/git-integration.md +226 -0
  96. package/plugins/pbr/references/integration-patterns.md +117 -0
  97. package/plugins/pbr/references/model-profiles.md +99 -0
  98. package/plugins/pbr/references/model-selection.md +31 -0
  99. package/plugins/pbr/references/pbr-rules.md +193 -0
  100. package/plugins/pbr/references/plan-authoring.md +181 -0
  101. package/plugins/pbr/references/plan-format.md +283 -0
  102. package/plugins/pbr/references/planning-config.md +213 -0
  103. package/plugins/pbr/references/questioning.md +214 -0
  104. package/plugins/pbr/references/reading-verification.md +127 -0
  105. package/plugins/pbr/references/stub-patterns.md +160 -0
  106. package/plugins/pbr/references/subagent-coordination.md +119 -0
  107. package/plugins/pbr/references/ui-formatting.md +399 -0
  108. package/plugins/pbr/references/verification-patterns.md +198 -0
  109. package/plugins/pbr/references/wave-execution.md +95 -0
  110. package/plugins/pbr/scripts/auto-continue.js +80 -0
  111. package/plugins/pbr/scripts/check-dangerous-commands.js +136 -0
  112. package/plugins/pbr/scripts/check-doc-sprawl.js +102 -0
  113. package/plugins/pbr/scripts/check-phase-boundary.js +196 -0
  114. package/plugins/pbr/scripts/check-plan-format.js +270 -0
  115. package/plugins/pbr/scripts/check-roadmap-sync.js +252 -0
  116. package/plugins/pbr/scripts/check-skill-workflow.js +262 -0
  117. package/plugins/pbr/scripts/check-state-sync.js +476 -0
  118. package/plugins/pbr/scripts/check-subagent-output.js +144 -0
  119. package/plugins/pbr/scripts/config-schema.json +251 -0
  120. package/plugins/pbr/scripts/context-budget-check.js +287 -0
  121. package/plugins/pbr/scripts/event-handler.js +151 -0
  122. package/plugins/pbr/scripts/event-logger.js +92 -0
  123. package/plugins/pbr/scripts/hook-logger.js +76 -0
  124. package/plugins/pbr/scripts/hooks-schema.json +79 -0
  125. package/plugins/pbr/scripts/log-subagent.js +152 -0
  126. package/plugins/pbr/scripts/log-tool-failure.js +88 -0
  127. package/plugins/pbr/scripts/pbr-tools.js +1301 -0
  128. package/plugins/pbr/scripts/post-write-dispatch.js +66 -0
  129. package/plugins/pbr/scripts/post-write-quality.js +207 -0
  130. package/plugins/pbr/scripts/pre-bash-dispatch.js +56 -0
  131. package/plugins/pbr/scripts/pre-write-dispatch.js +62 -0
  132. package/plugins/pbr/scripts/progress-tracker.js +228 -0
  133. package/plugins/pbr/scripts/session-cleanup.js +254 -0
  134. package/plugins/pbr/scripts/status-line.js +285 -0
  135. package/plugins/pbr/scripts/suggest-compact.js +119 -0
  136. package/plugins/pbr/scripts/task-completed.js +45 -0
  137. package/plugins/pbr/scripts/track-context-budget.js +119 -0
  138. package/plugins/pbr/scripts/validate-commit.js +200 -0
  139. package/plugins/pbr/scripts/validate-plugin-structure.js +172 -0
  140. package/plugins/pbr/skills/begin/SKILL.md +545 -0
  141. package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -0
  142. package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
  143. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -0
  144. package/plugins/pbr/skills/begin/templates/config.json.tmpl +63 -0
  145. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -0
  146. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -0
  147. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -0
  148. package/plugins/pbr/skills/build/SKILL.md +962 -0
  149. package/plugins/pbr/skills/config/SKILL.md +241 -0
  150. package/plugins/pbr/skills/continue/SKILL.md +127 -0
  151. package/plugins/pbr/skills/debug/SKILL.md +489 -0
  152. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -0
  153. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -0
  154. package/plugins/pbr/skills/discuss/SKILL.md +338 -0
  155. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
  156. package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -0
  157. package/plugins/pbr/skills/explore/SKILL.md +362 -0
  158. package/plugins/pbr/skills/health/SKILL.md +186 -0
  159. package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -0
  160. package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -0
  161. package/plugins/pbr/skills/help/SKILL.md +140 -0
  162. package/plugins/pbr/skills/import/SKILL.md +490 -0
  163. package/plugins/pbr/skills/milestone/SKILL.md +673 -0
  164. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -0
  165. package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -0
  166. package/plugins/pbr/skills/note/SKILL.md +212 -0
  167. package/plugins/pbr/skills/pause/SKILL.md +235 -0
  168. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -0
  169. package/plugins/pbr/skills/plan/SKILL.md +628 -0
  170. package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -0
  171. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -0
  172. package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
  173. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -0
  174. package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
  175. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -0
  176. package/plugins/pbr/skills/quick/SKILL.md +335 -0
  177. package/plugins/pbr/skills/resume/SKILL.md +388 -0
  178. package/plugins/pbr/skills/review/SKILL.md +652 -0
  179. package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -0
  180. package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
  181. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -0
  182. package/plugins/pbr/skills/scan/SKILL.md +269 -0
  183. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
  184. package/plugins/pbr/skills/setup/SKILL.md +227 -0
  185. package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -0
  186. package/plugins/pbr/skills/shared/config-loading.md +102 -0
  187. package/plugins/pbr/skills/shared/context-budget.md +40 -0
  188. package/plugins/pbr/skills/shared/context-loader-task.md +86 -0
  189. package/plugins/pbr/skills/shared/digest-select.md +79 -0
  190. package/plugins/pbr/skills/shared/domain-probes.md +125 -0
  191. package/plugins/pbr/skills/shared/error-reporting.md +79 -0
  192. package/plugins/pbr/skills/shared/gate-prompts.md +388 -0
  193. package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -0
  194. package/plugins/pbr/skills/shared/progress-display.md +53 -0
  195. package/plugins/pbr/skills/shared/revision-loop.md +81 -0
  196. package/plugins/pbr/skills/shared/state-loading.md +62 -0
  197. package/plugins/pbr/skills/shared/state-update.md +161 -0
  198. package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -0
  199. package/plugins/pbr/skills/status/SKILL.md +353 -0
  200. package/plugins/pbr/skills/todo/SKILL.md +181 -0
  201. package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -0
  202. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -0
  203. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -0
  204. package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -0
  205. package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -0
  206. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -0
  207. package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -0
  208. package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -0
  209. package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -0
  210. package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -0
  211. package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -0
  212. package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -0
  213. package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -0
  214. package/plugins/pbr/templates/continue-here.md.tmpl +73 -0
  215. package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -0
  216. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -0
  217. package/plugins/pbr/templates/research/STACK.md.tmpl +71 -0
  218. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -0
  219. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -0
  220. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -0
  221. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -0
@@ -0,0 +1,406 @@
1
+ /* ============================================
2
+ Towline Dashboard — Layout & Typography
3
+ ============================================ */
4
+
5
+ /* --- Custom Properties --- */
6
+ :root {
7
+ --sidebar-width: 220px;
8
+ --header-height: 3.5rem;
9
+ --content-max-width: 960px;
10
+ --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
11
+ --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Fira Code', monospace;
12
+ --space-xs: 0.25rem;
13
+ --space-sm: 0.5rem;
14
+ --space-md: 1rem;
15
+ --space-lg: 1.5rem;
16
+ --space-xl: 2rem;
17
+ --space-2xl: 3rem;
18
+ --radius-sm: 0.375rem;
19
+ --radius-md: 0.5rem;
20
+ --border-subtle: rgba(255, 255, 255, 0.08);
21
+ --bg-surface: rgba(255, 255, 255, 0.03);
22
+ --bg-surface-hover: rgba(255, 255, 255, 0.06);
23
+ --text-dim: rgba(255, 255, 255, 0.5);
24
+ }
25
+
26
+ /* --- Typography Override --- */
27
+ html {
28
+ font-family: var(--font-sans);
29
+ font-size: 15px;
30
+ letter-spacing: -0.01em;
31
+ }
32
+
33
+ code, pre, kbd, samp {
34
+ font-family: var(--font-mono);
35
+ font-size: 0.875em;
36
+ }
37
+
38
+ h1 { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.025em; margin-bottom: var(--space-sm); }
39
+ h2 { font-size: 1.35rem; font-weight: 600; letter-spacing: -0.02em; margin-top: var(--space-2xl); margin-bottom: var(--space-md); }
40
+ h3 { font-size: 1.1rem; font-weight: 600; }
41
+
42
+ p { line-height: 1.65; }
43
+
44
+ small { font-size: 0.8125rem; color: var(--text-dim); }
45
+
46
+ /* --- Page Grid Layout --- */
47
+ .page-wrapper {
48
+ display: grid;
49
+ gap: 0;
50
+ grid-template-columns: var(--sidebar-width) 1fr;
51
+ grid-template-rows: var(--header-height) 1fr auto;
52
+ grid-template-areas:
53
+ "header header"
54
+ "sidebar content"
55
+ "footer footer";
56
+ min-height: 100vh;
57
+ }
58
+
59
+ /* --- Header --- */
60
+ .page-wrapper > header {
61
+ grid-area: header;
62
+ display: flex;
63
+ align-items: center;
64
+ padding: 0 var(--space-xl);
65
+ border-bottom: 1px solid var(--border-subtle);
66
+ background: var(--bg-surface);
67
+ backdrop-filter: blur(8px);
68
+ position: sticky;
69
+ top: 0;
70
+ z-index: 10;
71
+ }
72
+
73
+ .page-wrapper > header nav {
74
+ display: flex;
75
+ justify-content: space-between;
76
+ align-items: center;
77
+ width: 100%;
78
+ margin: 0;
79
+ padding: 0;
80
+ }
81
+
82
+ .page-wrapper > header nav strong {
83
+ font-size: 1rem;
84
+ font-weight: 600;
85
+ letter-spacing: -0.02em;
86
+ white-space: nowrap;
87
+ }
88
+
89
+ .page-wrapper > header nav ul {
90
+ list-style: none;
91
+ margin: 0;
92
+ padding: 0;
93
+ }
94
+
95
+ /* --- Sidebar --- */
96
+ .page-wrapper > aside.sidebar {
97
+ grid-area: sidebar;
98
+ padding: var(--space-lg) 0;
99
+ border-right: 1px solid var(--border-subtle);
100
+ background: var(--bg-surface);
101
+ position: sticky;
102
+ top: var(--header-height);
103
+ height: calc(100vh - var(--header-height));
104
+ overflow-y: auto;
105
+ }
106
+
107
+ aside.sidebar nav ul {
108
+ list-style: none;
109
+ padding: 0;
110
+ margin: 0;
111
+ }
112
+
113
+ aside.sidebar nav li {
114
+ margin: 0;
115
+ padding: 0;
116
+ }
117
+
118
+ aside.sidebar nav a {
119
+ display: flex;
120
+ align-items: center;
121
+ padding: 0.6rem var(--space-lg);
122
+ text-decoration: none;
123
+ border-left: 3px solid transparent;
124
+ font-size: 0.9rem;
125
+ font-weight: 500;
126
+ color: var(--text-dim);
127
+ transition: all 0.15s ease;
128
+ }
129
+
130
+ aside.sidebar nav a:hover {
131
+ color: var(--pico-color);
132
+ background: var(--bg-surface-hover);
133
+ border-left-color: rgba(255, 255, 255, 0.15);
134
+ }
135
+
136
+ aside.sidebar nav a[aria-current="page"] {
137
+ font-weight: 600;
138
+ color: var(--pico-primary);
139
+ border-left-color: var(--pico-primary);
140
+ background: var(--bg-surface-hover);
141
+ }
142
+
143
+ /* --- Main Content --- */
144
+ .page-wrapper > main {
145
+ grid-area: content;
146
+ padding: var(--space-xl) var(--space-2xl);
147
+ overflow-y: auto;
148
+ max-width: calc(var(--content-max-width) + var(--space-2xl) * 2);
149
+ }
150
+
151
+ /* --- Footer --- */
152
+ .page-wrapper > footer {
153
+ grid-area: footer;
154
+ padding: var(--space-md) var(--space-xl);
155
+ border-top: 1px solid var(--border-subtle);
156
+ text-align: center;
157
+ }
158
+
159
+ .page-wrapper > footer small {
160
+ font-size: 0.75rem;
161
+ color: var(--text-dim);
162
+ }
163
+
164
+ /* --- Article Cards --- */
165
+ article {
166
+ border: 1px solid var(--border-subtle);
167
+ border-radius: var(--radius-md);
168
+ background: var(--bg-surface);
169
+ margin-bottom: var(--space-lg);
170
+ }
171
+
172
+ article > header {
173
+ border-bottom: 1px solid var(--border-subtle);
174
+ padding: var(--space-md) var(--space-lg);
175
+ background: rgba(255, 255, 255, 0.02);
176
+ border-radius: var(--radius-md) var(--radius-md) 0 0;
177
+ }
178
+
179
+ article > header strong {
180
+ font-size: 0.95rem;
181
+ font-weight: 600;
182
+ }
183
+
184
+ /* --- Tables --- */
185
+ .table-wrap {
186
+ overflow-x: auto;
187
+ -webkit-overflow-scrolling: touch;
188
+ margin: 0 calc(-1 * var(--space-lg));
189
+ padding: 0 var(--space-lg);
190
+ }
191
+
192
+ table {
193
+ font-size: 0.875rem;
194
+ border-collapse: collapse;
195
+ width: 100%;
196
+ }
197
+
198
+ th {
199
+ font-weight: 600;
200
+ font-size: 0.8125rem;
201
+ text-transform: uppercase;
202
+ letter-spacing: 0.04em;
203
+ color: var(--text-dim);
204
+ white-space: nowrap;
205
+ }
206
+
207
+ td {
208
+ vertical-align: top;
209
+ line-height: 1.5;
210
+ }
211
+
212
+ /* --- Progress Bar --- */
213
+ progress {
214
+ height: 0.5rem;
215
+ border-radius: 999px;
216
+ }
217
+
218
+ progress::-webkit-progress-bar {
219
+ background: rgba(255, 255, 255, 0.08);
220
+ border-radius: 999px;
221
+ }
222
+
223
+ progress::-webkit-progress-value {
224
+ background: var(--pico-primary);
225
+ border-radius: 999px;
226
+ transition: width 0.4s ease;
227
+ }
228
+
229
+ progress::-moz-progress-bar {
230
+ background: var(--pico-primary);
231
+ border-radius: 999px;
232
+ }
233
+
234
+ /* --- Details/Summary --- */
235
+ details {
236
+ margin: var(--space-sm) 0;
237
+ border: none;
238
+ }
239
+
240
+ details summary {
241
+ font-size: 0.875rem;
242
+ font-weight: 500;
243
+ cursor: pointer;
244
+ padding: var(--space-xs) 0;
245
+ color: var(--pico-primary);
246
+ }
247
+
248
+ details summary:hover {
249
+ text-decoration: underline;
250
+ }
251
+
252
+ details ul {
253
+ margin-top: var(--space-xs);
254
+ padding-left: var(--space-lg);
255
+ }
256
+
257
+ details li {
258
+ font-size: 0.875rem;
259
+ line-height: 1.6;
260
+ }
261
+
262
+ /* --- Inline Code --- */
263
+ :not(pre) > code {
264
+ font-size: 0.8125em;
265
+ padding: 0.15em 0.4em;
266
+ border-radius: var(--radius-sm);
267
+ background: rgba(255, 255, 255, 0.06);
268
+ }
269
+
270
+ /* --- Back Link --- */
271
+ main > p:first-of-type > a[href="/"] {
272
+ font-size: 0.875rem;
273
+ color: var(--text-dim);
274
+ text-decoration: none;
275
+ }
276
+
277
+ main > p:first-of-type > a[href="/"]:hover {
278
+ color: var(--pico-primary);
279
+ }
280
+
281
+ /* --- SSE Connection Status --- */
282
+ #sse-status {
283
+ display: inline-block;
284
+ width: 8px;
285
+ height: 8px;
286
+ border-radius: 50%;
287
+ margin-left: var(--space-sm);
288
+ vertical-align: middle;
289
+ transition: background-color 0.3s ease;
290
+ }
291
+
292
+ #sse-status[data-connected="true"] {
293
+ background-color: var(--status-complete);
294
+ }
295
+
296
+ #sse-status[data-connected="false"] {
297
+ background-color: var(--status-not-started);
298
+ }
299
+
300
+ /* --- Mobile hamburger toggle --- */
301
+ .sidebar-toggle {
302
+ display: none;
303
+ background: none;
304
+ border: 1px solid var(--border-subtle);
305
+ border-radius: var(--radius-sm);
306
+ color: var(--pico-color);
307
+ font-size: 1.25rem;
308
+ padding: 0.25rem 0.5rem;
309
+ cursor: pointer;
310
+ line-height: 1;
311
+ }
312
+
313
+ /* ============================================
314
+ Responsive Breakpoints
315
+ ============================================ */
316
+
317
+ /* Tablet: narrower sidebar */
318
+ @media (max-width: 1024px) {
319
+ :root {
320
+ --sidebar-width: 180px;
321
+ }
322
+
323
+ .page-wrapper > main {
324
+ padding: var(--space-lg) var(--space-xl);
325
+ }
326
+ }
327
+
328
+ /* Mobile: sidebar collapses to top nav */
329
+ @media (max-width: 768px) {
330
+ .page-wrapper {
331
+ grid-template-columns: 1fr;
332
+ grid-template-rows: var(--header-height) auto 1fr auto;
333
+ grid-template-areas:
334
+ "header"
335
+ "sidebar"
336
+ "content"
337
+ "footer";
338
+ }
339
+
340
+ .sidebar-toggle {
341
+ display: block;
342
+ }
343
+
344
+ .page-wrapper > aside.sidebar {
345
+ position: static;
346
+ height: auto;
347
+ border-right: none;
348
+ border-bottom: 1px solid var(--border-subtle);
349
+ padding: 0;
350
+ overflow: visible;
351
+ display: none;
352
+ }
353
+
354
+ .page-wrapper > aside.sidebar.open {
355
+ display: block;
356
+ }
357
+
358
+ aside.sidebar nav ul {
359
+ display: flex;
360
+ flex-wrap: wrap;
361
+ gap: 0;
362
+ padding: var(--space-sm) var(--space-md);
363
+ }
364
+
365
+ aside.sidebar nav a {
366
+ border-left: none;
367
+ border-bottom: 2px solid transparent;
368
+ padding: var(--space-sm) var(--space-md);
369
+ font-size: 0.85rem;
370
+ }
371
+
372
+ aside.sidebar nav a[aria-current="page"] {
373
+ border-left: none;
374
+ border-bottom-color: var(--pico-primary);
375
+ }
376
+
377
+ aside.sidebar nav a:hover {
378
+ border-left: none;
379
+ }
380
+
381
+ .page-wrapper > main {
382
+ padding: var(--space-lg) var(--space-md);
383
+ }
384
+
385
+ h1 { font-size: 1.4rem; }
386
+ h2 { font-size: 1.15rem; }
387
+
388
+ table {
389
+ font-size: 0.8125rem;
390
+ }
391
+ }
392
+
393
+ /* Small mobile */
394
+ @media (max-width: 480px) {
395
+ html {
396
+ font-size: 14px;
397
+ }
398
+
399
+ .page-wrapper > main {
400
+ padding: var(--space-md);
401
+ }
402
+
403
+ article > header {
404
+ padding: var(--space-sm) var(--space-md);
405
+ }
406
+ }
@@ -0,0 +1,98 @@
1
+ /* ============================================
2
+ Status Colors & Badges
3
+ ============================================ */
4
+
5
+ :root {
6
+ --status-complete: #34d399;
7
+ --status-in-progress: #fbbf24;
8
+ --status-blocked: #f87171;
9
+ --status-not-started: #6b7280;
10
+ }
11
+
12
+ /* Text color by status */
13
+ [data-status="complete"] { color: var(--status-complete); }
14
+ [data-status="in-progress"] { color: var(--status-in-progress); }
15
+ [data-status="blocked"],
16
+ [data-status="failed"] { color: var(--status-blocked); }
17
+ [data-status="not-started"],
18
+ [data-status="pending"] { color: var(--status-not-started); }
19
+ [data-status="passed"] { color: var(--status-complete); }
20
+ [data-status="partial"] { color: var(--status-in-progress); }
21
+
22
+ /* Status badge */
23
+ .status-badge {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ padding: 0.15rem 0.55rem;
27
+ border-radius: 999px;
28
+ font-size: 0.75rem;
29
+ font-weight: 600;
30
+ letter-spacing: 0.02em;
31
+ line-height: 1.5;
32
+ white-space: nowrap;
33
+ border: 1px solid transparent;
34
+ }
35
+
36
+ /* Dark-friendly badge colors — softer backgrounds with good contrast */
37
+ .status-badge[data-status="complete"] {
38
+ background: rgba(52, 211, 153, 0.12);
39
+ color: #6ee7b7;
40
+ border-color: rgba(52, 211, 153, 0.2);
41
+ }
42
+
43
+ .status-badge[data-status="in-progress"] {
44
+ background: rgba(251, 191, 36, 0.12);
45
+ color: #fcd34d;
46
+ border-color: rgba(251, 191, 36, 0.2);
47
+ }
48
+
49
+ .status-badge[data-status="blocked"],
50
+ .status-badge[data-status="failed"] {
51
+ background: rgba(248, 113, 113, 0.12);
52
+ color: #fca5a5;
53
+ border-color: rgba(248, 113, 113, 0.2);
54
+ }
55
+
56
+ .status-badge[data-status="not-started"],
57
+ .status-badge[data-status="pending"] {
58
+ background: rgba(107, 114, 128, 0.12);
59
+ color: #9ca3af;
60
+ border-color: rgba(107, 114, 128, 0.2);
61
+ }
62
+
63
+ .status-badge[data-status="passed"] {
64
+ background: rgba(52, 211, 153, 0.12);
65
+ color: #6ee7b7;
66
+ border-color: rgba(52, 211, 153, 0.2);
67
+ }
68
+
69
+ .status-badge[data-status="partial"] {
70
+ background: rgba(251, 191, 36, 0.12);
71
+ color: #fcd34d;
72
+ border-color: rgba(251, 191, 36, 0.2);
73
+ }
74
+
75
+ /* Priority badge variants */
76
+ .status-badge[data-priority="P0"] {
77
+ background: rgba(248, 113, 113, 0.12);
78
+ color: #fca5a5;
79
+ border-color: rgba(248, 113, 113, 0.2);
80
+ }
81
+
82
+ .status-badge[data-priority="P1"] {
83
+ background: rgba(251, 146, 60, 0.12);
84
+ color: #fdba74;
85
+ border-color: rgba(251, 146, 60, 0.2);
86
+ }
87
+
88
+ .status-badge[data-priority="P2"] {
89
+ background: rgba(251, 191, 36, 0.12);
90
+ color: #fcd34d;
91
+ border-color: rgba(251, 191, 36, 0.2);
92
+ }
93
+
94
+ .status-badge[data-priority="PX"] {
95
+ background: rgba(129, 140, 248, 0.12);
96
+ color: #a5b4fc;
97
+ border-color: rgba(129, 140, 248, 0.2);
98
+ }
@@ -0,0 +1,5 @@
1
+ // Update document.title from HX-Title response header on HTMX navigation
2
+ document.addEventListener('htmx:afterSettle', function(event) {
3
+ var title = event.detail.xhr && event.detail.xhr.getResponseHeader('HX-Title');
4
+ if (title) document.title = title;
5
+ });
@@ -0,0 +1,20 @@
1
+ // Mobile sidebar toggle
2
+ document.addEventListener('DOMContentLoaded', function() {
3
+ var toggle = document.querySelector('.sidebar-toggle');
4
+ var sidebar = document.querySelector('.sidebar');
5
+ if (toggle && sidebar) {
6
+ toggle.addEventListener('click', function() {
7
+ sidebar.classList.toggle('open');
8
+ });
9
+ }
10
+
11
+ // Close sidebar when a nav link is clicked (mobile)
12
+ var navLinks = sidebar ? sidebar.querySelectorAll('a') : [];
13
+ navLinks.forEach(function(link) {
14
+ link.addEventListener('click', function() {
15
+ if (window.innerWidth <= 768) {
16
+ sidebar.classList.remove('open');
17
+ }
18
+ });
19
+ });
20
+ });
@@ -0,0 +1,78 @@
1
+ import express from 'express';
2
+ import helmet from 'helmet';
3
+ import { join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname } from 'path';
6
+ import indexRouter from './routes/index.routes.js';
7
+ import pagesRouter from './routes/pages.routes.js';
8
+ import eventsRouter from './routes/events.routes.js';
9
+ import notFoundHandler from './middleware/notFoundHandler.js';
10
+ import errorHandler from './middleware/errorHandler.js';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ export function createApp(config) {
16
+ const app = express();
17
+
18
+ // Security headers via Helmet
19
+ // CSP allows CDN scripts (HTMX, Pico.css, htmx-ext-sse) and inline styles
20
+ app.use(helmet({
21
+ contentSecurityPolicy: {
22
+ directives: {
23
+ defaultSrc: ["'self'"],
24
+ scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
25
+ styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
26
+ imgSrc: ["'self'", "data:"],
27
+ connectSrc: ["'self'"],
28
+ fontSrc: ["'self'", "https://cdn.jsdelivr.net"]
29
+ }
30
+ }
31
+ }));
32
+ app.disable('x-powered-by');
33
+
34
+ // Store config for access in routes/services
35
+ app.locals.projectDir = config.projectDir;
36
+
37
+ // View engine setup -- all paths use path.join (cross-platform)
38
+ app.set('views', join(__dirname, 'views'));
39
+ app.set('view engine', 'ejs');
40
+
41
+ // Built-in middleware
42
+ app.use(express.json());
43
+ app.use(express.urlencoded({ extended: false }));
44
+
45
+ // Handle common browser auto-requests cleanly (no stack traces in logs)
46
+ app.get('/favicon.ico', (req, res) => res.status(204).end());
47
+ app.get('/sw.js', (req, res) => res.status(404).end());
48
+
49
+ // Static files
50
+ app.use(express.static(join(__dirname, '..', 'public')));
51
+
52
+ // Auto-set HX-Title on HTMX partial responses so document.title stays current
53
+ app.use((req, res, next) => {
54
+ if (req.get('HX-Request') === 'true') {
55
+ const originalRender = res.render.bind(res);
56
+ res.render = function(view, options, callback) {
57
+ if (options && options.title && !res.getHeader('HX-Title')) {
58
+ res.setHeader('HX-Title', `${options.title} - Plan-Build-Run`);
59
+ }
60
+ return originalRender(view, options, callback);
61
+ };
62
+ }
63
+ next();
64
+ });
65
+
66
+ // Routes
67
+ app.use('/', indexRouter);
68
+ app.use('/', pagesRouter);
69
+ app.use('/api/events', eventsRouter);
70
+
71
+ // 404 catch-all (after routes, before error handler)
72
+ app.use(notFoundHandler);
73
+
74
+ // Error handler MUST be registered last
75
+ app.use(errorHandler);
76
+
77
+ return app;
78
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Express error-handling middleware.
3
+ * MUST have exactly 4 parameters for Express to recognize it as an error handler.
4
+ */
5
+
6
+ // eslint-disable-next-line no-unused-vars
7
+ export default function errorHandler(err, req, res, next) {
8
+ // If headers already sent, delegate to Express default handler
9
+ if (res.headersSent) {
10
+ return next(err);
11
+ }
12
+
13
+ const isDev = process.env.NODE_ENV !== 'production';
14
+ const status = err.status || err.statusCode || 500;
15
+
16
+ // Logging -- skip stack traces for 404s (they're expected, not bugs)
17
+ if (status === 404) {
18
+ console.warn(`404: ${req.originalUrl}`);
19
+ } else {
20
+ console.error('Unhandled error:', err.message);
21
+ if (isDev) {
22
+ console.error(err.stack);
23
+ }
24
+ }
25
+
26
+ // Detect HTMX requests
27
+ const isHtmx = req.get('HX-Request') === 'true';
28
+
29
+ // Set Vary header for proper caching
30
+ res.setHeader('Vary', 'HX-Request');
31
+
32
+ // Build template data
33
+ const templateData = {
34
+ title: `Error ${status}`,
35
+ status,
36
+ message: err.message || 'Internal Server Error',
37
+ stack: isDev ? err.stack : null,
38
+ activePage: ''
39
+ };
40
+
41
+ // Render response
42
+ if (isHtmx) {
43
+ let html = `<h1>Error ${status}</h1><p>${templateData.message}</p>`;
44
+ if (templateData.stack) {
45
+ html += `<pre><code>${templateData.stack}</code></pre>`;
46
+ }
47
+ html += '<p><a href="/">Return to Dashboard</a></p>';
48
+ return res.status(status).send(html);
49
+ }
50
+
51
+ res.status(status).render('error', templateData);
52
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 404 catch-all handler for routes that don't match any defined route.
3
+ * Must be registered AFTER all route handlers but BEFORE the error handler.
4
+ */
5
+ export default function notFoundHandler(req, res, next) {
6
+ const err = new Error(`Page not found: ${req.originalUrl}`);
7
+ err.status = 404;
8
+ next(err);
9
+ }