@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.
- package/dist/config/license-plans.json +1 -0
- package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts +7 -1
- package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts.map +1 -1
- package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js +16 -3
- package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js.map +1 -1
- package/dist/engine/doc-generator/executive-summary/html-template.d.ts +5 -2
- package/dist/engine/doc-generator/executive-summary/html-template.d.ts.map +1 -1
- package/dist/engine/doc-generator/executive-summary/html-template.js +7 -3
- package/dist/engine/doc-generator/executive-summary/html-template.js.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/executive-report.d.ts +3 -1
- package/dist/engine/doc-generator/per-spec-report/executive-report.d.ts.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/executive-report.js +22 -2
- package/dist/engine/doc-generator/per-spec-report/executive-report.js.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts +5 -3
- package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/html-wrapper.js +27 -6
- package/dist/engine/doc-generator/per-spec-report/html-wrapper.js.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.d.ts +1 -0
- package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.d.ts.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.js +29 -0
- package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.js.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/spec-section-builders.d.ts +8 -1
- package/dist/engine/doc-generator/per-spec-report/spec-section-builders.d.ts.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js +188 -0
- package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/technical-report.d.ts +3 -1
- package/dist/engine/doc-generator/per-spec-report/technical-report.d.ts.map +1 -1
- package/dist/engine/doc-generator/per-spec-report/technical-report.js +24 -7
- package/dist/engine/doc-generator/per-spec-report/technical-report.js.map +1 -1
- package/dist/engine/doc-generator/portal/analytics-page.d.ts +8 -0
- package/dist/engine/doc-generator/portal/analytics-page.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/analytics-page.js +265 -0
- package/dist/engine/doc-generator/portal/analytics-page.js.map +1 -0
- package/dist/engine/doc-generator/portal/architecture-page.d.ts +6 -0
- package/dist/engine/doc-generator/portal/architecture-page.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/architecture-page.js +250 -0
- package/dist/engine/doc-generator/portal/architecture-page.js.map +1 -0
- package/dist/engine/doc-generator/portal/changelog-page.d.ts +6 -0
- package/dist/engine/doc-generator/portal/changelog-page.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/changelog-page.js +204 -0
- package/dist/engine/doc-generator/portal/changelog-page.js.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts +11 -0
- package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js +24 -0
- package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts +9 -0
- package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js +31 -0
- package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts +10 -0
- package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js +77 -0
- package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts +9 -0
- package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js +31 -0
- package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js.map +1 -0
- package/dist/engine/doc-generator/portal/decision-page.d.ts +6 -0
- package/dist/engine/doc-generator/portal/decision-page.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/decision-page.js +169 -0
- package/dist/engine/doc-generator/portal/decision-page.js.map +1 -0
- package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts +8 -0
- package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/decision-timeline-builder.js +131 -0
- package/dist/engine/doc-generator/portal/decision-timeline-builder.js.map +1 -0
- package/dist/engine/doc-generator/portal/index.d.ts +19 -0
- package/dist/engine/doc-generator/portal/index.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/index.js +21 -0
- package/dist/engine/doc-generator/portal/index.js.map +1 -0
- package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts +8 -0
- package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/kanban-view-builder.js +140 -0
- package/dist/engine/doc-generator/portal/kanban-view-builder.js.map +1 -0
- package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts +27 -0
- package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +139 -0
- package/dist/engine/doc-generator/portal/portal-breadcrumbs.js.map +1 -0
- package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts +8 -0
- package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/portal-landing-cards.js +177 -0
- package/dist/engine/doc-generator/portal/portal-landing-cards.js.map +1 -0
- package/dist/engine/doc-generator/portal/portal-navbar.d.ts +15 -0
- package/dist/engine/doc-generator/portal/portal-navbar.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/portal-navbar.js +143 -0
- package/dist/engine/doc-generator/portal/portal-navbar.js.map +1 -0
- package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +22 -0
- package/dist/engine/doc-generator/portal/portal-page-detector.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/portal-page-detector.js +58 -0
- package/dist/engine/doc-generator/portal/portal-page-detector.js.map +1 -0
- package/dist/engine/doc-generator/portal/portal-theme.d.ts +7 -0
- package/dist/engine/doc-generator/portal/portal-theme.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/portal-theme.js +53 -0
- package/dist/engine/doc-generator/portal/portal-theme.js.map +1 -0
- package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts +5 -0
- package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/risk-gauge-svg.js +80 -0
- package/dist/engine/doc-generator/portal/risk-gauge-svg.js.map +1 -0
- package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +11 -0
- package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/risk-matrix-svg.js +181 -0
- package/dist/engine/doc-generator/portal/risk-matrix-svg.js.map +1 -0
- package/dist/engine/doc-generator/portal/risk-page.d.ts +7 -0
- package/dist/engine/doc-generator/portal/risk-page.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/risk-page.js +219 -0
- package/dist/engine/doc-generator/portal/risk-page.js.map +1 -0
- package/dist/engine/doc-generator/portal/roadmap-page.d.ts +7 -0
- package/dist/engine/doc-generator/portal/roadmap-page.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/roadmap-page.js +204 -0
- package/dist/engine/doc-generator/portal/roadmap-page.js.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts +4 -0
- package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js +50 -0
- package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts +16 -0
- package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js +45 -0
- package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts +16 -0
- package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/line-chart.js +159 -0
- package/dist/engine/doc-generator/portal/svg-charts/line-chart.js.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts +7 -0
- package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts.map +1 -0
- package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js +52 -0
- package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts +8 -0
- package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +126 -0
- package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +10 -0
- package/dist/engine/doc-generator/proposal/proposal-generator.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-generator.js +93 -0
- package/dist/engine/doc-generator/proposal/proposal-generator.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts +7 -0
- package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js +31 -0
- package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts +12 -0
- package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-i18n.js +230 -0
- package/dist/engine/doc-generator/proposal/proposal-i18n.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts +8 -0
- package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +32 -0
- package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts +14 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +248 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts +13 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders.js +423 -0
- package/dist/engine/doc-generator/proposal/proposal-section-builders.js.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-themes.d.ts +4 -0
- package/dist/engine/doc-generator/proposal/proposal-themes.d.ts.map +1 -0
- package/dist/engine/doc-generator/proposal/proposal-themes.js +194 -0
- package/dist/engine/doc-generator/proposal/proposal-themes.js.map +1 -0
- package/dist/engine/spec-summary-html.d.ts.map +1 -1
- package/dist/engine/spec-summary-html.js +17 -5
- package/dist/engine/spec-summary-html.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/generate-proposal.d.ts +3 -0
- package/dist/tools/generate-proposal.d.ts.map +1 -0
- package/dist/tools/generate-proposal.js +166 -0
- package/dist/tools/generate-proposal.js.map +1 -0
- package/dist/types/docs.d.ts +75 -0
- package/dist/types/docs.d.ts.map +1 -1
- package/dist/types/portal.d.ts +128 -0
- package/dist/types/portal.d.ts.map +1 -0
- package/dist/types/portal.js +3 -0
- package/dist/types/portal.js.map +1 -0
- package/dist/types/proposal.d.ts +57 -0
- package/dist/types/proposal.d.ts.map +1 -0
- package/dist/types/proposal.js +2 -0
- package/dist/types/proposal.js.map +1 -0
- package/package.json +1 -1
- 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, '<')
|
|
8
|
+
.replace(/>/g, '>')
|
|
9
|
+
.replace(/"/g, '"');
|
|
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, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"');
|
|
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, '&')
|
|
83
|
+
.replace(/</g, '<')
|
|
84
|
+
.replace(/>/g, '>')
|
|
85
|
+
.replace(/"/g, '"');
|
|
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"}
|