diff --git a/components/HomeContent.tsx b/components/HomeContent.tsx index 1654c0d0..271a942f 100644 --- a/components/HomeContent.tsx +++ b/components/HomeContent.tsx @@ -10,6 +10,7 @@ import { HeroBackground } from "./HeroBackground"; import { HighlightedCode } from "./HighlightedCode"; import Layout from "./Layout"; import { Logo } from "./Logo"; +import { MixWidgetShowcase } from "./MixWidgetShowcase"; const fadeUp = { hidden: { opacity: 0, y: 20 }, @@ -201,6 +202,8 @@ Box(style: cardStyle, child: ...)`} /> {/* Install + CTA — constrained width */}
+ + {/* Install */} +
+ Code generation +

+ Turn a Styler into a widget API. +

+

+ Annotate the style you already use. Mix generates a typed{" "} + StatelessWidget that is ready for layouts, const constructors, + and your design system's public API. +

+
+ +
+
+
+ + + + + + + AppCard code generation + +
+ +
+ + @MixWidget() + + + → + + + AppCard + +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+
+ + + See the annotation and generated Dart + + → + + + + ); +} + +function EditorFileHeader({ + filename, + label, +}: { + filename: string; + label: string; +}) { + return ( +
+ {filename} + + {label} + +
+ ); +} diff --git a/components/landing/ack/AckHome.tsx b/components/landing/ack/AckHome.tsx index 75a2709a..7fd09879 100644 --- a/components/landing/ack/AckHome.tsx +++ b/components/landing/ack/AckHome.tsx @@ -47,8 +47,8 @@ export function AckHome() {
  • Runtime-first validation
  • -
  • Structured AI output
  • -
  • Provider-ready model
  • +
  • Bidirectional codecs
  • +
  • Optional code generation
@@ -240,7 +240,7 @@ export function AckHome() {
- OPTIONAL CODE GENERATION + TYPE-SAFE CODE GENERATION

Generate types, not duplicate models.

Annotate the schema you already trust. Ack generates a lightweight wrapper with typed getters and parse helpers—without changing the runtime schema.

Generate typed schemas @@ -253,7 +253,7 @@ export function AckHome() {
- AI-READY CODECS + BIDIRECTIONAL CODECS

Decode the wire. Keep rich Dart values.

AI providers exchange JSON strings. Ack.datetime() validates that wire value, decodes it to UTC DateTime, and encodes it back for the next boundary.

Meet codecs diff --git a/site-tests/ack-integration.test.mjs b/site-tests/ack-integration.test.mjs index a95486e5..c40c8763 100644 --- a/site-tests/ack-integration.test.mjs +++ b/site-tests/ack-integration.test.mjs @@ -104,3 +104,18 @@ test('resolves every Ack documentation and landing-page link', () => { assert.deepEqual(failures, []) }) + +test('features codecs and code generation on Ack entry pages', () => { + const landing = readFileSync(ackLanding, 'utf8') + const overview = readFileSync( + join(ackContentRoot, 'getting-started/overview.mdx'), + 'utf8', + ) + + for (const source of [landing, overview]) { + assert.match(source, /Codecs|CODECS/) + assert.match(source, /Code generation|CODE GENERATION/) + assert.match(source, /\/documentation\/ack\/advanced\/codecs/) + assert.match(source, /\/documentation\/ack\/advanced\/typesafe-schemas/) + } +}) diff --git a/site-tests/mix-codegen-integration.test.mjs b/site-tests/mix-codegen-integration.test.mjs new file mode 100644 index 00000000..612a4199 --- /dev/null +++ b/site-tests/mix-codegen-integration.test.mjs @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { join, resolve } from 'node:path' +import { test } from 'node:test' + +const root = resolve(import.meta.dirname, '..') + +test('shows MixWidget from the Mix homepage through generated output', () => { + const homeContent = readFileSync(join(root, 'components/HomeContent.tsx'), 'utf8') + const showcase = readFileSync(join(root, 'components/MixWidgetShowcase.tsx'), 'utf8') + const generatorGuide = readFileSync( + join(root, 'src/content/documentation/mix/ecosystem/mix-generator.mdx'), + 'utf8', + ) + + assert.match(homeContent, /import \{ MixWidgetShowcase \} from "\.\/MixWidgetShowcase"/) + assert.match(homeContent, //) + + assert.match(showcase, /@MixWidget/) + assert.match(showcase, /AppCard/) + assert.match(showcase, /\/documentation\/mix\/ecosystem\/mix-generator#mixwidget/) + assert.doesNotMatch(showcase, /widgetParameters/) + + assert.match(generatorGuide, /@MixWidget\(\)\s*\nfinal appCardStyle/) + assert.match(generatorGuide, /class AppCard extends StatelessWidget/) + assert.match(generatorGuide, /return appCardStyle\.call\(/) + assert.match(generatorGuide, /mix_generator:spec_styler_generator:\s*\n\s+enabled: true/) + assert.match(generatorGuide, /dart run build_runner build --delete-conflicting-outputs/) +}) diff --git a/src/content/documentation/ack/getting-started/overview.mdx b/src/content/documentation/ack/getting-started/overview.mdx index 0d549e38..44b0c722 100644 --- a/src/content/documentation/ack/getting-started/overview.mdx +++ b/src/content/documentation/ack/getting-started/overview.mdx @@ -40,6 +40,36 @@ On success, `getOrThrow()` returns your **validated data** as a `Map + + Codecs +

JSON at the edge. Rich Dart values inside.

+
+ Validate and decode strings or numbers into DateTime, Uri, Duration, enums, or your own runtime type—then encode them for the next boundary. +
+ Ack.datetime() + Explore codecs → +
+ + + Code generation +

Keep the schema. Add a typed API.

+
+ Add @AckType() to the schema you already trust and generate lightweight wrappers with typed getters, parse helpers, and no duplicated model. +
+ UserType.parse(json) + Generate typed schemas → +
+
+ ## What do you want to do? - **Validate an API response** — [Quickstart Tutorial](/documentation/ack/getting-started/quickstart-tutorial), then [JSON Serialization](/documentation/ack/essentials/json-serialization) diff --git a/src/content/documentation/mix/ecosystem/mix-generator.mdx b/src/content/documentation/mix/ecosystem/mix-generator.mdx index e105ad8f..cf979f98 100644 --- a/src/content/documentation/mix/ecosystem/mix-generator.mdx +++ b/src/content/documentation/mix/ecosystem/mix-generator.mdx @@ -1,29 +1,32 @@ --- title: mix_generator -description: "Generate Spec, Styler, and Mix property mixins with the mix_generator build_runner package" +description: "Generate Specs, Stylers, Mix properties, modifiers, and widget wrappers with mix_generator" --- import { Callout } from "nextra/components"; # mix_generator -`mix_generator` is a [build_runner](https://pub.dev/packages/build_runner) package that generates mixins and helpers for Mix’s core types. You annotate Spec, Styler, and property (“Mix”) classes in `mix_annotations`; the builder emits part files that implement `copyWith`, `lerp`, `merge`, `resolve`, setters, and related plumbing. +`mix_generator` is a [build_runner](https://pub.dev/packages/build_runner) package that turns Mix annotations into the repetitive code you should not have to maintain by hand. It can complete Specs, create Stylers, implement Mix properties and modifiers, and turn a named Styler into a reusable Flutter widget. ## What gets generated -The package registers **three builders** (see [`build.yaml` in the mix repo](https://github.com/btwld/mix/blob/main/packages/mix_generator/build.yaml)): +The package registers **six builders** (see [`build.yaml` in the Mix repo](https://github.com/btwld/mix/blob/main/packages/mix_generator/build.yaml)): -| Builder | Triggers on | Generated part (suffix) | Typical mixin role | +| Builder | Triggers on | Generated part (suffix) | Output | |---------|-------------|-------------------------|-------------------| -| `mix_generator` | `@MixableSpec` | `.mix_generator.g.part` | Spec: `copyWith`, equality, `lerp`, diagnostics | -| `styler_generator` | `@MixableStyler` | `.styler_generator.g.part` | Styler: setters, `merge`, `resolve`, `props`, optional `call` | -| `mixable_generator` | `@Mixable` | `.mixable_generator.g.part` | Property Mix: `merge`, `resolve`, `props`, diagnostics | +| `mix_generator` | `@MixableSpec` | `.mix_generator.g.part` | Completes the immutable Spec contract | +| `spec_styler_generator` (opt-in) | `@MixableSpec(target: …)` | `.spec_styler_generator.g.part` | Creates a full fluent Styler class | +| `styler_generator` | `@MixableStyler` | `.styler_generator.g.part` | Supports legacy handwritten Stylers | +| `mixable_generator` | `@Mixable` | `.mixable_generator.g.part` | Implements a compound Mix property | +| `mix_widget_generator` | `@MixWidget` | `.mix_widget_generator.g.part` | Creates a `StatelessWidget` around a Styler | +| `modifier_generator` | `@MixableModifier` | `.modifier_generator.g.part` | Completes a modifier and its Mix type | [`source_gen`](https://pub.dev/packages/source_gen) combines these parts into your hand-authored `part '…g.dart';` library. ```mermaid flowchart LR - A["Annotated Dart source"] --> B["mix_generator / styler_generator / mixable_generator"] + A["Annotated Dart source"] --> B["mix_generator builders"] B --> C["*.g.part fragments"] C --> D["combining_builder"] D --> E["*.g.dart part"] @@ -34,20 +37,20 @@ flowchart LR Add runtime and dev dependencies: ```bash -dart pub add mix mix_annotations -dart pub add dev:build_runner dev:mix_generator +flutter pub add mix mix_annotations +flutter pub add dev:build_runner dev:mix_generator ``` Minimal `pubspec.yaml` shape: ```yaml dependencies: - mix: ^2.0.0 - mix_annotations: ^2.0.0 + mix: ^2.1.0 + mix_annotations: ^2.1.3 dev_dependencies: - build_runner: ^2.4.0 - mix_generator: ^2.0.0 + build_runner: ^2.11.0 + mix_generator: ^2.1.3 ``` Versions should match what your Mix release expects; check the [changelog](https://github.com/btwld/mix/blob/main/packages/mix_generator/CHANGELOG.md) when upgrading. @@ -70,9 +73,69 @@ Contributors working inside the Mix repository often use `melos run gen:build` t ## Annotations (from `mix_annotations`) +### `@MixWidget` + +Use `@MixWidget` when a named Styler should become part of your widget API. The generator derives the widget name from a top-level lower-camel-case identifier ending in `Style`: `appCardStyle` becomes `AppCard`. + +Here is the complete handwritten file: + +```dart filename="app_card.dart" +import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; +import 'package:mix_annotations/mix_annotations.dart'; + +part 'app_card.g.dart'; + +@MixWidget() +final appCardStyle = BoxStyler() + .color(Colors.deepPurple) + .paddingAll(16) + .borderRounded(12); +``` + +Run the builder: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +The generated part contains a normal Flutter widget. This excerpt is the relevant output: + +```dart filename="app_card.g.dart" +class AppCard extends StatelessWidget { + const AppCard({super.key, this.child}); + + final Widget? child; + + @override + Widget build(BuildContext context) { + return appCardStyle.call( + key: this.key, + child: this.child, + ); + } +} +``` + +Use the generated type like any other widget: + +```dart +const AppCard( + child: Text('Generated from one Styler'), +); +``` + +No parameter configuration is needed here: a variable-backed `BoxStyler` generates the `key` and `child` widget surface shown above. Use `@MixWidget(name: 'Surface')` when the derived name is not the API you want. + + + `@MixWidget` requires an unprefixed Flutter widgets import because the generated part uses `Widget`, `StatelessWidget`, and `BuildContext` from the host library. + + +Function-backed styles work too. Factory parameters become widget constructor parameters, and a non-nullable enum parameter named `variant` can generate named constructors such as `Button.solid()` and `Button.ghost()`. + ### `@MixableSpec` -Use on immutable **Spec** classes that extend `Spec`. Controls which **methods** and **components** are generated via integer flags (see `GeneratedSpecMethods` and `GeneratedSpecComponents` in `mix_annotations`). +Use on immutable **Spec** classes. The generated mixin completes the `Spec` contract and can include `copyWith`, equality, interpolation, and diagnostics. Integer flags in `GeneratedSpecMethods` and `GeneratedSpecComponents` control the emitted surface. ```dart import 'package:flutter/foundation.dart'; @@ -83,8 +146,10 @@ part 'my_spec.g.dart'; @MixableSpec() @immutable -final class MySpec extends Spec with _$MySpecMethods { +final class MySpec with _$MySpec { + @override final String? name; + @override final int? age; const MySpec({this.name, this.age}); @@ -96,14 +161,26 @@ Flags let you skip generation you do not need, for example: - `GeneratedSpecMethods.skipLerp` — omit `lerp` - `GeneratedSpecMethods.skipCopyWith` — omit `copyWith` -### `@MixableStyler` +Add `target: YourWidget.new` when the generator should also create the matching Styler class from the Spec. + +The `spec_styler_generator` builder does not apply automatically. Enable it explicitly in your package's `build.yaml`: + +```yaml filename="build.yaml" +targets: + $default: + builders: + mix_generator:spec_styler_generator: + enabled: true +``` + +### `@MixableStyler` (legacy) -Use on **Styler** classes. In Mix 2, concrete box-like Stylers typically extend `MixStyler` and combine the generated mixin with shared style mixins (padding, borders, and so on). The generated piece adds setters, `merge`, `resolve`, `debugFillProperties`, `props`, and optionally widget `call`. Configure with `GeneratedStylerMethods` flags if you need to omit pieces (for example `GeneratedStylerMethods.skipCall`). +`@MixableStyler` remains available for handwritten Styler classes created before spec-driven generation. New Stylers should normally come from `@MixableSpec(target: YourWidget.new)`, which generates the fields, constructors, fluent methods, `call()`, merge, resolve, diagnostics, and props together. -See [`BoxStyler`](https://github.com/btwld/mix/blob/main/packages/mix/lib/src/specs/box/box_style.dart) in the Mix repository for a full reference implementation (imports, hand-written mixins, `const` `.create`, and `part` wiring). +See [`BoxSpec`](https://github.com/btwld/mix/blob/main/packages/mix/lib/src/specs/box/box_spec.dart) and its generated part in the Mix repository for a current spec-driven reference. - Custom Stylers should follow the same constructor patterns as core Mix Stylers. The [mix_lint](/documentation/mix/ecosystem/mix-lint) rule `mix_mixable_styler_has_create` enforces a `const` `.create` constructor for `@MixableStyler` classes. + When maintaining a legacy handwritten Styler, follow the same constructor patterns as core Mix Stylers. The [mix_lint](/documentation/mix/ecosystem/mix-lint) rule `mix_mixable_styler_has_create` enforces a `const` `.create` constructor for `@MixableStyler` classes. ### `@MixableField` @@ -133,7 +210,7 @@ After a successful build, `box_spec.g.dart` contains the `part of` directive and - **No output / stale errors:** Run `build_runner` with `--delete-conflicting-outputs` if a previous run left incompatible `*.g.dart` files. - **Builder not running:** Ensure `mix_generator` is in `dev_dependencies` and that your package (or app) imports files that carry the annotations; builders only visit included sources. -- **Monorepo `build.yaml`:** The Mix framework repo narrows `generate_for` globs for its own layout. **Your app** typically relies on the builders’ `auto_apply: dependents` behavior unless you add a custom `build.yaml`. +- **Monorepo `build.yaml`:** The Mix framework repo narrows `generate_for` globs for its own layout. **Your app** typically relies on `auto_apply: dependents` for the standard builders; `spec_styler_generator` remains opt-in as shown above. ## Debugging the generator