diff --git a/app.js b/app.js
index 0a3660b..986b1bd 100644
--- a/app.js
+++ b/app.js
@@ -4665,7 +4665,7 @@ function showPaywallExhausted(count, limit, options = {}) {
const resultsView = document.getElementById('results-view')
if (resultsView) resultsView.classList.remove('visible')
- document.body.classList.remove("results-fullscreen")
+ document.body.classList.remove("results-fullscreen", "results-with-sidebar")
const pipelineProgress = document.getElementById('pipeline-progress')
if (pipelineProgress) pipelineProgress.classList.remove('visible')
@@ -5601,7 +5601,7 @@ function showPipelineProgress() {
if (readyState) readyState.classList.add("hidden");
if (resultsView) resultsView.classList.remove("visible");
- document.body.classList.remove("results-fullscreen");
+ document.body.classList.remove("results-fullscreen", "results-with-sidebar");
if (progress) progress.classList.add("visible");
pipelineStartTime = Date.now();
@@ -5746,10 +5746,10 @@ function renderSummaryDetail(presentation) {
const score = presentation.score;
const scoreTone = score == null
- ? presentation.status
+ ? "neutral"
: score >= 80 ? "pass" : score >= 60 ? "warning" : "fail";
const scoreLabel = score == null
- ? "Not scored"
+ ? ""
: score >= 80 ? "Strong" : score >= 60 ? "Needs work" : "High risk";
const findings = presentation.findings.length
? `
@@ -5757,7 +5757,10 @@ function renderSummaryDetail(presentation) {
${presentation.findings.map((finding) => `
Is the generated code correct?
@@ -5815,9 +5818,23 @@ function renderSelectedArtifactReview(presentation) {
) || presentation.artifacts[0];
if (!selectedArtifact) return "";
+ const emptyFindingState = {
+ pass: {
+ icon: "pass",
+ message: "No file-specific issues were found.",
+ },
+ warning: {
+ icon: "warning",
+ message: "This file needs attention, but Code Review did not return a specific finding.",
+ },
+ fail: {
+ icon: "fail",
+ message: "This file is blocked, but Code Review did not return a specific finding.",
+ },
+ }[selectedArtifact.status];
const findings = selectedArtifact.findings.length
? selectedArtifact.findings.map(renderFinding).join("")
- : `
${reviewStatusIcon("pass")} No file-specific findings were returned.
`;
+ : `
${reviewStatusIcon(emptyFindingState.icon)} ${escapeHtml(emptyFindingState.message)}
`;
const dependencies = selectedArtifact.dependencies?.length
? selectedArtifact.dependencies.map((dependency) => `
${escapeHtml(dependency.name)}${dependency.version ? ` ${escapeHtml(dependency.version)}` : ""}${dependency.reason ? `${escapeHtml(dependency.reason)}
` : ""}
@@ -5936,10 +5953,7 @@ function renderBundleControls(presentation) {
function updateSelectedArtifactPanels() {
document.body.classList.add("results-fullscreen");
- document.body.classList.toggle(
- "results-with-sidebar",
- IS_DEV && new URLSearchParams(window.location.search).get("debugSidebar") === "1",
- );
+ document.body.classList.add("results-with-sidebar");
const resultsView = document.getElementById("results-view");
const codeOutput = document.getElementById("results-code-output");
const auditOutput = document.getElementById("results-audit-output");
@@ -5974,6 +5988,8 @@ function showResultsView(codeContent, auditContent) {
}
pipelineState.resultsViewMode = "summary";
document.body.classList.add("results-fullscreen");
+ const legacyReviewOutput = document.getElementById("step3-output");
+ if (legacyReviewOutput) legacyReviewOutput.textContent = "";
updateSelectedArtifactPanels();
updateDeployButtonVisibility();
diff --git a/index.html b/index.html
index 2cce9f2..3a5eacb 100644
--- a/index.html
+++ b/index.html
@@ -770,6 +770,14 @@
font-size: 12px;
font-weight: 700;
}
+ .review-empty-warning {
+ color: #92400e;
+ background: #fffbeb;
+ }
+ .review-empty-fail {
+ color: #b91c1c;
+ background: #fef2f2;
+ }
.review-muted { color: #94a3b8; font-size: 11px; }
.deploy-order-list,
.relationship-list,
@@ -1068,11 +1076,11 @@
.file-relationship-list li { color: #475569; font-size: 11px; }
.file-relationship-list li p { margin-top: 3px; color: #94a3b8; }
body.results-fullscreen .sidebar {
- display: none;
+ display: flex;
}
body.results-fullscreen .main-content {
display: block;
- width: 100vw;
+ width: auto;
height: 100dvh;
padding: 0;
}
@@ -1086,13 +1094,13 @@
}
body.results-fullscreen.results-with-sidebar .main-content {
width: auto;
- height: 100vh;
- padding: 36px;
+ height: 100dvh;
+ padding: 0;
}
body.results-fullscreen.results-with-sidebar .main-stage {
- inset: 28px;
- border-radius: 14px;
- box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 6px 24px rgba(0,0,0,0.05);
+ inset: 0;
+ border-radius: 0;
+ box-shadow: none;
}
body.results-fullscreen .results-header {
padding: 14px 28px;
@@ -1149,19 +1157,25 @@
letter-spacing: -0.025em;
}
body.results-fullscreen .results-summary-detail {
- flex: 0 1 auto;
- max-height: 340px;
- padding: 26px 36px 24px;
+ flex: 1 1 auto;
+ max-height: none;
+ padding: 28px 36px;
overflow-y: auto;
border-bottom: 1px solid #e5e7eb;
+ background: #f8fafc;
}
.review-summary {
display: grid;
grid-template-columns: minmax(0, 1fr) 184px;
- gap: 48px;
+ gap: 36px;
width: 100%;
- max-width: 1280px;
+ max-width: none;
margin: 0 auto;
+ padding: 24px;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ background: #fff;
+ box-shadow: 0 8px 24px -20px rgba(15, 23, 42, 0.28);
}
.review-summary-copy { min-width: 0; }
.results-file-count {
@@ -1200,6 +1214,7 @@
.review-score-pass { color: #166534; border-color: #86efac; background: #f0fdf4; }
.review-score-warning { color: #9a3412; border-color: #fdba74; background: #fff7ed; }
.review-score-fail { color: #991b1b; border-color: #fca5a5; background: #fef2f2; }
+ .review-score-neutral { color: #475569; border-color: #cbd5e1; background: #f8fafc; }
.review-score-label {
font-size: 11px;
font-weight: 800;
@@ -1274,6 +1289,19 @@
font-size: 14px;
line-height: 1.45;
}
+ .summary-findings li > span {
+ display: grid;
+ gap: 3px;
+ }
+ .summary-findings li strong {
+ color: #334155;
+ font-weight: 700;
+ }
+ .summary-findings li small {
+ color: #64748b;
+ font-size: 12px;
+ line-height: 1.45;
+ }
.summary-findings .review-status-icon { margin-top: 2px; }
.review-score-feedback {
width: 100%;
diff --git a/src/pipelineContracts.js b/src/pipelineContracts.js
index f9bab48..403512c 100644
--- a/src/pipelineContracts.js
+++ b/src/pipelineContracts.js
@@ -38,6 +38,11 @@ export function buildReviewPrompt(generatedBundle) {
return stringifyPipelinePayload({
task: "review_bundle",
generatedBundle: parseJsonIfPossible(generatedBundle),
+ outputRequirements: {
+ overall: ["status", "score", "summary", "findings"],
+ scoreRange: [0, 100],
+ eachArtifact: ["id", "review.status", "review.findings"],
+ },
});
}
diff --git a/src/pipelineContracts.test.js b/src/pipelineContracts.test.js
index bdeeb53..c311815 100644
--- a/src/pipelineContracts.test.js
+++ b/src/pipelineContracts.test.js
@@ -36,6 +36,18 @@ test("review prompt sends generated bundle data without system instructions", ()
assert.equal(payload.task, "review_bundle");
assert.equal(payload.generatedBundle.artifacts[0].id, "action-a");
+ assert.deepEqual(payload.outputRequirements.overall, [
+ "status",
+ "score",
+ "summary",
+ "findings",
+ ]);
+ assert.deepEqual(payload.outputRequirements.scoreRange, [0, 100]);
+ assert.deepEqual(payload.outputRequirements.eachArtifact, [
+ "id",
+ "review.status",
+ "review.findings",
+ ]);
assert.doesNotMatch(prompt, /Review every generated artifact/);
assert.doesNotMatch(prompt, /bundleReview/);
});
diff --git a/src/reviewPresentation.js b/src/reviewPresentation.js
index 79702ee..ec645ab 100644
--- a/src/reviewPresentation.js
+++ b/src/reviewPresentation.js
@@ -62,7 +62,7 @@ export function normalizeReviewStatus(value) {
const status = String(value || "").toLowerCase();
if (/(fail|error|critical|block|reject|invalid)/.test(status)) return "fail";
if (/(warn|attention|manual|partial|incomplete|concern)/.test(status)) return "warning";
- if (/(pass|success|ready|approve|valid|clean)/.test(status)) return "pass";
+ if (/(pass|success|ready|approve|valid|clean|info|notice)/.test(status)) return "pass";
return null;
}
@@ -177,12 +177,7 @@ function unwrapReviewPayload(reviewResult) {
}
const root = toObject(value);
- const nested = toObject(
- root.bundleReview
- || root.reviewResult
- || root.codeReview
- || root.result,
- );
+ const nested = toObject(root.reviewResult || root.codeReview || root.result);
return { rawText: "", root: Object.keys(nested).length ? nested : root };
}
@@ -268,7 +263,8 @@ export function buildReviewPresentation({ bundle, reviewResult }) {
const artifacts = asArray(safeBundle.artifacts);
const { root, rawText } = unwrapReviewPayload(reviewResult);
const overallReview = toObject(
- root.overallReview
+ root.bundleReview
+ || root.overallReview
|| root.overall
|| root.bundleSummary
|| root.summaryReview
diff --git a/src/reviewPresentation.test.js b/src/reviewPresentation.test.js
index b248d23..801eaac 100644
--- a/src/reviewPresentation.test.js
+++ b/src/reviewPresentation.test.js
@@ -155,3 +155,59 @@ test("leaves structured reviews without score fields unscored", () => {
assert.equal(presentation.score, null);
});
+
+test("preserves top-level artifact reviews alongside bundleReview", () => {
+ const presentation = buildReviewPresentation({
+ bundle,
+ reviewResult: {
+ artifacts: [
+ {
+ id: "format-payload",
+ review: {
+ status: "pass",
+ findings: [
+ {
+ severity: "info",
+ message: "Formatting logic is valid.",
+ suggestion: "No changes required.",
+ },
+ ],
+ },
+ },
+ {
+ id: "execute-webhook",
+ review: {
+ status: "pass",
+ findings: [
+ {
+ severity: "info",
+ message: "Webhook execution is valid.",
+ suggestion: "No changes required.",
+ },
+ ],
+ },
+ },
+ ],
+ bundleReview: {
+ status: "pass",
+ summary: "The complete bundle is ready to deploy.",
+ score: 96,
+ findings: [
+ {
+ severity: "info",
+ message: "The deploy order is correct.",
+ suggestion: "Deploy the files in the supplied order.",
+ },
+ ],
+ },
+ },
+ });
+
+ assert.equal(presentation.status, "pass");
+ assert.equal(presentation.score, 96);
+ assert.equal(presentation.summary, "The complete bundle is ready to deploy.");
+ assert.equal(presentation.findings[0].message, "The deploy order is correct.");
+ assert.deepEqual(presentation.counts, { pass: 2, warning: 0, fail: 0 });
+ assert.equal(presentation.reviewCoverage.reviewed, 2);
+ assert.equal(presentation.artifacts[0].findings[0].severity, "pass");
+});