@planu/cli 0.54.2 → 0.56.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 (178) hide show
  1. package/dist/config/license-plans.json +1 -0
  2. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts +7 -1
  3. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts.map +1 -1
  4. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js +16 -3
  5. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js.map +1 -1
  6. package/dist/engine/doc-generator/executive-summary/html-template.d.ts +5 -2
  7. package/dist/engine/doc-generator/executive-summary/html-template.d.ts.map +1 -1
  8. package/dist/engine/doc-generator/executive-summary/html-template.js +7 -3
  9. package/dist/engine/doc-generator/executive-summary/html-template.js.map +1 -1
  10. package/dist/engine/doc-generator/per-spec-report/executive-report.d.ts +3 -1
  11. package/dist/engine/doc-generator/per-spec-report/executive-report.d.ts.map +1 -1
  12. package/dist/engine/doc-generator/per-spec-report/executive-report.js +22 -2
  13. package/dist/engine/doc-generator/per-spec-report/executive-report.js.map +1 -1
  14. package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts +5 -3
  15. package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts.map +1 -1
  16. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js +27 -6
  17. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js.map +1 -1
  18. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.d.ts +1 -0
  19. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.d.ts.map +1 -1
  20. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.js +29 -0
  21. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.js.map +1 -1
  22. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.d.ts +8 -1
  23. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.d.ts.map +1 -1
  24. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js +188 -0
  25. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js.map +1 -1
  26. package/dist/engine/doc-generator/per-spec-report/technical-report.d.ts +3 -1
  27. package/dist/engine/doc-generator/per-spec-report/technical-report.d.ts.map +1 -1
  28. package/dist/engine/doc-generator/per-spec-report/technical-report.js +24 -7
  29. package/dist/engine/doc-generator/per-spec-report/technical-report.js.map +1 -1
  30. package/dist/engine/doc-generator/portal/analytics-page.d.ts +8 -0
  31. package/dist/engine/doc-generator/portal/analytics-page.d.ts.map +1 -0
  32. package/dist/engine/doc-generator/portal/analytics-page.js +265 -0
  33. package/dist/engine/doc-generator/portal/analytics-page.js.map +1 -0
  34. package/dist/engine/doc-generator/portal/architecture-page.d.ts +6 -0
  35. package/dist/engine/doc-generator/portal/architecture-page.d.ts.map +1 -0
  36. package/dist/engine/doc-generator/portal/architecture-page.js +250 -0
  37. package/dist/engine/doc-generator/portal/architecture-page.js.map +1 -0
  38. package/dist/engine/doc-generator/portal/changelog-page.d.ts +6 -0
  39. package/dist/engine/doc-generator/portal/changelog-page.d.ts.map +1 -0
  40. package/dist/engine/doc-generator/portal/changelog-page.js +204 -0
  41. package/dist/engine/doc-generator/portal/changelog-page.js.map +1 -0
  42. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts +11 -0
  43. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts.map +1 -0
  44. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js +24 -0
  45. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js.map +1 -0
  46. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts +9 -0
  47. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts.map +1 -0
  48. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js +31 -0
  49. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js.map +1 -0
  50. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts +10 -0
  51. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts.map +1 -0
  52. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js +77 -0
  53. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js.map +1 -0
  54. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts +9 -0
  55. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts.map +1 -0
  56. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js +31 -0
  57. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js.map +1 -0
  58. package/dist/engine/doc-generator/portal/decision-page.d.ts +6 -0
  59. package/dist/engine/doc-generator/portal/decision-page.d.ts.map +1 -0
  60. package/dist/engine/doc-generator/portal/decision-page.js +169 -0
  61. package/dist/engine/doc-generator/portal/decision-page.js.map +1 -0
  62. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts +8 -0
  63. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts.map +1 -0
  64. package/dist/engine/doc-generator/portal/decision-timeline-builder.js +131 -0
  65. package/dist/engine/doc-generator/portal/decision-timeline-builder.js.map +1 -0
  66. package/dist/engine/doc-generator/portal/index.d.ts +19 -0
  67. package/dist/engine/doc-generator/portal/index.d.ts.map +1 -0
  68. package/dist/engine/doc-generator/portal/index.js +21 -0
  69. package/dist/engine/doc-generator/portal/index.js.map +1 -0
  70. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts +8 -0
  71. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts.map +1 -0
  72. package/dist/engine/doc-generator/portal/kanban-view-builder.js +140 -0
  73. package/dist/engine/doc-generator/portal/kanban-view-builder.js.map +1 -0
  74. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts +27 -0
  75. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts.map +1 -0
  76. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +139 -0
  77. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js.map +1 -0
  78. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts +8 -0
  79. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts.map +1 -0
  80. package/dist/engine/doc-generator/portal/portal-landing-cards.js +177 -0
  81. package/dist/engine/doc-generator/portal/portal-landing-cards.js.map +1 -0
  82. package/dist/engine/doc-generator/portal/portal-navbar.d.ts +15 -0
  83. package/dist/engine/doc-generator/portal/portal-navbar.d.ts.map +1 -0
  84. package/dist/engine/doc-generator/portal/portal-navbar.js +143 -0
  85. package/dist/engine/doc-generator/portal/portal-navbar.js.map +1 -0
  86. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +22 -0
  87. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts.map +1 -0
  88. package/dist/engine/doc-generator/portal/portal-page-detector.js +58 -0
  89. package/dist/engine/doc-generator/portal/portal-page-detector.js.map +1 -0
  90. package/dist/engine/doc-generator/portal/portal-theme.d.ts +7 -0
  91. package/dist/engine/doc-generator/portal/portal-theme.d.ts.map +1 -0
  92. package/dist/engine/doc-generator/portal/portal-theme.js +53 -0
  93. package/dist/engine/doc-generator/portal/portal-theme.js.map +1 -0
  94. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts +5 -0
  95. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts.map +1 -0
  96. package/dist/engine/doc-generator/portal/risk-gauge-svg.js +80 -0
  97. package/dist/engine/doc-generator/portal/risk-gauge-svg.js.map +1 -0
  98. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +11 -0
  99. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts.map +1 -0
  100. package/dist/engine/doc-generator/portal/risk-matrix-svg.js +181 -0
  101. package/dist/engine/doc-generator/portal/risk-matrix-svg.js.map +1 -0
  102. package/dist/engine/doc-generator/portal/risk-page.d.ts +7 -0
  103. package/dist/engine/doc-generator/portal/risk-page.d.ts.map +1 -0
  104. package/dist/engine/doc-generator/portal/risk-page.js +219 -0
  105. package/dist/engine/doc-generator/portal/risk-page.js.map +1 -0
  106. package/dist/engine/doc-generator/portal/roadmap-page.d.ts +7 -0
  107. package/dist/engine/doc-generator/portal/roadmap-page.d.ts.map +1 -0
  108. package/dist/engine/doc-generator/portal/roadmap-page.js +204 -0
  109. package/dist/engine/doc-generator/portal/roadmap-page.js.map +1 -0
  110. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts +4 -0
  111. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts.map +1 -0
  112. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js +50 -0
  113. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js.map +1 -0
  114. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts +16 -0
  115. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts.map +1 -0
  116. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js +45 -0
  117. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js.map +1 -0
  118. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts +16 -0
  119. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts.map +1 -0
  120. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js +159 -0
  121. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js.map +1 -0
  122. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts +7 -0
  123. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts.map +1 -0
  124. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js +52 -0
  125. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js.map +1 -0
  126. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts +8 -0
  127. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts.map +1 -0
  128. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +126 -0
  129. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js.map +1 -0
  130. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +10 -0
  131. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts.map +1 -0
  132. package/dist/engine/doc-generator/proposal/proposal-generator.js +93 -0
  133. package/dist/engine/doc-generator/proposal/proposal-generator.js.map +1 -0
  134. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts +7 -0
  135. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts.map +1 -0
  136. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js +31 -0
  137. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js.map +1 -0
  138. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts +12 -0
  139. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts.map +1 -0
  140. package/dist/engine/doc-generator/proposal/proposal-i18n.js +230 -0
  141. package/dist/engine/doc-generator/proposal/proposal-i18n.js.map +1 -0
  142. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts +8 -0
  143. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts.map +1 -0
  144. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +32 -0
  145. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js.map +1 -0
  146. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts +14 -0
  147. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts.map +1 -0
  148. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +248 -0
  149. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js.map +1 -0
  150. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts +13 -0
  151. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts.map +1 -0
  152. package/dist/engine/doc-generator/proposal/proposal-section-builders.js +423 -0
  153. package/dist/engine/doc-generator/proposal/proposal-section-builders.js.map +1 -0
  154. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts +4 -0
  155. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts.map +1 -0
  156. package/dist/engine/doc-generator/proposal/proposal-themes.js +194 -0
  157. package/dist/engine/doc-generator/proposal/proposal-themes.js.map +1 -0
  158. package/dist/engine/spec-summary-html.d.ts.map +1 -1
  159. package/dist/engine/spec-summary-html.js +17 -5
  160. package/dist/engine/spec-summary-html.js.map +1 -1
  161. package/dist/index.js +2 -0
  162. package/dist/index.js.map +1 -1
  163. package/dist/tools/generate-proposal.d.ts +3 -0
  164. package/dist/tools/generate-proposal.d.ts.map +1 -0
  165. package/dist/tools/generate-proposal.js +166 -0
  166. package/dist/tools/generate-proposal.js.map +1 -0
  167. package/dist/types/docs.d.ts +75 -0
  168. package/dist/types/docs.d.ts.map +1 -1
  169. package/dist/types/portal.d.ts +128 -0
  170. package/dist/types/portal.d.ts.map +1 -0
  171. package/dist/types/portal.js +3 -0
  172. package/dist/types/portal.js.map +1 -0
  173. package/dist/types/proposal.d.ts +57 -0
  174. package/dist/types/proposal.d.ts.map +1 -0
  175. package/dist/types/proposal.js +2 -0
  176. package/dist/types/proposal.js.map +1 -0
  177. package/package.json +1 -1
  178. package/src/config/license-plans.json +1 -0
@@ -0,0 +1,169 @@
1
+ import { buildNavbar } from './portal-navbar.js';
2
+ import { getPortalThemeCSS } from './portal-theme.js';
3
+ import { buildDecisionTimeline, DECISION_TIMELINE_CSS } from './decision-timeline-builder.js';
4
+ function escapeHtml(s) {
5
+ return s
6
+ .replace(/&/g, '&')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;');
10
+ }
11
+ const CATEGORY_COLORS = {
12
+ architecture: '#7c3aed',
13
+ stack: '#2563eb',
14
+ ux: '#16a34a',
15
+ business: '#ea580c',
16
+ process: '#6b7280',
17
+ };
18
+ function buildPieSlicesSvg(data) {
19
+ const total = data.reduce((s, d) => s + d.value, 0);
20
+ if (total === 0) {
21
+ return '';
22
+ }
23
+ const CX = 80;
24
+ const CY = 80;
25
+ const R = 70;
26
+ const W = 220;
27
+ const H = 160;
28
+ let startAngle = -Math.PI / 2;
29
+ const slices = [];
30
+ const legends = [];
31
+ data.forEach((item, idx) => {
32
+ const sweep = (item.value / total) * 2 * Math.PI;
33
+ const endAngle = startAngle + sweep;
34
+ const x1 = CX + R * Math.cos(startAngle);
35
+ const y1 = CY + R * Math.sin(startAngle);
36
+ const x2 = CX + R * Math.cos(endAngle);
37
+ const y2 = CY + R * Math.sin(endAngle);
38
+ const largeArc = sweep > Math.PI ? 1 : 0;
39
+ const path = `M${CX},${CY} L${x1.toFixed(1)},${y1.toFixed(1)} A${R},${R} 0 ${largeArc},1 ${x2.toFixed(1)},${y2.toFixed(1)} Z`;
40
+ slices.push(`<path d="${path}" fill="${item.color}" stroke="white" stroke-width="2"><title>${escapeHtml(item.label)}: ${item.value}</title></path>`);
41
+ const legendY = 20 + idx * 18;
42
+ legends.push(`<rect x="${W - 100}" y="${legendY - 8}" width="10" height="10" fill="${item.color}"/>` +
43
+ `<text x="${W - 86}" y="${legendY}" font-size="10" fill="#374151">${escapeHtml(item.label)} (${item.value})</text>`);
44
+ startAngle = endAngle;
45
+ });
46
+ return `<svg viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg" style="max-width:220px;height:auto">
47
+ ${slices.join('\n ')}
48
+ ${legends.join('\n ')}
49
+ </svg>`;
50
+ }
51
+ function buildStatsSection(decisions) {
52
+ const byCategory = new Map();
53
+ const byDecidedBy = { human: 0, ai: 0, both: 0 };
54
+ for (const d of decisions) {
55
+ byCategory.set(d.category, (byCategory.get(d.category) ?? 0) + 1);
56
+ if (d.decidedBy in byDecidedBy) {
57
+ byDecidedBy[d.decidedBy]++;
58
+ }
59
+ }
60
+ const pieData = [...byCategory.entries()].map(([cat, val]) => ({
61
+ label: cat,
62
+ value: val,
63
+ color: CATEGORY_COLORS[cat],
64
+ }));
65
+ const pieSvg = buildPieSlicesSvg(pieData);
66
+ return `<section class="planu-card section-card">
67
+ <h2>Decision Statistics</h2>
68
+ <div class="stats-layout">
69
+ <div class="stats-pie">
70
+ <h3 class="stats-subtitle">By Category</h3>
71
+ ${pieSvg}
72
+ </div>
73
+ <div class="stats-counts">
74
+ <h3 class="stats-subtitle">Total: ${decisions.length}</h3>
75
+ <div class="decidedby-grid">
76
+ <div class="stat-box">
77
+ <span class="stat-num">${byDecidedBy.human}</span>
78
+ <span class="stat-lbl">Human</span>
79
+ </div>
80
+ <div class="stat-box">
81
+ <span class="stat-num">${byDecidedBy.ai}</span>
82
+ <span class="stat-lbl">AI</span>
83
+ </div>
84
+ <div class="stat-box">
85
+ <span class="stat-num">${byDecidedBy.both}</span>
86
+ <span class="stat-lbl">Both</span>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </section>`;
92
+ }
93
+ function buildSupersededSection(decisions) {
94
+ const superseded = decisions.filter((d) => d.status === 'superseded');
95
+ if (superseded.length === 0) {
96
+ return '';
97
+ }
98
+ const rows = superseded
99
+ .map((d) => `<tr>
100
+ <td>${escapeHtml(d.id)}</td>
101
+ <td class="text-strike">${escapeHtml(d.what)}</td>
102
+ <td>${escapeHtml(d.why)}</td>
103
+ <td>${escapeHtml(d.date)}</td>
104
+ </tr>`)
105
+ .join('\n');
106
+ return `<section class="planu-card section-card">
107
+ <h2>Superseded Decisions</h2>
108
+ <table class="simple-table">
109
+ <thead><tr><th>ID</th><th>Decision</th><th>Reason</th><th>Date</th></tr></thead>
110
+ <tbody>${rows}</tbody>
111
+ </table>
112
+ </section>`;
113
+ }
114
+ function buildPageCSS() {
115
+ return `
116
+ body { font-family: system-ui, sans-serif; margin: 0; padding: 0 16px 48px; background: var(--planu-bg); color: var(--planu-text); }
117
+ .page-title { font-size: 1.6rem; font-weight: 700; margin: 0 0 6px; }
118
+ .page-subtitle { color: #6b7280; margin: 0 0 24px; font-size: 0.95rem; }
119
+ .section-card { margin-bottom: 24px; }
120
+ h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 16px; color: var(--planu-primary); }
121
+ h3.stats-subtitle { font-size: 0.95rem; font-weight: 600; color: #374151; margin: 0 0 12px; }
122
+ .stats-layout { display: flex; gap: 32px; flex-wrap: wrap; align-items: flex-start; }
123
+ .stats-pie { flex: 1; min-width: 200px; }
124
+ .stats-counts { flex: 1; min-width: 150px; }
125
+ .decidedby-grid { display: flex; gap: 12px; }
126
+ .stat-box { text-align: center; padding: 10px 16px; background: #f3f4f6; border-radius: 8px; }
127
+ .stat-num { display: block; font-size: 1.6rem; font-weight: 700; color: var(--planu-primary); }
128
+ .stat-lbl { font-size: 0.78rem; color: #6b7280; }
129
+ .simple-table { width: 100%; border-collapse: collapse; font-size: 0.83rem; }
130
+ .simple-table th { background: #f3f4f6; padding: 8px 10px; text-align: left; border-bottom: 2px solid var(--planu-border); }
131
+ .simple-table td { padding: 8px 10px; border-bottom: 1px solid var(--planu-border); }
132
+ .text-strike { text-decoration: line-through; color: #9ca3af; }
133
+ .empty-state { text-align: center; padding: 64px 24px; color: #6b7280; }
134
+ .empty-state h3 { font-size: 1.2rem; }`;
135
+ }
136
+ /**
137
+ * Generate the decisions.html portal page.
138
+ */
139
+ export function generateDecisionPage(decisions) {
140
+ const navbar = buildNavbar('decisions', '');
141
+ const themeCSS = getPortalThemeCSS();
142
+ const pageCSS = buildPageCSS();
143
+ const bodyContent = decisions.length === 0
144
+ ? `<div class="empty-state"><h3>No decisions recorded yet.</h3><p>Use <code>log_decision</code> to capture architectural decisions.</p></div>`
145
+ : `${buildStatsSection(decisions)}
146
+ <section class="planu-card section-card">
147
+ <h2>Decision Timeline</h2>
148
+ ${buildDecisionTimeline(decisions)}
149
+ </section>
150
+ ${buildSupersededSection(decisions)}`;
151
+ return `<!DOCTYPE html>
152
+ <html lang="en">
153
+ <head>
154
+ <meta charset="UTF-8">
155
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
156
+ <title>Decisions — Planu Portal</title>
157
+ <style>${themeCSS}${pageCSS}${DECISION_TIMELINE_CSS}</style>
158
+ </head>
159
+ <body>
160
+ ${navbar}
161
+ <main>
162
+ <h1 class="page-title">🏛️ Decision Log</h1>
163
+ <p class="page-subtitle">Architectural and strategic decisions timeline.</p>
164
+ ${bodyContent}
165
+ </main>
166
+ </body>
167
+ </html>`;
168
+ }
169
+ //# sourceMappingURL=decision-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-page.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/decision-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Decision } from '../../../types/decisions.js';
2
+ export declare const DECISION_TIMELINE_CSS = "\n .decision-timeline { position: relative; padding: 16px 0; }\n .timeline-line {\n position: absolute; left: 50%; top: 0; bottom: 0;\n width: 2px; background: #e5e7eb; transform: translateX(-50%);\n }\n .timeline-item {\n position: relative; width: 50%; padding: 0 40px 24px;\n box-sizing: border-box;\n }\n .timeline-item.side-left { left: 0; text-align: right; }\n .timeline-item.side-right { left: 50%; text-align: left; }\n .timeline-dot {\n position: absolute; top: 16px;\n width: 12px; height: 12px;\n border-radius: 50%; border: 2px solid white;\n box-shadow: 0 0 0 2px #e5e7eb;\n }\n .side-left .timeline-dot { right: -6px; }\n .side-right .timeline-dot { left: -6px; }\n .decision-card {\n background: white; border-radius: 8px;\n padding: 12px 14px; font-size: 0.86rem;\n box-shadow: 0 1px 3px rgba(0,0,0,0.08);\n }\n .card-header { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }\n .side-left .card-header { justify-content: flex-end; }\n .decision-id { font-weight: 700; color: #374151; }\n .decision-date { color: #9ca3af; font-size: 0.78rem; margin-left: auto; }\n .decision-what { font-weight: 600; color: #1f2937; margin-bottom: 4px; }\n .decision-why { color: #6b7280; font-size: 0.82rem; margin-bottom: 6px; }\n .decision-alternatives { margin-top: 6px; }\n .alt-label { font-size: 0.75rem; color: #9ca3af; margin-right: 4px; }\n .alt-chip {\n display: inline-block; padding: 2px 6px; border-radius: 4px;\n background: #f3f4f6; color: #374151; font-size: 0.75rem; margin: 2px;\n }\n .category-badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }\n .spec-badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; background: #dbeafe; color: #1d4ed8; }\n .badge-human { background: #d1fae5; color: #065f46; }\n .badge-ai { background: #ede9fe; color: #5b21b6; }\n .badge-both { background: #fef3c7; color: #92400e; }\n .status-superseded .decision-card { opacity: 0.65; }\n .text-strikethrough { text-decoration: line-through; color: #9ca3af; }\n .status-review .decision-card { background: #fffbeb; }\n @media (max-width: 640px) {\n .timeline-line { display: none; }\n .timeline-item, .timeline-item.side-right { left: 0; width: 100%; text-align: left; padding: 0 0 16px 16px; }\n .side-left .card-header { justify-content: flex-start; }\n .timeline-dot { left: 0 !important; right: auto !important; }\n }";
3
+ /**
4
+ * Build an alternating vertical timeline of decisions.
5
+ * Returns empty string when no decisions.
6
+ */
7
+ export declare function buildDecisionTimeline(decisions: Decision[]): string;
8
+ //# sourceMappingURL=decision-timeline-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-timeline-builder.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/decision-timeline-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAoC,MAAM,6BAA6B,CAAC;AAwE9F,eAAO,MAAM,qBAAqB,w+EAkD9B,CAAC;AAEL;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAiBnE"}
@@ -0,0 +1,131 @@
1
+ function escapeHtml(s) {
2
+ return s
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;');
7
+ }
8
+ const CATEGORY_COLORS = {
9
+ architecture: '#7c3aed', // purple
10
+ stack: '#2563eb', // blue
11
+ ux: '#16a34a', // green
12
+ business: '#ea580c', // orange
13
+ process: '#6b7280', // grey
14
+ };
15
+ const DECIDED_BY_BADGE = {
16
+ human: 'badge-human',
17
+ ai: 'badge-ai',
18
+ both: 'badge-both',
19
+ };
20
+ const STATUS_CLASS = {
21
+ active: 'status-active',
22
+ superseded: 'status-superseded',
23
+ under_review: 'status-review',
24
+ };
25
+ function formatDate(dateStr) {
26
+ try {
27
+ const d = new Date(dateStr);
28
+ return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
29
+ }
30
+ catch {
31
+ return escapeHtml(dateStr);
32
+ }
33
+ }
34
+ function buildDecisionCard(decision, side) {
35
+ const color = CATEGORY_COLORS[decision.category];
36
+ const decidedBadgeClass = DECIDED_BY_BADGE[decision.decidedBy] ?? 'badge-human';
37
+ const statusClass = STATUS_CLASS[decision.status];
38
+ const alternativesHtml = decision.alternatives.length > 0
39
+ ? `<div class="decision-alternatives">
40
+ <span class="alt-label">Alternatives considered:</span>
41
+ ${decision.alternatives.map((a) => `<span class="alt-chip">${escapeHtml(a)}</span>`).join('')}
42
+ </div>`
43
+ : '';
44
+ const specBadge = decision.specId
45
+ ? `<span class="spec-badge">${escapeHtml(decision.specId)}</span>`
46
+ : '';
47
+ return `<div class="timeline-item side-${side}" data-status="${decision.status}">
48
+ <div class="timeline-dot" style="background:${color}"></div>
49
+ <div class="decision-card ${statusClass}" style="border-left:4px solid ${color}">
50
+ <div class="card-header">
51
+ <span class="decision-id">${escapeHtml(decision.id)}</span>
52
+ <span class="planu-badge ${decidedBadgeClass}">${escapeHtml(decision.decidedBy)}</span>
53
+ <span class="category-badge" style="background:${color}20;color:${color}">${escapeHtml(decision.category)}</span>
54
+ ${specBadge}
55
+ <span class="decision-date">${formatDate(decision.date)}</span>
56
+ </div>
57
+ <div class="decision-what ${decision.status === 'superseded' ? 'text-strikethrough' : ''}">${escapeHtml(decision.what)}</div>
58
+ <div class="decision-why">${escapeHtml(decision.why)}</div>
59
+ ${alternativesHtml}
60
+ </div>
61
+ </div>`;
62
+ }
63
+ export const DECISION_TIMELINE_CSS = `
64
+ .decision-timeline { position: relative; padding: 16px 0; }
65
+ .timeline-line {
66
+ position: absolute; left: 50%; top: 0; bottom: 0;
67
+ width: 2px; background: #e5e7eb; transform: translateX(-50%);
68
+ }
69
+ .timeline-item {
70
+ position: relative; width: 50%; padding: 0 40px 24px;
71
+ box-sizing: border-box;
72
+ }
73
+ .timeline-item.side-left { left: 0; text-align: right; }
74
+ .timeline-item.side-right { left: 50%; text-align: left; }
75
+ .timeline-dot {
76
+ position: absolute; top: 16px;
77
+ width: 12px; height: 12px;
78
+ border-radius: 50%; border: 2px solid white;
79
+ box-shadow: 0 0 0 2px #e5e7eb;
80
+ }
81
+ .side-left .timeline-dot { right: -6px; }
82
+ .side-right .timeline-dot { left: -6px; }
83
+ .decision-card {
84
+ background: white; border-radius: 8px;
85
+ padding: 12px 14px; font-size: 0.86rem;
86
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
87
+ }
88
+ .card-header { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }
89
+ .side-left .card-header { justify-content: flex-end; }
90
+ .decision-id { font-weight: 700; color: #374151; }
91
+ .decision-date { color: #9ca3af; font-size: 0.78rem; margin-left: auto; }
92
+ .decision-what { font-weight: 600; color: #1f2937; margin-bottom: 4px; }
93
+ .decision-why { color: #6b7280; font-size: 0.82rem; margin-bottom: 6px; }
94
+ .decision-alternatives { margin-top: 6px; }
95
+ .alt-label { font-size: 0.75rem; color: #9ca3af; margin-right: 4px; }
96
+ .alt-chip {
97
+ display: inline-block; padding: 2px 6px; border-radius: 4px;
98
+ background: #f3f4f6; color: #374151; font-size: 0.75rem; margin: 2px;
99
+ }
100
+ .category-badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
101
+ .spec-badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; background: #dbeafe; color: #1d4ed8; }
102
+ .badge-human { background: #d1fae5; color: #065f46; }
103
+ .badge-ai { background: #ede9fe; color: #5b21b6; }
104
+ .badge-both { background: #fef3c7; color: #92400e; }
105
+ .status-superseded .decision-card { opacity: 0.65; }
106
+ .text-strikethrough { text-decoration: line-through; color: #9ca3af; }
107
+ .status-review .decision-card { background: #fffbeb; }
108
+ @media (max-width: 640px) {
109
+ .timeline-line { display: none; }
110
+ .timeline-item, .timeline-item.side-right { left: 0; width: 100%; text-align: left; padding: 0 0 16px 16px; }
111
+ .side-left .card-header { justify-content: flex-start; }
112
+ .timeline-dot { left: 0 !important; right: auto !important; }
113
+ }`;
114
+ /**
115
+ * Build an alternating vertical timeline of decisions.
116
+ * Returns empty string when no decisions.
117
+ */
118
+ export function buildDecisionTimeline(decisions) {
119
+ if (decisions.length === 0) {
120
+ return '';
121
+ }
122
+ const sorted = [...decisions].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
123
+ const items = sorted
124
+ .map((d, i) => buildDecisionCard(d, i % 2 === 0 ? 'left' : 'right'))
125
+ .join('\n ');
126
+ return `<div class="decision-timeline">
127
+ <div class="timeline-line"></div>
128
+ ${items}
129
+ </div>`;
130
+ }
131
+ //# sourceMappingURL=decision-timeline-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-timeline-builder.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/decision-timeline-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export { getPortalThemeCSS } from './portal-theme.js';
2
+ export { buildNavbar, NAV_ITEMS } from './portal-navbar.js';
3
+ export type { PortalNavItem } from './portal-navbar.js';
4
+ export { buildBreadcrumbs, buildSpecNavigation } from './portal-breadcrumbs.js';
5
+ export type { BreadcrumbItem } from './portal-breadcrumbs.js';
6
+ export { buildQuickLinksSection } from './portal-landing-cards.js';
7
+ export { detectAvailablePages, detectAvailablePagesSync, DETECTABLE_PAGES, } from './portal-page-detector.js';
8
+ export { generateAnalyticsPage } from './analytics-page.js';
9
+ export { generateRoadmapPage } from './roadmap-page.js';
10
+ export { buildKanbanView } from './kanban-view-builder.js';
11
+ export { buildSvgBarChart } from './svg-charts/bar-chart.js';
12
+ export { buildSvgLineChart, buildSvgDualLineChart } from './svg-charts/line-chart.js';
13
+ export { buildSvgPieChart } from './svg-charts/pie-chart.js';
14
+ export { scaleLinear, svgText, CHART_COLORS } from './svg-charts/chart-utils.js';
15
+ export { computeVelocity } from './data-aggregators/velocity-aggregator.js';
16
+ export { computeBurndown } from './data-aggregators/burndown-aggregator.js';
17
+ export { detectBottlenecks } from './data-aggregators/bottleneck-detector.js';
18
+ export { computeAccuracy } from './data-aggregators/accuracy-aggregator.js';
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAChF,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAG3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGjF,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,21 @@
1
+ // Planu — Portal infrastructure barrel export
2
+ export { getPortalThemeCSS } from './portal-theme.js';
3
+ export { buildNavbar, NAV_ITEMS } from './portal-navbar.js';
4
+ export { buildBreadcrumbs, buildSpecNavigation } from './portal-breadcrumbs.js';
5
+ export { buildQuickLinksSection } from './portal-landing-cards.js';
6
+ export { detectAvailablePages, detectAvailablePagesSync, DETECTABLE_PAGES, } from './portal-page-detector.js';
7
+ // SPEC-142 — Analytics, Roadmap, Kanban
8
+ export { generateAnalyticsPage } from './analytics-page.js';
9
+ export { generateRoadmapPage } from './roadmap-page.js';
10
+ export { buildKanbanView } from './kanban-view-builder.js';
11
+ // SVG chart builders
12
+ export { buildSvgBarChart } from './svg-charts/bar-chart.js';
13
+ export { buildSvgLineChart, buildSvgDualLineChart } from './svg-charts/line-chart.js';
14
+ export { buildSvgPieChart } from './svg-charts/pie-chart.js';
15
+ export { scaleLinear, svgText, CHART_COLORS } from './svg-charts/chart-utils.js';
16
+ // Data aggregators
17
+ export { computeVelocity } from './data-aggregators/velocity-aggregator.js';
18
+ export { computeBurndown } from './data-aggregators/burndown-aggregator.js';
19
+ export { detectBottlenecks } from './data-aggregators/bottleneck-detector.js';
20
+ export { computeAccuracy } from './data-aggregators/accuracy-aggregator.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAE9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,wCAAwC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,qBAAqB;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEjF,mBAAmB;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Spec } from '../../../types/index.js';
2
+ /**
3
+ * Build an HTML fragment (not a full page) containing a kanban board.
4
+ * Suitable for embedding inside the portal index.html.
5
+ * Returns empty string when specs is empty.
6
+ */
7
+ export declare function buildKanbanView(specs: Spec[]): string;
8
+ //# sourceMappingURL=kanban-view-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kanban-view-builder.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/kanban-view-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAyBpD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAqCrD"}
@@ -0,0 +1,140 @@
1
+ const COLUMNS = [
2
+ { key: 'draft', label: 'Draft' },
3
+ { key: 'review', label: 'Review' },
4
+ { key: 'approved', label: 'Approved' },
5
+ { key: 'implementing', label: 'Implementing' },
6
+ { key: 'done', label: 'Done' },
7
+ ];
8
+ const DIFFICULTY_CLASSES = {
9
+ trivial: 'planu-badge-muted',
10
+ low: 'planu-badge-info',
11
+ medium: 'planu-badge-warning',
12
+ high: 'planu-badge-danger',
13
+ critical: 'planu-badge-danger',
14
+ };
15
+ const RISK_CLASSES = {
16
+ low: 'planu-badge-success',
17
+ medium: 'planu-badge-warning',
18
+ high: 'planu-badge-danger',
19
+ critical: 'planu-badge-danger',
20
+ };
21
+ /**
22
+ * Build an HTML fragment (not a full page) containing a kanban board.
23
+ * Suitable for embedding inside the portal index.html.
24
+ * Returns empty string when specs is empty.
25
+ */
26
+ export function buildKanbanView(specs) {
27
+ if (specs.length === 0) {
28
+ return '';
29
+ }
30
+ const byStatus = new Map();
31
+ for (const col of COLUMNS) {
32
+ byStatus.set(col.key, []);
33
+ }
34
+ for (const spec of specs) {
35
+ const col = byStatus.get(spec.status);
36
+ if (col) {
37
+ col.push(spec);
38
+ }
39
+ else {
40
+ // Unknown status goes to draft column
41
+ byStatus.get('draft')?.push(spec);
42
+ }
43
+ }
44
+ const columns = COLUMNS.map(({ key, label }) => {
45
+ const colSpecs = byStatus.get(key) ?? [];
46
+ const cards = colSpecs.map((s) => buildCard(s)).join('\n');
47
+ return `<div class="kanban-col">
48
+ <div class="kanban-col-header">
49
+ <span class="kanban-col-title">${escapeHtml(label)}</span>
50
+ <span class="planu-badge planu-badge-muted">${colSpecs.length}</span>
51
+ </div>
52
+ <div class="kanban-cards">
53
+ ${cards || '<p class="kanban-empty">No specs</p>'}
54
+ </div>
55
+ </div>`;
56
+ });
57
+ return `<style>${KANBAN_CSS}</style>
58
+ <div class="kanban-board" role="region" aria-label="Kanban board">
59
+ ${columns.join('\n ')}
60
+ </div>`;
61
+ }
62
+ function buildCard(spec) {
63
+ const diffClass = DIFFICULTY_CLASSES[spec.difficulty] ?? 'planu-badge-muted';
64
+ const riskClass = RISK_CLASSES[spec.risk] ?? 'planu-badge-muted';
65
+ const tags = spec.tags
66
+ .slice(0, 3)
67
+ .map((t) => `<span class="kanban-tag">${escapeHtml(t)}</span>`)
68
+ .join(' ');
69
+ const shortTitle = spec.title.length > 50 ? spec.title.slice(0, 49) + '…' : spec.title;
70
+ return `<div class="kanban-card">
71
+ <div class="kanban-card-id">${escapeHtml(spec.id)}</div>
72
+ <div class="kanban-card-title">${escapeHtml(shortTitle)}</div>
73
+ <div class="kanban-card-badges">
74
+ <span class="planu-badge ${diffClass}">${escapeHtml(String(spec.difficulty))}</span>
75
+ <span class="planu-badge ${riskClass}">${escapeHtml(spec.risk)}</span>
76
+ </div>
77
+ ${tags ? `<div class="kanban-card-tags">${tags}</div>` : ''}
78
+ </div>`;
79
+ }
80
+ function escapeHtml(str) {
81
+ return str
82
+ .replace(/&/g, '&amp;')
83
+ .replace(/</g, '&lt;')
84
+ .replace(/>/g, '&gt;')
85
+ .replace(/"/g, '&quot;');
86
+ }
87
+ const KANBAN_CSS = `
88
+ .kanban-board {
89
+ display: grid;
90
+ grid-template-columns: repeat(5, 1fr);
91
+ gap: 12px;
92
+ overflow-x: auto;
93
+ }
94
+ @media (max-width: 900px) {
95
+ .kanban-board { grid-template-columns: repeat(2, 1fr); }
96
+ }
97
+ @media (max-width: 500px) {
98
+ .kanban-board { grid-template-columns: 1fr; }
99
+ }
100
+ .kanban-col {
101
+ background: #f8f9fa;
102
+ border: 1px solid #e9ecef;
103
+ border-radius: 10px;
104
+ padding: 12px;
105
+ min-height: 120px;
106
+ }
107
+ .kanban-col-header {
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: space-between;
111
+ margin-bottom: 10px;
112
+ }
113
+ .kanban-col-title {
114
+ font-weight: 600;
115
+ font-size: 0.85rem;
116
+ color: #1a1a2e;
117
+ }
118
+ .kanban-cards { display: flex; flex-direction: column; gap: 8px; }
119
+ .kanban-card {
120
+ background: white;
121
+ border: 1px solid #e9ecef;
122
+ border-radius: 8px;
123
+ padding: 10px;
124
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04);
125
+ }
126
+ .kanban-card-id { font-size: 0.7rem; color: #9ca3af; margin-bottom: 4px; }
127
+ .kanban-card-title { font-size: 0.82rem; color: #2d3436; margin-bottom: 6px; line-height: 1.3; }
128
+ .kanban-card-badges { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 4px; }
129
+ .kanban-card-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
130
+ .kanban-tag {
131
+ font-size: 0.68rem;
132
+ background: #eff6ff;
133
+ color: #1d4ed8;
134
+ padding: 1px 6px;
135
+ border-radius: 99px;
136
+ }
137
+ .kanban-empty { font-size: 0.78rem; color: #9ca3af; text-align: center; padding: 12px 0; }
138
+ @media print { .kanban-board { display: none; } }
139
+ `;
140
+ //# sourceMappingURL=kanban-view-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kanban-view-builder.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/kanban-view-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { BreadcrumbItem } from '../../../types/docs.js';
2
+ export type { BreadcrumbItem };
3
+ /**
4
+ * Build a breadcrumb trail.
5
+ * Last item is rendered as plain text (current page); all others are links.
6
+ *
7
+ * Example: buildBreadcrumbs([
8
+ * { label: 'Dashboard', href: '../../index.html' },
9
+ * { label: 'SPEC-141', href: './executive-report.html' },
10
+ * { label: 'Executive Report' },
11
+ * ])
12
+ */
13
+ export declare function buildBreadcrumbs(items: BreadcrumbItem[]): string;
14
+ /**
15
+ * Build prev/next spec navigation links shown at the bottom of per-spec reports.
16
+ *
17
+ * @param currentSpecId - e.g. "SPEC-141"
18
+ * @param allSpecIds - All known spec IDs (used to find neighbours by numeric order).
19
+ * @param basePath - Path from current file to planu/specs/ directory, e.g. "../".
20
+ * Each neighbour link appends "{SPEC-ID-slug}/executive-report.html"
21
+ * but since we only have IDs (not slugs), we use the ID as-is.
22
+ * Callers can pass slug-aware paths if needed.
23
+ * @param reportFilename - The report file to link to in neighbour specs (e.g. "executive-report.html").
24
+ * @param specFolderFn - Optional function to resolve spec ID → folder name. Defaults to identity.
25
+ */
26
+ export declare function buildSpecNavigation(currentSpecId: string, allSpecIds: string[], basePath: string, reportFilename?: string, specFolderFn?: (specId: string) => string): string;
27
+ //# sourceMappingURL=portal-breadcrumbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-breadcrumbs.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/portal-breadcrumbs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,YAAY,EAAE,cAAc,EAAE,CAAC;AA6D/B;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAoBhE;AAiCD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,MAAM,EAChB,cAAc,SAA0B,EACxC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GACxC,MAAM,CAuBR"}