@facet-coverage/core 0.3.0 → 0.5.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 (75) hide show
  1. package/README.md +64 -574
  2. package/dist/cli/commands/generate.d.ts.map +1 -1
  3. package/dist/cli/commands/generate.js +224 -25
  4. package/dist/cli/commands/generate.js.map +1 -1
  5. package/dist/cli/commands/serve.d.ts +12 -0
  6. package/dist/cli/commands/serve.d.ts.map +1 -0
  7. package/dist/cli/commands/serve.js +76 -0
  8. package/dist/cli/commands/serve.js.map +1 -0
  9. package/dist/cli/index.js +10 -0
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/client/index.js +60 -0
  12. package/dist/core/FacetParser.d.ts +37 -1
  13. package/dist/core/FacetParser.d.ts.map +1 -1
  14. package/dist/core/FacetParser.js +123 -5
  15. package/dist/core/FacetParser.js.map +1 -1
  16. package/dist/core/TestScanner.d.ts +4 -1
  17. package/dist/core/TestScanner.d.ts.map +1 -1
  18. package/dist/core/TestScanner.js +42 -13
  19. package/dist/core/TestScanner.js.map +1 -1
  20. package/dist/core/Validator.d.ts +4 -0
  21. package/dist/core/Validator.d.ts.map +1 -1
  22. package/dist/core/Validator.js +22 -4
  23. package/dist/core/Validator.js.map +1 -1
  24. package/dist/server/DevServer.d.ts +48 -0
  25. package/dist/server/DevServer.d.ts.map +1 -0
  26. package/dist/server/DevServer.js +126 -0
  27. package/dist/server/DevServer.js.map +1 -0
  28. package/dist/server/HotReloadManager.d.ts +49 -0
  29. package/dist/server/HotReloadManager.d.ts.map +1 -0
  30. package/dist/server/HotReloadManager.js +187 -0
  31. package/dist/server/HotReloadManager.js.map +1 -0
  32. package/dist/server/index.d.ts +3 -0
  33. package/dist/server/index.d.ts.map +1 -0
  34. package/dist/server/index.js +6 -0
  35. package/dist/server/index.js.map +1 -0
  36. package/dist/server/routes.d.ts +12 -0
  37. package/dist/server/routes.d.ts.map +1 -0
  38. package/dist/server/routes.js +272 -0
  39. package/dist/server/routes.js.map +1 -0
  40. package/dist/server/views/Dashboard.d.ts +6 -0
  41. package/dist/server/views/Dashboard.d.ts.map +1 -0
  42. package/dist/server/views/Dashboard.js +121 -0
  43. package/dist/server/views/Dashboard.js.map +1 -0
  44. package/dist/server/views/FacetPage.d.ts +6 -0
  45. package/dist/server/views/FacetPage.d.ts.map +1 -0
  46. package/dist/server/views/FacetPage.js +190 -0
  47. package/dist/server/views/FacetPage.js.map +1 -0
  48. package/dist/server/views/FeaturePage.d.ts +6 -0
  49. package/dist/server/views/FeaturePage.d.ts.map +1 -0
  50. package/dist/server/views/FeaturePage.js +98 -0
  51. package/dist/server/views/FeaturePage.js.map +1 -0
  52. package/dist/server/views/Layout.d.ts +17 -0
  53. package/dist/server/views/Layout.d.ts.map +1 -0
  54. package/dist/server/views/Layout.js +59 -0
  55. package/dist/server/views/Layout.js.map +1 -0
  56. package/dist/server/views/Sidebar.d.ts +6 -0
  57. package/dist/server/views/Sidebar.d.ts.map +1 -0
  58. package/dist/server/views/Sidebar.js +59 -0
  59. package/dist/server/views/Sidebar.js.map +1 -0
  60. package/dist/server/views/TestPanel.d.ts +6 -0
  61. package/dist/server/views/TestPanel.d.ts.map +1 -0
  62. package/dist/server/views/TestPanel.js +69 -0
  63. package/dist/server/views/TestPanel.js.map +1 -0
  64. package/dist/server/views/scripts.d.ts +5 -0
  65. package/dist/server/views/scripts.d.ts.map +1 -0
  66. package/dist/server/views/scripts.js +203 -0
  67. package/dist/server/views/scripts.js.map +1 -0
  68. package/dist/server/views/styles.d.ts +6 -0
  69. package/dist/server/views/styles.d.ts.map +1 -0
  70. package/dist/server/views/styles.js +608 -0
  71. package/dist/server/views/styles.js.map +1 -0
  72. package/dist/types.d.ts +27 -3
  73. package/dist/types.d.ts.map +1 -1
  74. package/dist/types.js.map +1 -1
  75. package/package.json +9 -4
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearClientBundleCache = clearClientBundleCache;
4
+ exports.createRouter = createRouter;
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ // Cached client bundle
8
+ let clientBundle = null;
9
+ /**
10
+ * Clear the client bundle cache (for hot reload)
11
+ */
12
+ function clearClientBundleCache() {
13
+ clientBundle = null;
14
+ }
15
+ // Path to client source (relative to package root)
16
+ const CLIENT_ENTRY_PATH = 'src/server/client/index.tsx';
17
+ /**
18
+ * Build or load the client bundle
19
+ * - Production/installed: use pre-built bundle from dist/client/
20
+ * - Development: build at runtime for hot reload support
21
+ */
22
+ async function buildClientBundle(packageRoot) {
23
+ if (clientBundle)
24
+ return clientBundle;
25
+ // Check for pre-built bundle (production/installed package)
26
+ const preBuiltPath = (0, path_1.join)(packageRoot, 'dist/client/index.js');
27
+ if ((0, fs_1.existsSync)(preBuiltPath)) {
28
+ clientBundle = (0, fs_1.readFileSync)(preBuiltPath, 'utf-8');
29
+ return clientBundle;
30
+ }
31
+ // Development: build at runtime for hot reload
32
+ const entryPoint = (0, path_1.join)(packageRoot, CLIENT_ENTRY_PATH);
33
+ const result = await Bun.build({
34
+ entrypoints: [entryPoint],
35
+ target: 'browser',
36
+ format: 'esm',
37
+ minify: false, // Disable for debugging
38
+ define: {
39
+ 'process.env.NODE_ENV': '"production"',
40
+ },
41
+ });
42
+ if (!result.success) {
43
+ console.error('Client build failed:', result.logs);
44
+ throw new Error('Failed to build client bundle');
45
+ }
46
+ // Read the built file
47
+ const outputFile = result.outputs[0];
48
+ clientBundle = await outputFile.text();
49
+ return clientBundle;
50
+ }
51
+ /**
52
+ * Get the HTML shell for the SPA
53
+ */
54
+ function getHtmlShell() {
55
+ return `<!DOCTYPE html>
56
+ <html lang="en">
57
+ <head>
58
+ <meta charset="UTF-8">
59
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
60
+ <title>Facet Coverage</title>
61
+ <script src="https://cdn.tailwindcss.com"></script>
62
+ <script>
63
+ tailwind.config = {
64
+ darkMode: 'class',
65
+ theme: {
66
+ extend: {
67
+ colors: {
68
+ slate: {
69
+ 850: '#172033',
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ </script>
76
+ <style>
77
+ /* Custom scrollbar */
78
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
79
+ ::-webkit-scrollbar-track { background: #1e293b; }
80
+ ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
81
+ ::-webkit-scrollbar-thumb:hover { background: #64748b; }
82
+
83
+ /* Code styling */
84
+ code { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; }
85
+ </style>
86
+ </head>
87
+ <body class="dark">
88
+ <div id="app"></div>
89
+ <script type="module" src="/client.js"></script>
90
+ </body>
91
+ </html>`;
92
+ }
93
+ /**
94
+ * Simple router for handling HTTP requests
95
+ */
96
+ function createRouter(server, packageRoot) {
97
+ const routes = [];
98
+ function addRoute(pattern, handler) {
99
+ const paramNames = [];
100
+ const paramMatches = pattern.matchAll(/:([^/]+)/g);
101
+ for (const match of paramMatches) {
102
+ paramNames.push(match[1]);
103
+ }
104
+ const regexPattern = pattern
105
+ .replace(/:[^/]+/g, '([^/]+)')
106
+ .replace(/\//g, '\\/');
107
+ routes.push({
108
+ pattern: new RegExp(`^${regexPattern}$`),
109
+ paramNames,
110
+ handler,
111
+ });
112
+ }
113
+ // Serve client bundle
114
+ addRoute('/client.js', async () => {
115
+ try {
116
+ const bundle = await buildClientBundle(packageRoot);
117
+ return new Response(bundle, {
118
+ headers: { 'Content-Type': 'application/javascript' },
119
+ });
120
+ }
121
+ catch (err) {
122
+ console.error('Bundle error:', err);
123
+ return new Response('// Bundle error: ' + String(err), {
124
+ status: 500,
125
+ headers: { 'Content-Type': 'application/javascript' },
126
+ });
127
+ }
128
+ });
129
+ // API: Full coverage report
130
+ addRoute('/api/coverage', () => {
131
+ const report = server.getCoverageReport();
132
+ if (!report) {
133
+ return new Response(JSON.stringify({ error: 'Coverage not ready' }), {
134
+ status: 503,
135
+ headers: { 'Content-Type': 'application/json' },
136
+ });
137
+ }
138
+ return new Response(JSON.stringify(report), {
139
+ headers: { 'Content-Type': 'application/json' },
140
+ });
141
+ });
142
+ // API: Single facet with test details
143
+ addRoute('/api/facet/:id', (_req, params) => {
144
+ const report = server.getCoverageReport();
145
+ if (!report) {
146
+ return new Response(JSON.stringify({ error: 'Coverage not ready' }), {
147
+ status: 503,
148
+ headers: { 'Content-Type': 'application/json' },
149
+ });
150
+ }
151
+ for (const feature of report.features) {
152
+ const found = feature.facets.find(fc => fc.facet.id === params.id);
153
+ if (found) {
154
+ return new Response(JSON.stringify(found), {
155
+ headers: { 'Content-Type': 'application/json' },
156
+ });
157
+ }
158
+ }
159
+ return new Response(JSON.stringify({ error: 'Facet not found' }), {
160
+ status: 404,
161
+ headers: { 'Content-Type': 'application/json' },
162
+ });
163
+ });
164
+ // API: Test source code
165
+ addRoute('/api/test-source', (req) => {
166
+ const url = new URL(req.url);
167
+ const file = url.searchParams.get('file');
168
+ const lineParam = url.searchParams.get('line');
169
+ if (!file) {
170
+ return new Response(JSON.stringify({ error: 'Missing file parameter' }), {
171
+ status: 400,
172
+ headers: { 'Content-Type': 'application/json' },
173
+ });
174
+ }
175
+ const filePath = (0, path_1.join)(server.getCwd(), file);
176
+ if (!(0, fs_1.existsSync)(filePath)) {
177
+ return new Response(JSON.stringify({ error: 'File not found' }), {
178
+ status: 404,
179
+ headers: { 'Content-Type': 'application/json' },
180
+ });
181
+ }
182
+ try {
183
+ const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
184
+ const lines = content.split('\n');
185
+ const line = lineParam ? parseInt(lineParam, 10) : undefined;
186
+ let startLine = 0;
187
+ let endLine = lines.length;
188
+ if (line !== undefined && !isNaN(line)) {
189
+ startLine = Math.max(0, line - 25);
190
+ endLine = Math.min(lines.length, line + 25);
191
+ }
192
+ const excerpt = lines.slice(startLine, endLine).join('\n');
193
+ return new Response(JSON.stringify({
194
+ file,
195
+ code: excerpt,
196
+ startLine: startLine + 1,
197
+ endLine,
198
+ highlightLine: line,
199
+ }), {
200
+ headers: { 'Content-Type': 'application/json' },
201
+ });
202
+ }
203
+ catch {
204
+ return new Response(JSON.stringify({ error: 'Error reading file' }), {
205
+ status: 500,
206
+ headers: { 'Content-Type': 'application/json' },
207
+ });
208
+ }
209
+ });
210
+ // API: Markdown source - handles nested paths via query param
211
+ addRoute('/api/markdown', (req, _params) => {
212
+ const url = new URL(req.url);
213
+ const mdPath = url.searchParams.get('path') || '';
214
+ const report = server.getCoverageReport();
215
+ // Try to find the file in any feature path
216
+ let filePath = (0, path_1.join)(server.getCwd(), mdPath);
217
+ if (!(0, fs_1.existsSync)(filePath) && report) {
218
+ // Try each feature's path
219
+ for (const feature of report.features) {
220
+ const featurePath = (0, path_1.join)(feature.path, mdPath);
221
+ if ((0, fs_1.existsSync)(featurePath)) {
222
+ filePath = featurePath;
223
+ break;
224
+ }
225
+ }
226
+ }
227
+ if (!(0, fs_1.existsSync)(filePath)) {
228
+ return new Response(JSON.stringify({ error: 'File not found', tried: filePath }), {
229
+ status: 404,
230
+ headers: { 'Content-Type': 'application/json' },
231
+ });
232
+ }
233
+ try {
234
+ const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
235
+ return new Response(JSON.stringify({ path: mdPath, content }), {
236
+ headers: { 'Content-Type': 'application/json' },
237
+ });
238
+ }
239
+ catch {
240
+ return new Response(JSON.stringify({ error: 'Error reading file' }), {
241
+ status: 500,
242
+ headers: { 'Content-Type': 'application/json' },
243
+ });
244
+ }
245
+ });
246
+ return {
247
+ async handle(req) {
248
+ const url = new URL(req.url);
249
+ const pathname = url.pathname;
250
+ // Check API routes first
251
+ for (const route of routes) {
252
+ const match = pathname.match(route.pattern);
253
+ if (match) {
254
+ const params = {};
255
+ for (let i = 0; i < route.paramNames.length; i++) {
256
+ params[route.paramNames[i]] = decodeURIComponent(match[i + 1]);
257
+ }
258
+ const result = route.handler(req, params);
259
+ if (result instanceof Promise) {
260
+ return await result;
261
+ }
262
+ return result;
263
+ }
264
+ }
265
+ // For all other routes, serve the SPA shell
266
+ return new Response(getHtmlShell(), {
267
+ headers: { 'Content-Type': 'text/html' },
268
+ });
269
+ },
270
+ };
271
+ }
272
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { CoverageReport } from '../../types.js';
2
+ /**
3
+ * Render the dashboard page
4
+ */
5
+ export declare function renderDashboard(report: CoverageReport): string;
6
+ //# sourceMappingURL=Dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dashboard.d.ts","sourceRoot":"","sources":["../../../src/server/views/Dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAmH9D"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderDashboard = renderDashboard;
4
+ const Layout_js_1 = require("./Layout.js");
5
+ const Sidebar_js_1 = require("./Sidebar.js");
6
+ /**
7
+ * Render the dashboard page
8
+ */
9
+ function renderDashboard(report) {
10
+ const coverageClass = (0, Layout_js_1.getCoverageClass)(report.summary.percentage);
11
+ // Type breakdown
12
+ const typeCardsHtml = report.byType
13
+ .map(type => {
14
+ const typeClass = (0, Layout_js_1.getCoverageClass)(type.percentage);
15
+ return `
16
+ <div class="type-card">
17
+ <div class="type-header">
18
+ <span class="type-name">${(0, Layout_js_1.escapeHtml)(type.type)}</span>
19
+ <span class="type-percentage ${typeClass}">${type.percentage}%</span>
20
+ </div>
21
+ <div class="coverage-bar">
22
+ <div class="coverage-fill ${typeClass}" style="width: ${type.percentage}%"></div>
23
+ </div>
24
+ <div style="margin-top: 0.5rem; color: var(--color-muted); font-size: 0.8125rem;">
25
+ ${type.covered} / ${type.total} covered
26
+ </div>
27
+ </div>
28
+ `;
29
+ })
30
+ .join('');
31
+ // Feature cards
32
+ const featureCardsHtml = report.features
33
+ .map(feature => {
34
+ const featureClass = (0, Layout_js_1.getCoverageClass)(feature.percentage);
35
+ return `
36
+ <a href="/feature/${encodeURIComponent(feature.feature)}" class="feature-card">
37
+ <div class="feature-name">${(0, Layout_js_1.escapeHtml)(feature.feature)}</div>
38
+ <div class="coverage-bar">
39
+ <div class="coverage-fill ${featureClass}" style="width: ${feature.percentage}%"></div>
40
+ </div>
41
+ <div class="feature-stats">
42
+ <span>${feature.coveredFacets}/${feature.totalFacets} facets</span>
43
+ <span class="badge ${featureClass}">${feature.percentage}%</span>
44
+ </div>
45
+ </a>
46
+ `;
47
+ })
48
+ .join('');
49
+ // Uncovered facets (top 10)
50
+ const uncoveredHtml = report.uncovered.slice(0, 10)
51
+ .map(facet => `
52
+ <a href="/facet/${encodeURIComponent(facet.id)}" class="facet-item uncovered">
53
+ <div class="facet-header">
54
+ <span class="facet-id">${(0, Layout_js_1.escapeHtml)(facet.id)}</span>
55
+ <span class="facet-type">${(0, Layout_js_1.escapeHtml)(facet.type)}</span>
56
+ </div>
57
+ <div class="facet-source">${(0, Layout_js_1.escapeHtml)(facet.source.file)}#${(0, Layout_js_1.escapeHtml)(facet.source.section)}</div>
58
+ </a>
59
+ `)
60
+ .join('');
61
+ return `
62
+ <div class="app">
63
+ ${(0, Sidebar_js_1.renderSidebar)(report)}
64
+
65
+ <main class="main">
66
+ <div class="doc-panel">
67
+ <div class="card">
68
+ <h2>Overall Coverage</h2>
69
+ <div class="stats-grid">
70
+ <div class="stat">
71
+ <div class="stat-value ${coverageClass}">${report.summary.percentage}%</div>
72
+ <div class="stat-label">Coverage</div>
73
+ <div class="coverage-bar">
74
+ <div class="coverage-fill ${coverageClass}" style="width: ${report.summary.percentage}%"></div>
75
+ </div>
76
+ </div>
77
+ <div class="stat">
78
+ <div class="stat-value">${report.summary.totalFacets}</div>
79
+ <div class="stat-label">Total Facets</div>
80
+ </div>
81
+ <div class="stat">
82
+ <div class="stat-value success">${report.summary.coveredFacets}</div>
83
+ <div class="stat-label">Covered</div>
84
+ </div>
85
+ <div class="stat">
86
+ <div class="stat-value error">${report.summary.uncoveredFacets}</div>
87
+ <div class="stat-label">Uncovered</div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ ${report.byType.length > 0 ? `
93
+ <div class="card">
94
+ <h2>Coverage by Type</h2>
95
+ <div class="type-grid">
96
+ ${typeCardsHtml}
97
+ </div>
98
+ </div>
99
+ ` : ''}
100
+
101
+ <div class="card">
102
+ <h2>Features</h2>
103
+ <div class="feature-grid">
104
+ ${featureCardsHtml}
105
+ </div>
106
+ </div>
107
+
108
+ ${report.uncovered.length > 0 ? `
109
+ <div class="card">
110
+ <h2>Uncovered Facets${report.uncovered.length > 10 ? ` (showing 10 of ${report.uncovered.length})` : ''}</h2>
111
+ <div class="facet-list">
112
+ ${uncoveredHtml}
113
+ </div>
114
+ </div>
115
+ ` : ''}
116
+ </div>
117
+ </main>
118
+ </div>
119
+ `;
120
+ }
121
+ //# sourceMappingURL=Dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dashboard.js","sourceRoot":"","sources":["../../../src/server/views/Dashboard.ts"],"names":[],"mappings":";;AAOA,0CAmHC;AAzHD,2CAA2D;AAC3D,6CAA6C;AAE7C;;GAEG;AACH,SAAgB,eAAe,CAAC,MAAsB;IACpD,MAAM,aAAa,GAAG,IAAA,4BAAgB,EAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAElE,iBAAiB;IACjB,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM;SAChC,GAAG,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,SAAS,GAAG,IAAA,4BAAgB,EAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,OAAO;;;sCAGyB,IAAA,sBAAU,EAAC,IAAI,CAAC,IAAI,CAAC;2CAChB,SAAS,KAAK,IAAI,CAAC,UAAU;;;wCAGhC,SAAS,mBAAmB,IAAI,CAAC,UAAU;;;cAGrE,IAAI,CAAC,OAAO,MAAM,IAAI,CAAC,KAAK;;;OAGnC,CAAC;IACJ,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,gBAAgB;IAChB,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ;SACrC,GAAG,CAAC,OAAO,CAAC,EAAE;QACb,MAAM,YAAY,GAAG,IAAA,4BAAgB,EAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1D,OAAO;4BACe,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC;sCACzB,IAAA,sBAAU,EAAC,OAAO,CAAC,OAAO,CAAC;;wCAEzB,YAAY,mBAAmB,OAAO,CAAC,UAAU;;;oBAGrE,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,WAAW;iCAC/B,YAAY,KAAK,OAAO,CAAC,UAAU;;;OAG7D,CAAC;IACJ,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,4BAA4B;IAC5B,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SAChD,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;wBACM,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;;mCAEjB,IAAA,sBAAU,EAAC,KAAK,CAAC,EAAE,CAAC;qCAClB,IAAA,sBAAU,EAAC,KAAK,CAAC,IAAI,CAAC;;oCAEvB,IAAA,sBAAU,EAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAA,sBAAU,EAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;;KAEhG,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO;;QAED,IAAA,0BAAa,EAAC,MAAM,CAAC;;;;;;;;yCAQY,aAAa,KAAK,MAAM,CAAC,OAAO,CAAC,UAAU;;;8CAGtC,aAAa,mBAAmB,MAAM,CAAC,OAAO,CAAC,UAAU;;;;0CAI7D,MAAM,CAAC,OAAO,CAAC,WAAW;;;;kDAIlB,MAAM,CAAC,OAAO,CAAC,aAAa;;;;gDAI9B,MAAM,CAAC,OAAO,CAAC,eAAe;;;;;;YAMlE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;;;;gBAIvB,aAAa;;;WAGlB,CAAC,CAAC,CAAC,EAAE;;;;;gBAKA,gBAAgB;;;;YAIpB,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;;kCAER,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,mBAAmB,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE;;gBAEnG,aAAa;;;WAGlB,CAAC,CAAC,CAAC,EAAE;;;;GAIb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { FacetCoverage, FeatureCoverage } from '../../types.js';
2
+ /**
3
+ * Render a facet detail page with markdown content and tests
4
+ */
5
+ export declare function renderFacetPage(facetCoverage: FacetCoverage, feature: FeatureCoverage, cwd: string): string;
6
+ //# sourceMappingURL=FacetPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FacetPage.d.ts","sourceRoot":"","sources":["../../../src/server/views/FacetPage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIrE;;GAEG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE,MAAM,GACV,MAAM,CAqFR"}
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderFacetPage = renderFacetPage;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const Layout_js_1 = require("./Layout.js");
7
+ const TestPanel_js_1 = require("./TestPanel.js");
8
+ /**
9
+ * Render a facet detail page with markdown content and tests
10
+ */
11
+ function renderFacetPage(facetCoverage, feature, cwd) {
12
+ const { facet, covered, coveredBy } = facetCoverage;
13
+ const statusClass = covered ? 'success' : 'error';
14
+ const statusText = covered ? 'Covered' : 'Uncovered';
15
+ // Try to read the markdown source
16
+ // The source.file is relative to the feature directory, so use feature.path
17
+ let markdownContent = '';
18
+ const sourceFile = (0, path_1.join)(feature.path, facet.source.file);
19
+ if ((0, fs_1.existsSync)(sourceFile)) {
20
+ try {
21
+ const content = (0, fs_1.readFileSync)(sourceFile, 'utf-8');
22
+ markdownContent = renderMarkdown(content, facet.source.section);
23
+ }
24
+ catch {
25
+ markdownContent = '<p class="empty-state">Could not read source file</p>';
26
+ }
27
+ }
28
+ else {
29
+ markdownContent = '<p class="empty-state">Source file not found</p>';
30
+ }
31
+ return `
32
+ <div class="app">
33
+ <aside class="sidebar">
34
+ <div class="sidebar-header">
35
+ <h1>Facet Docs</h1>
36
+ </div>
37
+ <div class="sidebar-search">
38
+ <input type="text" id="sidebar-search" placeholder="Search facets... (Ctrl+K)" />
39
+ </div>
40
+ <nav class="sidebar-nav">
41
+ <div class="nav-section">
42
+ <a href="/" class="nav-section-header">
43
+ <span class="nav-section-title">Dashboard</span>
44
+ </a>
45
+ </div>
46
+ <div class="nav-section">
47
+ <a href="/feature/${encodeURIComponent(feature.feature)}" class="nav-section-header">
48
+ <span class="nav-section-title">${(0, Layout_js_1.escapeHtml)(feature.feature)}</span>
49
+ <span class="nav-section-badge ${(0, Layout_js_1.getCoverageClass)(feature.percentage)}">${feature.percentage}%</span>
50
+ </a>
51
+ <div class="nav-items">
52
+ ${feature.facets.map(fc => `
53
+ <a href="/facet/${encodeURIComponent(fc.facet.id)}"
54
+ class="nav-item ${fc.covered ? 'covered' : 'uncovered'}${fc.facet.id === facet.id ? ' active' : ''}"
55
+ title="${(0, Layout_js_1.escapeHtml)(fc.facet.id)}">
56
+ ${(0, Layout_js_1.escapeHtml)(fc.facet.id.split(':').pop() || fc.facet.id)}
57
+ </a>
58
+ `).join('')}
59
+ </div>
60
+ </div>
61
+ </nav>
62
+ </aside>
63
+
64
+ <main class="main">
65
+ <div class="doc-panel">
66
+ <div class="breadcrumb">
67
+ <a href="/">Dashboard</a>
68
+ <span class="breadcrumb-separator">/</span>
69
+ <a href="/feature/${encodeURIComponent(feature.feature)}">${(0, Layout_js_1.escapeHtml)(feature.feature)}</a>
70
+ <span class="breadcrumb-separator">/</span>
71
+ <span>${(0, Layout_js_1.escapeHtml)(facet.id)}</span>
72
+ </div>
73
+
74
+ <div class="card">
75
+ <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem;">
76
+ <div>
77
+ <h2 style="font-family: monospace; margin-bottom: 0.5rem;">${(0, Layout_js_1.escapeHtml)(facet.id)}</h2>
78
+ <div style="color: var(--color-muted); font-size: 0.875rem;">
79
+ <span class="facet-type" style="margin-right: 0.5rem;">${(0, Layout_js_1.escapeHtml)(facet.type)}</span>
80
+ ${(0, Layout_js_1.escapeHtml)(facet.source.file)}#${(0, Layout_js_1.escapeHtml)(facet.source.section)}
81
+ </div>
82
+ </div>
83
+ <span class="badge ${statusClass}">${statusText}</span>
84
+ </div>
85
+
86
+ <div class="markdown-content">
87
+ ${markdownContent}
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ ${(0, TestPanel_js_1.renderTestPanel)(coveredBy)}
93
+ </main>
94
+ </div>
95
+ `;
96
+ }
97
+ /**
98
+ * Simple markdown to HTML converter
99
+ */
100
+ function renderMarkdown(content, section) {
101
+ // Find the section in the markdown
102
+ const lines = content.split('\n');
103
+ let inSection = false;
104
+ let sectionLines = [];
105
+ let sectionLevel = 0;
106
+ for (const line of lines) {
107
+ // Check if this is a heading
108
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
109
+ if (headingMatch) {
110
+ const level = headingMatch[1].length;
111
+ const title = headingMatch[2];
112
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
113
+ if (slug === section || title.toLowerCase().includes(section.toLowerCase())) {
114
+ inSection = true;
115
+ sectionLevel = level;
116
+ sectionLines.push(line);
117
+ continue;
118
+ }
119
+ // End section if we hit a heading of same or higher level
120
+ if (inSection && level <= sectionLevel) {
121
+ break;
122
+ }
123
+ }
124
+ if (inSection) {
125
+ sectionLines.push(line);
126
+ }
127
+ }
128
+ // If we didn't find the section, show all content
129
+ if (sectionLines.length === 0) {
130
+ sectionLines = lines.slice(0, 50); // First 50 lines
131
+ if (lines.length > 50) {
132
+ sectionLines.push('...');
133
+ }
134
+ }
135
+ // Convert markdown to HTML (simple conversion)
136
+ return sectionLines
137
+ .map(line => convertMarkdownLine(line))
138
+ .join('\n');
139
+ }
140
+ /**
141
+ * Convert a single markdown line to HTML
142
+ */
143
+ function convertMarkdownLine(line) {
144
+ // Headings
145
+ const h1 = line.match(/^#\s+(.+)$/);
146
+ if (h1)
147
+ return `<h1>${(0, Layout_js_1.escapeHtml)(h1[1])}</h1>`;
148
+ const h2 = line.match(/^##\s+(.+)$/);
149
+ if (h2)
150
+ return `<h2>${(0, Layout_js_1.escapeHtml)(h2[1])}</h2>`;
151
+ const h3 = line.match(/^###\s+(.+)$/);
152
+ if (h3)
153
+ return `<h3>${(0, Layout_js_1.escapeHtml)(h3[1])}</h3>`;
154
+ const h4 = line.match(/^####\s+(.+)$/);
155
+ if (h4)
156
+ return `<h4>${(0, Layout_js_1.escapeHtml)(h4[1])}</h4>`;
157
+ // List items
158
+ const ul = line.match(/^[-*]\s+(.+)$/);
159
+ if (ul)
160
+ return `<li>${formatInline(ul[1])}</li>`;
161
+ const ol = line.match(/^\d+\.\s+(.+)$/);
162
+ if (ol)
163
+ return `<li>${formatInline(ol[1])}</li>`;
164
+ // Code block markers
165
+ if (line.startsWith('```'))
166
+ return '';
167
+ // Empty line
168
+ if (line.trim() === '')
169
+ return '<br>';
170
+ // Paragraph
171
+ return `<p>${formatInline(line)}</p>`;
172
+ }
173
+ /**
174
+ * Format inline markdown (bold, italic, code, links)
175
+ */
176
+ function formatInline(text) {
177
+ let result = (0, Layout_js_1.escapeHtml)(text);
178
+ // Bold
179
+ result = result.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
180
+ // Italic
181
+ result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>');
182
+ // Inline code
183
+ result = result.replace(/`([^`]+)`/g, '<code>$1</code>');
184
+ // Links (but not empty anchor links like [](#id))
185
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
186
+ // Remove invisible ID anchors [](#id)
187
+ result = result.replace(/\[\]\(#[^)]+\)/g, '');
188
+ return result;
189
+ }
190
+ //# sourceMappingURL=FacetPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FacetPage.js","sourceRoot":"","sources":["../../../src/server/views/FacetPage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { CoverageReport, FeatureCoverage } from '../../types.js';
2
+ /**
3
+ * Render a feature detail page
4
+ */
5
+ export declare function renderFeaturePage(feature: FeatureCoverage, report: CoverageReport): string;
6
+ //# sourceMappingURL=FeaturePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeaturePage.d.ts","sourceRoot":"","sources":["../../../src/server/views/FeaturePage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItE;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CA8F1F"}