@planu/cli 0.55.0 → 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 (162) 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/html-wrapper.d.ts +5 -3
  11. package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts.map +1 -1
  12. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js +27 -6
  13. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js.map +1 -1
  14. package/dist/engine/doc-generator/portal/analytics-page.d.ts +8 -0
  15. package/dist/engine/doc-generator/portal/analytics-page.d.ts.map +1 -0
  16. package/dist/engine/doc-generator/portal/analytics-page.js +265 -0
  17. package/dist/engine/doc-generator/portal/analytics-page.js.map +1 -0
  18. package/dist/engine/doc-generator/portal/architecture-page.d.ts +6 -0
  19. package/dist/engine/doc-generator/portal/architecture-page.d.ts.map +1 -0
  20. package/dist/engine/doc-generator/portal/architecture-page.js +250 -0
  21. package/dist/engine/doc-generator/portal/architecture-page.js.map +1 -0
  22. package/dist/engine/doc-generator/portal/changelog-page.d.ts +6 -0
  23. package/dist/engine/doc-generator/portal/changelog-page.d.ts.map +1 -0
  24. package/dist/engine/doc-generator/portal/changelog-page.js +204 -0
  25. package/dist/engine/doc-generator/portal/changelog-page.js.map +1 -0
  26. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts +11 -0
  27. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts.map +1 -0
  28. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js +24 -0
  29. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js.map +1 -0
  30. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts +9 -0
  31. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts.map +1 -0
  32. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js +31 -0
  33. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js.map +1 -0
  34. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts +10 -0
  35. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts.map +1 -0
  36. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js +77 -0
  37. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js.map +1 -0
  38. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts +9 -0
  39. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts.map +1 -0
  40. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js +31 -0
  41. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js.map +1 -0
  42. package/dist/engine/doc-generator/portal/decision-page.d.ts +6 -0
  43. package/dist/engine/doc-generator/portal/decision-page.d.ts.map +1 -0
  44. package/dist/engine/doc-generator/portal/decision-page.js +169 -0
  45. package/dist/engine/doc-generator/portal/decision-page.js.map +1 -0
  46. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts +8 -0
  47. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts.map +1 -0
  48. package/dist/engine/doc-generator/portal/decision-timeline-builder.js +131 -0
  49. package/dist/engine/doc-generator/portal/decision-timeline-builder.js.map +1 -0
  50. package/dist/engine/doc-generator/portal/index.d.ts +19 -0
  51. package/dist/engine/doc-generator/portal/index.d.ts.map +1 -0
  52. package/dist/engine/doc-generator/portal/index.js +21 -0
  53. package/dist/engine/doc-generator/portal/index.js.map +1 -0
  54. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts +8 -0
  55. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts.map +1 -0
  56. package/dist/engine/doc-generator/portal/kanban-view-builder.js +140 -0
  57. package/dist/engine/doc-generator/portal/kanban-view-builder.js.map +1 -0
  58. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts +27 -0
  59. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts.map +1 -0
  60. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +139 -0
  61. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js.map +1 -0
  62. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts +8 -0
  63. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts.map +1 -0
  64. package/dist/engine/doc-generator/portal/portal-landing-cards.js +177 -0
  65. package/dist/engine/doc-generator/portal/portal-landing-cards.js.map +1 -0
  66. package/dist/engine/doc-generator/portal/portal-navbar.d.ts +15 -0
  67. package/dist/engine/doc-generator/portal/portal-navbar.d.ts.map +1 -0
  68. package/dist/engine/doc-generator/portal/portal-navbar.js +143 -0
  69. package/dist/engine/doc-generator/portal/portal-navbar.js.map +1 -0
  70. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +22 -0
  71. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts.map +1 -0
  72. package/dist/engine/doc-generator/portal/portal-page-detector.js +58 -0
  73. package/dist/engine/doc-generator/portal/portal-page-detector.js.map +1 -0
  74. package/dist/engine/doc-generator/portal/portal-theme.d.ts +7 -0
  75. package/dist/engine/doc-generator/portal/portal-theme.d.ts.map +1 -0
  76. package/dist/engine/doc-generator/portal/portal-theme.js +53 -0
  77. package/dist/engine/doc-generator/portal/portal-theme.js.map +1 -0
  78. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts +5 -0
  79. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts.map +1 -0
  80. package/dist/engine/doc-generator/portal/risk-gauge-svg.js +80 -0
  81. package/dist/engine/doc-generator/portal/risk-gauge-svg.js.map +1 -0
  82. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +11 -0
  83. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts.map +1 -0
  84. package/dist/engine/doc-generator/portal/risk-matrix-svg.js +181 -0
  85. package/dist/engine/doc-generator/portal/risk-matrix-svg.js.map +1 -0
  86. package/dist/engine/doc-generator/portal/risk-page.d.ts +7 -0
  87. package/dist/engine/doc-generator/portal/risk-page.d.ts.map +1 -0
  88. package/dist/engine/doc-generator/portal/risk-page.js +219 -0
  89. package/dist/engine/doc-generator/portal/risk-page.js.map +1 -0
  90. package/dist/engine/doc-generator/portal/roadmap-page.d.ts +7 -0
  91. package/dist/engine/doc-generator/portal/roadmap-page.d.ts.map +1 -0
  92. package/dist/engine/doc-generator/portal/roadmap-page.js +204 -0
  93. package/dist/engine/doc-generator/portal/roadmap-page.js.map +1 -0
  94. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts +4 -0
  95. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts.map +1 -0
  96. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js +50 -0
  97. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js.map +1 -0
  98. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts +16 -0
  99. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts.map +1 -0
  100. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js +45 -0
  101. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js.map +1 -0
  102. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts +16 -0
  103. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts.map +1 -0
  104. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js +159 -0
  105. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js.map +1 -0
  106. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts +7 -0
  107. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts.map +1 -0
  108. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js +52 -0
  109. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js.map +1 -0
  110. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts +8 -0
  111. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts.map +1 -0
  112. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +126 -0
  113. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js.map +1 -0
  114. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +10 -0
  115. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts.map +1 -0
  116. package/dist/engine/doc-generator/proposal/proposal-generator.js +93 -0
  117. package/dist/engine/doc-generator/proposal/proposal-generator.js.map +1 -0
  118. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts +7 -0
  119. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts.map +1 -0
  120. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js +31 -0
  121. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js.map +1 -0
  122. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts +12 -0
  123. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts.map +1 -0
  124. package/dist/engine/doc-generator/proposal/proposal-i18n.js +230 -0
  125. package/dist/engine/doc-generator/proposal/proposal-i18n.js.map +1 -0
  126. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts +8 -0
  127. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts.map +1 -0
  128. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +32 -0
  129. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js.map +1 -0
  130. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts +14 -0
  131. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts.map +1 -0
  132. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +248 -0
  133. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js.map +1 -0
  134. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts +13 -0
  135. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts.map +1 -0
  136. package/dist/engine/doc-generator/proposal/proposal-section-builders.js +423 -0
  137. package/dist/engine/doc-generator/proposal/proposal-section-builders.js.map +1 -0
  138. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts +4 -0
  139. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts.map +1 -0
  140. package/dist/engine/doc-generator/proposal/proposal-themes.js +194 -0
  141. package/dist/engine/doc-generator/proposal/proposal-themes.js.map +1 -0
  142. package/dist/engine/spec-summary-html.d.ts.map +1 -1
  143. package/dist/engine/spec-summary-html.js +17 -5
  144. package/dist/engine/spec-summary-html.js.map +1 -1
  145. package/dist/index.js +2 -0
  146. package/dist/index.js.map +1 -1
  147. package/dist/tools/generate-proposal.d.ts +3 -0
  148. package/dist/tools/generate-proposal.d.ts.map +1 -0
  149. package/dist/tools/generate-proposal.js +166 -0
  150. package/dist/tools/generate-proposal.js.map +1 -0
  151. package/dist/types/docs.d.ts +41 -0
  152. package/dist/types/docs.d.ts.map +1 -1
  153. package/dist/types/portal.d.ts +128 -0
  154. package/dist/types/portal.d.ts.map +1 -0
  155. package/dist/types/portal.js +3 -0
  156. package/dist/types/portal.js.map +1 -0
  157. package/dist/types/proposal.d.ts +57 -0
  158. package/dist/types/proposal.d.ts.map +1 -0
  159. package/dist/types/proposal.js +2 -0
  160. package/dist/types/proposal.js.map +1 -0
  161. package/package.json +1 -1
  162. package/src/config/license-plans.json +1 -0
@@ -0,0 +1,219 @@
1
+ import { buildNavbar } from './portal-navbar.js';
2
+ import { getPortalThemeCSS } from './portal-theme.js';
3
+ import { buildRiskMatrixSvg } from './risk-matrix-svg.js';
4
+ import { buildRiskGaugeSvg } from './risk-gauge-svg.js';
5
+ function escapeHtml(s) {
6
+ return s
7
+ .replace(/&/g, '&')
8
+ .replace(/</g, '&lt;')
9
+ .replace(/>/g, '&gt;')
10
+ .replace(/"/g, '&quot;');
11
+ }
12
+ const STATUS_BADGE = {
13
+ identified: 'planu-badge-muted',
14
+ mitigating: 'planu-badge-info',
15
+ mitigated: 'planu-badge-success',
16
+ accepted: 'planu-badge-warning',
17
+ materialized: 'planu-badge-danger',
18
+ };
19
+ function statusBadge(status) {
20
+ const cls = STATUS_BADGE[status];
21
+ return `<span class="planu-badge ${cls}">${escapeHtml(status)}</span>`;
22
+ }
23
+ function categoryBadge(cat) {
24
+ const colors = {
25
+ technical: 'planu-badge-info',
26
+ security: 'planu-badge-danger',
27
+ integration: 'planu-badge-warning',
28
+ dependency: 'planu-badge-muted',
29
+ organizational: 'planu-badge-muted',
30
+ data: 'planu-badge-success',
31
+ };
32
+ return `<span class="planu-badge ${colors[cat]}">${escapeHtml(cat)}</span>`;
33
+ }
34
+ function buildRiskTable(allRisks, specMap) {
35
+ if (allRisks.length === 0) {
36
+ return `<p class="empty-message">No risks registered yet.</p>`;
37
+ }
38
+ const rows = allRisks
39
+ .map((r) => {
40
+ const specTitle = specMap.get(r.specId) ?? r.specId;
41
+ return `<tr>
42
+ <td>${escapeHtml(r.id)}</td>
43
+ <td title="${escapeHtml(r.specId)}">${escapeHtml(specTitle)}</td>
44
+ <td>${escapeHtml(r.description)}</td>
45
+ <td>${categoryBadge(r.category)}</td>
46
+ <td class="num">${r.probability}%</td>
47
+ <td class="num">$${r.impactUsd.toLocaleString()}</td>
48
+ <td class="num">${r.impactDays}d</td>
49
+ <td>${escapeHtml(r.mitigationPlan || '—')}</td>
50
+ <td>${escapeHtml(r.owner || '—')}</td>
51
+ <td>${statusBadge(r.status)}</td>
52
+ </tr>`;
53
+ })
54
+ .join('\n');
55
+ return `<div class="table-wrap">
56
+ <table class="risk-table sortable">
57
+ <thead>
58
+ <tr>
59
+ <th>ID</th>
60
+ <th>Spec</th>
61
+ <th>Description</th>
62
+ <th>Category</th>
63
+ <th class="sortable-col">Probability</th>
64
+ <th class="sortable-col">Impact $</th>
65
+ <th class="sortable-col">Impact Days</th>
66
+ <th>Mitigation</th>
67
+ <th>Owner</th>
68
+ <th>Status</th>
69
+ </tr>
70
+ </thead>
71
+ <tbody>
72
+ ${rows}
73
+ </tbody>
74
+ </table>
75
+ </div>`;
76
+ }
77
+ function buildCoverageSection(allRisks) {
78
+ if (allRisks.length === 0) {
79
+ return '';
80
+ }
81
+ const total = allRisks.length;
82
+ const withPlan = allRisks.filter((r) => r.mitigationPlan && r.mitigationPlan.trim().length > 0).length;
83
+ const mitigated = allRisks.filter((r) => r.status === 'mitigated').length;
84
+ const noOwner = allRisks.filter((r) => !r.owner || r.owner.trim() === '').length;
85
+ const planPct = Math.round((withPlan / total) * 100);
86
+ const mitigatedPct = Math.round((mitigated / total) * 100);
87
+ const warning = noOwner > 0
88
+ ? `<div class="coverage-warning">⚠️ <strong>${noOwner}</strong> risk${noOwner > 1 ? 's' : ''} without an assigned owner.</div>`
89
+ : '';
90
+ return `<section class="planu-card section-card">
91
+ <h2>Mitigation Coverage</h2>
92
+ <div class="coverage-grid">
93
+ <div class="coverage-item">
94
+ <span class="coverage-pct">${planPct}%</span>
95
+ <span class="coverage-label">With Mitigation Plan</span>
96
+ </div>
97
+ <div class="coverage-item">
98
+ <span class="coverage-pct coverage-green">${mitigatedPct}%</span>
99
+ <span class="coverage-label">Mitigated</span>
100
+ </div>
101
+ <div class="coverage-item">
102
+ <span class="coverage-pct coverage-red">${noOwner}</span>
103
+ <span class="coverage-label">Without Owner</span>
104
+ </div>
105
+ </div>
106
+ ${warning}
107
+ </section>`;
108
+ }
109
+ function buildScoreCard(registers, allRisks) {
110
+ const avgScore = registers.length > 0
111
+ ? Math.round(registers.reduce((s, r) => s + r.aggregateRiskScore, 0) / registers.length)
112
+ : 0;
113
+ const highCount = allRisks.filter((r) => r.probability >= 60 && r.impactUsd >= 5000).length;
114
+ const criticalCount = allRisks.filter((r) => r.probability >= 80 && r.impactUsd >= 20000).length;
115
+ const gaugeSvg = buildRiskGaugeSvg(avgScore);
116
+ return `<section class="planu-card section-card score-card">
117
+ <h2>Risk Score</h2>
118
+ <div class="score-layout">
119
+ <div class="gauge-wrap">${gaugeSvg}</div>
120
+ <div class="score-stats">
121
+ <div class="stat-item">
122
+ <span class="stat-num">${allRisks.length}</span>
123
+ <span class="stat-label">Total Risks</span>
124
+ </div>
125
+ <div class="stat-item stat-warn">
126
+ <span class="stat-num">${highCount}</span>
127
+ <span class="stat-label">High Priority</span>
128
+ </div>
129
+ <div class="stat-item stat-danger">
130
+ <span class="stat-num">${criticalCount}</span>
131
+ <span class="stat-label">Critical</span>
132
+ </div>
133
+ <div class="stat-item">
134
+ <span class="stat-num">${registers.length}</span>
135
+ <span class="stat-label">Specs with Risks</span>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </section>`;
140
+ }
141
+ function buildPageCSS() {
142
+ return `
143
+ body { font-family: system-ui, sans-serif; margin: 0; padding: 0 16px 48px; background: var(--planu-bg); color: var(--planu-text); }
144
+ .page-title { font-size: 1.6rem; font-weight: 700; margin: 0 0 6px; }
145
+ .page-subtitle { color: #6b7280; margin: 0 0 24px; font-size: 0.95rem; }
146
+ .section-card { margin-bottom: 24px; }
147
+ h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 16px; color: var(--planu-primary); }
148
+ .matrix-wrap { overflow-x: auto; }
149
+ .matrix-wrap svg { max-width: 100%; height: auto; }
150
+ .table-wrap { overflow-x: auto; }
151
+ .risk-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
152
+ .risk-table th { background: #f3f4f6; text-align: left; padding: 8px 10px; font-weight: 600; white-space: nowrap; border-bottom: 2px solid var(--planu-border); }
153
+ .risk-table td { padding: 8px 10px; border-bottom: 1px solid var(--planu-border); vertical-align: top; }
154
+ .risk-table tr:hover td { background: #f9fafb; }
155
+ .risk-table .num { text-align: right; white-space: nowrap; }
156
+ .score-layout { display: flex; align-items: flex-start; gap: 24px; flex-wrap: wrap; }
157
+ .gauge-wrap { width: 200px; flex-shrink: 0; }
158
+ .gauge-wrap svg { width: 100%; height: auto; }
159
+ .score-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; flex: 1; }
160
+ .stat-item { text-align: center; padding: 12px; background: #f9fafb; border-radius: 8px; }
161
+ .stat-num { display: block; font-size: 1.8rem; font-weight: 700; color: var(--planu-primary); }
162
+ .stat-warn .stat-num { color: var(--planu-warning); }
163
+ .stat-danger .stat-num { color: var(--planu-danger); }
164
+ .stat-label { font-size: 0.78rem; color: #6b7280; }
165
+ .coverage-grid { display: flex; gap: 24px; flex-wrap: wrap; }
166
+ .coverage-item { text-align: center; min-width: 100px; }
167
+ .coverage-pct { display: block; font-size: 2rem; font-weight: 700; color: var(--planu-primary); }
168
+ .coverage-green { color: var(--planu-success); }
169
+ .coverage-red { color: var(--planu-danger); }
170
+ .coverage-label { font-size: 0.82rem; color: #6b7280; }
171
+ .coverage-warning { margin-top: 12px; padding: 10px 14px; background: #fef3c7; border-radius: 8px; font-size: 0.88rem; color: #92400e; }
172
+ .empty-state { text-align: center; padding: 48px 24px; color: #6b7280; }
173
+ .empty-state h3 { font-size: 1.2rem; margin-bottom: 8px; }
174
+ .empty-message { color: #9ca3af; font-style: italic; }`;
175
+ }
176
+ /**
177
+ * Generate the risks.html portal page.
178
+ */
179
+ export function generateRiskPage(risks, specs) {
180
+ const specMap = new Map(specs.map((s) => [s.id, s.title]));
181
+ const allRisks = risks.flatMap((r) => r.risks);
182
+ const navbar = buildNavbar('risks', '');
183
+ const themeCSS = getPortalThemeCSS();
184
+ const pageCSS = buildPageCSS();
185
+ const matrixSvg = buildRiskMatrixSvg(allRisks);
186
+ const matrixSection = allRisks.length > 0
187
+ ? `<section class="planu-card section-card">
188
+ <h2>Risk Matrix</h2>
189
+ <div class="matrix-wrap">${matrixSvg}</div>
190
+ </section>`
191
+ : '';
192
+ const bodyContent = allRisks.length === 0
193
+ ? `<div class="empty-state"><h3>No risk data available</h3><p>Use <code>estimate</code> with risk analysis to populate this page.</p></div>`
194
+ : `${buildScoreCard(risks, allRisks)}
195
+ ${matrixSection}
196
+ <section class="planu-card section-card">
197
+ <h2>Risk Registry</h2>
198
+ ${buildRiskTable(allRisks, specMap)}
199
+ </section>
200
+ ${buildCoverageSection(allRisks)}`;
201
+ return `<!DOCTYPE html>
202
+ <html lang="en">
203
+ <head>
204
+ <meta charset="UTF-8">
205
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
206
+ <title>Risks — Planu Portal</title>
207
+ <style>${themeCSS}${pageCSS}</style>
208
+ </head>
209
+ <body>
210
+ ${navbar}
211
+ <main>
212
+ <h1 class="page-title">⚠️ Risk Register</h1>
213
+ <p class="page-subtitle">Risk matrix, registry, and mitigation coverage across all specs.</p>
214
+ ${bodyContent}
215
+ </main>
216
+ </body>
217
+ </html>`;
218
+ }
219
+ //# sourceMappingURL=risk-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-page.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/risk-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Spec } from '../../../types/index.js';
2
+ /**
3
+ * Generate the full roadmap.html page for the Planu portal.
4
+ * Returns an HTML string ready to write to disk.
5
+ */
6
+ export declare function generateRoadmapPage(specs: Spec[], ganttStartDate?: string): string;
7
+ //# sourceMappingURL=roadmap-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roadmap-page.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/roadmap-page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAKpD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAqClF"}
@@ -0,0 +1,204 @@
1
+ import { getPortalThemeCSS } from './portal-theme.js';
2
+ import { buildNavbar } from './portal-navbar.js';
3
+ import { buildGanttChart } from '../proposal/proposal-gantt-builder.js';
4
+ /**
5
+ * Generate the full roadmap.html page for the Planu portal.
6
+ * Returns an HTML string ready to write to disk.
7
+ */
8
+ export function generateRoadmapPage(specs, ganttStartDate) {
9
+ const navbar = buildNavbar('roadmap', '', ['roadmap', 'dashboard']);
10
+ const ganttHtml = buildGanttSection(specs, ganttStartDate);
11
+ const phaseSummary = buildPhaseSummary(specs);
12
+ const milestonesHtml = buildMilestonesSection(specs);
13
+ return `<!DOCTYPE html>
14
+ <html lang="en">
15
+ <head>
16
+ <meta charset="UTF-8"/>
17
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
18
+ <title>Roadmap — Planu</title>
19
+ <style>
20
+ ${getPortalThemeCSS()}
21
+ ${ROADMAP_CSS}
22
+ </style>
23
+ </head>
24
+ <body>
25
+ ${navbar}
26
+ <main class="roadmap-main">
27
+ <h1 class="roadmap-title">🗺️ Roadmap</h1>
28
+ <section class="planu-card roadmap-section">
29
+ <h2>Gantt Chart</h2>
30
+ ${ganttHtml}
31
+ </section>
32
+ <section class="planu-card roadmap-section">
33
+ <h2>Phase Summary</h2>
34
+ ${phaseSummary}
35
+ </section>
36
+ <section class="planu-card roadmap-section">
37
+ <h2>Milestones</h2>
38
+ ${milestonesHtml}
39
+ </section>
40
+ </main>
41
+ </body>
42
+ </html>`;
43
+ }
44
+ // --- Gantt section ---
45
+ function buildGanttSection(specs, startDate) {
46
+ if (specs.length === 0) {
47
+ return '<p class="roadmap-empty">No specs to display.</p>';
48
+ }
49
+ const gantt = buildGanttChart(specs, undefined, startDate);
50
+ if (!gantt) {
51
+ return '<p class="roadmap-empty">Could not generate Gantt chart.</p>';
52
+ }
53
+ return gantt;
54
+ }
55
+ // --- Phase summary section ---
56
+ function buildPhaseSummary(specs) {
57
+ if (specs.length === 0) {
58
+ return '<p class="roadmap-empty">No specs available.</p>';
59
+ }
60
+ const tagMap = new Map();
61
+ for (const spec of specs) {
62
+ const tags = spec.tags.length > 0 ? spec.tags : ['untagged'];
63
+ const primaryTag = tags[0] ?? 'untagged';
64
+ if (!tagMap.has(primaryTag)) {
65
+ tagMap.set(primaryTag, []);
66
+ }
67
+ const tagSpecs = tagMap.get(primaryTag);
68
+ if (tagSpecs) {
69
+ tagSpecs.push(spec);
70
+ }
71
+ }
72
+ const cards = Array.from(tagMap.entries()).map(([tag, tagSpecs]) => {
73
+ const doneCount = tagSpecs.filter((s) => s.status === 'done').length;
74
+ const total = tagSpecs.length;
75
+ const pct = total > 0 ? Math.round((doneCount / total) * 100) : 0;
76
+ return `<div class="phase-card planu-card">
77
+ <div class="phase-tag">${escapeHtml(tag)}</div>
78
+ <div class="phase-progress-bar">
79
+ <div class="phase-progress-fill" style="width:${pct}%"></div>
80
+ </div>
81
+ <div class="phase-stats">${doneCount} / ${total} done (${pct}%)</div>
82
+ </div>`;
83
+ });
84
+ return `<div class="phase-grid">${cards.join('\n')}</div>`;
85
+ }
86
+ // --- Milestones section ---
87
+ function buildMilestonesSection(specs) {
88
+ if (specs.length === 0) {
89
+ return '<p class="roadmap-empty">No milestones found.</p>';
90
+ }
91
+ const sorted = [...specs].sort((a, b) => {
92
+ const da = a.actuals?.completedAt ?? a.updatedAt;
93
+ const db = b.actuals?.completedAt ?? b.updatedAt;
94
+ return da.localeCompare(db);
95
+ });
96
+ const items = sorted.map((spec) => {
97
+ const isDone = spec.status === 'done';
98
+ const date = spec.actuals?.completedAt ?? spec.updatedAt;
99
+ const dateLabel = date ? date.slice(0, 10) : '—';
100
+ const dotClass = isDone ? 'milestone-dot-done' : 'milestone-dot-pending';
101
+ const depLabels = spec.dependencies.length > 0
102
+ ? `<div class="milestone-deps">⤵ ${spec.dependencies.map(escapeHtml).join(', ')}</div>`
103
+ : '';
104
+ return `<div class="milestone-item">
105
+ <div class="milestone-dot ${dotClass}"></div>
106
+ <div class="milestone-body">
107
+ <div class="milestone-header">
108
+ <span class="milestone-id">${escapeHtml(spec.id)}</span>
109
+ <span class="milestone-title">${escapeHtml(spec.title)}</span>
110
+ <span class="milestone-date">${dateLabel}</span>
111
+ </div>
112
+ ${depLabels}
113
+ </div>
114
+ </div>`;
115
+ });
116
+ return `<div class="milestone-timeline">${items.join('\n')}</div>`;
117
+ }
118
+ // --- Helpers ---
119
+ function escapeHtml(str) {
120
+ return str
121
+ .replace(/&/g, '&amp;')
122
+ .replace(/</g, '&lt;')
123
+ .replace(/>/g, '&gt;')
124
+ .replace(/"/g, '&quot;');
125
+ }
126
+ const ROADMAP_CSS = `
127
+ .roadmap-main {
128
+ max-width: 1100px;
129
+ margin: 0 auto;
130
+ padding: 0 16px 40px;
131
+ }
132
+ .roadmap-title { font-size: 1.5rem; color: var(--planu-primary); margin-bottom: 20px; }
133
+ .roadmap-empty { color: #9ca3af; font-style: italic; padding: 12px 0; }
134
+ .roadmap-section { margin-bottom: 24px; }
135
+ .roadmap-section h2 {
136
+ font-size: 1.05rem;
137
+ color: var(--planu-secondary);
138
+ margin-bottom: 14px;
139
+ border-bottom: 1px solid var(--planu-border);
140
+ padding-bottom: 6px;
141
+ }
142
+ /* Gantt reuse */
143
+ .gantt-container { overflow-x: auto; }
144
+ .gantt-header { display: flex; margin-bottom: 4px; }
145
+ .gantt-header-label { width: 160px; flex-shrink: 0; font-size: 0.75rem; color: #9ca3af; }
146
+ .gantt-header-weeks { display: flex; flex: 1; gap: 2px; }
147
+ .gantt-week { flex: 1; font-size: 0.7rem; color: #9ca3af; text-align: center; }
148
+ .gantt-grid { display: flex; flex-direction: column; gap: 4px; }
149
+ .gantt-row { display: flex; align-items: center; height: 28px; }
150
+ .gantt-label { width: 160px; flex-shrink: 0; font-size: 0.78rem; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
151
+ .gantt-track { position: relative; flex: 1; height: 20px; background: #f3f4f6; border-radius: 4px; }
152
+ .gantt-bar {
153
+ position: absolute;
154
+ height: 100%;
155
+ border-radius: 4px;
156
+ color: white;
157
+ font-size: 0.68rem;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ white-space: nowrap;
162
+ overflow: hidden;
163
+ }
164
+ /* Phase cards */
165
+ .phase-grid {
166
+ display: grid;
167
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
168
+ gap: 12px;
169
+ }
170
+ .phase-card { padding: 12px 14px; }
171
+ .phase-tag { font-weight: 600; font-size: 0.85rem; color: var(--planu-primary); margin-bottom: 8px; }
172
+ .phase-progress-bar {
173
+ background: #e9ecef;
174
+ border-radius: 99px;
175
+ height: 8px;
176
+ overflow: hidden;
177
+ margin-bottom: 6px;
178
+ }
179
+ .phase-progress-fill { background: var(--planu-accent); height: 100%; border-radius: 99px; }
180
+ .phase-stats { font-size: 0.75rem; color: #6b7280; }
181
+ /* Milestones */
182
+ .milestone-timeline { display: flex; flex-direction: column; gap: 0; }
183
+ .milestone-item { display: flex; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f3f4f6; }
184
+ .milestone-dot {
185
+ width: 14px;
186
+ height: 14px;
187
+ border-radius: 50%;
188
+ flex-shrink: 0;
189
+ margin-top: 3px;
190
+ }
191
+ .milestone-dot-done { background: var(--planu-success); }
192
+ .milestone-dot-pending { background: #e9ecef; border: 2px solid #9ca3af; }
193
+ .milestone-body { flex: 1; }
194
+ .milestone-header { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
195
+ .milestone-id { font-size: 0.72rem; color: #9ca3af; flex-shrink: 0; }
196
+ .milestone-title { font-size: 0.85rem; color: #2d3436; flex: 1; }
197
+ .milestone-date { font-size: 0.72rem; color: #6b7280; flex-shrink: 0; }
198
+ .milestone-deps { font-size: 0.72rem; color: #9ca3af; margin-top: 2px; }
199
+ @media print {
200
+ .roadmap-main { max-width: 100%; }
201
+ .gantt-container { overflow-x: visible; }
202
+ }
203
+ `;
204
+ //# sourceMappingURL=roadmap-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roadmap-page.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/roadmap-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { BarChartData, BarChartOptions } from '../../../../types/portal.js';
2
+ /** Build a responsive SVG bar chart. Returns empty string when data is empty. */
3
+ export declare function buildSvgBarChart(data: BarChartData[], options?: BarChartOptions): string;
4
+ //# sourceMappingURL=bar-chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-chart.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/svg-charts/bar-chart.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAKjF,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,CAyCxF"}
@@ -0,0 +1,50 @@
1
+ import { scaleLinear, svgText, CHART_COLORS, escapeXml } from './chart-utils.js';
2
+ const PADDING = { top: 40, right: 20, bottom: 60, left: 50 };
3
+ /** Build a responsive SVG bar chart. Returns empty string when data is empty. */
4
+ export function buildSvgBarChart(data, options) {
5
+ if (data.length === 0) {
6
+ return '';
7
+ }
8
+ const vbW = options?.width ?? 500;
9
+ const vbH = options?.height ?? 300;
10
+ const title = options?.title ?? '';
11
+ const innerW = vbW - PADDING.left - PADDING.right;
12
+ const innerH = vbH - PADDING.top - PADDING.bottom;
13
+ const maxVal = Math.max(...data.map((d) => d.value), 1);
14
+ const yScale = scaleLinear([0, maxVal], [innerH, 0]);
15
+ const barW = Math.max(2, Math.floor(innerW / data.length) - 4);
16
+ const bars = data.map((d, i) => {
17
+ const x = PADDING.left + i * (innerW / data.length) + (innerW / data.length - barW) / 2;
18
+ const barH = innerH - yScale(d.value);
19
+ const y = PADDING.top + yScale(d.value);
20
+ const color = d.color ?? CHART_COLORS[i % CHART_COLORS.length] ?? '#e94560';
21
+ const labelX = x + barW / 2;
22
+ const labelY = vbH - PADDING.bottom + 16;
23
+ const shortLabel = d.label.length > 10 ? d.label.slice(0, 9) + '…' : d.label;
24
+ return `<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${barW}" height="${Math.max(0, barH).toFixed(1)}" fill="${escapeXml(color)}" rx="2"/>
25
+ ${svgText(labelX, labelY, shortLabel, { fontSize: 10, anchor: 'middle', fill: '#6b7280' })}
26
+ ${svgText(labelX, y - 4, String(d.value), { fontSize: 10, anchor: 'middle', fill: '#2d3436' })}`;
27
+ });
28
+ const yTicks = buildYTicks(maxVal, vbW, PADDING, innerH, yScale);
29
+ const titleEl = title
30
+ ? svgText(vbW / 2, 20, title, { fontSize: 13, anchor: 'middle', fill: '#1a1a2e' })
31
+ : '';
32
+ return `<svg viewBox="0 0 ${vbW} ${vbH}" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:${vbW}px">
33
+ ${titleEl}
34
+ ${yTicks}
35
+ ${bars.join('\n ')}
36
+ <line x1="${PADDING.left}" y1="${PADDING.top}" x2="${PADDING.left}" y2="${PADDING.top + innerH}" stroke="#e9ecef" stroke-width="1"/>
37
+ <line x1="${PADDING.left}" y1="${PADDING.top + innerH}" x2="${PADDING.left + innerW}" y2="${PADDING.top + innerH}" stroke="#e9ecef" stroke-width="1"/>
38
+ </svg>`;
39
+ }
40
+ function buildYTicks(maxVal, vbW, padding, _innerH, yScale) {
41
+ const tickCount = 4;
42
+ return Array.from({ length: tickCount + 1 }, (_, i) => {
43
+ const val = (maxVal / tickCount) * i;
44
+ const y = padding.top + yScale(val);
45
+ const label = Number.isInteger(val) ? String(val) : val.toFixed(1);
46
+ return `<line x1="${padding.left}" y1="${y.toFixed(1)}" x2="${padding.left + (vbW - padding.left - padding.right)}" y2="${y.toFixed(1)}" stroke="#f3f4f6" stroke-width="1"/>
47
+ ${svgText(padding.left - 6, y + 4, label, { fontSize: 9, anchor: 'end', fill: '#9ca3af' })}`;
48
+ }).join('\n ');
49
+ }
50
+ //# sourceMappingURL=bar-chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-chart.js","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/svg-charts/bar-chart.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /** 8 visually distinct colours for chart series. */
2
+ export declare const CHART_COLORS: string[];
3
+ /**
4
+ * Create a linear scale function that maps values from [domainMin, domainMax]
5
+ * to [rangeMin, rangeMax].
6
+ */
7
+ export declare function scaleLinear(domain: [number, number], range: [number, number]): (val: number) => number;
8
+ /** Render an SVG <text> element. */
9
+ export declare function svgText(x: number, y: number, text: string, opts?: {
10
+ fontSize?: number;
11
+ anchor?: string;
12
+ fill?: string;
13
+ }): string;
14
+ /** Escape XML special chars for safe SVG embedding. */
15
+ export declare function escapeXml(str: string): string;
16
+ //# sourceMappingURL=chart-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-utils.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/svg-charts/chart-utils.ts"],"names":[],"mappings":"AAEA,oDAAoD;AACpD,eAAO,MAAM,YAAY,EAAE,MAAM,EAWhC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACxB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAQzB;AAED,oCAAoC;AACpC,wBAAgB,OAAO,CACrB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D,MAAM,CAMR;AAED,uDAAuD;AACvD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7C"}
@@ -0,0 +1,45 @@
1
+ // Planu — Shared SVG chart utilities
2
+ /** 8 visually distinct colours for chart series. */
3
+ export const CHART_COLORS = [
4
+ '#e94560',
5
+ '#0891b2',
6
+ '#059669',
7
+ '#d97706',
8
+ '#7c3aed',
9
+ '#6366f1',
10
+ '#0f766e',
11
+ '#b45309',
12
+ '#db2777',
13
+ '#2563eb',
14
+ ];
15
+ /**
16
+ * Create a linear scale function that maps values from [domainMin, domainMax]
17
+ * to [rangeMin, rangeMax].
18
+ */
19
+ export function scaleLinear(domain, range) {
20
+ const [d0, d1] = domain;
21
+ const [r0, r1] = range;
22
+ const domainSpan = d1 - d0;
23
+ if (domainSpan === 0) {
24
+ return () => r0;
25
+ }
26
+ return (val) => r0 + ((val - d0) / domainSpan) * (r1 - r0);
27
+ }
28
+ /** Render an SVG <text> element. */
29
+ export function svgText(x, y, text, opts) {
30
+ const fontSize = opts?.fontSize ?? 12;
31
+ const anchor = opts?.anchor ?? 'start';
32
+ const fill = opts?.fill ?? '#2d3436';
33
+ const escaped = escapeXml(text);
34
+ return `<text x="${x}" y="${y}" font-size="${fontSize}" text-anchor="${anchor}" fill="${fill}">${escaped}</text>`;
35
+ }
36
+ /** Escape XML special chars for safe SVG embedding. */
37
+ export function escapeXml(str) {
38
+ return str
39
+ .replace(/&/g, '&amp;')
40
+ .replace(/</g, '&lt;')
41
+ .replace(/>/g, '&gt;')
42
+ .replace(/"/g, '&quot;')
43
+ .replace(/'/g, '&apos;');
44
+ }
45
+ //# sourceMappingURL=chart-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-utils.js","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/svg-charts/chart-utils.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,oDAAoD;AACpD,MAAM,CAAC,MAAM,YAAY,GAAa;IACpC,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACV,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,MAAwB,EACxB,KAAuB;IAEvB,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC;IACvB,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,CAAC;IAC3B,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAW,EAAU,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,OAAO,CACrB,CAAS,EACT,CAAS,EACT,IAAY,EACZ,IAA4D;IAE5D,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,OAAO,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,IAAI,SAAS,CAAC;IACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,YAAY,CAAC,QAAQ,CAAC,gBAAgB,QAAQ,kBAAkB,MAAM,WAAW,IAAI,KAAK,OAAO,SAAS,CAAC;AACpH,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { LinePoint, LineChartOptions } from '../../../../types/portal.js';
2
+ /**
3
+ * Build a responsive SVG line chart for one or two series.
4
+ * When a second series is passed via options it is drawn with a dashed line.
5
+ * Returns empty string when points array is empty.
6
+ */
7
+ export declare function buildSvgLineChart(points: LinePoint[], options?: LineChartOptions): string;
8
+ /**
9
+ * Build a dual-series SVG line chart (real vs ideal).
10
+ * Returns empty string when either series is empty.
11
+ */
12
+ export declare function buildSvgDualLineChart(series1: LinePoint[], series2: LinePoint[], options?: LineChartOptions & {
13
+ label1?: string;
14
+ label2?: string;
15
+ }): string;
16
+ //# sourceMappingURL=line-chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"line-chart.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/svg-charts/line-chart.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAK/E;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAgEzF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,EAAE,EACpB,OAAO,EAAE,SAAS,EAAE,EACpB,OAAO,CAAC,EAAE,gBAAgB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,MAAM,CA4ER"}