@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 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.12",
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 app = createApp(Component)
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
- createRoot(document.getElementById('app')).render(React.createElement(Component))
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 — mounts a single component in isolation via Vite
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
- // Proxy this HTML through Vite so module imports resolve correctly
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
- // Send to Vite's HTML transform endpoint via proxy
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: "/__gsd_preview",
379
+ path: "/__gsd-preview.html",
353
380
  method: "GET",
354
381
  headers: { ...req.headers, host: `${targetUrl.hostname}:${targetUrl.port}` },
355
382
  };
356
- // Vite won't know this path — serve directly but let browser resolve modules from Vite
357
- const buf = Buffer.from(html, "utf8");
358
- res.writeHead(200, {
359
- "Content-Type": "text/html",
360
- "Content-Length": buf.length,
361
- "Cache-Control": "no-cache",
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
- res.end(buf);
408
+ proxyReq.end();
364
409
  return;
365
410
  }
366
411