fix: name custom actions the way FlutterFlow files them - #56
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
Writing a fixed constraint like ^1.0.0 pins every newly added package to whatever shipped under its first major, so a project ends up depending on 1.0.0 of a package with current releases available. Adds pubVersions (semver compare, constraint containment, prerelease detection), pubRegistry (pub.dev lookup picking the newest release the project's own SDK floor can build, with an 8s timeout that falls back to an unconstrained dependency rather than stalling a deploy the user is watching), and dependencyResolution (keeps a constraint the project already declares, rewriting it only when the generated code needs a minimum that constraint provably cannot resolve). Wired through pubspecSync and bundleDeployPlanner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A commit failed with: init_qa_analytics.dart: Action "initQaAnalytics" declaration not found. FlutterFlow snake_cases an artifact name naively - an underscore before every capital - so initQAAnalytics is filed as init_q_a_analytics.dart. The generated file used the human-idiomatic init_qa_analytics.dart, which the deploy reads back as initQaAnalytics, a name nowhere in the code. The two sibling actions in the same bundle round-tripped fine because they carry no acronym. getCustomActionFileNameError checks in FlutterFlow's own direction: the identifier derived from the file name must equal the declared function. It reuses deriveIdentifierName, now exported, rather than duplicating the derivation that goes over the wire. Wired into both validateDartFile (deploy gate) and validateArtifactCompatibility (review), since a rule in only one place labels a finding blocking and still deploys. The CustomClass file-name rule goes the other way. A Code File's path is a free-text field in the FlutterFlow editor, so q_a_service.dart holding class QAService is valid - the rule was blocking a pairing FlutterFlow accepts. Downgraded to a review warning, removed from the deploy gate, and the false "FlutterFlow expects" wording dropped. Also: - Summary tab: manual steps are informational, not alerts. They never fed the status computation, yet carried the alert triangle in orange-900 rust beside a 95/100 score. Now an info icon, real amber, and a lead line saying they do not block. Red is reserved for what genuinely stops a push. - Review contract asks for manualActions with exclusions, so it stops volunteering work FlutterFlow already does - creating the action, declaring parameters read off the signature. Matches the key the review already emits. - Workflow steps no longer name the model that runs each one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Your organization has used all 50 credits included in the free plan this billing period. To keep receiving reviews, upgrade your plan.
|
@greptileai please review pr and resolve conflicts |
Greptile SummaryThis PR aligns Custom Action filenames with FlutterFlow’s identifier derivation, relaxes Code File naming to advisory status, updates review/manual-action presentation, and adds SDK-aware pub.dev dependency resolution.
Confidence Score: 4/5The dependency-resolution defect should be fixed before merging because projects using ^0.0.x packages can receive a pubspec that cannot satisfy generated code requirements. The new caret arithmetic overstates the versions admitted by ^0.0.x constraints, causing dependency planning to preserve an incompatible existing pin instead of raising it to the generated code’s required patch. Files Needing Attention: src/pubVersions.js, src/dependencyResolution.js Important Files Changed
|
| function caretUpperBound(version) { | ||
| const parsed = parseVersion(version); | ||
| if (!parsed) return null; | ||
| if (parsed.major > 0) return `${parsed.major + 1}.0.0`; | ||
| return `0.${parsed.minor + 1}.0`; | ||
| } |
There was a problem hiding this comment.
Incorrect zero-major caret bound
When generated code requires a newer patch than an existing ^0.0.x dependency, this upper-bound calculation treats the constraint as reaching the next minor version, so dependency planning preserves an incompatible constraint and the FlutterFlow project build can fail.
| function caretUpperBound(version) { | |
| const parsed = parseVersion(version); | |
| if (!parsed) return null; | |
| if (parsed.major > 0) return `${parsed.major + 1}.0.0`; | |
| return `0.${parsed.minor + 1}.0`; | |
| } | |
| function caretUpperBound(version) { | |
| const parsed = parseVersion(version); | |
| if (!parsed) return null; | |
| if (parsed.major > 0) return `${parsed.major + 1}.0.0`; | |
| if (parsed.minor > 0) return `0.${parsed.minor + 1}.0`; | |
| return `0.0.${parsed.patch + 1}`; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/pubVersions.js
Line: 78-83
Comment:
**Incorrect zero-major caret bound**
When generated code requires a newer patch than an existing `^0.0.x` dependency, this upper-bound calculation treats the constraint as reaching the next minor version, so dependency planning preserves an incompatible constraint and the FlutterFlow project build can fail.
```suggestion
function caretUpperBound(version) {
const parsed = parseVersion(version);
if (!parsed) return null;
if (parsed.major > 0) return `${parsed.major + 1}.0.0`;
if (parsed.minor > 0) return `0.${parsed.minor + 1}.0`;
return `0.0.${parsed.patch + 1}`;
}
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Review flagged caretUpperBound as wrong for ^0.0.x, suggesting it should reach 0.0.4 rather than 0.1.0. That is npm's rule. Pub has no 0.0.x special case: `^` resolves through pub_semver's Version.nextBreaking, which is `if (major == 0) return _incrementMinor();`, so ^0.0.3 means >=0.0.3 <0.1.0. The code was already correct; the tests only covered ^0.19.0, so nothing pinned the boundary that made it look wrong. Adds that coverage and records why narrowing it would regress - constraintContains would reject versions the project can resolve, and dependency resolution would rewrite a constraint that was never broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main landed PR #54, which reworked the same review contract this branch touches. Both sides added manualActions to the review's output requirements, independently. Resolution keeps main's shape and this branch's constraint: - outputRequirements uses main's `bundleReview` key, not this branch's `overall`. It mirrors the object the review stage actually emits, so it is the better name; the auto-merge had also duplicated manualActions in that list, now a single entry. - The manualActions definition/exclude/preferEmpty block is kept. main asks the review FOR manual actions but never bounds them, which is what let the review volunteer work FlutterFlow already does. - Dropped this branch's `assert.doesNotMatch(prompt, /bundleReview/)`. It guarded against leaking system-prompt vocabulary while the key was `overall`; with main's rename bundleReview is the contract's own key, so the assertion is now false by construction. dist/index.html was regenerated rather than hand-merged. Suite 196/196. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dist/assets/ is gitignored, so `git add -A` silently skipped the emitted bundle while still staging the deletion of the previous one. The tracked dist/index.html referenced /assets/index-DKBhAJWY.js with no such file in the tree, so serving the committed dist/ verbatim 404s on the module script and the app never boots. Every previous "build: rebuild dist" commit force-added the matching bundle - main tracks index-C8GiNBZX.js and references exactly that - so this restores the repo's existing invariant rather than introducing one. Caught by cloud review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bug
A deploy failed with:
FlutterFlow snake_cases an artifact name naively — an underscore before every capital — so
initQAAnalyticsis filed asinit_q_a_analytics.dart. The generated file used the human-idiomaticinit_qa_analytics.dart, which the deploy reads back asinitQaAnalytics, a name that appears nowhere in the code.The same bundle shipped
logAppErrorandlogOpFailed, which round-tripped fine — no acronym, no loss. That contrast is what identified it, and both are now regression fixtures.Fixes
getCustomActionFileNameErrorchecks in FlutterFlow's own direction: the identifier derived from the file name must equal the declared function. It reusesderiveIdentifierName(now exported) instead of duplicating the derivation that goes over the wire — that duplication is exactly how a check drifts from reality. Wired into bothvalidateDartFile(deploy gate) andvalidateArtifactCompatibility(review), because a rule in only one surface labels a finding blocking and then deploys it anyway.The client validator could not have caught this before:
normalizeFunctionNamelowercases and strips punctuation, soinitQAAnalyticsandinitQaAnalyticslooked identical to it.The CustomClass file-name rule went the other way and is relaxed. A Code File's path is a free-text field in the FlutterFlow editor, so
q_a_service.dartholdingclass QAServiceis valid and deploys. The rule was blocking a pairing FlutterFlow accepts. It is now a review warning, removed from the deploy gate, with the false "FlutterFlow expects" wording dropped.Also in here
manualActionswith explicit exclusions, so the review stops volunteering work FlutterFlow already does — creating the action, declaring parameters read off the signature.manualActionsis the key the review stage already emits, withtitle/location/timing/detail.^1.0.0, falling back to an unconstrained dependency rather than stalling a deploy.Upstream
The root cause was the BuildShip system prompts, which never stated the naming rule. Those are corrected and deployed separately (
buildship@1f406a0). This PR is the enforcement backstop: the prompts are guidance, these validators are the gate.Verification
npm test192/192 ·npm run buildclean ·node --check app.jsOne pre-existing fixture was corrected rather than the rule — a return-type test paired
supported.dartwith aloadProductfunction, a combination FlutterFlow would genuinely reject.Not visually verified: Chrome tooling failed throughout with a cross-extension error, so the Summary-tab restyle is confirmed by computed values and the emitted bundle, not by eye.
🤖 Generated with Claude Code