Skip to content

Fix empty preview when parametric turns finish without SCAD (#181)#191

Open
adoodevv wants to merge 1 commit into
Adam-CAD:masterfrom
adoodevv:fix/issue-181-missing-scad-feedback
Open

Fix empty preview when parametric turns finish without SCAD (#181)#191
adoodevv wants to merge 1 commit into
Adam-CAD:masterfrom
adoodevv:fix/issue-181-missing-scad-feedback

Conversation

@adoodevv

@adoodevv adoodevv commented Jun 12, 2026

Copy link
Copy Markdown

Summary

  • Detect parametric turns that finish without a successful build_parametric_model call and show explicit errors in chat, toasts, and the preview pane instead of a silent empty viewer.
  • Salvage OpenSCAD from markdown in assistant text when the model skips the tool, with compile validation before loading the preview.
  • Reject premature answer_user calls before a successful build and auto-continue the agent loop so the model can retry.
  • Strengthen the parametric system prompt and Claude 5 step-0 handling (no invalid toolChoice values for Anthropic).
  • Add shared helpers and unit tests for missing-build detection and auto-continue behavior.

Fixes #181

Test plan

  • node --test shared/parametricParts.test.ts shared/parametricAgentLoop.test.ts (16 tests pass)
  • npm run typecheck
  • Parametric chat with Claude Fable 5: "Create a mobile phone stand" — preview renders on success
  • If model skips SCAD: red chat banner + retry, destructive toast, preview message "No model was generated..."
  • Retry on a failed turn starts a new generation
  • Compile-error tool path still shows error in the CAD tool block

Summary by cubic

Fixes empty preview in parametric chats when the model ends a turn without calling build_parametric_model. Adds clear errors, auto-continue, and SCAD recovery to keep users unblocked. Fixes #181.

  • Bug Fixes

    • Show explicit errors when no model is built (chat banner, destructive toast, preview message).
    • Block answer_user before a successful build_parametric_model; persist an error and keep the turn running.
    • Detect and mark missing-build turns in chat and editor instead of showing an empty viewer.
  • New Features

    • Salvage OpenSCAD from assistant text; compile-check and auto-load into the preview when valid.
    • Auto-continue the parametric loop after a resolved build or rejected answer_user via shouldAutoContinueParametricBuild.
    • Strengthen the parametric system prompt and Claude 5 step-0 handling; restrict active tools to build_parametric_model and log turns that finish without a build.
    • Add shared helpers and unit tests for missing-build detection and loop behavior.

Written for commit 7da254c. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

@adoodevv is attempting to deploy a commit to the Adam Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds explicit error surfacing when a parametric turn finishes without a build_parametric_model call: red chat banners, destructive toasts, empty-preview messages, and an auto-continue loop that rejects premature answer_user calls and retries. It also refactors the auto-continue predicate into a unit-tested shared module and tightens the system prompt and Claude 5 toolChoice handling.

  • shouldAutoContinueParametricBuild is cleanly extracted into parametricAgentLoop.ts with 4 new unit tests; the rejection of premature answer_user and the error-state re-trigger are correct.
  • The intended SCAD salvage path in ChatSession.tsx (previewScadColoredViaToolWorker + onViewArtifact + "Model code recovered" toast) is unreachable dead code: shouldReportMissingParametricBuild explicitly returns false when SCAD is salvageable, so extractScadFromAssistantParts is always undefined inside that guard.
  • extractScadFromText carries a redundant second regex pattern (the first already handles plain fences via its optional language group), and dropTextFromParametricBuildMessage has a dead !hasParametricBuildAttempt check that is always true within the !hasBuild branch.

Confidence Score: 3/5

Merging as-is ships the error-surfacing UI correctly, but the SCAD salvage feature described in the PR is broken and will silently fail for every case where the model embeds code in text instead of calling the tool.

The core defensive UI (red banner, destructive toast, empty-preview message, auto-continue on rejected answer_user) all work. The problem is that the 'salvage from text' path — the other half of the stated fix for #181 — is dead code: the guard condition that enables the recovery branch also guarantees the recovery data is absent. Users who hit the exact scenario the salvage path was built for (model writes SCAD to prose instead of calling the tool) will always see the failure toast even when perfectly valid code was produced.

src/components/chat/ChatSession.tsx — the salvage logic in onFinish needs the guard restructured so extractScadFromAssistantParts is checked before (not inside) the shouldReportMissingParametricBuild gate.

Important Files Changed

Filename Overview
src/components/chat/ChatSession.tsx Adds premature answer_user rejection, salvage-from-text attempt, and swaps the auto-continue predicate — but the salvage branch (previewScadColoredViaToolWorker + onViewArtifact) is unreachable dead code due to a logic inversion with shouldReportMissingParametricBuild.
shared/parametricParts.ts Adds helpers for detecting missing builds, salvaging SCAD from text, and the MISSING_BUILD_NOTICE constant. Logic is mostly sound; redundant second regex pattern in extractScadFromText is the only code-quality issue.
shared/parametricAgentLoop.ts New module exporting shouldAutoContinueParametricBuild; cleanly extracted from ChatSession with expanded handling for rejected answer_user. Logic is correct and well-tested.
src/server/aiChat.ts Strengthens parametric system prompt, broadens onFinish logging to all fresh parametric turns, and injects MISSING_BUILD_NOTICE via the overloaded dropTextFromParametricBuildMessage. The !hasParametricBuildAttempt guard is redundant dead code inside the !hasBuild block.
src/components/chat/MessageBubble.tsx Adds the red 'No 3D model was generated' error banner with a Retry button when shouldReportMissingParametricBuild returns true for the last message. Straightforward and correct.
src/views/EditorView.tsx Replaces the generic 'Send a message to start creating' placeholder with a 'No model was generated' message when the last parametric turn had no artifact. Simple and correct.
shared/parametricParts.test.ts Extends existing tests with 8 new cases covering isParametricArtifact, parametricTurnMissingBuild, shouldReportMissingParametricBuild, extractScadFromText, and getAssistantSalvageText. All cases exercise actual logic.
shared/parametricAgentLoop.test.ts New test file with 4 focused unit tests for shouldAutoContinueParametricBuild covering all meaningful states. Tests are clean and verify behavior rather than mock calls.

Reviews (1): Last reviewed commit: "Fix empty preview when parametric turns ..." | Re-trigger Greptile

Comment on lines +659 to +694
const recoveredCode = extractScadFromAssistantParts(message.parts);
if (recoveredCode) {
void (async () => {
try {
await previewScadColoredViaToolWorker(recoveredCode);
onViewArtifact(
{
title: 'Recovered model',
version: 'v1',
code: recoveredCode,
},
message.id,
);
toast({
title: 'Model code recovered',
description:
'OpenSCAD was found in the reply text and loaded into the preview.',
});
} catch (error) {
console.warn('Recovered OpenSCAD failed to compile:', error);
toast({
title: 'Found code but it did not compile',
description:
'OpenSCAD was in the reply but could not be rendered. Try retrying the prompt.',
variant: 'destructive',
});
}
})();
} else {
toast({
title: 'No model was generated',
description:
'Adam replied without OpenSCAD code. Try again or retry with a different model.',
variant: 'destructive',
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Salvage path is unreachable dead code

shouldReportMissingParametricBuild returns false when canSalvageParametricBuildFromText is true (line 292 in parametricParts.ts). Both canSalvageParametricBuildFromText and extractScadFromAssistantParts call the same underlying extractScadFromText(getAssistantSalvageText(...)), so whenever the guard lets us inside the if (shouldReportMissingParametricBuild(...)) block, recoveredCode is unconditionally undefined. The entire if (recoveredCode) branch — previewScadColoredViaToolWorker, onViewArtifact, and the "Model code recovered" toast — can never execute. The whole salvage-from-text feature described in the PR is dead on arrival; every case lands in the destructive-toast else.

Comment thread shared/parametricParts.ts
Comment on lines +309 to +312
const fencePatterns = [
/```(?:openscad|scad|open-scad)?\s*\n([\s\S]*?)```/gi,
/```\s*\n([\s\S]*?)```/gi,
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The second fence pattern is fully subsumed by the first. (?:openscad|scad|open-scad)? is optional, so pattern 1 already matches plain ``` blocks (zero-width match → \s* backtracks off the newline → matches). Pattern 2 will never find a block that pattern 1 missed; it just re-scans all the same fences a second time.

Suggested change
const fencePatterns = [
/```(?:openscad|scad|open-scad)?\s*\n([\s\S]*?)```/gi,
/```\s*\n([\s\S]*?)```/gi,
];
const fencePatterns = [
/```(?:openscad|scad|open-scad)?\s*\n([\s\S]*?)```/gi,
];

Comment thread src/server/aiChat.ts
Comment on lines +581 to +585
if (
options?.leafRole === 'user' &&
!hasParametricBuildAttempt(parts) &&
!extractScadFromAssistantParts(parts)
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 !hasParametricBuildAttempt(parts) is always true here — the enclosing if (!hasBuild) already confirmed no tool-build_parametric_model part exists, and hasParametricBuildAttempt performs the identical check. The condition adds cognitive noise without any guard value.

Suggested change
if (
options?.leafRole === 'user' &&
!hasParametricBuildAttempt(parts) &&
!extractScadFromAssistantParts(parts)
) {
if (
options?.leafRole === 'user' &&
!extractScadFromAssistantParts(parts)
) {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Re-trigger cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Model sometimes returns no SCAD code, causing empty output

1 participant