@tekyzinc/gsd-t 2.73.19 → 2.73.20

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,16 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [2.73.20] - 2026-04-09
6
+
7
+ ### Added
8
+ - **Editable fixture data** — segment label, value, and color fields in the Data Props tree are now clickable to edit. Color fields use a color picker. Changes tracked alongside CSS/SVG changes in the review output.
9
+ - **Better SVG tree labels** — circle/arc nodes show stroke color, width, and radius. Path nodes show fill/stroke color. SVG root shows viewBox.
10
+ - **Deeper SVG tree traversal** — SVG subtrees traverse up to depth 8 (was 4), ensuring individual arc segments appear in the element tree.
11
+
12
+ ### Removed
13
+ - **`percentages_shown`** from donut chart fixture — redundant with `segments[].value`.
14
+
5
15
  ## [2.73.19] - 2026-04-09
6
16
 
7
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "2.73.19",
3
+ "version": "2.73.20",
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",
@@ -898,7 +898,9 @@
898
898
  let keyCounter = 0;
899
899
 
900
900
  function buildTree(el, depth) {
901
- if (depth > 4) return null; // limit depth
901
+ // SVG subtrees get deeper traversal (arcs are nested)
902
+ const isSvgSubtree = el instanceof SVGElement || el.closest("svg");
903
+ if (depth > (isSvgSubtree ? 8 : 4)) return null;
902
904
  const tag = el.tagName.toLowerCase();
903
905
  const s = getComputedStyle(el);
904
906
  const rect = el.getBoundingClientRect();
@@ -933,11 +935,20 @@
933
935
  label = "Col " + colIdx;
934
936
  if (text && text.length < 15) label += ' "' + text + '"';
935
937
  } else if (tag === "svg") {
936
- label = "svg";
938
+ const vb = el.getAttribute("viewBox");
939
+ label = "svg" + (vb ? ` [${vb}]` : "");
937
940
  } else if (tag === "circle") {
938
- label = "arc/circle";
941
+ const stroke = el.getAttribute("stroke") || "";
942
+ const fill = el.getAttribute("fill") || "";
943
+ const color = stroke && stroke !== "none" ? stroke : fill && fill !== "none" ? fill : "";
944
+ const r = el.getAttribute("r") || "";
945
+ const sw = el.getAttribute("stroke-width") || "";
946
+ label = "arc" + (color ? " " + color : "") + (sw ? " w:" + sw : "") + (r ? " r:" + r : "");
939
947
  } else if (tag === "path") {
940
- label = "path";
948
+ const stroke = el.getAttribute("stroke") || "";
949
+ const fill = el.getAttribute("fill") || "";
950
+ const color = stroke && stroke !== "none" ? stroke : fill && fill !== "none" ? fill : "";
951
+ label = "path" + (color ? " " + color : "");
941
952
  } else if (s.display === "flex" || s.display === "inline-flex") {
942
953
  label = "flex " + (s.flexDirection === "column" ? "col" : "row");
943
954
  } else if (s.display === "grid") {
@@ -973,10 +984,16 @@
973
984
  node.props.fontWeight = s.fontWeight;
974
985
  if (tag === "th") node.props.background = s.backgroundColor;
975
986
  }
976
- if (tag === "circle") {
987
+ if (tag === "circle" || tag === "ellipse") {
977
988
  node.props.stroke = el.getAttribute("stroke");
978
989
  node.props.strokeWidth = el.getAttribute("stroke-width");
979
990
  node.props.r = el.getAttribute("r");
991
+ node.props.fill = el.getAttribute("fill");
992
+ node.props.strokeDasharray = el.getAttribute("stroke-dasharray");
993
+ } else if (tag === "path" || tag === "line" || tag === "rect") {
994
+ node.props.stroke = el.getAttribute("stroke");
995
+ node.props.strokeWidth = el.getAttribute("stroke-width");
996
+ node.props.fill = el.getAttribute("fill");
980
997
  }
981
998
  if (s.display === "flex" || s.display === "inline-flex") {
982
999
  node.props.gap = s.gap;
@@ -1079,7 +1079,7 @@
1079
1079
  const body = document.createElement("div");
1080
1080
  body.style.cssText = "padding-left:8px;";
1081
1081
 
1082
- function renderValue(key, val, parent, depth) {
1082
+ function renderValue(key, val, parent, depth, propPath) {
1083
1083
  if (Array.isArray(val)) {
1084
1084
  const row = document.createElement("div");
1085
1085
  row.className = "prop-group-header";
@@ -1093,6 +1093,7 @@
1093
1093
  if (expanded) row.querySelector("span").textContent = "▾ " + key;
1094
1094
  parent.appendChild(row);
1095
1095
  val.forEach((item, i) => {
1096
+ const itemPath = propPath + "." + key + "[" + i + "]";
1096
1097
  if (typeof item === "object" && item !== null) {
1097
1098
  const itemRow = document.createElement("div");
1098
1099
  itemRow.style.paddingLeft = ((depth + 1) * 12) + "px";
@@ -1100,7 +1101,7 @@
1100
1101
  itemRow.textContent = `[${i}]`;
1101
1102
  items.appendChild(itemRow);
1102
1103
  for (const [k, v] of Object.entries(item)) {
1103
- renderValue(k, v, items, depth + 2);
1104
+ renderValue(k, v, items, depth + 2, itemPath);
1104
1105
  }
1105
1106
  } else {
1106
1107
  const itemRow = document.createElement("div");
@@ -1124,23 +1125,37 @@
1124
1125
  if (expanded) row.querySelector("span").textContent = "▾ " + key;
1125
1126
  parent.appendChild(row);
1126
1127
  for (const [k, v] of Object.entries(val)) {
1127
- renderValue(k, v, items, depth + 1);
1128
+ renderValue(k, v, items, depth + 1, propPath + "." + key);
1128
1129
  }
1129
1130
  parent.appendChild(items);
1130
1131
  } else {
1131
1132
  const row = document.createElement("div");
1132
1133
  row.className = "prop-row";
1133
1134
  row.style.paddingLeft = (depth * 12) + "px";
1134
- const displayVal = typeof val === "string" && val.match(/^#[0-9a-fA-F]{3,8}$/)
1135
+ const isColor = typeof val === "string" && val.match(/^#[0-9a-fA-F]{3,8}$/);
1136
+ const displayVal = isColor
1135
1137
  ? `<span class="color-swatch" style="background:${val}"></span>${val}`
1136
1138
  : escapeHtml(String(val));
1137
- row.innerHTML = `<span class="prop-name">${escapeHtml(key)}</span><span class="prop-value">${displayVal}</span>`;
1139
+
1140
+ const nameEl = document.createElement("span");
1141
+ nameEl.className = "prop-name";
1142
+ nameEl.textContent = key;
1143
+ row.appendChild(nameEl);
1144
+
1145
+ const valEl = document.createElement("span");
1146
+ valEl.className = "prop-value editable";
1147
+ valEl.setAttribute("data-prop", "fixture:" + propPath + "." + key);
1148
+ valEl.setAttribute("data-original", String(val));
1149
+ valEl.innerHTML = displayVal;
1150
+
1151
+ valEl.addEventListener("click", () => startFixtureEdit(valEl, propPath + "." + key, String(val), isColor));
1152
+ row.appendChild(valEl);
1138
1153
  parent.appendChild(row);
1139
1154
  }
1140
1155
  }
1141
1156
 
1142
1157
  for (const [k, v] of Object.entries(fixture)) {
1143
- renderValue(k, v, body, 0);
1158
+ renderValue(k, v, body, 0, "");
1144
1159
  }
1145
1160
  container.appendChild(body);
1146
1161
 
@@ -1586,6 +1601,96 @@
1586
1601
  });
1587
1602
  }
1588
1603
 
1604
+ function startFixtureEdit(valEl, fixturePath, currentVal, isColor) {
1605
+ if (valEl.querySelector("select, input")) return;
1606
+
1607
+ const compId = filteredQueue[selectedIdx]?.id;
1608
+ const compChanges = changes.get(compId) || [];
1609
+ const prop = "fixture:" + fixturePath;
1610
+ const existing = compChanges.find(c => c.property === prop);
1611
+ const displayVal = existing ? existing.newValue : currentVal;
1612
+
1613
+ const inputEl = document.createElement("input");
1614
+ inputEl.className = "prop-edit-input";
1615
+ inputEl.type = isColor ? "color" : "text";
1616
+ inputEl.value = displayVal;
1617
+
1618
+ valEl.innerHTML = "";
1619
+ valEl.appendChild(inputEl);
1620
+ inputEl.focus();
1621
+ if (!isColor) {
1622
+ const numMatch = displayVal.match(/^(-?[\d.]+)/);
1623
+ if (numMatch) {
1624
+ inputEl.setSelectionRange(0, numMatch[1].length);
1625
+ } else {
1626
+ inputEl.select();
1627
+ }
1628
+ }
1629
+
1630
+ let cancelled = false;
1631
+ function commit() {
1632
+ if (cancelled) return;
1633
+ const newVal = inputEl.value.trim();
1634
+ if (newVal && newVal !== currentVal) {
1635
+ // Track as a fixture data change
1636
+ if (compId) {
1637
+ if (!changes.has(compId)) changes.set(compId, []);
1638
+ const list = changes.get(compId);
1639
+ const existingIdx = list.findIndex(c => c.property === prop);
1640
+ const change = {
1641
+ path: "(fixture data)",
1642
+ property: prop,
1643
+ oldValue: currentVal,
1644
+ newValue: newVal,
1645
+ };
1646
+ if (existingIdx >= 0) {
1647
+ list[existingIdx] = change;
1648
+ } else {
1649
+ list.push(change);
1650
+ }
1651
+ renderChanges(compId);
1652
+ renderComponentList();
1653
+ }
1654
+
1655
+ if (isColor) {
1656
+ valEl.innerHTML = `<span class="color-swatch" style="background:${newVal}"></span>${newVal}`;
1657
+ } else {
1658
+ valEl.textContent = newVal;
1659
+ }
1660
+ valEl.classList.add("changed");
1661
+ } else {
1662
+ if (isColor && (existing ? existing.newValue : currentVal).match(/^#/)) {
1663
+ const v = existing ? existing.newValue : currentVal;
1664
+ valEl.innerHTML = `<span class="color-swatch" style="background:${v}"></span>${v}`;
1665
+ } else {
1666
+ valEl.textContent = existing ? existing.newValue : currentVal;
1667
+ }
1668
+ if (existing) valEl.classList.add("changed");
1669
+ }
1670
+ }
1671
+
1672
+ if (isColor) {
1673
+ inputEl.addEventListener("change", () => { commit(); cancelled = true; });
1674
+ inputEl.addEventListener("blur", () => { if (!cancelled) { valEl.innerHTML = `<span class="color-swatch" style="background:${currentVal}"></span>${currentVal}`; } });
1675
+ } else {
1676
+ inputEl.addEventListener("blur", () => { if (!cancelled) commit(); });
1677
+ }
1678
+ inputEl.addEventListener("keydown", (e) => {
1679
+ if (e.key === "Enter") { commit(); cancelled = true; inputEl.blur(); }
1680
+ if (e.key === "Escape") {
1681
+ cancelled = true;
1682
+ if (inputEl.parentElement) inputEl.remove();
1683
+ if (isColor) {
1684
+ const v = existing ? existing.newValue : currentVal;
1685
+ valEl.innerHTML = `<span class="color-swatch" style="background:${v}"></span>${v}`;
1686
+ } else {
1687
+ valEl.textContent = existing ? existing.newValue : currentVal;
1688
+ }
1689
+ if (existing) valEl.classList.add("changed");
1690
+ }
1691
+ });
1692
+ }
1693
+
1589
1694
  function renderPropertyValues(styles) {
1590
1695
  // Update existing property values in the inspector
1591
1696
  inspectorProps.querySelectorAll(".prop-value[data-prop]").forEach(el => {