@tekyzinc/gsd-t 2.73.12 → 2.73.14
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/scripts/gsd-t-design-review-server.js +59 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [2.73.14] - 2026-04-08
|
|
6
|
+
|
|
7
|
+
### Fixed (review UI — component preview rendering)
|
|
8
|
+
- **Preview HTML now proxied through Vite** for module resolution. Bare module specifiers (`'vue'`, `'react'`) are transformed by Vite into resolved paths. Previously served static HTML which caused `Failed to resolve module specifier "vue"` error.
|
|
9
|
+
- **Test fixture props extracted from design contracts** — reads `## Test Fixture` JSON block, strips metadata keys, passes as component props. Components now render with sample data.
|
|
10
|
+
- **Playwright-verified** — ChartDonut renders 5-segment donut with center value, sublabel, and percentage labels from contract fixture data.
|
|
11
|
+
|
|
5
12
|
## [2.73.12] - 2026-04-08
|
|
6
13
|
|
|
7
14
|
### Added (review UI — isolated component preview + tier tabs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "2.73.
|
|
3
|
+
"version": "2.73.14",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 56 slash commands with headless CI/CD mode, graph-powered code analysis, real-time agent dashboard, execution intelligence, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,8 +51,32 @@ function findGlobalStyles() {
|
|
|
51
51
|
const FRAMEWORK = detectFramework();
|
|
52
52
|
const GLOBAL_STYLES = findGlobalStyles();
|
|
53
53
|
|
|
54
|
+
function extractFixtureFromContract(componentPath) {
|
|
55
|
+
// Map source path → contract path: src/components/elements/ChartDonut.vue → .gsd-t/contracts/design/elements/chart-donut.contract.md
|
|
56
|
+
const match = componentPath.match(/src\/components\/(\w+)\/(\w+)\.vue$/);
|
|
57
|
+
if (!match) return null;
|
|
58
|
+
const [, tier, name] = match;
|
|
59
|
+
// PascalCase → kebab-case
|
|
60
|
+
const kebab = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
61
|
+
const contractPath = path.join(PROJECT_DIR, ".gsd-t", "contracts", "design", tier, `${kebab}.contract.md`);
|
|
62
|
+
try {
|
|
63
|
+
const content = fs.readFileSync(contractPath, "utf8");
|
|
64
|
+
const fixtureMatch = content.match(/## Test Fixture[\s\S]*?```json\s*([\s\S]*?)```/);
|
|
65
|
+
if (!fixtureMatch) return null;
|
|
66
|
+
const fixture = JSON.parse(fixtureMatch[1]);
|
|
67
|
+
// Remove metadata keys (__ prefixed)
|
|
68
|
+
const props = {};
|
|
69
|
+
for (const [k, v] of Object.entries(fixture)) {
|
|
70
|
+
if (!k.startsWith("__")) props[k] = v;
|
|
71
|
+
}
|
|
72
|
+
return props;
|
|
73
|
+
} catch { return null; }
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
function generatePreviewHtml(componentPath) {
|
|
55
77
|
const linkTags = GLOBAL_STYLES.map(s => ` <link rel="stylesheet" href="/${s}">`).join("\n");
|
|
78
|
+
const fixture = extractFixtureFromContract(componentPath);
|
|
79
|
+
const propsJson = fixture ? JSON.stringify(fixture) : "{}";
|
|
56
80
|
|
|
57
81
|
let mountScript;
|
|
58
82
|
if (FRAMEWORK === "vue") {
|
|
@@ -60,7 +84,8 @@ function generatePreviewHtml(componentPath) {
|
|
|
60
84
|
<script type="module">
|
|
61
85
|
import { createApp } from 'vue'
|
|
62
86
|
import Component from '/${componentPath}'
|
|
63
|
-
const
|
|
87
|
+
const props = ${propsJson}
|
|
88
|
+
const app = createApp(Component, props)
|
|
64
89
|
app.mount('#app')
|
|
65
90
|
</script>`;
|
|
66
91
|
} else if (FRAMEWORK === "react") {
|
|
@@ -69,7 +94,8 @@ function generatePreviewHtml(componentPath) {
|
|
|
69
94
|
import React from 'react'
|
|
70
95
|
import { createRoot } from 'react-dom/client'
|
|
71
96
|
import Component from '/${componentPath}'
|
|
72
|
-
|
|
97
|
+
const props = ${propsJson}
|
|
98
|
+
createRoot(document.getElementById('app')).render(React.createElement(Component, props))
|
|
73
99
|
</script>`;
|
|
74
100
|
} else {
|
|
75
101
|
mountScript = `<script type="module">import '/${componentPath}'</script>`;
|
|
@@ -81,7 +107,6 @@ function generatePreviewHtml(componentPath) {
|
|
|
81
107
|
<meta charset="UTF-8">
|
|
82
108
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
83
109
|
<title>Preview: ${path.basename(componentPath)}</title>
|
|
84
|
-
<script type="module" src="/@vite/client"></script>
|
|
85
110
|
${linkTags}
|
|
86
111
|
<style>
|
|
87
112
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
@@ -335,7 +360,7 @@ const server = http.createServer((req, res) => {
|
|
|
335
360
|
return;
|
|
336
361
|
}
|
|
337
362
|
|
|
338
|
-
// Component preview —
|
|
363
|
+
// Component preview — writes temp HTML to project, proxies through Vite for module resolution
|
|
339
364
|
if (pathname === "/review/preview") {
|
|
340
365
|
const component = parsed.query.component;
|
|
341
366
|
if (!component) {
|
|
@@ -343,24 +368,44 @@ const server = http.createServer((req, res) => {
|
|
|
343
368
|
res.end("Missing ?component= parameter");
|
|
344
369
|
return;
|
|
345
370
|
}
|
|
346
|
-
//
|
|
371
|
+
// Write preview HTML to project dir so Vite transforms bare module specifiers (e.g., 'vue' → /node_modules/.vite/deps/vue.js)
|
|
372
|
+
const previewFile = path.join(PROJECT_DIR, "__gsd-preview.html");
|
|
347
373
|
const html = generatePreviewHtml(component);
|
|
348
|
-
|
|
374
|
+
try { fs.writeFileSync(previewFile, html); } catch { /* ignore */ }
|
|
375
|
+
// Proxy through Vite so it transforms the HTML
|
|
349
376
|
const proxyOpts = {
|
|
350
377
|
hostname: targetUrl.hostname,
|
|
351
378
|
port: targetUrl.port,
|
|
352
|
-
path: "/
|
|
379
|
+
path: "/__gsd-preview.html",
|
|
353
380
|
method: "GET",
|
|
354
381
|
headers: { ...req.headers, host: `${targetUrl.hostname}:${targetUrl.port}` },
|
|
355
382
|
};
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
"
|
|
360
|
-
|
|
361
|
-
|
|
383
|
+
const proxyReq = http.request(proxyOpts, (proxyRes) => {
|
|
384
|
+
const chunks = [];
|
|
385
|
+
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
|
386
|
+
proxyRes.on("end", () => {
|
|
387
|
+
let transformed = Buffer.concat(chunks).toString("utf8");
|
|
388
|
+
// Inject review overlay if not already present
|
|
389
|
+
if (!transformed.includes("review/inject.js")) {
|
|
390
|
+
transformed = transformed.replace("</body>", '<script src="/review/inject.js"></script>\n</body>');
|
|
391
|
+
}
|
|
392
|
+
const buf = Buffer.from(transformed, "utf8");
|
|
393
|
+
res.writeHead(proxyRes.statusCode, {
|
|
394
|
+
...proxyRes.headers,
|
|
395
|
+
"content-length": buf.length,
|
|
396
|
+
"cache-control": "no-cache",
|
|
397
|
+
});
|
|
398
|
+
res.end(buf);
|
|
399
|
+
// Clean up temp file
|
|
400
|
+
try { fs.unlinkSync(previewFile); } catch { /* ignore */ }
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
proxyReq.on("error", () => {
|
|
404
|
+
res.writeHead(502, { "Content-Type": "text/html" });
|
|
405
|
+
res.end("<h1>Dev server unreachable</h1>");
|
|
406
|
+
try { fs.unlinkSync(previewFile); } catch { /* ignore */ }
|
|
362
407
|
});
|
|
363
|
-
|
|
408
|
+
proxyReq.end();
|
|
364
409
|
return;
|
|
365
410
|
}
|
|
366
411
|
|