fr-mvvm-contract
Create or adapt ACDD Flutter projects across Android, iOS, macOS, Web, Windows, and Linux; create, validate, or evolve FlowR component contracts, typed Pages, and cross-page modules; and collect, package, or project-configure synchronization of generated BFF contracts. Use for new acdd_scaffold projects, existing-project adaptation, contract-first FlowR page or component work, typed route refactors, and BFF delivery archives.
How do I install this agent skill?
npx skills add https://github.com/hu-wentao/flowr --skill fr-mvvm-contractIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides a comprehensive suite of tools for Flutter project scaffolding and contract-first development. It automates project creation, dependency management, and API contract validation. No malicious behaviors such as prompt injection, data exfiltration, or unauthorized command execution were detected. Network operations are limited to fetching OpenAPI specifications for validation purposes, and shell commands are used appropriately for Flutter development tasks.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
FR MVVM Contract
Mode Selection
- For a new project or
acdd_scaffold, readreferences/acdd_scaffold.mdand usescripts/acdd_scaffold.py. Do not run the project profile resolver before the target project exists. - For an existing Flutter project that must adopt the standard scaffold structure, run:
uv run python <skill-root>/scripts/resolve.py --task adapt_project
Follow the resolved inventory, mapping, approval, migration, and validation
workflow. Treat this skill's assets/acdd_scaffold/ templates and
references/acdd_scaffold.md boundaries as the standard. Never run
acdd_scaffold.py --apply against the existing project.
- For contract work in an existing project, run:
uv run python <skill-root>/scripts/resolve.py --task <gen_page|gen_component|validate|validate_routes|refresh|package_bff|generate_openapi>
Read the resolved instructions once per instructions_id.
- For backend OpenAPI-to-Retrofit generation, run the resolver with task
generate_openapi, readreferences/generate_openapi.md, and usescripts/openapi_to_retrofit.py. Project-specific generic request and response wrappers belong in.agents/skills-config/fr-mvvm-contract/config.yaml; never infer or hard-code their non-generic fields in the reusable skill.
Source-First Layout
Choose ownership and directory by reuse scope:
- Put a route-owned component under
lib/app/<route-segment>/beside its optional page adapter. - Put a component reused by multiple routes under
lib/components/<component-name>/. Do not duplicate it under each route. - Keep a Widget used only inside one component private in that component's
.v.dart. - Put a plain Widget reused inside one route under
lib/app/<route-segment>/widgets/. - Put a plain Widget reused by multiple routes under
lib/widgets/. - Do not give a plain Widget a contract, Provider, Event, or ViewModel. Promote it to a component only when it owns independent state, API, Event, or ViewModel responsibilities.
- In an existing project with an established equivalent root, preserve that
root unless the explicit
adapt_projectworkflow approves a move. - Group tightly related Pages into a cross-page module under one feature
directory. Its basename-matching module export must document
Pages:andPage Data Flow:. Readreferences/validate_routes.mdbefore creating or refactoring that boundary.
A reusable feature component is one Dart library:
order_content/
order_content.dart
order_content.c.dart
order_content.v.dart
order_content.vm.dart
order_content.srv.dart # generated Retrofit declaration in BFF-JSON mode
order_content.srv.g.dart # generated by retrofit_generator
order_content.bff.md # required in BFF-JSON mode
order_content.freezed.dart # generated by Freezed
order_content.g.dart # generated by json_serializable
order_content.dart owns all imports and part declarations. Its .c.dart,
.v.dart, and .vm.dart files use part of 'order_content.dart'; and never
declare imports. Write every contract section in .c.dart with consecutive
/// documentation comments; do not use /* ... */ contract blocks.
A page is that component plus an optional, independent typed route adapter:
order_content.page.dart
The adapter imports order_content.dart, extends GoRouteData, and directly
builds OrderContentView. It is never a Widget or a part of the component.
Deleting the adapter must leave the component usable by another page, sheet,
tab, or dialog.
Naming And Ownership
XxxPagelives inxxx.page.dart, extendsGoRouteDatawith the generated$XxxPagemixin, owns route inputs as constructor fields, and expands them into ordinaryXxxViewfields. It is not a Widget.XxxViewis the public component entry and lives in the component library. It creates its ownFrProviderand dispatches its startup Event.XxxViewModel extends FrBlocViewModel<XxxEvent, XxxModel>lives in.vm.dart; all external writes useadd(event).- Component fields, models, DTOs, Events, BFF/service declarations, and the component contract belong to the component library.
- Name component state
XxxModel, BFF request and response boundariesXxxBffReqandXxxBffRsp, and BFF-only nested dataXxxDto. A project request-data-envelope profile may explicitly allow a rootXxxRequestDtoin place ofXxxBffReq. - Do not generate Intent or callback output protocols. Component interactions use the Bloc Event hierarchy. Follow the project's established navigation mechanism from Event handlers.
- A View-owned Provider creates an independent VM lifecycle per embedding. Use it for feature components with independent state/API ownership; pure presentation subcomponents do not receive a VM.
Page Route Inputs And Component Inputs
- Do not declare
XxxPageArgs.XxxPageis the single typed route input model; declare path, query, and$extrainputs as its constructor fields. - The component library (
xxx.dartand its parts) must never reference its own siblingXxxPage, generated route mixin,GoRouterState, or import its ownxxx.page.dart. It may import another target route's.page.dartto use its generated Page helper or target-owned PageExtra for typed navigation. XxxPage.buildexpands its route fields into ordinary named fields onXxxView.- Do not declare component input wrappers named
XxxArgsorXxxConfig. - Pass only the fields needed by
XxxViewModelfrom the View's Provider factory; do not pass the Page route object into the component library. - Use a route-owned
XxxPageExtraonly when several non-URL values must travel together through$extra. Declare it directly in the targetxxx.page.dart, never in an independent model file. Treat it only as a route transport model, not domain data or ViewModel state. The target Page must expand it into ordinary View fields. Never put credentials or tokens in path/query.
| Type | File | Consumers |
|---|---|---|
OrderContentPage.orderId | order_content.page.dart | Path input and route system |
OrderContentPage.entryPoint | order_content.page.dart | Query input and route system |
OrderContentView.orderId | order_content.c.dart | View and ViewModel |
OrderContentView.entryPoint | order_content.c.dart | View and ViewModel |
Use this standard conversion shape:
@TypedGoRoute<OrderContentPage>(path: '/orders/:orderId')
class OrderContentPage extends GoRouteData with $OrderContentPage {
const OrderContentPage({required this.orderId, required this.entryPoint});
final String orderId;
final String entryPoint;
@override
Widget build(BuildContext context, GoRouterState state) => OrderContentView(
orderId: orderId,
entryPoint: entryPoint,
);
}
// order_content.c.dart
class OrderContentView extends StatelessWidget {
const OrderContentView({
required this.orderId,
required this.entryPoint,
super.key,
});
final String orderId;
final String entryPoint;
}
Page fields describe how the route enters; ordinary View fields describe what the component needs to run. Keep that boundary explicit even when names match.
Page Contract
xxx.page.dart declares one direct route-to-view adapter:
@TypedGoRoute<OrderContentPage>(path: '/orders/:orderId')
class OrderContentPage extends GoRouteData with $OrderContentPage {
/* route fields -> View fields */
}
The route path is read from @TypedGoRoute, and the primary View is inferred
from XxxPage.build; do not duplicate either fact in documentation comments.
The primary View may compose any number of public/shared components recorded in
Components:; it does not limit a page to one component. A page file may
declare additional typed Page variants for distinct URLs only when every
variant directly builds the same primary View; keep the basename-matching
XxxPage as the primary entry.
Typed Routing
- New scaffolds use
go_router_builderby default. Keep it andbuild_runnerindev_dependencies; every independentxxx.page.dartgenerates its own$appRoutes, and the rootapp_router.dartspreads those prefixed lists. - Before adding or changing a route, read
references/typed-routing.md. - For a cross-page module or PageExtra migration, resolve
validate_routes, readreferences/validate_routes.md, and run its module validator. - Make
XxxPagetheGoRouteData; do not create a separateXxxRouteorXxxPageArgs. Itsbuilddirectly constructs the primaryXxxView. - Navigate with generated route helpers when the destination is known in app
code. Keep raw URI navigation only at explicit external/dynamic URI
boundaries.
validate_routesrejects fixedcontext.go/push/replacecalls andAppRoutes.xxxindirection when the URI matches a typed Page; document exceptional compatibility boundaries with the required reasoned marker fromreferences/validate_routes.md.
Contract-First Workflow
- Inspect Figma, shared component and Widget catalogs, nearby usage, and API
context. When the request supplies multiple Figma nodes, first read
references/figma-screen-audit.mdand account for every supplied URL as a primary Frame, same-owner state, visual reference, or explicit exclusion. Present the resulting logical page/state ownership map before drafting; never infer route or contract count from link count or visual similarity. Default toBFF-JSONwhen no concrete API is supplied. Only an explicitapimode may omit the BFF artifact. Readreferences/api-contract-semantics.md; draw the cross-component data and business flow before defining DTOs. - For
gen_page, draftxxx.page.dart,xxx.dart, andxxx.c.dartonly:
uv run python <skill-root>/scripts/draft_contract.py \
--name order_content --dir lib/app/order_content \
--figma-url <url> --mode bff-json --route <route> \
--theme <none|material|app-shared|component>
For app-shared or component, also pass --theme-type <ThemeType>.
Use lib/app/<route-segment>/ for a route-owned component and
lib/components/<component-name>/ --component-only for a component reused
across routes. Use an existing project's established equivalent roots when
they differ, unless an approved adaptation moves them.
3. Bind the primary Figma Frame and every declared Figma States Frame back
to the generated Dart files before contract review. Never bind Figma References or Figma Excluded. Read references/figma-node-binding.md, run
scripts/prepare_figma_binding.py, and execute its emitted writeCode with
Figma MCP use_figma. This must write the versioned complete .c.dart
shared-plugin-data set and create or update one compact yellow card directly
above the concrete page Frame, showing its authoritative .c.dart contract
path as the complete card text, without a Contract label or other prefix.
Prepare page contracts and target Frames one at a time; a page contract must
target its exact Figma Frame, never a Section containing several pages.
Execute the
emitted verifyCode in a second use_figma call and inspect its screenshot.
Do not continue if a URL lacks node-id, a page URL targets a non-Frame,
either representation is missing or stale, the card is not above its page,
a path is not visibly rendered, or the independent readback differs.
4. Internally classify each UI-facing BFF API as query or command; do not ask the user to
choose a type or write an API-type field. Let AI organize one Behavior:
section. For a query, define UI Data, Source, Loading/Refresh, and
Empty/Error. For a command, define Effect, Success, Failure with App
recovery, and Navigation. Trace every UI API request field to its source and
purpose. Reference each downstream backend operation with Backend Calls:
using an .openapi.json path relative to the configured local OpenAPI root
(the project root by default) or HTTP(S) URL plus its exact method/request
path. Describe only orchestration in Backend Call Flow:;
never duplicate downstream Req/Rsp. Set BFF Service to the generated Dart service class, such
as [OrderContentService]; every BFF-JSON contract requires runtime
integration. If
any semantic answer is unknown, stop for user input; never invent
/bootstrap, nextRoute, proof, result, or error placeholders.
Write descriptive contract values in the resolved Contract Description Language. This applies to Behavior entries, Request Field Sources
purpose prose, and Notes. Keep stable labels, code identifiers, types,
methods, paths, enum literals, and authoritative source expressions
unchanged.
Keep the remaining approval contract minimal: Figma, API/BFF, state ownership,
components, shared Widgets, widget tree, theme, Event and ViewModel
references, models, and concise notes. Page Support contains only route and
primary View facts.
Define Widget Tree as a concise hierarchy of key Widgets that lets a
reviewer understand the View from .c.dart alone. Include a Widget when it
is directly interactive, carries primary information, expresses an
important state, determines the functional structure, or is a shared
component developers must recognize. Prefer 4–8 key Widgets and keep even
complex Views to at most 12; fold larger trees into business-level regions,
use × N for repeated items, and mark conditional states briefly.
Preserve only necessary hierarchy. Omit formulaic _XxxViewBody nodes,
FrProvider, FrConsumer, Builder, layout glue such as Padding,
SizedBox, Spacer, Align, Expanded, Flexible, and SafeArea, pure
decoration such as Divider and DecoratedBox, and component-internal
labels/icons/spacing already covered by the parent component. Omit Row,
Column, Stack, and Container unless one is essential to disambiguate
business structure. A semantic private Widget such as [_HomeHeader] may
remain.
Do not replace key Widget references with prose such as confirmation form.
Do not draw a UI diagram or reproduce the complete runtime Widget tree.
Components: remains the dependency/reuse inventory and need not match the
concise Widget Tree. Replace the generated TODO with an informative,
concise tree before contract review and approval.
Remove the unused query or command fields from Behavior, replace every
pending marker, then define DTO fields and synchronize typed XxxPage
route fields to the final ordinary XxxView fields. The draft shell deliberately names not-yet-generated
parts, so this review state is not a compilation or analyzer gate.
5. Present the UI API method/path and Req/Rsp/Error, backend OpenAPI references
with every request path, call flow, AI-organized behavior, field provenance,
and the generated service class together. Ask the user only about uncertain
authoritative facts. Stop for review unless an active goal continues
without interruption.
6. Validate the approved source contract before deriving files. This phase
rejects semantic/API placeholders, mixed or incomplete query/command
behavior, untraceable request fields, UI-only command responses, invalid
typed Page route-field conversion, incomplete Theme declarations, and missing direct
dependencies, but does not require Freezed/JSON output yet:
uv run python <skill-root>/scripts/validate_contract.py \
--page-file path/to/xxx.page.dart --phase contract
- For all non-contract work, read the contract through scripts rather than manually deriving decisions from raw Dart:
uv run python <skill-root>/scripts/read_contract.py \
--page-file path/to/xxx.page.dart
uv run python <skill-root>/scripts/read_contract.py \
--component-file path/to/xxx.dart
- Prepare derived parts only from the approved reader output. The generator
preflights the complete contract, Theme target, dependencies, and BFF
extractor before committing any file. It prepares Theme changes, the BFF
artifact, then
.vm.dartand.v.dartstubs as one rollback-protected file set. An extractor or Theme failure must leave every prior file unchanged:
uv run python <skill-root>/scripts/generate_from_contract.py \
--page-file path/to/xxx.page.dart --write-stubs
Every BFF-JSON contract declares BFF Service: [Type]. Use one Service per
component; put every approved component endpoint on that Service as a semantic
operation. Derive the lower-camel operation name from its request DTO: a request
matching the component name, such as ConfirmPasswordBffReq, produces
confirmPassword; an additional request, such as
ConfirmPasswordPolicyBffReq, produces policy. Never generate generic
call or execute public operations.
A project may opt into interceptor-owned request data envelopes. In that
profile, a non-GET root XxxRequestDto is the business payload and the
generated Retrofit operation marks it with the configured @Extra; the
project interceptor adds {data: payload} before encryption. A project may
also require every XxxBffRsp to model a complete gateway response such as
{state, code, message, data}. In that case the original business response is
the value of its data field, not a replacement for the outer envelope.
Generated *.bff.md files use YAML Front Matter and separate inline UI API
DTOs from OpenAPI-owned backend operations, backend call flow, frontend UI
data, and integration mapping. Render UI State exclusively as a JSON5 code
block: every field has consecutive Model, Dart type, and Authority: Frontend
comments. Do not use Markdown tables for UI State. Read
references/bff-dual-authority.md before changing artifact structure,
ownership, generation, parsing, or validation.
Render BFF artifacts in this fixed order: YAML metadata, 后端逻辑流程接口, and
前端 UI 数据接口. The backend domain contains only OpenAPI document references,
the BFF-used API list, API use cases, and call sequence. Backend developers
alone create and edit backend APIs and DTOs. AI may create only UI-facing BFF
paths and XxxBffReq/XxxBffRsp DTOs from approved Figma/UI requirements.
When xxx.srv.dart does not exist, the generator reads the freshly rendered
xxx.bff.md and creates an independent @RestApi abstract class Type; its
factory redirects directly to Retrofit's generated _Type. Do not generate a
second XxxRetrofitApi class or a wrapper Service class. Keep typed public
operations directly on the Service when an endpoint has no path parameters;
annotate the typed XxxBffReq (or explicitly profiled XxxRequestDto) as
@Body() or @Queries(). Every request DTO must explicitly declare
Map<String, dynamic> toJson(); so Retrofit discovers
serialization through the abstract Freezed boundary. Only endpoints with path
parameters use a private annotated JSON-map transport method plus a same-file
typed extension; that adapter removes path fields from Body/Queries payloads.
The component shell imports this service library. The
generated service consumes the application-
provided Dio without adding or changing interceptors. Register logging,
authentication, data conversion, retry, and other shared interceptors once
where the root Provider creates Dio; new scaffolds use
lib/core/interceptors/interceptors.dart. Keep the shared Dio factory in
interceptors.dart and place every related concrete interceptor in the same
lib/core/interceptors/ directory. Generated services use @RestApi() and a
factory Type(Dio dio) constructor; base URL ownership belongs to
AppEnv.apiBaseUrl and createAppDio(AppEnv), never project skill config or a
service annotation/constructor. Provide AppEnvViewModel through
ChangeNotifierProvider with FrChangeNotifierMx, then key the Dio/business
runtime subtree by environment identity so an environment update disposes the
old Dio and business state. Clear installed persistent token/cookie stores
before publishing that update. After first
generation, treat .srv.dart as project code: developers may change Retrofit
parameters, annotations, headers, and bodies, and no generation or refresh
flow may overwrite it. Run build_runner to generate xxx.srv.g.dart, then
implement .vm.dart before .v.dart. The generator never replaces a
handwritten service or implemented derived file.
--replace-derived-stubs may
refresh only files that still contain its generated-stub marker; deprecated
--force has the same restricted behavior.
- Format handwritten Dart, run build_runner (including typed routes), then require final validation and the repository analyzer:
fvm dart format path/to/component/files
fvm dart run build_runner build
uv run python <skill-root>/scripts/validate_contract.py \
--page-file path/to/xxx.page.dart --phase final
fvm flutter analyze
Do not create or persist a JSON spec file. Do not register the route until the component passes final validation and analysis.
BFF Delivery Package
After all component BFF artifacts are generated and current, resolve
package_bff and run its package command. The generic command collects every
project *.bff.md into build/bff-contracts.zip while preserving relative
paths. OpenAPI documents remain independently owned references and are never
included in the BFF package. Read references/package_bff.md for exclusions
and project configuration.
Allow a project profile to override package or declare an optional sync
command. Treat sync as a separate external mutation: show its destination
and side effects and obtain explicit authorization before copying, committing,
or pushing to another repository. Once that authorization is explicit, always
run the resolved sync command after validation and packaging, even when every
local *.bff.md is current and Git reports no BFF changes. Local freshness does
not prove destination parity; let the configured sync command compare the
destination and decide whether a commit is necessary. Resolver execution never
authorizes or runs configured commands.
Theme Contracts
- Use exactly
Theme: none,Theme: material,Theme: app-shared [ThemeType], orTheme: component [ThemeType]. - Do not declare a separate
Theme Ownershipsection.app-sharedandcomponentselectfr_mvvm_themeownership directly fromTheme. - Treat any other Theme text as legacy. The reader may expose it with a migration warning, but validation and derived generation must stop until the declaration is migrated.
- When the project directly depends on
fr_mvvm_themeand the approved contract usesapp-sharedorcomponent, load theflowr-usageskill. Readreferences/fr-mvvm-theme-install.mdwhen package or root extension injection is missing; otherwise readreferences/fr-mvvm-theme.md. - Generate one app-shared Theme type under
lib/core, register it as a namedAppThemeModelfield, and keep thatFrPageThemeobject as a top-leveltoJson()value. Reuse the same type for every contract that names it. - Generate a component-owned Theme as
xxx.thm.dartand add it to the component shell. - Read custom Theme values with
context.ofThm<ThemeType>(). Never replace an approvedFrPageThemewithabstract final class XxxColors. - For
material, read shared semantic colors fromTheme.of(context).colorScheme; do not generate a page color table or aFrPageTheme.
Validation
- Use
--phase contractbeforegenerate_from_contract.py. Use--phase finalonly after.srv/.vm/.vimplementation and build_runner. Omitting--phaseretains the legacy source-validation behavior for compatibility; it is not the final completion gate. - Every UI-facing BFF API declares one
Behavior:section whose fields let the parser infer internalqueryorcommandkind. The contract exposes no API-type field. Every BFF request field declares one authoritative source and UI API purpose. Every downstream backend call uses a unique id, an.openapi.jsonlocation relative to the configured OpenAPI root (the project root by default) or an HTTP(S) URL, and an exact method/request path; the call flow references every id and does not duplicate backend Req/Rsp schemas. Readreferences/api-contract-semantics.mdfor syntax. - A command response must contain a non-UI result referenced by
Success. UI/navigation fields cannot be the only command response, and every failure maps to App recovery/display. - Every BFF-JSON contract declares
BFF Service: [Type], pointing to the single component@RestApiclass generated inxxx.srv.dart. A Service may contain multiple semantic operations; every endpoint must have one request/response pair, a unique request DTO-derived operation name, and an awaited ViewModel integration. Validation also requires ViewModel injection, async request/response handling, failure state, submitting/loading recovery, and success-before-navigation part of final validation. Contract-only BFF delivery is not supported. - A component must not import or reference its sibling
.page.dartadapter or sibling PageExtra. A source component may depend on another target Page adapter for typed navigation. Widget Tree:must exist, begin with the component's publicXxxView, and reference at least one key Widget after the root. It must contain no TODO, formulaic_XxxViewBody, state/implementation wrapper, or deterministic layout/decorative noise, and no more than 12 non-root Widget references.app-sharedandcomponentmust name a Theme type. The type must extendFrPageTheme<ThemeType>; app-shared types must be registered inAppThemeModel, retained as objects bytoJson(), and injected from the rootThemeData(extensions: theme.data.extensions)path..v.dartmust not statically referenceXxxColorsfor anapp-sharedorcomponentTheme contract.materialcontracts must useTheme.of(context).colorScheme..c.dartmust not declare a type whose name ends inPageArgs,Args, orConfigfor component input wrapping..c.dartcontract sections must use consecutive///documentation comments. Block-comment contracts are invalid.- Component sources must not reference their own
XxxPage,GoRouterState, generated sibling route mixin, or sibling.page.dart; cross-route target Page/PageExtra references are allowed. - Component navigation to an internally known typed Page must use
XxxPage(...).go/push/replace(context). Fixed raw URI calls andAppRoutes.xxxare invalid substitutes except at a reasoned compatibility boundary defined byvalidate_routes. .page.dartdeclaresXxxPage extends GoRouteData with $XxxPage, contains noXxxPageArgs, and expands every route field into ordinary View fields.read_contract.py --component-filemust work after removing.page.dart.- A page adapter must import its sibling component library. Every typed Page
variant must declare a string-literal
@TypedGoRoutepath and directly build the same publicXxxView; Route and Component doc markers are redundant and must not be generated. xxx.bff.md,xxx.srv.dart, andxxx.srv.g.dartare component assets, not page assets.xxx.srv.dartis an independent Retrofit library imported by the component shell; it is not a Dartpartof the component.xxx.bff.mdis mandatory in BFF-JSON mode and omitted only in explicit API mode. It begins withbff-md-meta/v5YAML Front Matter and separates the UI API Contract, Backend Call Contract, UI Contract, and Integration Mapping. UI State is one JSON5 code block, not a Markdown table, with Model, Dart type, and Frontend-authority comments on every field.- BFF-JSON contracts import
fr_acdd, declare exactly one@FrAcddPage(mode: FrAcddMode.bff), at least one root@FrAcddDto, and use@FrAcddFreezedJSONplusfromJsonfor every BFF DTO. Every referencedXxxBffReq(or explicitly profiledXxxRequestDto) also explicitly declaresMap<String, dynamic> toJson();for Retrofit serialization.BFF-API:names the UI-facing HTTP method, path, request DTO, andXxxBffRsp; DTOs used only inside that UI API boundary useXxxDto. Backend operations never add Dart DTOs to this section and are resolved throughBackend Calls:. - Generate or check BFF delivery with
generate_bff.py --component-file path/to/xxx.dart [--check]. Treat extractor preflight or dependency incompatibility as a hard failure. This command immediately reads every BFF Markdown endpoint and creates one componentxxx.srv.dartonly when absent.--checkconfirms the referenced class and.srv.g.dartexist without comparing.srv.dartwith the initial template. - Final validation requires every declared Dart part to exist, rejects the
generated
.v/.vmstub marker, and requires.freezed.dartplus.g.dartwhenever@FrState/@FrStateJsonenables JSON generation. - Use
@FrState/@FrStateJsonFreezed models; keep model/view helpers and Event handlers in.vm.dart. - Both
@FrStateand@FrStateJsonrequire JSON code generation because both presets enabletoJson. Declarepart 'xxx.g.dart';besidepart 'xxx.freezed.dart';. In the package that owns the model, addjson_annotationas a direct runtime dependency andjson_serializableas a direct dev dependency. Never addjson_annotationwith--dev. - Generated
_$XxxToJson/_$XxxFromJsonfunctions may exist only in the generated.g.dart. Never define them in.c.dart,.v.dart,.vm.dart, or.srv.dart. - When a generated JSON function is missing, check the owning package's
json_annotation/json_serializabledependencies and the shell's.g.dartpart, then runfvm dart run build_runner build. Never repair generation by writing the function in a VM or another source part. - Component BFF services require direct runtime dependencies on
dio,efficient_dio_logger, andretrofit, plus direct dev dependencies onbuild_runnerandretrofit_generator. New ACDD projects install them during initialization. The application root registersEffDioLogger()once on its environment-configured sharedDio; generated services never mutate that instance or own its base URL. - Format changed Dart files, run build_runner when generated parts change, and run the repository analyzer command.
Compatibility
acdd_scaffoldsupports Android, iOS, macOS, Web, Windows, and Linux while retaining Android+iOS as the default. It applies macOS deployment, storage, entitlement, and Debug signing configuration only when macOS is selected.- macOS Debug uses an embedded development-only encryption key and separate unsandboxed entitlement so local startup does not require Keychain, an Apple Team, or a personal certificate. Profile and Release remain sandboxed and use Keychain Sharing; configure project-owned identity and signing before distribution. Never use Debug storage for real sensitive data.
- Existing-project adaptation preserves the project's current platform targets, organization identifiers, routes, business behavior, and platform-native configuration unless the user explicitly approves changing them.
- Adaptation is structural, not a destructive regeneration: merge required
scaffold responsibilities into existing code and do not overwrite the
project with
acdd_scaffold.py. - Existing projects keep their current page roots; only new scaffolded projects
default route-owned components to
lib/app/<route-segment>/and cross-route components tolib/components/<component-name>/. Route-owned shared Widgets default tolib/app/<route-segment>/widgets/; cross-route shared Widgets default tolib/widgets/. When the explicitadapt_projecttask is requested, move code toward those roots only through an approved current-to-target mapping. - Figma bindings use the
flowr/contract_bindingversioned shared-plugin-data schema plus one deterministic compact yellow contract card above every primary or declared state Frame. Reference and excluded nodes never receive bindings or cards. Always replace the complete sorted.c.dartpath set and update the visible.c.dartline without addingContractor another prefix for create, move, split, or merge; no Section-level page aggregation, below-page placement, hidden-only, or legacy schema behavior is supported. Page and component generation must pass independent shared-data, visible-content, placement, and screenshot readback gates. - The contract workflow replaces the old JSON-first
new_page.py --spec-fileand singlexxx_page.dartlayout. No compatibility mode is provided. - Strict contract/final validation rejects legacy API contracts without a
complete query or command
Behavior, BFF request provenance, or the required generated BFF Service class.BFF Runtime,BFF Service: none, and omitted BFF Service declarations are obsolete. Drafts no longer contain a usable default method/path. - Unmigrated contracts that contain neither
Backend CallsnorBackend Call Flowreproduce v4 artifacts for compatibility. New drafts contain both sections and producebff-md-meta/v5; adding either section opts the component into v5. - Local OpenAPI references resolve from the project root when no profile is configured. Projects may configure another contained checkout root, but BFF packages and synchronization never include local OpenAPI documents; publish those documents through their independent authority.
- Free-text Theme declarations are legacy schema. They remain readable only to
produce an explicit migration warning; refresh, generation, and strict
validation require one of the structured Theme forms above. Existing
noneandmaterialdeclarations keep their behavior.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/hu-wentao/flowr/fr-mvvm-contract">View fr-mvvm-contract on skillZs</a>