Skip to content

[STU-147] Single-file generation: fix project list 403 + widget commit failures - #60

Open
sgardoll wants to merge 5 commits into
mainfrom
sgardoll/stu-147-single-file-custom-code-generation-fails-with-project-list
Open

[STU-147] Single-file generation: fix project list 403 + widget commit failures#60
sgardoll wants to merge 5 commits into
mainfrom
sgardoll/stu-147-single-file-custom-code-generation-fails-with-project-list

Conversation

@sgardoll

@sgardoll sgardoll commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Issue

https://linear.app/stuart-gardoll/issue/STU-147/single-file-custom-code-generation-fails-with-project-list-and-code

Single-file custom code generation failed twice: listing FlutterFlow projects returned 403 Unauthorized, and after generation the commit was rejected by FlutterFlow with "Custom widget code is not formattable" and "No widget "LiquidGlassOrbs" found."

Root causes

1. Project list 403 — wrong endpoint path

listProjects was the only FlutterFlow call in FlutterFlowApiClient that did not use ${this.baseUrl}<method>: it hardcoded https://api.flutterflow.io/v2/l/listProjects (note the stray /l/ segment). Every working call (exportCode, syncCustomCodeChanges) targets ${baseUrl}<method>. The gateway rejects unknown/unauthorized paths with 401/403 before the key is even evaluated, so a key that syncs fine could not list projects.

Additionally, 401/403 surfaced as a raw status line. The app's re-auth mechanism for static FlutterFlow keys is re-entering the key in API Keys settings (the projects dropdown re-fetches on blur), but nothing told the user to do that.

2a. "not formattable" — markdown artifacts in generated Dart

LLM responses arrive wrapped in markdown fences, with a BOM, or padded with prose. The old fence stripping only handled exact ^```dart\n / \n```$ shapes and missed everything else (prose around the pair, interior fences, trailing text after the closing fence), all of which dart_style rejects.

2b. "No widget found" — file name vs declared class

FlutterFlow derives a custom widget's identity from the committed file name (its naive snake_case of the class: LiquidGlassOrbsliquid_glass_orbs.dart). The single-file flow named the file after the artifact's display name ("Liquid Glass Orbs") or artifactName instead of the class actually declared in the code, so FlutterFlow looked for a widget that didn't exist under that name.

Fixes

  • Endpoint: listProjects now calls ${this.baseUrl}listProjects first; the legacy /v2/l/ path is retried once on 404 only, so an auth rejection is never masked by a retry.
  • Auth errors: 401/403 throw an actionable message naming the fix (re-enter a current key / verify key scope in API Keys settings); server detail is preserved for diagnostics.
  • Sanitization: new src/flutterFlowCodeSanitizer.jssanitizeGeneratedDart() strips BOM, all fence lines (wrapped and multi-block responses), outer prose when fenced, and blank padding before validation.
  • Formatter gate: findUnbalancedBracketError() runs pre-commit with a comment/string/interpolation-aware scanner (handles nested ${user['name']} cases) and reports the exact line and bracket, replacing FlutterFlow's opaque post-push rejection.
  • Widget naming: prepareCodeForCommit derives the commit file name from the widget class actually declared in the sanitized code (widgetFileNameForClass), logging any rename; fileName from the validated artifact is threaded through getCurrentArtifactMetadata → both commit paths. A defense-in-depth pre-commit check reports precise mismatch errors if anything slips through.
  • Consolidated: reuses existing tested helpers (deriveIdentifierName(·, "W"), exported identifierToFlutterFlowFileStem) rather than duplicating them.

Tests

  • New src/flutterFlowCodeSanitizer.test.js: 20 tests covering sanitization (fences/prose/BOM/truncated/multi-block), widget-class extraction (transitive superclasses, comment/string immunity), bracket scanning (interpolation nesting, triple quotes, line-numbered errors), naive snake_case round-trips incl. acronyms (QAReportq_a_report), and the exact STU-147 scenario.
  • npm test: 226/226 pass (206 existing + 20 new)
  • node --check clean on app.js and all touched files
  • npm run build succeeds

Caveats

  • Live verification of the listProjects endpoint switch needs a real FlutterFlow API key against api.flutterflow.io (the convention-path change is inferred from every other working call in this client; the legacy-path fallback covers older deployments).

Fixes STU-147

Greptile Summary

This PR corrects FlutterFlow project listing, sanitizes generated Dart, validates source before single-file commits, and derives widget filenames from declared classes.

  • Uses the conventional project-list endpoint with a legacy 404 fallback and actionable authorization errors.
  • Adds Dart-aware Markdown sanitization and lexical validation before committing generated code.
  • Aligns single-widget commit filenames with FlutterFlow’s class-name convention.

Confidence Score: 3/5

The PR is not yet safe to merge because valid generated code can be silently truncated or incorrectly rejected during sanitization.

Scanning unsanitized prose as Dart can hide later fenced code blocks, while raw-string detection inside interpolation can misparse a valid trailing backslash and block the commit.

Files Needing Attention: src/flutterFlowCodeSanitizer.js

Important Files Changed

Filename Overview
src/flutterFlowCodeSanitizer.js Adds Dart-aware sanitization and lexical checks, but raw prose can hide later fences and an interpolated raw string can be falsely rejected.
app.js Integrates endpoint correction, generated-code sanitization, widget filename derivation, and single-file pre-commit validation.
src/flutterFlowArtifactValidation.js Exports the existing FlutterFlow identifier-to-file-stem helper for reuse by widget filename derivation.
src/flutterFlowCodeSanitizer.test.js Adds extensive sanitizer and scanner regression coverage but omits the two residual edge cases identified above.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Generated response] --> B[Sanitize Markdown and BOM]
  B --> C[Detect widget class]
  C --> D[Derive FlutterFlow filename]
  D --> E[Run pre-commit checks]
  E --> F[Build sync metadata]
  F --> G[Commit to FlutterFlow]
Loading

Fix all with Greploop Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
### Issue 1
src/flutterFlowCodeSanitizer.js:48
**Prose hides later code fences**

When prose before a later fenced Dart block contains an unmatched token such as `/*`, scanning the raw response extends a comment range to EOF and classifies the later fence markers as comment content. The sanitizer then silently omits that Dart block, allowing incomplete widget code to be committed.

### Issue 2
src/flutterFlowCodeSanitizer.js:300-307
**Interpolation breaks raw-string detection**

When a raw string ending in a backslash occurs inside an interpolation, the `$` before the inner expression matches `IDENTIFIER_TAIL`, so the `r` prefix is ignored. The scanner treats the backslash as escaping the closing quote, reports an unclosed string or interpolation, and blocks valid Dart at the pre-commit gate.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (5): Last reviewed commit: "STU-147: nested-comment-aware ranges; un..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used (3)

Comment thread src/flutterFlowCodeSanitizer.js Outdated
Comment thread src/flutterFlowCodeSanitizer.js Outdated
@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

1 similar comment
@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread src/flutterFlowCodeSanitizer.js Outdated
@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

1 similar comment
@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread src/flutterFlowCodeSanitizer.js
Comment thread src/flutterFlowCodeSanitizer.js
@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

1 similar comment
@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread src/flutterFlowCodeSanitizer.js Outdated
if (!code.trim()) return "";

const lines = code.split("\n");
const { commentAndStringRanges } = scanDartSource(code);

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 Prose hides later code fences

When prose before a later fenced Dart block contains an unmatched token such as /*, scanning the raw response extends a comment range to EOF and classifies the later fence markers as comment content. The sanitizer then silently omits that Dart block, allowing incomplete widget code to be committed.

Knowledge Base Used: FlutterFlow artifact pipeline

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/flutterFlowCodeSanitizer.js
Line: 48

Comment:
**Prose hides later code fences**

When prose before a later fenced Dart block contains an unmatched token such as `/*`, scanning the raw response extends a comment range to EOF and classifies the later fence markers as comment content. The sanitizer then silently omits that Dart block, allowing incomplete widget code to be committed.

**Knowledge Base Used:** [FlutterFlow artifact pipeline](https://app.greptile.com/connect-i-o/-/custom-context/knowledge-base/sgardoll/customcodeconnectforflutterflow/-/docs/flutterflow-artifact-pipeline.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Comment on lines +300 to +307
if (ch === "'" || ch === '"') {
const triple = src.slice(i, i + 3) === ch.repeat(3);
// r'...' / R'''...''' are raw strings: the r must not be the tail of a
// longer identifier, or `var bar'` style code would misclassify.
const prev = i > 0 ? src[i - 1] : "";
const beforePrev = i > 1 ? src[i - 2] : "";
const raw =
(prev === "r" || prev === "R") && !IDENTIFIER_TAIL.test(beforePrev);

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 Interpolation breaks raw-string detection

When a raw string ending in a backslash occurs inside an interpolation, the $ before the inner expression matches IDENTIFIER_TAIL, so the r prefix is ignored. The scanner treats the backslash as escaping the closing quote, reports an unclosed string or interpolation, and blocks valid Dart at the pre-commit gate.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/flutterFlowCodeSanitizer.js
Line: 300-307

Comment:
**Interpolation breaks raw-string detection**

When a raw string ending in a backslash occurs inside an interpolation, the `$` before the inner expression matches `IDENTIFIER_TAIL`, so the `r` prefix is ignored. The scanner treats the backslash as escaping the closing quote, reports an unclosed string or interpolation, and blocks valid Dart at the pre-commit gate.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@sgardoll

Copy link
Copy Markdown
Owner Author

@greptile review

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.

1 participant