From 095df5b0637ce45e0c5b93dfb69a9a767521bc65 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 27 Mar 2026 08:14:03 -0600 Subject: [PATCH] feat: dual-mode feedback + post-submit lifecycle in comparison board When __GSTACK_SERVER_URL is set (injected by $D serve), the board POSTs feedback to the server instead of only writing to hidden DOM elements. After submit: disables all inputs, shows "Return to your coding agent." After regenerate: shows spinner, polls /api/progress, auto-refreshes on ready. On POST failure: shows copyable JSON fallback. On progress timeout (5 min): shows error with /design-shotgun prompt. DOM fallback preserved for headed browser mode and tests. Co-Authored-By: Claude Opus 4.6 (1M context) --- design/src/compare.ts | 112 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/design/src/compare.ts b/design/src/compare.ts index bcf20a55..bededfe9 100644 --- a/design/src/compare.ts +++ b/design/src/compare.ts @@ -346,22 +346,124 @@ export function generateCompareHtml(images: string[]): string { submitRegenerate(detail); }); + function postFeedback(feedback) { + if (!window.__GSTACK_SERVER_URL) return Promise.resolve(null); + return fetch(window.__GSTACK_SERVER_URL + '/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(feedback), + }).then(function(r) { return r.json(); }).catch(function() { return null; }); + } + + function disableAllInputs() { + document.querySelectorAll('input, button, textarea, .star, .regen-chiclet').forEach(function(el) { + el.disabled = true; + el.style.pointerEvents = 'none'; + el.style.opacity = '0.5'; + }); + } + + function showPostSubmitState() { + disableAllInputs(); + document.querySelector('.regenerate-bar').style.display = 'none'; + document.getElementById('submit-btn').style.display = 'none'; + document.getElementById('success-msg').style.display = 'block'; + document.getElementById('success-msg').innerHTML = + 'Feedback received! Return to your coding agent.' + + '
Want to make more changes? Run /design-shotgun again.'; + } + + function showRegeneratingState() { + disableAllInputs(); + document.querySelector('.variants').innerHTML = + '
' + + '
Generating new designs...
' + + '
' + + '
'; + document.querySelector('.regenerate-bar').style.display = 'none'; + document.querySelector('.submit-bar').style.display = 'none'; + document.querySelector('.overall-section').style.display = 'none'; + startProgressPolling(); + } + + function startProgressPolling() { + if (!window.__GSTACK_SERVER_URL) return; + var pollCount = 0; + var maxPolls = 150; // 5 min at 2s intervals + var pollInterval = setInterval(function() { + pollCount++; + if (pollCount >= maxPolls) { + clearInterval(pollInterval); + document.querySelector('.variants').innerHTML = + '
' + + '
Something went wrong.
' + + '
Run /design-shotgun again in your coding agent.
' + + '
'; + return; + } + fetch(window.__GSTACK_SERVER_URL + '/api/progress') + .then(function(r) { return r.json(); }) + .then(function(data) { + if (data.status === 'serving') { + clearInterval(pollInterval); + window.location.reload(); + } + }) + .catch(function() { + // Server gone, stop polling + clearInterval(pollInterval); + document.querySelector('.variants').innerHTML = + '
' + + '
Connection lost.
' + + '
Run /design-shotgun again in your coding agent.
' + + '
'; + }); + }, 2000); + } + + function showPostFailure(feedback) { + disableAllInputs(); + var json = JSON.stringify(feedback, null, 2); + document.getElementById('success-msg').style.display = 'block'; + document.getElementById('success-msg').innerHTML = + '
Connection lost. Copy your feedback below and paste it in your coding agent:
' + + '
' +
+      json.replace(/' +
+      'Click to copy';
+  }
+
   function submitRegenerate(detail) {
-    const feedback = collectFeedback();
+    var feedback = collectFeedback();
     feedback.regenerated = true;
     feedback.regenerateAction = detail;
     document.getElementById('feedback-result').textContent = JSON.stringify(feedback);
     document.getElementById('status').textContent = 'regenerate';
+    postFeedback(feedback).then(function(result) {
+      if (result && result.received) {
+        showRegeneratingState();
+      } else if (window.__GSTACK_SERVER_URL) {
+        showPostFailure(feedback);
+      }
+    });
   }
 
   // Submit button
-  document.getElementById('submit-btn').addEventListener('click', () => {
-    const feedback = collectFeedback();
+  document.getElementById('submit-btn').addEventListener('click', function() {
+    var feedback = collectFeedback();
     feedback.regenerated = false;
     document.getElementById('feedback-result').textContent = JSON.stringify(feedback);
     document.getElementById('status').textContent = 'submitted';
-    document.getElementById('submit-btn').disabled = true;
-    document.getElementById('success-msg').style.display = 'block';
+    postFeedback(feedback).then(function(result) {
+      if (result && result.received) {
+        showPostSubmitState();
+      } else if (window.__GSTACK_SERVER_URL) {
+        showPostFailure(feedback);
+      } else {
+        // DOM-only mode (legacy / test)
+        document.getElementById('submit-btn').disabled = true;
+        document.getElementById('success-msg').style.display = 'block';
+      }
+    });
   });
 
   function collectFeedback() {