hu-wentao/flowr · Archived

fr-mvvm-contract

Create or adapt ACDD Flutter projects across Android, iOS, macOS, Web, Windows, and Linux; audit application identity, Android/iOS icons, developer signing evidence, minimum platform versions, and push configuration; create, validate, or evolve FlowR contracts, typed Pages, cross-page modules, navigation shells, endpoint Behaviors, and frontend interaction Flows; audit or repair project-configured Figma-bound screens, including runtime text, formatting, data binding, and layout; generate, query…

First seen May 31, 2026

Installation

$ npx skills add hu-wentao/flowr --skill fr-mvvm-contract

Summary

  • Create or adapt ACDD Flutter projects across Android, iOS, macOS, Web, Windows, and Linux; audit application identity, Android/iOS icons, developer signing evidence, minimum platform versions, and push configuration; create, validate, or evolve FlowR contracts, typed Pages, cross-page modules, navigation shells, endpoint Behaviors, and frontend interaction Flows; audit or repair project-configured Figma-bound screens, including runtime text, formatting, data binding, and layout; generate, query, package, or synchronize BFF contracts; and assess Flutter packaging optimizations when explicitly requested.
  • Use for acdd_scaffold, existing-project adaptation, application-information or release-readiness checks, contract-first FlowR work, typed-route or bottom-navigation refactors, BFF frontend interaction logic, changes to Views whose contracts declare Figma, Figma fidelity repair, BFF inventory or delivery, and explicit Flutter build optimization requests.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from hu-wentao/flowr.

npx skills add hu-wentao/flowr

Browse all from hu-wentao/flowr

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 2
License LICENSE
Default branch main
Open issues 0
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 64,435 B
  • docs SUMMARY.md 989 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 49 installs

SKILL.md

FR MVVM Contract

Run every bundled Python entrypoint with uv run --script <path>. Add or update its PEP 723 dependencies with uv add --script <path> <dependency>; never invoke a bundled script with python, python3, or uv run python.

Mode Selection

  • For a new project or acdd_scaffold, read

references/acddscaffold.md and use scripts/acddscaffold.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 --script <skill-root>/scripts/resolve.py --task adapt_project

Follow the resolved inventory, mapping, approval, migration, and validation workflow. Treat this skill's assets/acddscaffold/ templates and references/acddscaffold.md boundaries as the standard. Never run acdd_scaffold.py --apply against the existing project.

  • To check what application-level configuration an existing Flutter project

still lacks compared with a common complete Android/iOS app, run:

uv run --script <skill-root>/scripts/resolve.py --task check_app_info

Read the resolved references/checkappinfo.md instructions and report the evidence for every required item. Treat developer accounts and certificates that cannot be proven from repository files as externally unverified; do not edit identifiers, signing, platform targets, icons, or push configuration during the check.

  • For contract work in an existing project, run:
uv run --script <skill-root>/scripts/resolve.py --task <gen_page|gen_component|extract_shared_ui|validate|validate_routes|validate_navigation_shell|audit_figma_fidelity|audit_figma_data|refresh|package_bff|generate_openapi>

Read the resolved instructions once per instructions_id.

  • For backend OpenAPI-to-Retrofit generation, run the resolver with task

generateopenapi, read references/generateopenapi.md, and use scripts/openapitoretrofit.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.

  • For a screen whose implementation must be audited or repaired against Figma,

resolve auditfigmafidelity, read its resolved project profile, and run the declared audit command before and after changes. Discover screen participation from each primary .c.dart contract's Figma Fidelity: disposition; never maintain a second page registry. Keep the viewport, asset-lock path, and regression-test owner in .c.dart. Keep only exact export identities, repository paths, and hashes in the referenced asset lock under .agents/skills-config/fr-mvvm-contract/; never hard-code one product screen in this reusable skill. Before implementing or approving the screen, read references/figmaflutterdesigntocode.md to constrain the mandatory Figma MCP acquisition skill to evidence collection, then follow references/auditfigmafidelity.md, run its SVG scan and safe normalization pipeline for exported SVG assets, and preserve icon placement-box versus visual-glyph dimensions and exact typography. The SVG pipeline never auto-repairs geometry, and a structural audit pass is not visual approval.

  • For a macOS window derived from a Figma Frame, establish whether the Frame

specifies the Flutter content viewport or the visible native window frame before changing MainFlutterWindow.swift. Preserve the content viewport with setContentSize and contentMinSize/contentMaxSize. When the Frame explicitly specifies the visible macOS window, use setFrame, minSize, and maxSize instead; content-size constraints add the title-bar height and make the outer window too large. Keep native title-bar behavior unless the design expressly requires a borderless window, then verify with fvm flutter build macos --debug.

  • Before editing any Figma-bound Page or component, read

references/figmareleasemanagement.md and run scripts/resolvefigmarelease.py for its .c.dart. When project config declares releases, treat active_release as explicit authority; never infer the latest release by sorting names. A stale result requires lightweight structure discovery in the active file and touched-contract migration before implementation. Migrate automatically only when route identity, business responsibility, navigation context, primary Frame, and state Frames identify one unambiguous successor. Block on missing, split, merged, or ambiguous successors. Honor only a reasoned Figma Release Override; never create one automatically to silence drift.

  • Treat copy, formatter, runtime-data binding, and layout-constraint edits as

Figma-bound work whenever the owning .c.dart declares Figma:. Resolve auditfigmafidelity and use references/figmaflutterdesigntocode.md before choosing a runtime text layout. Do not turn the width of one Figma sample value into a universal runtime constraint.

  • For two or more destinations that share a persistent bottom navigation,

resolve validatenavigationshell, read references/navigation-shells.md, and run the declared validator before changing Pages or routes. Classify the shared shell before auditing each destination as a standalone screen.

  • Do not propose command-line packaging or dependency-download optimizations

during ordinary project creation, adaptation, validation, or repair. Only when the user explicitly requests build or packaging optimization, read references/optional-build-packaging-optimizations.md, present the relevant options, and obtain the authorization required by the target repository before applying them.

Source-First Layout

Choose ownership and directory by reuse scope:

  • Use feature directories only for grouping. Put every Page/Component module

in its own basename-matching leaf directory; never place different module shells or *.c.dart contracts in the same directory.

  • Put a route-owned component under

lib/app/<feature>/<component-name>/ 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 every implementation artifact of a cross-route component in its own

lib/components/<component-name>/ directory. Do not place or leave module-specific files in lib/core/.

  • 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 presentation Widget a contract, Provider, Event, or

ViewModel. Promote it to a component when it encapsulates a reusable business capability, app/page state integration, independent state, API, Event, or ViewModel responsibility.

  • In an existing project with an established equivalent root, preserve that

root unless the explicit adapt_project workflow approves a move. Preserving a root never permits several modules to share one leaf directory.

Shared UI Discovery And Extension

Before modifying a Page or route-owned component, discover reusable UI in this order:

  1. Search lib/components/ for a component whose Capabilities: matches the

requested capability.

  1. Search lib/widgets/ for a shared Widget module whose Capabilities:

matches the requested capability.

  1. Reuse an existing public View or public Widget when it satisfies the

requirement.

  1. When an existing module owns the capability but has no suitable public

entry, add a semantic public View or Widget in that same module and update its public list.

  1. Create a new component only when no component owns the required stateful or

business capability. Create a Widget module only for a cross-page pure presentation need.

Run scripts/discoveruireuse.py --project-root <project-root> --capability "<requested capability>" before this decision. Treat its output as a catalog, not an automatic ownership decision: resolve close semantic matches from the module contract before creating a new module.

Extract Shared UI

Use extractsharedui when an existing route-owned View contains a UI entry that must be reused by another route. Resolve this task, read references/extractsharedui.md, and run extractsharedui.py in dry-run mode before changing Dart sources. The tool produces a reviewable ownership and file-migration manifest; --apply is permitted only after the manifest is approved.

Classify by ownership, not by similar appearance:

  • A presentation entry with inputs and callbacks only is a shared Widget under

lib/widgets/; it has no contract, Provider, Event, or ViewModel.

  • An entry with independent state, API, Event, or ViewModel is a feature

component under lib/components/<name>/ and follows the complete gen_component contract workflow. Do not mechanically move it with the Widget extractor.

  • Similar widgets with different visual or interaction contracts stay

separate until an approved design decision defines a common public API.

After extraction, add Capabilities: and Public Widgets: to the shared Widget provider, update consumers' Widget Tree: entries, rerun reuse discovery, and validate formatting, tests, and analysis.

Capabilities: and public API lists are owned by the provider module:

  • A component contract (lib/components/<name>/<name>.c.dart) declares

Capabilities: and Public Views:.

  • A shared Widget module public entry under lib/widgets/ declares

Capabilities: and Public Widgets:.

  • Every item in a public list uses a bracket reference and must be accessible

from that module's public entry.

Consumer Page contracts do not declare Components: or Shared Widgets:. Their Widget Tree: records the key public Views and Widgets actually composed by the Page. A Page owns placement and composition; the provider module owns the reusable UI and its interaction.

  • Group tightly related Pages into a cross-page module under one feature

directory, but keep every Page/Component module in a separate child leaf directory. The feature's basename-matching module export must document Pages: and Page Data Flow:. Read references/validate_routes.md before creating or refactoring that boundary.

Persistent Navigation Shell Ownership

Before treating several Figma Frames or route roots as complete Pages, determine whether they are destinations of one persistent navigation shell. Read references/navigation-shells.md whenever two or more destinations share a bottom bar.

  • One shell owns the outer Scaffold, persistent top-region host, and one bottom

navigation instance.

  • Branch Page adapters retain route inputs and page-scoped Providers; branch

Views contain content only.

  • The bottom-navigation Widget accepts selection state and callbacks. It never

imports branch Page adapters, performs navigation, or owns guarded-entry preflight.

  • A root action with no preflight may navigate from the Shell View callback. A

root action requiring permission, validation, API, business policy, or async preflight uses a Shell-owned component ViewModel and listener; branch, bottom-navigation, and target Page ViewModels do not own its gate.

  • Use StatefulShellRoute.indexedStack by default for transition-free branch

switching with retained independent branch state.

  • Retained branch state does not keep query data fresh. When a shell branch

owns a query API, detect an actual inactive-to-active transition and dispatch its established load/refresh Event into the retained page ViewModel. Do not duplicate the initial Startup Event, refresh on an ordinary rebuild, or recreate the Provider. Make overlapping refreshes latest-result-safe.

  • A zero-duration Page transition or NoTransitionPage is not a persistent

shell repair.

  • Root fullscreen routes and overlays cover the shell through the root

navigator/overlay; a branch ViewModel does not hide or recreate shell chrome.

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        # only for page- or component-owned state
  order_content.srv.dart       # SDK adapter over lib/api/gen
  order_content.bff.md         # required in BFF-JSON mode
  order_content.freezed.dart   # generated by Freezed
  order_content.g.dart         # generated by json_serializable

ordercontent.dart owns all imports and part declarations. Its .c.dart, .v.dart, and optional .vm.dart files use part of 'ordercontent.dart'; and never declare imports. Write every contract section in .c.dart with consecutive /// documentation comments; do not use / ... / contract blocks.

Treat .c.dart as the source contract, not the View implementation file. Put its contract comment before part of, followed only by stable contract types such as Models, Events, DTOs, and business enums when they exist. Put every public and private Widget/View declaration, constructor, build, FrView, and visual composition in .v.dart. New drafts follow this layout; the reader may still recognize one legacy View declared in .c.dart for migration.

A page is that component plus an optional, independent typed route adapter:

order_content.page.dart

The adapter imports order_content.dart, extends GoRouteData, owns the page-scoped FrProvider, and builds OrderContentView below it. It is never a Widget or a part of the component. Deleting the adapter removes the page-owned lifecycle; app-owned and stateless components remain independently 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 the page ViewModel factory and/or ordinary XxxView fields. It owns the page-scoped Provider and dispatches a startup Event only when the contract declares Startup Event: [XxxStarted]. It is not a Widget.

  • Public *View entries live in .v.dart and are listed authoritatively under

Public Views: in .c.dart. A component may expose multiple semantic Views. Each consumes its upstream page/app state or ordinary inputs and does not create a Provider by default.

  • For every newly generated public Widget carrying @FrAcddPage, read

references/widget-preview.md and put a fully configured Flutter SDK @Preview(...) on its public constructor. Derive name from Figma.Frame, group from the @FrAcddPage namespace, and size from the authoritative viewport; require a public wrapper that supplies its preview runtime context.

  • XxxViewModel extends FrBlocViewModel<XxxEvent, XxxModel> lives in

.vm.dart only when this module owns page- or component-scoped state; 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.

  • A component Service imports the concrete SDK from lib/api/gen and calls it

directly. Permit a semantic typedef for an SDK request type when the ViewModel constructs that request. Keep response signatures in their original generated SDK form by default. Every alias must preserve the exact SDK type, fields, generics, and serialization shape.

  • When a frontend BFF-UI-API entry has the same HTTP method and path as a

backend-owned business API, it is the same direct backend boundary rather than a second UI API. Its XxxBffReq reference must be an exact typedef of the generated SDK request payload; never declare a replacement Freezed DTO or copy, add, remove, or rename request fields. A multi-call UI aggregate must use a distinct approved UI boundary, or BFF-UI-API: - when it is local orchestration with no standalone UI HTTP endpoint.

  • Assign interaction authority before declaring Events:

- Keep BuildContext, unconditional known typed Page navigation, overlays, controllers, and other presentation-only operations in the View callback when they do not change durable/business/API state. Such operations normally need no Event and no Interactions: Flow. Common examples are non-exhaustive; do not add exhaustive operation syntax validation. - Use a Bloc Event and document an Interactions: Flow only when the ViewModel owns model/business changes, API or service work, validation, guard/concurrency policy, retry/recovery, or an observable state outcome. - Treat picker, URL, share, and clipboard actions as boundary cases. Keep the platform invocation in the View when presentation-only; move decisions and results through the ViewModel when permissions, validation, persistence, API work, business policy, or model changes are involved. - When Page entry requires permission, validation, API, business policy, or asynchronous/concurrent preflight, the click expresses intent only. Use Trigger -> Event -> ViewModel preflight -> observable approved/blocked outcome + nullable semantic navigation signal -> View FrListener/FrConsumer -> typed Page navigation. Inject gateways into the ViewModel; never await a gate in a StatefulWidget and navigate directly afterward. Default to ignore-while-active. Emit each declared phase atomically through one direct emit(state.copyWith(...)): Pending includes the signal reset, approved Success includes both the real approved outcome and exact signal, and blocked/exception outcomes never set the signal. - Do not generate Intent or callback-output protocols as a second state channel. Ordinary reusable Widgets may accept input callbacks.

  • For business/API-result navigation, or a Uses: local Flow whose ViewModel

owns a real state/business decision, use Navigation: view-listener-on-success [Model].field = Enum.member. Give each nullable semantic enum signal exactly one owning Flow. Reset it to null in Pending State, set only the exact member in Success State, and never assign its field: named argument from Failure, another Flow/handler, or undeclared ViewModel code. A local Flow's separate Success outcome must not be an obvious field = state.field no-op. A View FrListener/FrConsumer binds the exact ViewModel and Model generic types and uses only an exact transition parent, an exact equality early-return guard, or one exact transition-and-member && condition before navigating inside the exact enum-member braced branch. The ViewModel must not own BuildContext, router types, Page objects, or router calls. Retain Navigation: none when no navigation follows. Direct presentation routing remains a View callback with no Event or Flow only when entry is unconditional.

  • Put a Provider at the state owner's lifecycle and at the lowest common

ancestor of all consumers: - app-owned [AppViewModel]: the root AppProviders owns it; the component consumes it directly and declares no local VM, Event, Model, or Provider. - page-owned [XxxViewModel]: xxx.page.dart creates it and dispatches the optional declared Startup Event; every Widget in that page subtree can consume it. - none: the component uses inputs/callbacks or Widget-local ephemeral state and declares no VM, Event, Model, or Provider. - component-owned [XxxViewModel]: explicit opt-in only for an independently embeddable compound component that owns a distinct lifecycle and has multiple descendants sharing its state.

  • Cross-route shared components must not bind to a route-specific Page

ViewModel. Consume app-owned state when the state is genuinely global; otherwise accept ordinary inputs/callbacks.

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 consumes every route field in its ViewModel factory and/or

ordinary named fields on XxxView.

  • Do not declare component input wrappers named XxxArgs or XxxConfig.
  • Pass only the route fields needed by XxxViewModel from the Page'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. Declare it with @FrAcddFreezedJSON, a redirecting const factory, and fromJson; add the page .freezed.dart part and retain its shared generated .page.g.dart part. Configure one application-owned GoRouter.extraCodec with tagged encode/decode cases for every PageExtra. Preserve the approved PageExtra field contract during migration; serialization requirements do not authorize changing field names, values, ownership, or lifecycle. Read references/typed-routing.md for the required shape.

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.v.dart View only, when rendering needs it
OrderContentView.entryPoint order_content.v.dart View only, when rendering needs it

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) => FrProvider(
        (context) => OrderContentViewModel(
          orderId: orderId,
          entryPoint: entryPoint,
        ),
        onCreated: (context, vm) => vm.add(const OrderContentStarted()),
        child: const OrderContentView(),
      );
}

Page fields describe how the route enters and initialize page-owned state. Ordinary View fields are reserved for values the View itself must render without reading that state. Keep route types out of the component library.

Page Contract

xxx.page.dart declares one route-to-Provider-to-view adapter:

@TypedGoRoute<OrderContentPage>(path: '/orders/:orderId')
class OrderContentPage extends GoRouteData with $OrderContentPage {
  /* route fields -> page ViewModel and/or 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 and Widgets recorded in its Widget Tree:; 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 builds the same primary View; keep the basename-matching XxxPage as the primary entry.

Typed Routing

  • New scaffolds use gorouterbuilder by default. Keep it and build_runner

in devdependencies; every independent xxx.page.dart generates its own $appRoutes, and the root approuter.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.

  • For persistent bottom-navigation destinations, resolve

validatenavigationshell; branch switching uses the shell's goBranch API and is not Page-to-Page typed navigation.

  • Make XxxPage the GoRouteData; do not create a separate XxxRoute or

XxxPageArgs. Its build creates the page scope and constructs the primary XxxView below it.

  • Navigate with generated route helpers when the destination is known in app

code. Keep raw URI navigation only at explicit external/dynamic URI boundaries. validateroutes 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/validateroutes.md.

Contract-First Workflow

  1. Inspect Figma, run shared UI discovery, inspect the matching component and

Widget module catalogs, nearby usage, and API context. Record one outcome: reuse an existing public entry; extend its owning module because no entry fits; or create a module because no module owns the capability. When the request supplies multiple Figma nodes or a Figma container node, 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. Inspect a container's structure before requesting any full design context. Find requested pages by their visible page-title text first, then resolve each matching text node to its owning Frame. Treat the Frame name as a candidate hint only because designers may leave it stale or unrelated. Confirm the screen from its hierarchy, navigation context, distinctive content, and render when needed; do not mistake a menu item or embedded title for the page itself. Record the confirmed node-id, current Frame name, and visible page title as one association. Select concrete Frames rather than reading the container as though it were a page. Present the resulting logical page/state ownership map before drafting. When primary Frames share a bottom navigation, also present their shell membership and classify shell roots, branch children, root fullscreen routes, and overlays before selecting Page or Scaffold ownership; never infer route or contract count from link count or visual similarity. Page drafts default to BFF-JSON when no concrete API is supplied. Shared component drafts default to local with State Ownership: none. Read references/api-contract-semantics.md; draw the cross-component data and business flow before defining DTOs.

  1. For gen_page, draft xxx.page.dart, xxx.dart, xxx.c.dart, and the

reviewable public-View stub in xxx.v.dart:

uv run --script <skill-root>/scripts/draft_contract.py \
  --name order_content --dir lib/app/order_content \
  --figma-url <url> --figma-frame <exact-current-frame-name> \
  --figma-page-title <exact-visible-page-title|none> \
  --mode bff-json --route <route> \
  --preview-width <width> --preview-height <height> \
  --preview-wrapper <public-wrapper> \
  --theme <none|material|app-shared|component>

Read references/widget-preview.md before choosing the viewport and wrapper. Pass --preview-wrapper-import <dart-uri> when the public wrapper is declared outside the generated component library. The generator rejects missing Preview size or wrapper inputs instead of inventing them. For app-shared or component, also pass --theme-type <ThemeType>. Repeat --extra-field name:DartType when the Page needs multiple $extra values. The draft emits the required @FrAcddFreezedJSON PageExtra, generated parts, typed $extra, and PageExtra-to-ViewModel expansion. Register every emitted type in the application route-extra codec before final validation.

Use lib/app/<route-segment>/ for a route-owned component and lib/components/<component-name>/ --component-only for a component reused across routes. --component-only defaults to --mode local --state-owner none, so it emits no VM, Model, Event, Provider, Freezed, JSON, or BFF assets. Use --state-owner app --state-type <AppViewModel> when it consumes an existing root-provided VM. Use --state-owner component only after explicitly proving an independent component lifecycle; component-owned API/BFF modes require that flag. Use an existing project's established equivalent roots when they differ, unless an approved adaptation moves them.

  1. 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. Record the primary Frame's exact current name, visible page title, and complete node-specific URL under Figma: so node-id ↔ Frame name ↔ Page Title remains explicit. Use Page Title: none only for an intentionally titleless page or component. Record each Figma States target as only its node-id, resolved against that primary design file. Never repeat the design URL in Figma States. For an approved screen, add exactly one Figma Fidelity: section in this fixed shape:

/// Figma Fidelity:
/// - Viewport: 360 x 780
/// - Asset Lock: .agents/skills-config/fr-mvvm-contract/order-figma-assets.lock.json
/// - Regression Test: orderFigmaFidelity renders approved states

Use Asset Lock: none only when the authoritative Frame has no exported assets. Do not put the Figma file/node, viewport, routes, copy, source assertions, or test names in the lock. Do not write contract metadata, shared plugin data, or visible cards into Figma. Use the one-line Figma Fidelity: excluded | <reason> form instead when the current implementation is explicitly outside the approved gate. 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 the primary URL lacks node-id, a page target is 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.

  1. Before writing a Figma-derived View, read references/figmafilldata.md.

Classify every non-copy Figma fill in the .c.dart Figma Data: section. A draft may retain a TODO(figma-data) marker or a Binding: pending declaration; contract and final validation reject both. Do not use Figma sample values as production defaults. Use scripts/figmafilldata.py to report pending, invalid, and legacy-unreviewed declarations across a project.

  1. Internally classify each UI-facing BFF endpoint as query or command;

do not ask the user to choose a type or write an API-type field. Read references/frontend-interactions.md. Use its request boundary as the endpoint identity, write exactly one record under Behaviors: and one endpoint-scoped Request Field Sources: record, then define structured Interactions: Flows only for ViewModel-owned Trigger, Bloc Event, guard, state phases, concurrency, and navigation-signal coordination. Do not turn View-local callbacks into Events or Flows. The same component may own query and command endpoints. Do not author SDK Calls, SDK Call Flow, backend method/path annotations, or backend orchestration in .c.dart. Backend developers own those facts and edit only the protected backend section of xxx.bff.md. The skill may define and refresh the frontend UI data API, UI DTOs, state, endpoint behavior, interaction Flows, structure, and integration mapping. It must preserve the complete backend section byte-for-byte. Set BFF Service to the Dart SDK-adapter service class, such as [OrderContentService]; every BFF-JSON contract requires runtime integration. When a data boundary is unknown or the required evidence has not been supplied, add Data Boundary: with a stable TODO(data-boundary): <capability> — <missing authority/evidence> marker. Do not turn that uncertainty into BFF-UI-API: -, sample-data behavior, or an invented method/path. BFF-UI-API: - is allowed only after an explicit, approved API-less/local-only decision. Search for TODO(data-boundary) before approval; contract and final validation reject unresolved markers. Never invent /bootstrap, nextRoute, proof, result, or error placeholders.

Write descriptive contract values in the resolved Contract Description Language. This applies to Behavior values, interaction state/feedback prose, Request Field Sources purpose prose, and Notes. Keep stable labels, Flow IDs, code identifiers, types, methods, paths, enum literals, and authoritative source expressions unchanged.

Keep the remaining approval contract minimal: Figma, API/BFF, state ownership, reusable UI in the 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.

When Public Views: lists more than one entry, write one Widget Tree bullet per public View in the same order. Do not add a discriminator enum merely to force semantically different entries through one generic View.

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. The provider module's public list is the dependency/reuse inventory; the concise Widget Tree records the entries actually composed here. Replace the generated TODO with an informative, concise tree before contract review and approval. For every Behaviors: record, remove the unused query or command fields; complete every Interactions: Flow, resolve every TODO(data-boundary) and other 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.

  1. Present each UI API method/path and Req/Rsp/Error, its endpoint Behavior,

endpoint-scoped field provenance, every frontend interaction Flow, and the SDK-adapter service class. A non-BFF local component may explicitly declare structured Interactions: only for ViewModel-owned local Flows; every such Flow uses Uses: local and requires no endpoint, BFF Service, SDK, or bff.md. Do not present, propose, or edit backend APIs or flow; backend developers maintain them in xxx.bff.md.

  1. Validate the approved source contract before deriving files. This phase

rejects semantic/API placeholders, missing or incomplete endpoint Behaviors, untraceable request fields, missing interaction coverage, invalid Event/Widget/Model/response references, 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 --script <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 --script <skill-root>/scripts/read_contract.py \
  --page-file path/to/xxx.page.dart
uv run --script <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 --script <skill-root>/scripts/generate_from_contract.py \
  --page-file path/to/xxx.page.dart --write-stubs

Every BFF-JSON contract with runtime backend calls declares BFF Service: [Type]. Use one Service per component. The Service imports the required concrete SDK files from lib/api/gen, constructs configured generic wrappers internally, and exposes semantic methods to its ViewModel. Do not generate a frontend Retrofit client from the UI data API.

A project may 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 begin with compact bff-md-meta/v9 YAML Front Matter containing schema, @FrAcddPage namespace/version, the authoritative UI design source, a namespaced mdq v2 API-record contract, and no derivable contract-file path. They separate inline UI API DTOs from OpenAPI-owned backend operations, backend call flow, frontend UI data, integration mapping, and a generated API Query Records verification projection. The UI Contract contains UI State, endpoint Behaviors, generated 前端交互逻辑, and UI Structure in that order. Render UI State exclusively as one 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: title, 后端业务流程与业务逻辑 API, and 前端 UI 数据接口. The backend domain contains only OpenAPI document references, the business API annotations and business flow written by backend developers. Each business API annotation retains only method/path, parameter names and types, and response DTO type. Backend developers may add prose, JSON, DTO field examples, schema excerpts, or code blocks elsewhere in this protected domain; the parser preserves that material and excludes it from machine API records. Backend developers alone create and edit this entire domain. AI may create only UI-facing paths and XxxBffReq/XxxBffRsp DTOs from approved Figma/UI requirements. It must not treat a backend business API method/path as a new UI-facing path. On an exact method/path match, the request boundary remains OpenAPI-owned and the frontend contract may only give its generated SDK payload type an exact XxxBffReq typedef.

The generator never creates or overwrites xxx.srv.dart. Implement it from the backend-owned BFF flow with concrete lib/api/gen clients, then import it from the component shell. A request type constructed by the ViewModel may receive a semantic typedef; response signatures remain the original generated SDK type unless the response itself must be stored or passed as a declared type. The Service uses the application-provided Dio and does not add interceptors or own a base URL. Implement .vm.dart before .v.dart. --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), refresh

the BFF verification projection after Service/ViewModel integration, then require final validation and the repository analyzer:

fvm dart format path/to/component/files
fvm dart run build_runner build
uv run --script <skill-root>/scripts/generate_bff.py \
  --component-file path/to/xxx.dart
uv run --script <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 packagebff 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/packagebff.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. An explicit request to sync, publish, or update the configured shared repository is that authorization, including its required push to the configured ref; never request a second push confirmation. Once 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.

Use these delivery outcomes exactly when the configured destination is a shared authority repository:

  • packaged: the delivery archive exists only in the source repository.
  • published: the configured remote ref has been read back and its commit is

exactly the destination checkout commit produced or selected by sync.

Only published satisfies a request to sync, publish, deliver, or update the shared authority repository. A local file, local checkout, or local commit is never evidence that the repository was updated. If publication authorization is absent, report the current non-published state and request that authorization; do not claim completion. If authorization is present, do not stop after commit: push and verify the exact remote ref and commit. A successful command without that remote evidence is not a delivery outcome, so report the task as incomplete. Project profiles may define which user phrases grant publication authorization; follow that definition without asking again, but never infer broader mutations. If unrelated commits would be included in the push, stop and report that scope conflict instead of publishing them under the sync authorization.

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 frmvvmtheme 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 frmvvmtheme 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

  • Run validatenavigationshell whenever a Page is a declared persistent

shell destination. A valid result requires one shell owner, passive bottom navigation, content-only branch Views, stateful indexed-stack routing, and focused runtime coverage. Query-owning branches also require API-call-count coverage proving one initial load and one new request per inactive-to-active reactivation. A final-state-only pumpAndSettle() assertion does not prove transition-free switching.

  • Use --phase contract before generatefromcontract.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 Page/Component module must own its leaf directory. Validation rejects

any sibling module shell or *.c.dart contract with a different basename.

  • Every UI-facing BFF endpoint has one request-boundary-scoped Behaviors:

record whose fields let the parser infer query or command; one component may own both kinds. The contract exposes no API-type field. Every endpoint has one scoped Request Field Sources: record, and every request field declares one authoritative source and UI API purpose. Every endpoint is used by at least one structured Interactions: Flow. Read references/frontend-interactions.md for the fixed grammar. Backend developers record each business API in the protected BFF Markdown section as - [id] METHOD /path | Parameters: name Type[, ...] | Response: Type, then reference every id from ### 业务流程. The skill validates these annotations against OpenAPI and lib/api/gen but never edits them. Preserve backend-authored JSON/DTO/code examples, and never project them into the machine API inventory or frontend DTOs. 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 with backend calls declares BFF Service: [Type],

pointing to the single component SDK-adapter class in xxx.srv.dart. The Service must import at least one concrete SDK file from lib/api/gen and must not be @RestApi. Validation also requires ViewModel injection and one exact handler per Flow. Final validation proves each Flow's Event dispatch/registration, guard and concurrency policy, matching request and awaited response, pending/success/failure state writes, and any declared nullable semantic navigation signal plus its exact View-owned FrListener/FrConsumer transition branch. It scans executable signal field: assignments throughout the ViewModel, rejects occurrences outside the owning handler or allowed Pending/Success regions, preserves direct emit(state.copyWith(...)) proof for declared state writes, and rejects BuildContext, standard Flutter router types, typed Page calls, and conventional distinctive navigator/router calls. Apply these interaction contract and final-runtime checks to every non-BFF local component that explicitly declares structured Interactions:; local Flows allow only Uses: local and do not require an endpoint, BFF Service, SDK, or bff.md. Local components with omitted Interactions: or Interactions: none remain compatible. These static checks prove direct phase emissions, basic lexical order, and navigation-signal ownership; they do not prove complete Dart control-flow mutual exclusion. Require focused ViewModel tests for the blocked path returning without an approved signal, exception handling, the approved path, and repeat taps while active. Continue applying the routing boundary to BFF ViewModels, including API-less components with Interactions: none. State Ownership: none still requires no ViewModel. 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.

  • Public Views: is the authoritative inventory for new and migrated modules;

every listed class must be public and declared by the component library, and every exposed public *View must be listed. Page adapters still select one primary View from this inventory.

  • Widget Tree: must exist and contain one root per public View in declared

order, with at least one key Widget after each 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 before part of; block-comment contracts are invalid. Public and private View/Widget declarations belong in .v.dart.

  • Every primary Figma: contract must declare exactly one Figma Fidelity:

disposition. Aggregate discovery rejects missing/invalid dispositions, unsafe or reused asset-lock paths, duplicate audited Figma bindings, unused or unrendered locked assets, and a missing declared regression test or viewport. Figma Fidelity: excluded | <reason> is an explicit unapproved state, not evidence of visual completion.

  • When project config declares figma.releases, aggregate discovery also

resolves every concrete contract file key. gradual enforcement reports unexcepted archived bindings as stale migration work; strict rejects them. Unknown and candidate releases, malformed overrides, and mixed-file declarations always fail. A pinned archived release requires Figma Release Override with a matching release and non-empty reason.

  • 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.

  • Every XxxPageExtra must use the @FrAcddFreezedJSON Freezed shape,

generated JSON hooks, and the application route-extra codec. Validation requires explicit encoder and decoder coverage for every transported PageExtra; toJson() without typed fromJson restoration is invalid.

  • .page.dart declares XxxPage extends GoRouteData with $XxxPage, contains

no XxxPageArgs, and consumes every route field in the page ViewModel factory and/or ordinary View fields.

  • State Ownership: page-owned [XxxViewModel] requires every typed Page

variant to create FrProvider; when Startup Event: [XxxStarted] is declared, every variant dispatches it in .page.dart. The component View must not create another Provider.

  • State Ownership: app-owned [AppViewModel] and none reject local Provider,

VM part, Events, and Models. component-owned [XxxViewModel] requires a View-owned Provider and is an explicit exception.

  • 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 build the same public XxxView below its page scope; Route and Component doc markers are redundant and must not be generated.

  • xxx.bff.md and xxx.srv.dart are component assets, not

page assets. xxx.srv.dart is an independent SDK-adapter 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 compact bff-md-meta/v9 YAML Front Matter followed by its BFF contract title, then separates the backend logic, UI API Contract, UI Contract (including endpoint Behaviors and 前端交互逻辑), Integration Mapping, and mdq-backed API Query Records. Read namespace and contract_version from @FrAcddPage(version: ...); the annotation field is exactly version, never contractVersion, and the artifact schema version is not the contract version. UI State is one JSON5 code block, not a Markdown table, with Model, Dart type, and Frontend-authority comments on every field.

  • API Query Records is the one flat GFM matrix intentionally retained for

collection queries. It must expose backend, UI, runtime-only, and API-less dispositions with explicit contract and integration statuses. Runtime-only rows report missing backend authority without modifying the backend-owned section. generate_bff.py --check rejects stale rows after Service or ViewModel changes.

  • 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 deterministic UI DTO serialization, except an exact direct-backend SDK typedef, whose generated target owns serialization. BFF-UI-API: names the UI-facing HTTP method, path, request DTO, and XxxBffRsp; DTOs used only inside that UI API boundary use XxxDto. Treat BFF-API: as a migration-only legacy alias. Read it without changing semantics, never emit it, and let normal generation or refresh rewrite the source label and derived output to BFF-UI-API:. Keep --check read-only and report the migration command instead of editing the contract. Backend operations never add Dart DTOs to this section. Backend method/path/type annotations and flow live only in the backend-owned BFF Markdown section. A Service consumes the referenced concrete SDK API directly; it must not add an aggregate SDK client or a synthetic backend boundary.

  • Before drafting, generating, refreshing, or finally validating BFF-JSON

contracts, run ensurefracdd.py --project-root <owning-package> to require fracdd >= 0.7.0. It attempts pub add for missing/old hosted dependencies and pub upgrade fracdd for path/git dependencies. If the resolved path/git source remains old, stop and report that its source revision must be updated; never replace a path/git source with hosted implicitly. Use --check only for explicitly read-only verification.

  • Generate or check BFF delivery with

generatebff.py --component-file path/to/xxx.dart [--check]. Normal generation performs the same fracdd >= 0.7.0 check and automatic upgrade attempt before extractor preflight. --check is dependency-read-only: it fails with an upgrade instruction instead of editing pubspec.yaml or the lockfile, but still runs Dart/extractor checks and may update tool caches or use temporary files. generatefromcontract.py also keeps dependency mutation outside its transactional artifact plan, so run ensurefracdd.py explicitly before it. Treat extractor preflight or dependency incompatibility as a hard failure. This command refreshes only frontend-owned BFF content and preserves the backend section. --check confirms the referenced SDK-adapter class without comparing .srv.dart with a generated template.

  • Final validation requires every declared Dart part to exist, rejects the

generated .v and applicable .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 jsonannotation as a direct runtime dependency and jsonserializable 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

jsonannotation / jsonserializable 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 SDK-adapter services use the application-owned Dio through the

generated clients in lib/api/gen. The application root registers shared interceptors once; component 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 is read-only for contract tracking. Every new or modified primary

contract records the exact current Frame name, visible page title, and complete node-specific URL in .c.dart; each Figma States entry records only its node-id. Existing Frame/Node contracts remain readable. No flowr shared plugin data, cards, annotations, or equivalent contract metadata is written into Figma.

  • Existing full-URL Figma States entries remain readable for compatibility;

every new or modified state declaration uses only node-id. No compatibility configuration is required.

  • Figma release configuration is optional. Projects without it retain

contract-only binding behavior. Projects that enable it keep only global immutable release names, file keys, statuses, the explicit active release, and enforcement mode in skills-config; the concrete primary URL and state node IDs remain solely in .c.dart.

  • The contract workflow replaces the old JSON-first new_page.py --spec-file

and single xxx_page.dart layout. No compatibility mode is provided.

  • BFF v9 and library-shell multi-endpoint extraction require `fr_acdd >=

0.7.0. The workflow checks the resolved Pub version, not only the dependency constraint. Hosted dependencies are automatically advanced to ^0.7.0` when possible; path/git sources retain their declared source and must themselves resolve a compatible version.

  • BFF v9 is a breaking frontend-contract migration. Strict contract/final

validation rejects singular BFF Behavior:, component-global request provenance, incomplete endpoint Behaviors:, missing Interactions:, v8 artifacts, or a missing BFF Service class. Explicit non-BFF API: mode keeps its singular Behavior: grammar. BFF Runtime, BFF Service: none, and omitted BFF Service declarations are obsolete. Regeneration upgrades the metadata/frontend domain to v9 while preserving the backend domain byte-for-byte.

  • Backend Calls, Backend Call Flow, SDK Calls, and SDK Call Flow are

rejected in frontend .c.dart contracts. Backend developers maintain the protected 后端业务流程与业务逻辑 API section in xxx.bff.md.

  • 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.

  • Newly generated @FrAcddPage Widgets use Flutter's experimental

package:flutter/widget_previews.dart API and therefore require Flutter 3.35 or newer. Existing Widgets are not migrated automatically.