Repository navigation
STU-379: Preserve previous results through refinement and build-error regeneration #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3688,6 +3688,109 @@ function updateModelInfo(selectedModel) { | |
| console.log(`Step 3 (Code Review): ${getModelLabel(CODE_REVIEW_MODEL)}`) | ||
| } | ||
|
|
||
| /** | ||
| * Snapshot the current successful generation result so a failed refinement or | ||
| * build-error replacement can restore it verbatim. The selected artifact, full | ||
| * bundle, prompt, review and selection are held untouched until a replacement | ||
| * generation AND review both succeed. | ||
| */ | ||
| function snapshotGenerationResult() { | ||
| return { | ||
| step1Result: pipelineState.step1Result, | ||
| step2Result: pipelineState.step2Result, | ||
| step3Result: pipelineState.step3Result, | ||
| artifactBundle: pipelineState.artifactBundle, | ||
| bundleReview: pipelineState.bundleReview, | ||
| selectedArtifactId: pipelineState.selectedArtifactId, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Bring a prior successful result back into shared state and reconcile the | ||
| * selection to an id that still exists in the restored bundle (so the prior | ||
| * result never points at a stale artifact that the failed replacement no | ||
| * longer defined). | ||
| */ | ||
| function restoreGenerationResult(snapshot) { | ||
| if (!snapshot) return; | ||
| Object.assign(pipelineState, snapshot); | ||
| const ids = new Set((pipelineState.artifactBundle?.artifacts || []).map((a) => a.id)); | ||
| if (!ids.has(pipelineState.selectedArtifactId)) { | ||
| pipelineState.selectedArtifactId = | ||
| getPrimaryArtifact(pipelineState.artifactBundle)?.id || null; | ||
| } | ||
| } | ||
|
|
||
| const RESULTS_REPLACEMENT_ERROR_ID = "results-replacement-error"; | ||
|
|
||
| /** Persistent error + retry UI that sits over the retained previous result. */ | ||
| function showReplacementFailure(error, { stage = 2, runId, retry }) { | ||
| if (!isCurrentPipelineRun(runId)) return; | ||
| const banner = document.getElementById(RESULTS_REPLACEMENT_ERROR_ID); | ||
| if (!banner) return; | ||
| const failure = classifyPipelineError(error); | ||
| const titleEl = document.getElementById("results-replacement-error-title"); | ||
| const messageEl = document.getElementById("results-replacement-error-message"); | ||
| const retryEl = document.getElementById("results-replacement-error-retry"); | ||
| const upgradeEl = document.getElementById("results-replacement-error-upgrade"); | ||
| if (titleEl) titleEl.textContent = `${PIPELINE_STAGE_LABELS[stage] || "Regeneration"}: ${failure.title}`; | ||
| if (messageEl) messageEl.textContent = failure.message; | ||
| banner.hidden = false; | ||
|
|
||
| // A quota/usage-limit rejection is the one failure a protected retry cannot | ||
| // fix — re-firing launches another generation into an already-exhausted | ||
| // allowance, a dead end. Surface the upgrade affordance as the primary | ||
| // action, exactly as the non-replacement path does. Every other failure | ||
| // keeps the retry. | ||
| let focusTarget = null; | ||
| if (upgradeEl) { | ||
| upgradeEl.hidden = !failure.canUpgrade; | ||
| if (failure.canUpgrade) { | ||
| upgradeEl.textContent = "View plans"; | ||
| upgradeEl.onclick = () => openPricingModal(); | ||
| focusTarget = upgradeEl; | ||
| } | ||
| } | ||
| if (retryEl) { | ||
| retryEl.hidden = failure.canUpgrade; | ||
| if (!failure.canUpgrade) { | ||
| retryEl.onclick = () => { | ||
| hideReplacementFailure(); | ||
| if (typeof retry === "function") retry(); | ||
| }; | ||
| retryEl.textContent = "Retry"; | ||
| focusTarget = retryEl; | ||
| } | ||
| } | ||
| // Keyboard accessible: a failed replacement lands focus on the action that | ||
| // can actually resolve it. | ||
| (focusTarget || banner).focus({ preventScroll: true }); | ||
| } | ||
|
|
||
| function hideReplacementFailure() { | ||
| const banner = document.getElementById(RESULTS_REPLACEMENT_ERROR_ID); | ||
| if (banner) banner.hidden = true; | ||
| } | ||
|
|
||
| /** | ||
| * Shared completion path for a replacement run (refinement / build-error fix): | ||
| * the previous successful result is restored verbatim, the Results surface is | ||
| * repainted from it, and a persistent error + retry banner is shown. A toast | ||
| * alone is insufficient - the old code and review must stay inspectable and | ||
| * copyable while the user decides whether to retry. | ||
| */ | ||
| function restoreAndShowReplacementFailure(error, { previous, stage, runId, retry }) { | ||
| if (!isCurrentPipelineRun(runId)) return; | ||
| if (error.isUsageLimit) updateUsageDisplay(); | ||
| restoreGenerationResult(previous); | ||
| hidePipelineProgress(); | ||
| setGenerationStageVisible(true); | ||
| showResultsView(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Failure restoration closes artifact view When refinement fails from an artifact tab, Learn moreA result has two related selection fields: Example: A user opens artifact B and clicks Refine. If generation fails, artifact B remains in Recommended fix: Include Was this helpful? React with 👍 or 👎 to provide feedback.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Restoration clears prior feedback After feedback was submitted, any replacement failure calls Learn moreThe failure path restores the old generation rather than committing a new one. However, showResultsView always calls Example: A user votes thumbs-up, starts refinement, and the review fails. The old code returns with both feedback buttons unpressed, so another thumbs-up sends feedback again for the same generation. Recommended fix: Repaint a restored result without resetting feedback. Add a Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| updateSelectedArtifactPanels(); | ||
| showReplacementFailure(error, { stage, runId, retry }); | ||
| updateDeployButtonVisibility(); | ||
| } | ||
|
|
||
| async function runRefinement() { | ||
| console.log("runRefinement called"); | ||
|
|
||
|
|
@@ -3702,6 +3805,10 @@ async function runRefinement() { | |
| callEndpoint('standardRegenerate', pipelineState.step2Result, pipelineState.step1Result) | ||
| const btns = document.querySelectorAll(".btn-refine-action"); | ||
|
|
||
| // Hold the prior successful result verbatim until a replacement generation | ||
| // AND review both succeed; a failure restores it below. | ||
| const previousResult = snapshotGenerationResult(); | ||
|
|
||
| btns.forEach((btn) => { | ||
| btn.disabled = true; | ||
| btn.innerHTML = `<svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
|
|
@@ -3711,7 +3818,9 @@ async function runRefinement() { | |
| }); | ||
|
|
||
| try { | ||
| hideReplacementFailure(); | ||
| const selectedArtifact = getSelectedArtifact(); | ||
| const refinedArtifactId = selectedArtifact.id; | ||
| const refinementPrompt = buildArtifactRegenerationPrompt({ | ||
| bundleSpec: pipelineState.step1Result, | ||
| artifactBundle: JSON.stringify(pipelineState.artifactBundle || pipelineState.step2Result), | ||
|
|
@@ -3763,6 +3872,16 @@ async function runRefinement() { | |
| updateBundleReviewFromReviewResult(); | ||
| completePipelineStage(3, runId); | ||
|
|
||
| // The whole replacement (generation + review) succeeded, so the new result | ||
| // is committed. Reconcile the selection onto the re-refined artifact if it | ||
| // still exists, otherwise fall back to the new bundle's primary — either | ||
| // way the tabs are rebuilt from the new bundle, so none go stale. | ||
| if (pipelineState.artifactBundle?.artifacts?.some((a) => a.id === refinedArtifactId)) { | ||
| pipelineState.selectedArtifactId = refinedArtifactId; | ||
| } else { | ||
| pipelineState.selectedArtifactId = getPrimaryArtifact(pipelineState.artifactBundle)?.id || null; | ||
| } | ||
|
|
||
| const auditOutput = document.getElementById("step3-output"); | ||
| auditOutput.textContent = pipelineState.step3Result; | ||
|
|
||
|
|
@@ -3782,14 +3901,15 @@ async function runRefinement() { | |
| review: 3, | ||
| }); | ||
|
|
||
| if (error.isUsageLimit) { | ||
| updateUsageDisplay(); | ||
| } | ||
|
|
||
| selectWorkflowStep(errorStep); | ||
| showStepLoading(errorStep, false); | ||
| showPipelineFailure(error, { stage: errorStep, runId, retry: runRefinement }); | ||
| updateStepIndicator(errorStep, "error"); | ||
| // Restore the previous successful result and keep it visible and copyable | ||
| // with persistent error + retry UI; the failed replacement never owns the | ||
| // screen. | ||
| restoreAndShowReplacementFailure(error, { | ||
| previous: previousResult, | ||
| stage: errorStep, | ||
| runId, | ||
| retry: runRefinement, | ||
| }); | ||
| } finally { | ||
| // The control's own busy affordance is restored even for an abandoned | ||
| // run — the run guard only protects shared pipeline state. | ||
|
|
@@ -3865,7 +3985,12 @@ async function regenerateFromPastedErrors() { | |
| </svg> Fixing…` | ||
| } | ||
|
|
||
| // Hold the prior successful result verbatim until the bundle replacement | ||
| // generation AND review both succeed; a failure restores it below. | ||
| const previousResult = snapshotGenerationResult() | ||
|
|
||
| try { | ||
| hideReplacementFailure() | ||
| const refinementPrompt = buildBundleRegenerationPrompt({ | ||
| bundleSpec: pipelineState.step1Result, | ||
| artifactBundle: JSON.stringify(pipelineState.artifactBundle || pipelineState.step2Result), | ||
|
|
@@ -3921,14 +4046,15 @@ async function regenerateFromPastedErrors() { | |
| review: 3, | ||
| }) | ||
|
|
||
| if (error.isUsageLimit) { | ||
| updateUsageDisplay() | ||
| } | ||
|
|
||
| selectWorkflowStep(errorStep) | ||
| showStepLoading(errorStep, false) | ||
| showPipelineFailure(error, { stage: errorStep, runId, retry: regenerateFromPastedErrors }) | ||
| updateStepIndicator(errorStep, "error") | ||
| // Restore the previous successful result and keep it visible and copyable | ||
| // with persistent error + retry UI; the failed replacement never owns the | ||
| // screen. | ||
| restoreAndShowReplacementFailure(error, { | ||
| previous: previousResult, | ||
| stage: errorStep, | ||
| runId, | ||
| retry: regenerateFromPastedErrors, | ||
| }) | ||
| } finally { | ||
| if (btn) { | ||
| btn.disabled = false | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a replacement fails, this banner is only hidden by Retry or by starting another replacement. If the user edits the prompt and completes a fresh full pipeline, the old failure remains over the new successful result. Its Retry button then runs the old replacement action against the new current result. Clear the replacement failure state when abandoning or resetting a result, or before starting every full pipeline.
Greptile automatically discovered a related ticket stating that persistent retry UI must preserve and act on the prior successful result, which informed this comment.
Knowledge Base Used: Review and results presentation
Source Used: Linear — Preserve previous results through refinement and build-error regeneration
Prompt To Fix With AI