skillZs
LIVE SKILL TAGS
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
REAL INSTALL DATA
← back to all skills
hu-wentao/flowr28 installs

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-contract
view source ↗

Is 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, read references/acdd_scaffold.md and use scripts/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, read references/generate_openapi.md, and use scripts/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_project workflow approves a move.
  • Group tightly related Pages into a cross-page module under one feature directory. Its basename-matching module export must document Pages: and Page Data Flow:. Read references/validate_routes.md before 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

  • XxxPage lives in xxx.page.dart, extends GoRouteData with the generated $XxxPage mixin, owns route inputs as constructor fields, and expands them into ordinary XxxView fields. It is not a Widget.
  • XxxView is the public component entry and lives in the component library. It creates its own FrProvider and dispatches its startup Event.
  • XxxViewModel extends FrBlocViewModel<XxxEvent, XxxModel> lives in .vm.dart; all external writes use add(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 boundaries XxxBffReq and XxxBffRsp, and BFF-only nested data XxxDto. A project request-data-envelope profile may explicitly allow a root XxxRequestDto in place of XxxBffReq.
  • 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. XxxPage is the single typed route input model; declare path, query, and $extra inputs as its constructor fields.
  • The component library (xxx.dart and its parts) must never reference its own sibling XxxPage, generated route mixin, GoRouterState, or import its own xxx.page.dart. It may import another target route's .page.dart to use its generated Page helper or target-owned PageExtra for typed navigation.
  • XxxPage.build expands its route fields into ordinary named fields on XxxView.
  • Do not declare component input wrappers named XxxArgs or XxxConfig.
  • Pass only the fields needed by XxxViewModel from the View's Provider factory; do not pass the Page route object into the component library.
  • Use a route-owned XxxPageExtra only when several non-URL values must travel together through $extra. Declare it directly in the target xxx.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.
TypeFileConsumers
OrderContentPage.orderIdorder_content.page.dartPath input and route system
OrderContentPage.entryPointorder_content.page.dartQuery input and route system
OrderContentView.orderIdorder_content.c.dartView and ViewModel
OrderContentView.entryPointorder_content.c.dartView 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_builder by default. Keep it and build_runner in dev_dependencies; every independent xxx.page.dart generates its own $appRoutes, and the root app_router.dart spreads 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, read references/validate_routes.md, and run its module validator.
  • Make XxxPage the GoRouteData; do not create a separate XxxRoute or XxxPageArgs. Its build directly constructs the primary XxxView.
  • 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_routes rejects fixed context.go/push/replace calls and AppRoutes.xxx indirection when the URI matches a typed Page; document exceptional compatibility boundaries with the required reasoned marker from references/validate_routes.md.

Contract-First Workflow

  1. 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.md and 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 to BFF-JSON when no concrete API is supplied. Only an explicit api mode may omit the BFF artifact. Read references/api-contract-semantics.md; draw the cross-component data and business flow before defining DTOs.
  2. For gen_page, draft xxx.page.dart, xxx.dart, and xxx.c.dart only:
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
  1. 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
  1. 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.dart and .v.dart stubs 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.

  1. 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], or Theme: component [ThemeType].
  • Do not declare a separate Theme Ownership section. app-shared and component select fr_mvvm_theme ownership directly from Theme.
  • 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_theme and the approved contract uses app-shared or component, load the flowr-usage skill. Read references/fr-mvvm-theme-install.md when package or root extension injection is missing; otherwise read references/fr-mvvm-theme.md.
  • Generate one app-shared Theme type under lib/core, register it as a named AppThemeModel field, and keep that FrPageTheme object as a top-level toJson() value. Reuse the same type for every contract that names it.
  • Generate a component-owned Theme as xxx.thm.dart and add it to the component shell.
  • Read custom Theme values with context.ofThm<ThemeType>(). Never replace an approved FrPageTheme with abstract final class XxxColors.
  • For material, read shared semantic colors from Theme.of(context).colorScheme; do not generate a page color table or a FrPageTheme.

Validation

  • Use --phase contract before generate_from_contract.py. Use --phase final only after .srv/.vm/.v implementation and build_runner. Omitting --phase retains 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 internal query or command kind. 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.json location 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. Read references/api-contract-semantics.md for 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 @RestApi class generated in xxx.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.dart adapter 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 public XxxView, 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-shared and component must name a Theme type. The type must extend FrPageTheme<ThemeType>; app-shared types must be registered in AppThemeModel, retained as objects by toJson(), and injected from the root ThemeData(extensions: theme.data.extensions) path.
  • .v.dart must not statically reference XxxColors for an app-shared or component Theme contract. material contracts must use Theme.of(context).colorScheme.
  • .c.dart must not declare a type whose name ends in PageArgs, Args, or Config for component input wrapping.
  • .c.dart contract 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 and AppRoutes.xxx are invalid substitutes except at a reasoned compatibility boundary defined by validate_routes.
  • .page.dart declares XxxPage extends GoRouteData with $XxxPage, contains no XxxPageArgs, and expands every route field into ordinary View fields.
  • read_contract.py --component-file must work after removing .page.dart.
  • A page adapter must import its sibling component library. Every typed Page variant must declare a string-literal @TypedGoRoute path and directly build the same public XxxView; Route and Component doc markers are redundant and must not be generated.
  • xxx.bff.md, xxx.srv.dart, and xxx.srv.g.dart are component assets, not page assets. xxx.srv.dart is an independent Retrofit library imported by the component shell; it is not a Dart part of the component. xxx.bff.md is mandatory in BFF-JSON mode and omitted only in explicit API mode. It begins with bff-md-meta/v5 YAML 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 @FrAcddFreezedJSON plus fromJson for every BFF DTO. Every referenced XxxBffReq (or explicitly profiled XxxRequestDto) also explicitly declares Map<String, dynamic> toJson(); for Retrofit serialization. BFF-API: names the UI-facing HTTP method, path, request DTO, and XxxBffRsp; DTOs used only inside that UI API boundary use XxxDto. Backend operations never add Dart DTOs to this section and are resolved through Backend 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 component xxx.srv.dart only when absent. --check confirms the referenced class and .srv.g.dart exist without comparing .srv.dart with the initial template.
  • Final validation requires every declared Dart part to exist, rejects the generated .v/.vm stub marker, and requires .freezed.dart plus .g.dart whenever @FrState / @FrStateJson enables JSON generation.
  • Use @FrState / @FrStateJson Freezed models; keep model/view helpers and Event handlers in .vm.dart.
  • Both @FrState and @FrStateJson require JSON code generation because both presets enable toJson. Declare part 'xxx.g.dart'; beside part 'xxx.freezed.dart';. In the package that owns the model, add json_annotation as a direct runtime dependency and json_serializable as a direct dev dependency. Never add json_annotation with --dev.
  • Generated _$XxxToJson / _$XxxFromJson functions 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_serializable dependencies and the shell's .g.dart part, then run fvm 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, and retrofit, plus direct dev dependencies on build_runner and retrofit_generator. New ACDD projects install them during initialization. The application root registers EffDioLogger() once on its environment-configured shared Dio; 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_scaffold supports 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 to lib/components/<component-name>/. Route-owned shared Widgets default to lib/app/<route-segment>/widgets/; cross-route shared Widgets default to lib/widgets/. When the explicit adapt_project task is requested, move code toward those roots only through an approved current-to-target mapping.
  • Figma bindings use the flowr / contract_binding versioned 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.dart path set and update the visible .c.dart line without adding Contract or 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-file and single xxx_page.dart layout. 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 Calls nor Backend Call Flow reproduce v4 artifacts for compatibility. New drafts contain both sections and produce bff-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 none and material declarations keep their behavior.

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>