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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/HomeContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -201,6 +202,8 @@ Box(style: cardStyle, child: ...)`} />
{/* Install + CTA — constrained width */}
<Layout>
<div className="relative z-10">
<MixWidgetShowcase />

{/* Install */}
<motion.section
className="not-prose cli-section section-gap"
Expand Down
133 changes: 133 additions & 0 deletions components/MixWidgetShowcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import Link from "next/link";

import { HighlightedCode } from "./HighlightedCode";

const STYLE_SOURCE = `@MixWidget()
final appCardStyle = BoxStyler()
.color(Colors.deepPurple)
.paddingAll(16)
.borderRounded(12);`;

const GENERATED_WIDGET = `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,
);
}
}`;

const WIDGET_USAGE = `const AppCard(
child: Text(
'A real Flutter widget',
),
);`;

export function MixWidgetShowcase() {
return (
<section className="not-prose section-gap" aria-labelledby="mix-widget-title">
<div className="section-header">
<span className="mono-label">Code generation</span>
<h2 id="mix-widget-title" className="section-title">
Turn a Styler into a widget API.
</h2>
<p className="mt-4 max-w-[580px] text-base leading-relaxed text-[var(--mix-text-muted)]">
Annotate the style you already use. Mix generates a typed{" "}
<code>StatelessWidget</code> that is ready for layouts, const constructors,
and your design system&apos;s public API.
</p>
</div>

<div className="overflow-hidden rounded-2xl border border-white/[0.09] bg-[var(--mix-bg)] shadow-[0_24px_80px_rgba(0,0,0,0.22)]">
<div className="flex flex-col gap-3 border-b border-[var(--mix-border-card)] bg-[var(--mix-surface)] px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-3">
<span className="flex gap-1.5" aria-hidden>
<span className="size-2 rounded-full bg-white/15" />
<span className="size-2 rounded-full bg-white/15" />
<span className="size-2 rounded-full bg-white/15" />
</span>
<span className="font-mono text-[11px] text-white/75">
AppCard code generation
</span>
</div>

<div className="flex items-center gap-2 font-mono text-[11px]">
<code className="!border-0 !bg-transparent !p-0 text-[var(--mix-text-muted)]">
@MixWidget()
</code>
<span aria-hidden className="text-white/25">
</span>
<code className="!border-0 !bg-transparent !p-0 text-white/70">
AppCard
</code>
</div>
</div>

<div className="grid lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)] lg:grid-rows-[auto_1fr]">
<article className="min-w-0 border-b border-[var(--mix-border-card)] lg:col-start-1 lg:row-start-1">
<EditorFileHeader filename="app_card.dart" label="You write" />
<div className="px-6 py-7 sm:px-7 sm:py-8">
<HighlightedCode
code={STYLE_SOURCE}
className="overflow-x-auto font-mono text-[13px] leading-[1.65]"
/>
</div>
</article>

<article className="flex min-w-0 flex-col border-b border-[var(--mix-border-card)] lg:col-start-2 lg:row-span-2 lg:row-start-1 lg:border-b-0 lg:border-l">
<EditorFileHeader filename="app_card.g.dart" label="Generated" />
<div className="flex-1 px-6 py-7 sm:px-7 sm:py-8">
<HighlightedCode
code={GENERATED_WIDGET}
className="overflow-x-auto font-mono text-[13px] leading-[1.65]"
/>
</div>
</article>

<article className="min-w-0 lg:col-start-1 lg:row-start-2">
<EditorFileHeader filename="usage.dart" label="Use it" />
<div className="px-6 py-6 sm:px-7">
<HighlightedCode
code={WIDGET_USAGE}
className="overflow-x-auto font-mono text-[13px] leading-[1.65]"
/>
</div>
</article>
</div>
</div>

<Link
href="/documentation/mix/ecosystem/mix-generator#mixwidget"
className="group mt-5 inline-flex items-center gap-2 text-sm font-medium text-[var(--mix-text-muted)] transition-colors hover:text-[var(--mix-accent)]"
>
See the annotation and generated Dart
<span aria-hidden className="transition-transform group-hover:translate-x-0.5">
</span>
</Link>
</section>
);
}

function EditorFileHeader({
filename,
label,
}: {
filename: string;
label: string;
}) {
return (
<div className="flex items-center justify-between gap-4 border-b border-[var(--mix-border-card)] px-5 py-3.5">
<span className="font-mono text-[11px] text-white/70">{filename}</span>
<span className="font-mono text-[9px] uppercase tracking-[0.15em] text-[var(--mix-text-muted)]">
{label}
</span>
</div>
);
}
8 changes: 4 additions & 4 deletions components/landing/ack/AckHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export function AckHome() {

<ul className="ack-hero-notes" aria-label="Ack highlights">
<li>Runtime-first validation</li>
<li>Structured AI output</li>
<li>Provider-ready model</li>
<li>Bidirectional codecs</li>
<li>Optional code generation</li>
</ul>
</div>

Expand Down Expand Up @@ -240,7 +240,7 @@ export function AckHome() {

<article className="ack-feature-card ack-feature-types">
<div className="ack-feature-copy">
<span className="ack-feature-index">OPTIONAL CODE GENERATION</span>
<span className="ack-feature-index">TYPE-SAFE CODE GENERATION</span>
<h3>Generate types, not duplicate models.</h3>
<p>Annotate the schema you already trust. Ack generates a lightweight wrapper with typed getters and parse helpers—without changing the runtime schema.</p>
<Link href="/documentation/ack/advanced/typesafe-schemas">Generate typed schemas <span aria-hidden="true">→</span></Link>
Expand All @@ -253,7 +253,7 @@ export function AckHome() {

<article className="ack-feature-card ack-feature-codecs">
<div className="ack-feature-copy">
<span className="ack-feature-index">AI-READY CODECS</span>
<span className="ack-feature-index">BIDIRECTIONAL CODECS</span>
<h3>Decode the wire. Keep rich Dart values.</h3>
<p>AI providers exchange JSON strings. <code>Ack.datetime()</code> validates that wire value, decodes it to UTC <code>DateTime</code>, and encodes it back for the next boundary.</p>
<Link href="/documentation/ack/advanced/codecs">Meet codecs <span aria-hidden="true">→</span></Link>
Expand Down
15 changes: 15 additions & 0 deletions site-tests/ack-integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
}
})
29 changes: 29 additions & 0 deletions site-tests/mix-codegen-integration.test.mjs
Original file line number Diff line number Diff line change
@@ -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, /<MixWidgetShowcase \/>/)

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/)
})
30 changes: 30 additions & 0 deletions src/content/documentation/ack/getting-started/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ On success, `getOrThrow()` returns your **validated data** as a `Map<String, Obj
- **Errors you can act on.** Every failure points at the exact field and the reason it failed.
- **Just Dart.** A fluent API with no required build step — reach for code generation only when you want it.

## Go beyond validation

<div className="not-prose my-8 grid gap-4 md:grid-cols-2">
<a
href="/documentation/ack/advanced/codecs"
className="group rounded-2xl border border-white/10 bg-white/[0.035] p-6 transition-colors hover:border-[#6e8bff]/50 hover:bg-[#315cff]/[0.07]"
>
<span className="font-mono text-[11px] uppercase tracking-[0.12em] text-[#6e8bff]">Codecs</span>
<h3 className="mt-3 text-lg font-semibold tracking-tight text-white">JSON at the edge. Rich Dart values inside.</h3>
<div className="mt-2 text-sm leading-6 text-[#9aa8c7]">
Validate and decode strings or numbers into DateTime, Uri, Duration, enums, or your own runtime type—then encode them for the next boundary.
</div>
<code className="mt-5 inline-block !border-white/10 !bg-black/20 text-[#72e1ff]">Ack.datetime()</code>
<span className="mt-5 block text-sm font-medium text-[#b8c5e3] transition-colors group-hover:text-[#62d8ff]">Explore codecs →</span>
</a>

<a
href="/documentation/ack/advanced/typesafe-schemas"
className="group rounded-2xl border border-white/10 bg-white/[0.035] p-6 transition-colors hover:border-[#6e8bff]/50 hover:bg-[#315cff]/[0.07]"
>
<span className="font-mono text-[11px] uppercase tracking-[0.12em] text-[#6e8bff]">Code generation</span>
<h3 className="mt-3 text-lg font-semibold tracking-tight text-white">Keep the schema. Add a typed API.</h3>
<div className="mt-2 text-sm leading-6 text-[#9aa8c7]">
Add @AckType() to the schema you already trust and generate lightweight wrappers with typed getters, parse helpers, and no duplicated model.
</div>
<code className="mt-5 inline-block !border-white/10 !bg-black/20 text-[#72e1ff]">UserType.parse(json)</code>
<span className="mt-5 block text-sm font-medium text-[#b8c5e3] transition-colors group-hover:text-[#62d8ff]">Generate typed schemas →</span>
</a>
</div>

## 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)
Expand Down
Loading
Loading