@tekyzinc/gsd-t 2.73.24 → 2.73.25

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,12 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [2.73.25] - 2026-04-09
6
+
7
+ ### Added
8
+ - **Undo remove** — excluded elements show as struck-through with a green ↩ restore button instead of disappearing. Click to undo before submitting.
9
+ - **Contract deletion on submit** — when excluded elements are submitted, their contract files and source files are deleted from the project. The `/review/api/exclude` endpoint handles cleanup.
10
+
5
11
  ## [2.73.24] - 2026-04-09
6
12
 
7
13
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "2.73.24",
3
+ "version": "2.73.25",
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",
@@ -620,6 +620,48 @@ const server = http.createServer((req, res) => {
620
620
  return;
621
621
  }
622
622
 
623
+ if (pathname === "/review/api/exclude" && req.method === "POST") {
624
+ // Remove excluded elements: delete contract files and remove from queue
625
+ let body = "";
626
+ req.on("data", (chunk) => { body += chunk; });
627
+ req.on("end", () => {
628
+ try {
629
+ const { excludedIds } = JSON.parse(body);
630
+ const removed = [];
631
+ for (const id of excludedIds) {
632
+ const item = reviewQueue.find(q => q.id === id);
633
+ if (!item || !item.sourcePath) continue;
634
+ const match = item.sourcePath.match(/src\/components\/(\w+)\/(\w+)\.vue$/);
635
+ if (!match) continue;
636
+ const [, tier, name] = match;
637
+ const kebab = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
638
+ const contractPath = path.join(PROJECT_DIR, ".gsd-t", "contracts", "design", tier, `${kebab}.contract.md`);
639
+ if (fs.existsSync(contractPath)) {
640
+ fs.unlinkSync(contractPath);
641
+ removed.push({ id, contract: contractPath });
642
+ }
643
+ // Remove source file too
644
+ const srcPath = path.join(PROJECT_DIR, item.sourcePath);
645
+ if (fs.existsSync(srcPath)) {
646
+ fs.unlinkSync(srcPath);
647
+ removed.push({ id, source: srcPath });
648
+ }
649
+ }
650
+ // Remove from queue
651
+ for (let i = reviewQueue.length - 1; i >= 0; i--) {
652
+ if (excludedIds.includes(reviewQueue[i].id)) reviewQueue.splice(i, 1);
653
+ }
654
+ broadcast("queue-update", reviewQueue);
655
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
656
+ res.end(JSON.stringify({ ok: true, removed }));
657
+ } catch (err) {
658
+ res.writeHead(400, { "Content-Type": "application/json" });
659
+ res.end(JSON.stringify({ error: err.message }));
660
+ }
661
+ });
662
+ return;
663
+ }
664
+
623
665
  if (pathname === "/review/api/write-source" && req.method === "POST") {
624
666
  // Apply CSS property changes back to source files
625
667
  let body = "";
@@ -195,11 +195,13 @@
195
195
 
196
196
  .component-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
197
197
  .component-type { font-size: 10px; color: #94a3b8; }
198
- .remove-btn {
199
- display: none; background: none; border: none; color: #ef4444; font-size: 16px;
198
+ .remove-btn, .undo-remove-btn {
199
+ display: none; background: none; border: none; font-size: 14px;
200
200
  cursor: pointer; padding: 0 4px; line-height: 1; opacity: 0.6;
201
201
  }
202
- .remove-btn:hover { opacity: 1; }
202
+ .remove-btn { color: #ef4444; }
203
+ .undo-remove-btn { color: #22c55e; display: block; }
204
+ .remove-btn:hover, .undo-remove-btn:hover { opacity: 1; }
203
205
  .component-item:hover .remove-btn { display: block; }
204
206
 
205
207
  /* ── Submit bar ─────────────────────────────────── */
@@ -959,28 +961,45 @@
959
961
  statusClass = "rejected";
960
962
  }
961
963
 
962
- if (excluded.has(item.id)) return; // skip excluded elements
964
+ const isExcluded = excluded.has(item.id);
963
965
 
964
966
  const div = document.createElement("div");
965
- div.className = `component-item${idx === selectedIdx ? " selected" : ""}`;
966
- div.innerHTML = `
967
- <div class="component-status ${statusClass}"></div>
968
- <div class="component-name">${item.name || item.id}</div>
969
- <div class="component-type">${item.type || ""}</div>
970
- <button class="remove-btn" title="Remove from review">×</button>
971
- `;
972
- div.querySelector(".component-name").addEventListener("click", () => selectComponent(idx));
973
- div.querySelector(".component-status").addEventListener("click", () => selectComponent(idx));
974
- div.querySelector(".remove-btn").addEventListener("click", (e) => {
975
- e.stopPropagation();
976
- excluded.add(item.id);
977
- comments.set(item.id, "EXCLUDED — not in Figma design");
978
- if (selectedIdx === idx) {
979
- selectedIdx = Math.min(idx, filteredQueue.filter(q => !excluded.has(q.id)).length - 1);
980
- }
981
- renderComponentList();
982
- updateSubmitStats();
983
- });
967
+ div.className = `component-item${idx === selectedIdx ? " selected" : ""}${isExcluded ? " excluded" : ""}`;
968
+
969
+ if (isExcluded) {
970
+ div.innerHTML = `
971
+ <div class="component-status" style="background:#ef4444"></div>
972
+ <div class="component-name" style="text-decoration:line-through;opacity:0.5">${item.name || item.id}</div>
973
+ <button class="undo-remove-btn" title="Restore element">↩</button>
974
+ `;
975
+ div.querySelector(".undo-remove-btn").addEventListener("click", (e) => {
976
+ e.stopPropagation();
977
+ excluded.delete(item.id);
978
+ comments.delete(item.id);
979
+ renderComponentList();
980
+ updateSubmitStats();
981
+ });
982
+ } else {
983
+ div.innerHTML = `
984
+ <div class="component-status ${statusClass}"></div>
985
+ <div class="component-name">${item.name || item.id}</div>
986
+ <div class="component-type">${item.type || ""}</div>
987
+ <button class="remove-btn" title="Remove from review">×</button>
988
+ `;
989
+ div.querySelector(".component-name").addEventListener("click", () => selectComponent(idx));
990
+ div.querySelector(".component-status").addEventListener("click", () => selectComponent(idx));
991
+ div.querySelector(".remove-btn").addEventListener("click", (e) => {
992
+ e.stopPropagation();
993
+ excluded.add(item.id);
994
+ comments.set(item.id, "EXCLUDED — not in Figma design");
995
+ if (selectedIdx === idx) {
996
+ const remaining = filteredQueue.filter(q => !excluded.has(q.id));
997
+ selectedIdx = remaining.length > 0 ? filteredQueue.indexOf(remaining[0]) : -1;
998
+ }
999
+ renderComponentList();
1000
+ updateSubmitStats();
1001
+ });
1002
+ }
984
1003
  componentList.appendChild(div);
985
1004
  });
986
1005
  }
@@ -2192,6 +2211,15 @@
2192
2211
  body: JSON.stringify({ changes: allChanges }),
2193
2212
  });
2194
2213
  }
2214
+
2215
+ // Delete contracts and source files for excluded elements
2216
+ if (excluded.size > 0) {
2217
+ await fetch("/review/api/exclude", {
2218
+ method: "POST",
2219
+ headers: { "Content-Type": "application/json" },
2220
+ body: JSON.stringify({ excludedIds: Array.from(excluded) }),
2221
+ });
2222
+ }
2195
2223
  }
2196
2224
  } catch (err) {
2197
2225
  submitAll.textContent = "Error — retry";