Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 69 additions & 36 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
import { createModelArmorError } from "./src/modelArmorResponse.js";
import {
getBlockingWidgetErrors,
getCustomActionFileNameError,
getCustomActionReturnTypeError,
getCustomClassFileNameError,
getDeclaredDartTypes,
validateBundleCompatibility,
} from "./src/flutterFlowArtifactValidation.js";
Expand All @@ -31,9 +31,11 @@ import { extractPackageImports } from "./src/dartPackageImports.js";
import { readProvisionResponse } from "./src/provisionStream.js";
import { buildFlutterFlowSyncMetadata } from "./src/flutterFlowSyncMetadata.js";
import {
applyDependencyOverrides,
mergeDependenciesIntoYaml,
validateProjectPubspec,
} from "./src/pubspecSync.js";
import { planDependencyChanges } from "./src/dependencyResolution.js";
import { escapeAttr, escapeHtml, escapeHtmlText } from "./src/htmlEscape.js";
import { resolvePipelineErrorStep } from "./src/pipelineErrors.js";
import {
Expand Down Expand Up @@ -1898,18 +1900,25 @@ async function provisionMissingCodeFiles(

/**
* Reads the project's current pubspec.yaml, then merges in the packages the
* generated code needs.
* generated code needs at versions the project can actually build.
*
* FlutterFlow applies the pushed `serialized_yaml` as the project's complete
* dependency set, so this must start from the file already in the project.
* Synthesizing one would silently drop every package the project already had.
*
* A package the project already declares keeps its version. One it does not
* gets the newest pub.dev release compatible with the SDK floor the project's
* own `environment:` block declares.
*
* @param {FlutterFlowApiClient} apiClient - Client for the target project
* @param {Object<string, string>} newDependencies - name -> version constraint
* @param {Object<string, string>} newDependencies - name -> the minimum
* version the generated code requires, or "" when it needs no specific one
* @returns {Promise<{
* yaml: string,
* added: string[],
* alreadyPresent: string[],
* overridden: Array<{name: string, from: string, to: string}>,
* warnings: string[],
* remoteFiles: Map<string, string>,
* }>}
* @throws If the project's pubspec.yaml cannot be read, so a deploy fails
Expand Down Expand Up @@ -1941,8 +1950,32 @@ async function resolveProjectPubspec(apiClient, newDependencies = {}) {
projectSourceCache.set(cacheKey, projectSource);
}

const plan = await planDependencyChanges(
projectSource.pubspecYaml,
newDependencies,
);
const overrides = applyDependencyOverrides(
projectSource.pubspecYaml,
plan.overrides,
);
const merged = mergeDependenciesIntoYaml(overrides.yaml, plan.additions);

plan.warnings.forEach((warning) => console.warn(`[pubspec] ${warning}`));
if (merged.added.length > 0) {
console.log(
"Adding dependencies:",
merged.added.map((name) => `${name}: ${plan.additions[name] || "any"}`).join(", "),
plan.sdk.dartSdkFloor ? `(resolved for Dart ${plan.sdk.dartSdkFloor})` : "",
);
}
plan.kept.forEach(({ name, constraint }) =>
console.log(`Keeping your existing ${name}: ${constraint || "(non-version source)"}`),
);

return {
...mergeDependenciesIntoYaml(projectSource.pubspecYaml, newDependencies),
...merged,
overridden: overrides.overridden,
warnings: plan.warnings,
remoteFiles: projectSource.files,
};
}
Expand Down Expand Up @@ -2168,14 +2201,18 @@ import '/flutter_flow/uploaded_file.dart';
}

/**
* Extracts pubspec dependencies from generated code analysis.
* Extracts the packages generated code imports.
*
* Each is left without a version: the deploy resolves one against the
* project's own pubspec and SDK floor rather than guessing here.
*
* @param {string} code - Dart code to analyze
* @returns {Object} Map of package names to versions
* @returns {Object} Map of package names to required minimum versions
*/
function extractDependencies(code) {
const deps = {};
for (const name of extractPackageImports(code)) {
deps[name] = "^1.0.0";
deps[name] = "";
}
return deps;
}
Expand Down Expand Up @@ -2296,13 +2333,19 @@ function validateDartFile(
declaredTypes,
});
if (returnTypeError) errors.push(returnTypeError);
}

if (codeType === CodeType.CODE_FILE) {
const fileNameError = getCustomClassFileNameError(fileName, content);
const fileNameError = getCustomActionFileNameError(
fileName,
content,
artifactName,
);
if (fileNameError) errors.push(fileNameError);
}

// A Code File's path is author-controlled in FlutterFlow, so a file name that
// disagrees with the declared class is a naming convention, not something
// FlutterFlow rejects. It stays a review warning and no longer blocks here.

return {
valid: errors.length === 0,
errors,
Expand Down Expand Up @@ -3185,32 +3228,11 @@ function copyCode(elementId) {
});
}

// The workflow steps deliberately no longer name the model that runs each one -
// which model handles Prompt Architect, Code Generator or Code Review is our
// call, not something the user has to reason about. Logging stays for support.
function updateModelInfo(selectedModel) {
// Update step 1 (Prompt Architect) model label - uses PROMPT_ARCHITECT_MODEL
const step1Label = document.getElementById("step1-model-label")
if (step1Label) {
step1Label.textContent = getModelLabel(PROMPT_ARCHITECT_MODEL)
}

// Update step 2 (Code Generator) model label - shows selected model
// If user is on free tier and selected a paid model, show: "Selected Model → Free Model"
const effectiveModel = getEffectiveModel(selectedModel)
const step2Label = document.getElementById("step2-model-label")
if (step2Label) {
if (effectiveModel !== selectedModel) {
// Free tier user selected a paid model - show both
step2Label.textContent = `${getModelLabel(selectedModel)} → ${getModelLabel(effectiveModel)} (Free Tier)`
} else {
// User's selection matches effective model
step2Label.textContent = getModelLabel(selectedModel)
}
}

// Update step 3 (Code Review) model label - uses CODE_REVIEW_MODEL
const step3Label = document.getElementById("step3-model-label")
if (step3Label) {
step3Label.textContent = getModelLabel(CODE_REVIEW_MODEL)
}

console.log(`Step 1 (Prompt Architect): ${getModelLabel(PROMPT_ARCHITECT_MODEL)}`)
if (effectiveModel !== selectedModel) {
Expand Down Expand Up @@ -4904,7 +4926,14 @@ function openCommitConfirmModal(codeInfo, checks, deps, bundlePlan = null) {
const depsSection = document.getElementById("confirm-deps-section");
if (deps && Object.keys(deps).length > 0) {
depsList.innerHTML = Object.entries(deps)
.map(([name, version]) => `<li>• ${escapeHtmlText(name)}: ${escapeHtmlText(version)}</li>`)
.map(([name, version]) => {
// A package with no declared minimum has its version resolved against
// the project's pubspec at push time, so promising one here would lie.
const constraint = version
? `at least ${escapeHtmlText(version)}`
: "version resolved from your project";
return `<li>• ${escapeHtmlText(name)}: ${constraint}</li>`;
})
.join("");
depsSection.classList.remove("hidden");
} else {
Expand Down Expand Up @@ -5495,6 +5524,9 @@ function reviewStatusIcon(status, className = "") {
pass: `<path d="M20 6 9 17l-5-5"/>`,
warning: `<path d="M12 9v4m0 4h.01"/><path d="M10.3 3.6 2.2 18a2 2 0 0 0 1.7 3h16.2a2 2 0 0 0 1.7-3L13.7 3.6a2 2 0 0 0-3.4 0Z"/>`,
fail: `<path d="m15 9-6 6m0-6 6 6"/><circle cx="12" cy="12" r="9"/>`,
// Manual follow-up is information, not an alert - the triangle is reserved
// for findings that actually stop the push.
info: `<circle cx="12" cy="12" r="9"/><path d="M12 16v-4m0-4h.01"/>`,
};
return `<svg class="review-status-icon ${className}" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths[status] || paths.warning}</svg>`;
}
Expand Down Expand Up @@ -5571,7 +5603,8 @@ function renderSummaryDetail(presentation) {
const manualSteps = presentation.manualSteps.length
? `
<section class="summary-manual-callout">
<h3>${reviewStatusIcon("warning")} Complete in FlutterFlow</h3>
<h3>${reviewStatusIcon("info")} Complete in FlutterFlow</h3>
<p class="summary-manual-lead">For your information — these don't block the deploy. Finish them by hand in the FlutterFlow editor once the code is pushed.</p>
<ul>
${presentation.manualSteps.map((step) => `
<li>
Expand Down
132 changes: 68 additions & 64 deletions dist/assets/index-C8GiNBZX.js → dist/assets/index-DKBhAJWY.js

Large diffs are not rendered by default.

27 changes: 15 additions & 12 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -717,10 +717,10 @@
display: flex;
align-items: flex-start;
gap: 9px;
color: #78350f;
color: #3f3f46;
font-size: 12px;
}
.manual-step p { margin-top: 3px; color: #92400e; line-height: 1.45; }
.manual-step p { margin-top: 3px; color: #52525b; line-height: 1.45; }
.results-summary-detail {
flex: 1;
min-height: 0;
Expand Down Expand Up @@ -1244,18 +1244,24 @@
.summary-manual-callout {
margin-top: 18px;
padding: 13px 15px;
border: 1px solid #fed7aa;
border: 1px solid #fde68a;
border-radius: 8px;
background: #fff7ed;
background: #fffbeb;
}
.summary-manual-callout h3 {
display: flex;
align-items: center;
gap: 7px;
color: #9a3412;
color: #b45309;
font-size: 13px;
font-weight: 750;
}
.summary-manual-lead {
margin: 6px 0 0;
color: #92400e;
font-size: 12px;
line-height: 1.45;
}
.summary-manual-callout h3 .review-status-icon {
width: 16px;
height: 16px;
Expand All @@ -1268,12 +1274,12 @@
list-style: disc;
}
.summary-manual-callout li {
color: #7c2d12;
color: #3f3f46;
font-size: 14px;
line-height: 1.45;
}
.summary-manual-callout li strong { font-weight: 700; }
.summary-manual-callout li span { display: block; margin-top: 2px; color: #9a3412; }
.summary-manual-callout li strong { font-weight: 700; color: #27272a; }
.summary-manual-callout li span { display: block; margin-top: 2px; color: #52525b; }
.summary-findings {
display: grid;
gap: 7px;
Expand Down Expand Up @@ -2358,7 +2364,7 @@
.pm-cards { padding: 0 16px; }
}
</style>
<script type="module" crossorigin src="/assets/index-C8GiNBZX.js"></script>
<script type="module" crossorigin src="/assets/index-DKBhAJWY.js"></script>
</head>
<body>
<div class="app-shell">
Expand Down Expand Up @@ -2456,7 +2462,6 @@
</svg>
<span class="workflow-label">Prompt Architect</span>
</div>
<span id="step1-model-label" class="text-sm text-gray-400" style="margin-left:22px;">Gemini 3.6 Flash</span>
</div>
<div id="step1-status" class="status-icon">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand All @@ -2473,7 +2478,6 @@
</svg>
<span class="workflow-label">Code Generator</span>
</div>
<span id="step2-model-label" class="text-sm text-gray-400" style="margin-left:22px;">Gemini 3.6 Flash</span>
</div>
<div id="step2-status" class="status-icon">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand All @@ -2490,7 +2494,6 @@
</svg>
<span class="workflow-label">Code Review</span>
</div>
<span id="step3-model-label" class="text-sm text-gray-400" style="margin-left:22px;">Gemini 3.6 Flash</span>
</div>
<div id="step3-status" class="status-icon">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand Down
25 changes: 14 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -717,10 +717,10 @@
display: flex;
align-items: flex-start;
gap: 9px;
color: #78350f;
color: #3f3f46;
font-size: 12px;
}
.manual-step p { margin-top: 3px; color: #92400e; line-height: 1.45; }
.manual-step p { margin-top: 3px; color: #52525b; line-height: 1.45; }
.results-summary-detail {
flex: 1;
min-height: 0;
Expand Down Expand Up @@ -1244,18 +1244,24 @@
.summary-manual-callout {
margin-top: 18px;
padding: 13px 15px;
border: 1px solid #fed7aa;
border: 1px solid #fde68a;
border-radius: 8px;
background: #fff7ed;
background: #fffbeb;
}
.summary-manual-callout h3 {
display: flex;
align-items: center;
gap: 7px;
color: #9a3412;
color: #b45309;
font-size: 13px;
font-weight: 750;
}
.summary-manual-lead {
margin: 6px 0 0;
color: #92400e;
font-size: 12px;
line-height: 1.45;
}
.summary-manual-callout h3 .review-status-icon {
width: 16px;
height: 16px;
Expand All @@ -1268,12 +1274,12 @@
list-style: disc;
}
.summary-manual-callout li {
color: #7c2d12;
color: #3f3f46;
font-size: 14px;
line-height: 1.45;
}
.summary-manual-callout li strong { font-weight: 700; }
.summary-manual-callout li span { display: block; margin-top: 2px; color: #9a3412; }
.summary-manual-callout li strong { font-weight: 700; color: #27272a; }
.summary-manual-callout li span { display: block; margin-top: 2px; color: #52525b; }
.summary-findings {
display: grid;
gap: 7px;
Expand Down Expand Up @@ -2455,7 +2461,6 @@
</svg>
<span class="workflow-label">Prompt Architect</span>
</div>
<span id="step1-model-label" class="text-sm text-gray-400" style="margin-left:22px;">Gemini 3.6 Flash</span>
</div>
<div id="step1-status" class="status-icon">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand All @@ -2472,7 +2477,6 @@
</svg>
<span class="workflow-label">Code Generator</span>
</div>
<span id="step2-model-label" class="text-sm text-gray-400" style="margin-left:22px;">Gemini 3.6 Flash</span>
</div>
<div id="step2-status" class="status-icon">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand All @@ -2489,7 +2493,6 @@
</svg>
<span class="workflow-label">Code Review</span>
</div>
<span id="step3-model-label" class="text-sm text-gray-400" style="margin-left:22px;">Gemini 3.6 Flash</span>
</div>
<div id="step3-status" class="status-icon">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand Down
4 changes: 4 additions & 0 deletions src/artifactBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export function normalizeDependencies(rawDependencies) {
return {
name,
version: dep.version || null,
// Set only when the code will not compile below this version. It is
// the one thing that can overrule a version the project already
// declares, so an unflagged version stays advisory.
versionRequired: Boolean(dep.versionRequired || dep.required),
inferred: Boolean(dep.inferred),
...(dep.reason ? { reason: dep.reason } : {}),
};
Expand Down
2 changes: 1 addition & 1 deletion src/artifactBundle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test("normalizes a single widget fixture into a one-artifact bundle", () => {
assert.equal(bundle.artifacts[0].codeType, "W");
assert.deepEqual(bundle.deployOrder, ["custom-widget-tool-call-card"]);
assert.deepEqual(bundle.artifacts[0].dependencies, [
{ name: "agent_kit", version: "^0.1.2", inferred: false },
{ name: "agent_kit", version: "^0.1.2", versionRequired: false, inferred: false },
]);
});

Expand Down
Loading