The user's constraint: validation logic must live on the back-end (BuildShip REVIEW_SYSTEM prompt), not duplicated in the browser bundle (app.js). A stale client must not bypass rules.
Browser (app.js) BuildShip (service-runpipeline-THIN)
───────────────── ─────────────────────────────────────
validateFileMap REVIEW_SYSTEM prompt (d32928b7)
└─ validateDartFile (deploy gate) ├─ Widget rules (required, non-nullable, unsupported types)
├─ forbiddenPatterns ✗ ├─ CustomAction file name (naive snake_case)
├─ import validation ✗ ├─ CustomAction return type
├─ getBlockingWidgetErrors ✗ ├─ CodeFile naming (info only)
├─ getCustomActionReturnTypeError ✗ ├─ manualActions contract
└─ getCustomActionFileNameError ✗ ├─ Asset-without-anchor (warning)
├─ dispose() checks
├─ MediaQuery vs LayoutBuilder
├─ Duplicate FF import header
├─ Image.asset with param path
├─ onPanStart without onPanCancel
└─ MISSING: forbiddenPatterns, import validation
What: getBlockingWidgetErrors(content) at app.js:2450 runs the same WIDGET_RULES (required-public-param, non-nullable-public-field, unsupported-param-type) that the REVIEW_SYSTEM prompt already covers.
Fix: Remove the getBlockingWidgetErrors call and the CodeType.WIDGET block from validateDartFile. The review runs server-side before the Deploy button is visible; a widget that fails review never reaches the deploy gate.
Files changed:
app.js: Delete lines 2446-2451 (theCodeType.WIDGETblock callinggetBlockingWidgetErrors)app.js: Remove the import ofgetBlockingWidgetErrorsat the top of the file
What: getCustomActionReturnTypeError at app.js:2454-2458 duplicates the REVIEW_SYSTEM rule "Mark the artifact fail when a CustomAction returns an arbitrary CustomClass, Code File class, or CustomEnum."
Fix: Remove the returnTypeError check and the CodeType.ACTION block from validateDartFile.
Files changed:
app.js: Delete lines 2453-2466 (the entireCodeType.ACTIONblock)
What: getCustomActionFileNameError at app.js:2460-2465 duplicates the REVIEW_SYSTEM rule about FlutterFlow's naive snake_case.
Fix: Covered by the same deletion as Gap 2 (same CodeType.ACTION block).
What: forbiddenPatterns (main, runApp, MaterialApp, Scaffold) and import validation exist ONLY in validateDartFile (app.js:2380-2431). The REVIEW_SYSTEM prompt never checks for these. If the client is removed, these checks disappear entirely.
Fix A (chosen): Add forbidden-pattern and import-validation rules to the REVIEW_SYSTEM prompt on BuildShip. These are deterministic mechanical checks an LLM can perform reliably. Once the prompt is deployed, remove the checks from app.js.
Fix B (rejected): Keep them in validateDartFile — violates the "back-end only" constraint.
Server-side text to add to REVIEW_SYSTEM:
- Mark a CustomWidget fail when it declares main(), calls runApp(), or instantiates MaterialApp or Scaffold. FlutterFlow hosts the widget inside its own widget tree; these top-level app constructs conflict with the host and prevent compilation.
- Check imports for correctness: a CustomFunction may only import dart:* (no packages); a CustomWidget/CustomAction must not import from unknown paths. Flag a non-dart: import in a CustomFunction as fail, and flag an import path that is neither dart:, package:flutter/, /flutter_flow/, /custom_code/, nor /backend/ as a warning (the project's own import tree resolves differently).
Files changed:
- BuildShip
REVIEW_SYSTEMprompt: Add forbidden-patterns + import-validation rules app.js: Delete lines 2380-2431 (forbiddenPatterns + import validation)
What: reviewPresentation.js MANUAL_STEP_KEYS (line 4-15) scavenges 10 key names from model output: manualSteps, manualActions, requiredActions, flutterFlowSteps, flutterFlowActions, requiredUserActions, requiredFlutterFlowActions, flutterFlowSetup, userActions, nextSteps. The REVIEW_SYSTEM prompt now has a typed manualActions contract with 4 exclusions and preferEmpty. But nextSteps and userActions are generic dumping grounds that can leak noise past the contract.
Fix: Reduce MANUAL_STEP_KEYS to only the contracted key: ["manualActions"]. The REVIEW_SYSTEM prompt already defines this field precisely. The other 9 keys were scavenged from an era when the contract didn't exist.
Files changed:
src/reviewPresentation.js: ReduceMANUAL_STEP_KEYSfrom 10 keys to["manualActions"]
After all removals, validateDartFile becomes:
function validateDartFile(fileName, content, codeType, declaredTypes, artifactName) {
return { valid: true, errors: [] };
}This is a no-op. The function can be deleted and validateFileMap simplified to only check:
- Empty files (mechanical, not code-level)
- File size > 100KB (mechanical)
- pubspec.yaml validation (separate validator)
Or: validateDartFile can be kept as a stub that checks the review result's compatibility.valid flag — a "was this reviewed and did it pass?" gate rather than re-running individual rules.
Decision needed: Keep the stub or delete validateDartFile entirely?
- Gap 1: Widget rules client-side duplicate removed (
app.js—getBlockingWidgetErrorscall deleted) - Gap 2: CustomAction return type client-side duplicate removed (
app.js—getCustomActionReturnTypeErrorcall deleted) - Gap 3: CustomAction file name client-side duplicate removed (
app.js—getCustomActionFileNameErrorcall deleted) - Gap 4 client-side: Forbidden patterns + import validation removed from
app.js - Gap 4 server-side:
REVIEW_SYSTEMon BuildShipservice-runpipeline-THINmatchesBUILDSHIP_REVIEW_SYSTEM_PROMPT_UPDATED.txt(checked 2026-09-20 against the BuildShip checkout's recorded value). The prompts are node inputs, not workflow code: they are pasted into the BuildShip editor by hand, a workflow push over the MCP does not carry them, and their source of truth is the private BuildShip workflow rather than this public repository. See DEPLOYMENT.md - Gap 5: MANUAL_STEP_KEYS narrowed to
["manualActions"]inreviewPresentation.js - Gap 6: Surgical server-side code fixes — review stage returns
fixedSourcefor mechanical issues; deploy planner prefersfixedCodeovercode
function validateDartFile(fileName, content, codeType, declaredTypes, artifactName) {
const errors = [];
const hasWidgetClass = WIDGET_CLASS_REGEX.test(content);
const hasStateClass = STATE_CLASS_REGEX.test(content);
// Only remaining gate: structural check the server cannot reliably
// verify — the code must contain a recognizable widget class.
if (codeType === CodeType.WIDGET && !hasWidgetClass && !hasStateClass) {
errors.push(
"No widget class definition found (must extend StatelessWidget or StatefulWidget)",
);
}
return { valid: errors.length === 0, errors };
}All code-level validation (widget params, return types, file names, forbidden patterns, imports) now lives exclusively in the REVIEW_SYSTEM prompt on BuildShip. The review stage also applies surgical mechanical fixes (Color.withOpacity, duplicate headers, wrong import paths, main/runApp/MaterialApp/Scaffold removal, missing dispose/onPanCancel stubs) and returns corrected code via fixedSource, which the deploy planner uses in preference to the original.
- Gap 5 — DONE
- Gaps 1-4 client-side — DONE
- Gap 6 (surgical fixes) — DONE
- Gap 4 server-side — DONE:
BUILDSHIP_REVIEW_SYSTEM_PROMPT_UPDATED.txtmatches BuildShipservice-runpipeline-THINnoded32928b7(REVIEW_SYSTEM) as recorded in the BuildShip checkout (checked 2026-09-20). Reminder: this prompt is a node input pasted in the BuildShip editor, not workflow code — the MCP push does not deploy it, and it is intentionally untracked here (public repo) - No final cleanup needed —
validateDartFileis now minimal (one structural check)
npm test— 205 tests pass, 0 failnpm run build— bundle compiles (392.23 KB)- BuildShip
REVIEW_SYSTEMmatchesBUILDSHIP_REVIEW_SYSTEM_PROMPT_UPDATED.txtas recorded in the BuildShip checkout (checked 2026-09-20). Deployment targets are listed in DEPLOYMENT.md — the three step prompts (Architect, Generator, Review) are updated by hand in the BuildShip editor, and their source of truth is the private BuildShip workflow rather than this public repository (the working copies are gitignored on purpose) reviewPresentation.js— tests pass, no regressionsbundleDeployPlanner.js—fixedCodepreference tests pass