diff --git a/.agents/skills/contribute-blog/SKILL.md b/.agents/skills/contribute-blog/SKILL.md
new file mode 100644
index 00000000000..a1ea5c693ed
--- /dev/null
+++ b/.agents/skills/contribute-blog/SKILL.md
@@ -0,0 +1,73 @@
+---
+name: contribute-blog
+description: >-
+ Create, import, edit, or review posts for the
+ official Flutter blog at flutter.dev/blog,
+ including metadata, media, authors, and publication checks.
+---
+
+# Contribute to the Flutter blog
+
+## Editorial approach
+
+Preserve the author's voice, intent, and requested scope.
+First person, contractions, anecdotes, humor, enthusiasm,
+and future-facing announcements can all be appropriate.
+Don't rewrite them merely to match documentation conventions.
+
+Apply `write-technical-docs` and `format-markdown` selectively for clarity,
+accuracy, accessibility, links, code formatting, and sound structure.
+Their documentation-specific preferences aren't blanket blog requirements:
+semantic line breaks and an 80-character limit aren't required,
+especially when importing or lightly editing an existing post.
+
+Fix or flag substantive issues with source fidelity,
+technical accuracy, accessibility, metadata, or rendering.
+Treat preference-only rewrites as suggestions
+unless copyediting was requested.
+
+## Read relevant reference material
+
+- For a new post, import, metadata or media change,
+ structural edit, or full review, read
+ [Flutter blog post format](references/post-format.md).
+- For a Google Doc import, also read
+ [Import from Google Docs](references/google-doc-import.md).
+- To add or update an author or resolve an unknown author ID, read
+ [Manage blog authors](references/authors.md).
+
+A small prose-only correction might need no reference.
+Consult `sites/www/content/blog/data.yaml`,
+`sites/www/content/blog/authors.yaml`, comparable recent posts,
+or implementation source when a convention is unclear.
+
+## Code and quality checks
+
+- If changing or writing Dart or Flutter samples,
+ apply `write-dart` and `write-flutter` as relevant.
+ Validate complete examples when practical and
+ label intentional omissions or pseudocode clearly.
+- Before calling a post ready,
+ search the changed content for unresolved markers and placeholders,
+ such as `TODO`, `TBD`, or placeholder notes.
+ Report intentional markers or blockers instead of silently removing them.
+
+## Validate proportionally
+
+For a new post or a change to metadata or content,
+run the build and link-reference checks:
+
+```bash
+dart run dash_site --site=www build
+dart run dash_site --site=www check-link-references
+```
+
+When layout, media, metadata, or content significantly changes,
+consider previewing the site locally:
+
+```bash
+dart run dash_site --site=www serve
+```
+
+For a prose-only edit that doesn't change links,
+direct inspection of the content can be enough.
diff --git a/.agents/skills/contribute-blog/references/authors.md b/.agents/skills/contribute-blog/references/authors.md
new file mode 100644
index 00000000000..6bd6327c4fa
--- /dev/null
+++ b/.agents/skills/contribute-blog/references/authors.md
@@ -0,0 +1,70 @@
+# Manage blog authors
+
+Use this reference when adding or updating an author,
+or when a post's author ID doesn't resolve.
+
+Authors are defined in `sites/www/content/blog/authors.yaml`, and
+their local profile images live in `sites/www/content/blog/author_images/`.
+
+The top-level key in `authors.yaml` is a stable content ID,
+not necessarily the author's current social handle.
+
+## Add or resolve an author
+
+Before adding a record,
+search `authors.yaml` by ID, display name, and profile URL.
+Use verified or author-provided details.
+Don't invent a name, profile, or image.
+For a new ID, choose a recognizable lowercase handle or concise stable slug,
+then insert the record alphabetically by ID.
+
+`name` is required while `image`, `imageUrl`, and `link` are optional:
+
+```yaml
+author-id:
+ name: "Author Display Name"
+ image: "author-id.webp"
+ link: "https://github.com/author-id"
+```
+
+- `image` names a local image file in `author_images/`.
+- `imageUrl` accepts an external URL to use as a fallback to `image`.
+ Prefer a local `image` over setting `imageUrl`. Don't set both.
+- `link` is the destination linked from the byline. Prefer their GitHub profile.
+
+## Author images
+
+Name a local image after the exact author ID and
+use its real lowercase `.jpg` or `.webp` extension,
+such as `author-id.webp`.
+Optimize it for a small square display and a centered circular crop.
+Use an author-supplied or appropriately reusable image.
+
+## Update an author
+
+Keep the existing ID when an author's name, handle, link, or image changes.
+If the ID must change, update every occurrence in post frontmatter.
+
+When replacing an image with a different filename or extension,
+search for references to the old file before removing it.
+Don't remove an apparently unused author without checking all posts.
+
+## Configure the authors of a post
+
+In a post's `index.md` frontmatter:
+
+For a single author, use a YAML scalar:
+
+```yaml
+author: author-id
+```
+
+For multiple authors, use an ordered list:
+
+```yaml
+author:
+ - first-author
+ - second-author
+```
+
+List order controls the displayed byline.
diff --git a/.agents/skills/contribute-blog/references/google-doc-import.md b/.agents/skills/contribute-blog/references/google-doc-import.md
new file mode 100644
index 00000000000..fc0fb55d218
--- /dev/null
+++ b/.agents/skills/contribute-blog/references/google-doc-import.md
@@ -0,0 +1,99 @@
+# Import from Google Docs
+
+Use this reference to convert an authored Google Doc into a Flutter blog post
+without turning the import into an unsolicited rewrite.
+
+## Get the source
+
+First, try to download the source yourself.
+Open the document in an authenticated browser and choose
+**File > Download > Web Page (.html, zipped)**.
+An authenticated Google Docs or Drive connector is also suitable
+if it can produce the complete HTML export and original media.
+
+If you can't access the document, the download fails,
+or the export is incomplete,
+ask the user to download and provide the HTML zip.
+Don't reconstruct content that the available source omits.
+
+Import the document body as exported.
+Ignore comment threads and suggestion metadata,
+don't reproduce or act on them.
+Extract exports into a temporary directory and
+don't commit the source archive or intermediate conversion files.
+
+## Convert content, not export debris
+
+Preserve the document's structure and meaning,
+including headings, prose, lists, tables, emphasis, links,
+code, images, and captions.
+Convert ordinary content to clean Markdown,
+using site components when they provide necessary behavior.
+
+Strip Google Docs export artifacts:
+
+- **Redirects**:
+ Replace Google redirect and tracking URLs with their direct destinations.
+- **Styling spans & non-breaking spaces**:
+ Remove non-relevant markup and structure, such as
+ inline CSS, font declarations, classes, empty anchors, and wrapping spans.
+- Replace ` ` with standard whitespace.
+- **Code snippets**:
+ Convert exported code to fenced Markdown blocks
+ with appropriate language identifiers, such as `dart` or `bash`.
+ Google Docs might represent code as `
` tags with
+ ` ` indentation or as single-cell tables.
+- **Titles**:
+ Don't replicate the document title as an `# H1` heading in the Markdown body.
+ The `title` in the post's frontmatter supplies the page H1.
+- **Footnotes**:
+ Preserve footnote content,
+ but omit generated Google Docs anchor navigation links.
+- **Videos**: Convert standalone YouTube links into `` components.
+
+Keep the author's person, tone, pacing, spelling variety,
+heading style, and rhetorical choices unless editing was requested.
+Correct conversion errors,
+but surface optional prose changes separately.
+
+## Resolve authors
+
+Identify the ordered authors from an explicit byline or user context,
+not from document ownership or editor metadata.
+
+To resolve their IDs and configure the post's frontmatter,
+follow the steps in [Manage blog authors](authors.md).
+If the source lacks enough information to identify or resolve an author,
+ask the user for their GitHub or other profile URL.
+
+## Reconcile images
+
+Map each exported image to
+its source position, caption, and purpose
+before renaming or moving it:
+
+- Place images in the post's `images/` directory
+ with concise, descriptive names.
+- Keep the highest-quality intended source.
+ Convert static raster images to WebP when quality and clarity are preserved.
+- If the image is a JPEG, prefer a `.jpg` extension over `.jpeg`.
+- Preserve intentional animation
+ and provide a static `socialImage` when needed.
+- Follow the alt text and `` guidance in
+ [Flutter blog post format](post-format.md).
+- If an image's purpose or placement is ambiguous,
+ ask for guidance rather than guessing.
+
+## Check fidelity
+
+Compare the converted post with the source section by section.
+Confirm that no prose, list item, code block, table row, image, caption,
+footnote, or intended link is missing or misplaced.
+Check heading hierarchy, link destinations, image-caption pairings,
+and meaningful emphasis.
+Report any placeholder or editorial hold in the document body
+that prevents the post from being ready.
+
+Finish with the format and accessibility checks in
+[Flutter blog post format](post-format.md)
+and the validation workflow in the [parent skill](../SKILL.md).
diff --git a/.agents/skills/contribute-blog/references/post-format.md b/.agents/skills/contribute-blog/references/post-format.md
new file mode 100644
index 00000000000..19b010852a1
--- /dev/null
+++ b/.agents/skills/contribute-blog/references/post-format.md
@@ -0,0 +1,168 @@
+# Flutter blog post format
+
+Use this reference for new posts, imports, metadata or media changes,
+structural edits, and full reviews in `sites/www/content/blog/`.
+
+## Location and publication
+
+Each post uses this structure:
+
+```text
+- sites/www/content/blog//
+ - index.md
+ - images/
+```
+
+The directory name becomes the public URL path, like `/blog/`.
+Don't rename a published post unless a URL change is requested
+and redirects are handled.
+Keep source documents, export archives, notes, and scratch files elsewhere.
+
+A future `publishDate` doesn't hide a post,
+and there is no `draft` frontmatter field.
+
+## Post frontmatter
+
+A typical post begins with the following frontmatter:
+
+```yaml
+---
+title: "A concise post title"
+description: >-
+ A short, plain-text summary that stands on its own.
+publishDate: YYYY-MM-DD
+author: author-id
+image: images/card-image.webp
+category: deep-dive
+layout: blog
+---
+```
+
+`title` (required)
+: Supplies the page H1, card title, social title, and feed title.
+ Don't repeat it as an `# H1` heading in the Markdown body.
+
+`description` (required)
+: Plain text used in the subtitle, cards, metadata, and feed.
+ Avoid Markdown formatting or links here.
+
+`publishDate` (required)
+: The publication date in `YYYY-MM-DD` format.
+ Preserve it when editing an existing post unless a date change is requested.
+
+`author` (required)
+: One or more author IDs from `sites/www/content/blog/authors.yaml`.
+ To set up authors, follow [Manage blog authors](authors.md).
+
+`category` (required)
+: A valid category slug from `sites/www/content/blog/data.yaml`.
+ Current categories include `release`, `news`, `deep-dive`, and `case-study`.
+
+`layout` (required)
+: Must be `blog`.
+
+`image` (optional)
+: Post-relative path (for example, `images/hero.webp`) to the card and
+ default social preview image.
+
+`socialImage` (optional)
+: Static image override (WebP or PNG under 5 MB) for social sharing metadata
+ when `image` is animated or exceeds 5 MB.
+
+## Card and social images
+
+`image` is optional in the data model but
+recommended when suitable artwork exists.
+It supplies the blog card image and social preview.
+The body layout doesn't render `image` automatically.
+If the hero image should appear in the post's body,
+include it explicitly in the Markdown content with ``.
+
+Use post-local paths such as `images/hero.webp`.
+Prefer optimized WebP for new static raster artwork when quality permits.
+Don't upscale sources or commit unused variants.
+
+Set `socialImage` when `image` is animated (such as a GIF),
+larger than 5 MB, or otherwise unsuitable for social previews:
+
+```yaml
+image: images/animated-hero.gif
+socialImage: images/social-cover.webp
+```
+
+Use a static WebP or PNG under 5 MB for `socialImage`.
+
+## Media in the body
+
+### Images
+
+Use `` for post-local figures, asset processing, or captions:
+
+```markdown
+
+```
+
+Keep the entire component tag on one source line.
+
+The common `` attributes are:
+
+- `src` (required):
+ Post-local path to the image file (for example, `images/diagram.webp`).
+- `alt` (required for new images):
+ Alternative text for accessibility.
+- `caption` (optional):
+ Visible figure caption text.
+- `figure` (optional):
+ Wraps the image and caption in a `` element.
+ Use it when setting `caption`.
+
+Make the accessibility choice explicit:
+
+- Give informative images concise, purposeful alt text.
+- Use `alt=""` for decorative images or images that only repeat nearby text.
+- Explain a chart's key findings in surrounding prose or its caption.
+ Don't rely on pixels alone.
+- Ensure animation isn't the only way to perceive important information.
+
+### Videos
+
+Embed YouTube videos using `` on a single line:
+
+```markdown
+
+```
+
+## Body content and formatting
+
+### Structure
+
+- Start the body with an introduction, hero image, or `## H2` heading.
+ The layout renders the title, description, and author metadata automatically.
+- Use semantic headings without skipping levels.
+
+### Markdown source formatting
+
+- Preserve valid source formatting and the author's stylistic choices.
+- Use semantic line breaks when drafting new prose,
+ but don't reflow imported or existing text only to meet a line-length rule.
+- Keep each Markdown link, inline code span, table row,
+ or component tag on a single line.
+
+### Links
+
+- Use descriptive link text.
+- Verify all destination URLs.
+- Use root-relative paths for internal blog links,
+ such as `/blog/`.
+- Use absolute URLs for docs pages,
+ such as `https://docs.flutter.dev/...`.
+
+### Code, tables, and custom markup
+
+- Use fenced code blocks with accurate language identifiers.
+- Verify commands, API names, and other technical details.
+- Make intentional omissions in code samples clear.
+- Don't use tables only for visual layout.
+ Give them a header row and sensible reading order.
+- Only use raw HTML or site components when
+ Markdown can't express the required behavior.
diff --git a/.agents/skills/publish-blog/SKILL.md b/.agents/skills/publish-blog/SKILL.md
deleted file mode 100644
index b7ef277808f..00000000000
--- a/.agents/skills/publish-blog/SKILL.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-name: publish-blog
-description: Publish a new article to the Flutter blog from a Google Doc.
----
-
-# Publish Blog Article
-
-## Overview
-
-Use this skill to add a new article to the Flutter blog
-by extracting content from a Google Doc,
-formatting it,
-and placing it in the correct directory.
-
-## Workflow
-
-### 1. Get content and images from Google Doc
-
-- [ ] Ask the user to provide a link to the Google Doc.
-- [ ] In your browser, open the Google Doc.
-- [ ] Download the document as a Web Page (.html, zipped) via
- **File > Download > Web Page**
- (or ask the user to provide the zip if download fails).
-- [ ] Extract the text content from the downloaded HTML file
- and convert it to Markdown.
-- [ ] Extract the images from the `images/` folder in the zip file.
-- [ ] If the hero/cover image is an animated GIF,
- ask the author for a high-quality static image (WebP or PNG)
- for social media previews.
- If none is available, extract a representative frame from the GIF
- and convert it to WebP.
- Keep the social image under 5 MB.
-- [ ] Create a new folder for the blog post in `sites/www/content/blog/`
- using a concise title.
-- [ ] Save the Markdown content to `index.md`
- and move the images to the `images/` directory in that folder.
-- [ ] In the frontmatter, set `image:` to the image intended for blog cards.
- If that image isn't suitable for social previews, such as when it's an
- animated GIF or exceeds 5 MB, set `socialImage:` to a static WebP or PNG.
- `socialImage` controls `twitter:image` and `og:image` and
- falls back to `image` when omitted.
-- [ ] In the Markdown body,
- reference the intended hero image using ``.
- An animated GIF or static image are both ok.
-- [ ] Reference other images in the Markdown file using `` or
- standard Markdown image tags as appropriate.
-
-### 2. Format the markdown
-
-- [ ] Apply semantic line breaks to any lines over 80 characters.
-- [ ] Ensure there is a blank line after each heading.
-
-### 3. Add the author
-
-- [ ] Ask the user to provide the author's GitHub handle.
- Search in `sites/www/content/blog/authors.yaml` for the handle.
-- [ ] If the author is not found, add a new author entry.
- Add the image to `sites/www/content/blog/authors_images/`
- and any other metadata you can find from the user's GitHub profile.
-- [ ] Update the frontmatter for the blog post with the author's handle.
-
-### 4. Review
-
-- [ ] Don't create a PR until the user has looked over the article.
diff --git a/.agents/skills/write-technical-docs/SKILL.md b/.agents/skills/write-technical-docs/SKILL.md
index c4291fd8ae7..f9c866086fe 100644
--- a/.agents/skills/write-technical-docs/SKILL.md
+++ b/.agents/skills/write-technical-docs/SKILL.md
@@ -81,11 +81,17 @@ Apply these rules to most developer-facing prose.
only when it's part of the quoted content.
For ordinary prose quotations,
place commas and periods inside the closing quotation mark.
- For examples, see [Punctuation](references/punctuation.md).
+ For examples, consult [Punctuation](references/punctuation.md).
- Use the **serial comma**, also called the **Oxford comma**:
"buttons, links, and menus".
- Use **descriptive link text** that names the destination.
Never link bare "click here", "here", "this", or "read more".
+- Avoid using "see", "see the", or "for more information, see" to introduce
+ links or cross-references.
+ Prefer action-oriented or descriptive phrasing such as
+ "refer to", "consult", or "visit"
+ (for example, "To learn about X, visit Y" or
+ "For details, consult the X documentation").
- Write _and_, not `&`, except in code, a space-constrained label,
or when matching a UI label.
- Write **unambiguous dates**:
@@ -107,7 +113,7 @@ Apply these rules to most developer-facing prose.
- **Don't pre-announce** unreleased features, dates, or plans.
Document only released behavior.
- For additional guidance, see [Voice and tone](references/voice-and-tone.md).
+ For additional guidance, consult [Voice and tone](references/voice-and-tone.md).
- Write for a **global audience**: avoid idioms, cultural references,
humor that doesn't translate, and directional words that assume a layout.
- Use **inclusive language**:
@@ -116,7 +122,7 @@ Apply these rules to most developer-facing prose.
_placeholder_ over _dummy_,
and people-first or community-preferred phrasing.
For additional guidance,
- see [Inclusive and global writing](references/inclusive-and-global.md)
+ consult [Inclusive and global writing](references/inclusive-and-global.md)
and [Word choice](references/word-choice.md).
## References
diff --git a/.agents/skills/write-technical-docs/references/formatting-and-structure.md b/.agents/skills/write-technical-docs/references/formatting-and-structure.md
index b0b709eb817..adc2ae6fa05 100644
--- a/.agents/skills/write-technical-docs/references/formatting-and-structure.md
+++ b/.agents/skills/write-technical-docs/references/formatting-and-structure.md
@@ -227,10 +227,15 @@ Use these rules to structure and format documentation.
or "read more" as the link text.
- Don't use a bare URL as link text in prose.
Link a meaningful phrase.
-- Introduce links naturally:
+- Introduce links naturally without using "see":
"For more information about quotas,
- see [Quotas and limits][]".
+ consult [Quotas and limits][]",
+ or "To learn about quotas, visit [Quotas and limits][]".
Use "about", not "on".
+- Avoid using "see", "see the", or "for more information, see"
+ when directing readers to links or cross-references.
+ Prefer action-oriented or descriptive phrasing such as
+ "refer to", "consult", or "visit".
- Don't say "the link below".
Link the actual thing.
- Write headings that produce stable, readable anchor links:
diff --git a/.agents/skills/write-technical-docs/references/word-choice.md b/.agents/skills/write-technical-docs/references/word-choice.md
index 70363f896a5..621d68ad5a7 100644
--- a/.agents/skills/write-technical-docs/references/word-choice.md
+++ b/.agents/skills/write-technical-docs/references/word-choice.md
@@ -3,7 +3,7 @@
Use this reference for common terms and naming rules in developer documentation.
For UI verbs such as _click_, _tap_, and _select_,
-see [UI elements and interaction](code-and-ui.md#ui-elements-and-interaction).
+consult [UI elements and interaction](code-and-ui.md#ui-elements-and-interaction).
For a strict terminology review or guidance about a term not covered here,
consult the [strict-review word list](word-list.yaml).
@@ -39,6 +39,10 @@ An explicit project convention takes precedence.
- Instead of _above_ and _below_,
refer to the named element or use _earlier_, _preceding_,
_later_, or _following_ as appropriate.
+- Avoid using _see_ or _see the_ to introduce links or references.
+ Prefer _refer to_, _consult_, or _visit_
+ (for example, "To learn about X, visit Y" or
+ "For details, consult the X documentation").
- Replace _via_ with _through_, _with_, or _by using_.
- Replace the verbs _leverage_ and _utilize_ with _use_.
- Use _sign in_, not _log in_, unless the UI uses _log in_.
diff --git a/.agents/skills/write-technical-docs/references/word-list.yaml b/.agents/skills/write-technical-docs/references/word-list.yaml
index 58a37d80b97..7f946692e37 100644
--- a/.agents/skills/write-technical-docs/references/word-list.yaml
+++ b/.agents/skills/write-technical-docs/references/word-list.yaml
@@ -2008,9 +2008,12 @@
Pluralize as SDKs without an apostrophe.
- term: "see"
- status: ok
+ status: caution
+ prefer: ["refer to", "consult", "visit"]
guidance: >-
- OK as a general term and for links and cross-references.
+ Avoid using to introduce links and cross-references.
+ Prefer action-oriented or descriptive phrasing such as
+ refer to, consult, or visit (for example, "To learn about X, visit Y").
- term: "select"
status: ok
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index be371cd1457..5676bd04765 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -36,7 +36,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28
+ uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -47,7 +47,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28
+ uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -61,4 +61,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28
+ uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 26e0b4bbe35..737b95fec7d 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- - uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
+ - uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d
with:
sdk: beta
- name: Fetch Dart dependencies
@@ -64,7 +64,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- - uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
+ - uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d
with:
sdk: beta
- name: Fetch Dart dependencies
@@ -87,7 +87,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- - uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
+ - uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d
with:
sdk: beta
- name: Fetch Dart dependencies
diff --git a/.github/workflows/www.yml b/.github/workflows/www.yml
index 6499041d05f..d1b27a2a5a4 100644
--- a/.github/workflows/www.yml
+++ b/.github/workflows/www.yml
@@ -43,7 +43,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- - uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
+ - uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d
with:
sdk: beta
- name: Fetch Dart dependencies
@@ -66,7 +66,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- - uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
+ - uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d
with:
sdk: beta
- name: Fetch Dart dependencies
diff --git a/AGENTS.md b/AGENTS.md
index 478c011e62e..7ef7b2b50db 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -89,6 +89,12 @@ Writing should be consistent across the site and follow the
In Markdown files, use [semantic line breaks](https://sembr.org/) and
try to keep each line under 80 characters long.
+#### Cross-references and links
+
+Avoid using "see", "see the", or "for more information, see" to introduce
+links or references. Prefer action-oriented or descriptive phrasings such as
+"refer to", "consult", or "visit" (for example, "To learn about X, visit Y").
+
## Coding guidelines
All Dart code should follow [Effective Dart](https://dart.dev/effective-dart),
diff --git a/examples/googleapis/pubspec.yaml b/examples/googleapis/pubspec.yaml
index 5334a9fa1cb..86323ddb376 100644
--- a/examples/googleapis/pubspec.yaml
+++ b/examples/googleapis/pubspec.yaml
@@ -11,5 +11,5 @@ dependencies:
flutter:
sdk: flutter
google_sign_in: ^7.2.0
- googleapis: ^16.0.0
+ googleapis: ^17.0.0
http: ^1.6.0
diff --git a/examples/ui/navigation/pubspec.yaml b/examples/ui/navigation/pubspec.yaml
index ed2f016a830..29c189d771a 100644
--- a/examples/ui/navigation/pubspec.yaml
+++ b/examples/ui/navigation/pubspec.yaml
@@ -10,7 +10,7 @@ dependencies:
flutter:
sdk: flutter
- go_router: ^17.5.0
+ go_router: ^18.0.0
flutter:
uses-material-design: true
diff --git a/packages/site_shared/lib/_sass/components/_button.scss b/packages/site_shared/lib/_sass/components/_button.scss
index 79b5b15e8f5..2ce2f7322ea 100644
--- a/packages/site_shared/lib/_sass/components/_button.scss
+++ b/packages/site_shared/lib/_sass/components/_button.scss
@@ -39,6 +39,16 @@ button {
padding: 0.4rem 0.9rem;
text-decoration: none;
cursor: pointer;
+
+ &:disabled {
+ cursor: default;
+ opacity: 0.5;
+ }
+ }
+
+ &.compact-button {
+ gap: 0.2rem;
+ padding: 0.25rem 0.65rem;
}
&.filled-button {
@@ -52,11 +62,11 @@ button {
font-size: 20px;
}
- &:hover {
+ &:not(:disabled):hover {
@include mixins.interaction-style(8%);
}
- &:active {
+ &:not(:disabled):active {
@include mixins.interaction-style(16%);
}
}
@@ -64,11 +74,11 @@ button {
&.text-button {
color: var(--site-primary-color);
- &:hover {
+ &:not(:disabled):hover {
@include mixins.interaction-style(4%);
}
- &:active {
+ &:not(:disabled):active {
@include mixins.interaction-style(8%);
}
}
@@ -77,11 +87,11 @@ button {
color: var(--site-primary-color);
border: 1px solid var(--site-primary-color);
- &:hover {
+ &:not(:disabled):hover {
@include mixins.interaction-style(4%);
}
- &:active {
+ &:not(:disabled):active {
@include mixins.interaction-style(8%);
}
}
@@ -103,7 +113,7 @@ button {
font-size: 1.75rem;
}
- &:hover {
+ &:not(:disabled):hover {
color: var(--site-base-fgColor);
}
}
diff --git a/packages/site_shared/lib/_sass/components/_tags.scss b/packages/site_shared/lib/_sass/components/_tags.scss
new file mode 100644
index 00000000000..7c56bb17f8a
--- /dev/null
+++ b/packages/site_shared/lib/_sass/components/_tags.scss
@@ -0,0 +1,88 @@
+.tag-label {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.25rem;
+
+ border-radius: var(--site-radius);
+ background-color: var(--tag-bgColor);
+ color: var(--tag-fgColor);
+ font-family: var(--site-ui-fontFamily);
+ line-height: 1;
+ text-align: center;
+ text-wrap: nowrap;
+ user-select: none;
+
+ &.tag-blue {
+ --tag-bgColor: var(--site-tag-blue-bgColor);
+ --tag-fgColor: var(--site-tag-blue-fgColor);
+ }
+
+ &.tag-green {
+ --tag-bgColor: var(--site-tag-green-bgColor);
+ --tag-fgColor: var(--site-tag-green-fgColor);
+ }
+
+ &.tag-teal {
+ --tag-bgColor: var(--site-tag-teal-bgColor);
+ --tag-fgColor: var(--site-tag-teal-fgColor);
+ }
+
+ &.tag-purple {
+ --tag-bgColor: var(--site-tag-purple-bgColor);
+ --tag-fgColor: var(--site-tag-purple-fgColor);
+ }
+
+ &.tag-magenta {
+ --tag-bgColor: var(--site-tag-magenta-bgColor);
+ --tag-fgColor: var(--site-tag-magenta-fgColor);
+ }
+
+ &.tag-red {
+ --tag-bgColor: var(--site-tag-red-bgColor);
+ --tag-fgColor: var(--site-tag-red-fgColor);
+ }
+
+ &.tag-orange {
+ --tag-bgColor: var(--site-tag-orange-bgColor);
+ --tag-fgColor: var(--site-tag-orange-fgColor);
+ }
+
+ &.tag-amber {
+ --tag-bgColor: var(--site-tag-amber-bgColor);
+ --tag-fgColor: var(--site-tag-amber-fgColor);
+ }
+
+ &.tag-grey {
+ --tag-bgColor: var(--site-tag-grey-bgColor);
+ --tag-fgColor: var(--site-tag-grey-fgColor);
+ }
+
+ &.tag-subtle-grey {
+ --tag-bgColor: var(--site-tag-subtleGrey-bgColor);
+ --tag-fgColor: var(--site-tag-subtleGrey-fgColor);
+
+ box-shadow: inset 0 0 0 1px var(--site-tag-subtleGrey-borderColor);
+ }
+
+ &.tag-small {
+ padding: 0.3rem 0.5rem;
+ font-size: 0.8rem;
+ }
+
+ &.tag-regular {
+ padding: 0.5rem 0.75rem;
+ font-size: 1rem;
+ }
+
+ .material-symbols {
+ font-size: 1.25em;
+ }
+}
+
+.tags {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ gap: 0.5rem;
+}
diff --git a/packages/site_shared/lib/components/common/button.dart b/packages/site_shared/lib/components/common/button.dart
index 85feaebbd5d..2393c7f4706 100644
--- a/packages/site_shared/lib/components/common/button.dart
+++ b/packages/site_shared/lib/components/common/button.dart
@@ -18,6 +18,7 @@ class Button extends StatelessComponent {
this.href,
this.content,
this.style = ButtonStyle.text,
+ this.size = ButtonSize.regular,
this.id,
this.attributes = const {},
this.classes,
@@ -30,6 +31,7 @@ class Button extends StatelessComponent {
final String? content;
final String? title;
final ButtonStyle style;
+ final ButtonSize size;
final String? icon;
final String? trailingIcon;
final String? id;
@@ -50,6 +52,7 @@ class Button extends StatelessComponent {
final mergedClasses = [
style.cssClass,
+ if (size == ButtonSize.compact) 'compact-button',
if ((icon != null || trailingIcon != null) && content == null)
'icon-button',
...?classes,
@@ -83,6 +86,9 @@ class Button extends StatelessComponent {
}
}
+/// The amount of space used within a [Button].
+enum ButtonSize { regular, compact }
+
enum ButtonStyle {
filled,
outlined,
diff --git a/packages/site_shared/lib/components/common/tags.dart b/packages/site_shared/lib/components/common/tags.dart
index a37641e2597..6484bbd4e46 100644
--- a/packages/site_shared/lib/components/common/tags.dart
+++ b/packages/site_shared/lib/components/common/tags.dart
@@ -5,17 +5,25 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
+import '../../util.dart';
import 'material_icon.dart';
/// A display of multiple categorical or descriptive tags.
class Tags extends StatelessComponent {
- const Tags(this.tags);
+ /// Creates a collection containing [tags].
+ const Tags(this.tags, {this.classes});
+ /// The tags displayed in this collection.
final List tags;
+ /// Additional CSS classes to apply to this tag container.
+ final List? classes;
+
@override
- Component build(BuildContext context) =>
- div(classes: 'tags', [for (final tag in tags) tag]);
+ Component build(BuildContext context) => div(
+ classes: ['tags', ...?classes].toClasses,
+ [for (final tag in tags) tag],
+ );
}
/// An individual tag to categorize an item,
@@ -23,26 +31,95 @@ class Tags extends StatelessComponent {
///
/// Generally displayed within a [Tags] component.
class Tag extends StatelessComponent {
- const Tag(this.content, {this.icon, this.title, this.label, this.color});
+ /// Creates a tag displaying [content].
+ const Tag(
+ this.content, {
+ this.icon,
+ this.title,
+ this.label,
+ this.color = .grey,
+ this.size = .regular,
+ this.classes,
+ });
+ /// The text displayed in this tag.
final String content;
+
+ /// The ID of the optional Material Symbols icon displayed before [content].
final String? icon;
+
+ /// The optional tooltip text for this tag.
final String? title;
+
+ /// The accessible label for this tag.
+ ///
+ /// If omitted, [title] is used as the accessible label.
final String? label;
- final String? color;
+
+ /// The color treatment for this tag.
+ final TagColor color;
+
+ /// The size of this tag's text and padding.
+ final TagSize size;
+
+ /// Additional CSS classes to apply to this tag.
+ final List? classes;
@override
- Component build(BuildContext context) {
- return div(
- classes: 'tag-label',
- attributes: {
- 'title': ?title,
- 'aria-label': ?(label ?? title),
- },
- [
- if (icon case final iconId?) MaterialIcon(iconId),
- span([.text(content)]),
- ],
- );
- }
+ Component build(BuildContext context) => div(
+ classes: [
+ 'tag-label',
+ color._className,
+ size._className,
+ ...?classes,
+ ].toClasses,
+ attributes: {
+ 'title': ?title,
+ 'aria-label': ?(label ?? title),
+ },
+ [
+ if (icon case final iconId?) MaterialIcon(iconId),
+ span([.text(content)]),
+ ],
+ );
+}
+
+/// A supported color for a [Tag].
+enum TagColor {
+ blue,
+ green,
+ teal,
+ purple,
+ magenta,
+ red,
+ orange,
+ amber,
+ grey,
+ subtleGrey;
+
+ /// The CSS class that applies this tag color.
+ String get _className => switch (this) {
+ TagColor.blue => 'tag-blue',
+ TagColor.green => 'tag-green',
+ TagColor.teal => 'tag-teal',
+ TagColor.purple => 'tag-purple',
+ TagColor.magenta => 'tag-magenta',
+ TagColor.red => 'tag-red',
+ TagColor.orange => 'tag-orange',
+ TagColor.amber => 'tag-amber',
+ TagColor.grey => 'tag-grey',
+ TagColor.subtleGrey => 'tag-subtle-grey',
+ };
+}
+
+/// A supported size for a [Tag].
+enum TagSize {
+ small,
+ regular;
+
+ /// The CSS class that applies this tag size.
+ String get _className => switch (this) {
+ TagSize.small => 'tag-small',
+ TagSize.regular => 'tag-regular',
+ };
}
diff --git a/packages/site_shared/lib/src/blog/models.dart b/packages/site_shared/lib/src/blog/models.dart
index 3a55cb93ae9..69afa19d681 100644
--- a/packages/site_shared/lib/src/blog/models.dart
+++ b/packages/site_shared/lib/src/blog/models.dart
@@ -73,7 +73,7 @@ extension type Author(Map data) {
String? resolveImageUrl(BuildContext context) {
if (image case final localImage? when localImage.isNotEmpty) {
- return context.resolveAsset('/blog/authors_images/$localImage');
+ return context.resolveAsset('/blog/author_images/$localImage');
}
if (imageUrl case final url? when url.isNotEmpty) {
return url;
diff --git a/sites/docs/firebase.json b/sites/docs/firebase.json
index 4bcb70d2efc..1198ab9e8c4 100644
--- a/sites/docs/firebase.json
+++ b/sites/docs/firebase.json
@@ -35,12 +35,29 @@
{ "source": "/ai-best-practices/:rest*", "destination": "/ai/best-practices/:rest*", "type": 301 },
{ "source": "/ai-toolkit", "destination": "/ai/ai-toolkit", "type": 301 },
{ "source": "/ai-toolkit/:rest*", "destination": "/ai/ai-toolkit/:rest*", "type": 301 },
- { "source": "/ai/flutter-ext-for-gemini", "destination": "/ai/antigravity-cli", "type": 301 },
- { "source": "/ai/gemini-cli-extension", "destination": "/ai/antigravity-cli", "type": 301 },
+ { "source": "/ai/flutter-ext-for-gemini", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/gemini-cli-extension", "destination": "/ai/get-started", "type": 301 },
{ "source": "/ai/best-practices/tool-calls-aka-function-calls", "destination": "/ai/best-practices/tool-calls", "type": 301 },
- { "source": "/tools/antigravity", "destination": "/ai/antigravity", "type": 301 },
- { "source": "/ai/gemini-code-assist", "destination": "/ai/coding-assistants", "type": 301 },
+ { "source": "/tools/antigravity", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/gemini-code-assist", "destination": "/ai/get-started", "type": 301 },
{ "source": "/ai/firebase-ai-logic", "destination": "https://firebase.google.com/docs/ai-logic/get-started?platform=flutter", "type": 301 },
+ { "source": "/ai/create-with-ai", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/create-with-ai/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/coding-assistants", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/coding-assistants/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/antigravity", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/antigravity/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/antigravity-cli", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/antigravity-cli/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/agent-skills", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/agent-skills/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/mcp-server", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/mcp-server/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/ai-rules", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/ai-rules/:rest*", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/best-practices/developer-experience", "destination": "/ai/get-started", "type": 301 },
+ { "source": "/ai/evals", "destination": "/ai/tools", "type": 301 },
+ { "source": "/ai/evals/:rest*", "destination": "/ai/tools", "type": 301 },
{ "source": "/android-release", "destination": "/deployment/android", "type": 301 },
{ "source": "/animations", "destination": "/ui/animations", "type": 301 },
{ "source": "/animations/:rest*", "destination": "/ui/animations/:rest*", "type": 301 },
@@ -211,7 +228,7 @@
{ "source": "/release/breaking-changes/win_lifecycle_process_function", "destination": "/release/breaking-changes/win-lifecycle-process-function", "type": 301 },
{ "source": "/release/archive", "destination": "/install/archive", "type": 301 },
{ "source": "/release/upgrade", "destination": "/install/upgrade", "type": 301 },
- { "source": "/resources/ai-overview", "destination": "/ai/create-with-ai", "type": 301 },
+ { "source": "/resources/ai-overview", "destination": "/ai/get-started", "type": 301 },
{ "source": "/resources/books", "destination": "/reference/learning-resources", "type": 301 },
{ "source": "/resources/bootstrap-into-dart", "destination": "https://dart.dev/learn", "type": 301 },
{ "source": "/resources/compatibility", "destination": "/release/compatibility-policy", "type": 301 },
@@ -500,6 +517,7 @@
{ "source": "/go/flutter-platform-views-windows", "destination": "https://docs.google.com/document/d/10nVF2f-QTQ5o2HMEZ1fUlBJzK4dEmhuPHTVFEgW-JRs/edit?usp=sharing&resourcekey=0-J-Lsp7xfA-Nr5EMr1av-Xg", "type": 301 },
{ "source": "/go/flutter-plugin-languages", "destination": "https://docs.google.com/document/d/1Ok_mUPgmw8_l-ynLueEKtXm2Fs48lEVB0JqZd7M7uyA/edit", "type": 301 },
{ "source": "/go/flutter-plugins-repo-migration", "destination": "https://docs.google.com/document/d/1VihAgx1e-X_H8VRc9WmUv_7qqsi81eeHrrhwhHgwtjQ/edit?usp=sharing", "type": 301 },
+ { "source": "/go/flutter-pluggable-ci-design", "destination": "https://docs.google.com/document/d/1YuIN1aHzfU90NLXQ8adp7Nd5ke3AUhX5mS8jf2eLRzo/edit?tab=t.0", "type": 301 },
{ "source": "/go/flutter-skia-ios-font-problem", "destination": "https://docs.google.com/document/d/1UkAhEKUEWNUjfKlqoBjJnx8zXrqErIqidPYjd9FBwsg", "type": 301 },
{ "source": "/go/flutter-style-updates", "destination": "https://docs.google.com/document/d/1Ao6RZmI4F8VSCpN-89o0Y6bZXhYb1-qgVGV5tALxm48", "type": 301 },
{ "source": "/go/flutter-support-multi-architecture", "destination": "https://docs.google.com/document/d/19tzWySgtgtTA99XQsjx5Pg0SFJeZKXyUlYavR0EXv8c/edit?usp=sharing", "type": 301 },
diff --git a/sites/docs/lib/_sass/_site.scss b/sites/docs/lib/_sass/_site.scss
index 99092c6a676..b5078123f61 100644
--- a/sites/docs/lib/_sass/_site.scss
+++ b/sites/docs/lib/_sass/_site.scss
@@ -11,9 +11,11 @@
@use 'base/utils';
// Styles for individual components or content types, alphabetically ordered.
+@use 'components/architecture-recommendations';
@use 'components/content';
@use 'components/expansion-list';
@use 'components/filter-search';
+@use 'components/filterable-index';
@use 'components/footer';
@use 'components/header';
@use 'components/icons';
@@ -22,7 +24,6 @@
@use 'components/os-selector';
@use 'components/pagenav';
@use 'components/platform-cards';
-@use 'components/pill';
@use 'components/sidebar';
@use 'components/side-menu';
@use 'components/trailing';
@@ -44,6 +45,7 @@
@use 'package:site_shared/_sass/components/stepper';
@use 'package:site_shared/_sass/components/summary-card';
@use 'package:site_shared/_sass/components/tabs';
+@use 'package:site_shared/_sass/components/tags';
@use 'package:site_shared/_sass/components/theming';
@use 'package:site_shared/_sass/components/tooltip';
diff --git a/sites/docs/lib/_sass/base/_root.scss b/sites/docs/lib/_sass/base/_root.scss
index 4e302a796c3..deceb9c3150 100644
--- a/sites/docs/lib/_sass/base/_root.scss
+++ b/sites/docs/lib/_sass/base/_root.scss
@@ -94,6 +94,28 @@ body {
--site-alert-warning-color: #9e6300;
--site-alert-error-color: #cd3434;
+ --site-tag-blue-bgColor: #0468D7;
+ --site-tag-blue-fgColor: #fff;
+ --site-tag-green-bgColor: #188038;
+ --site-tag-green-fgColor: #fff;
+ --site-tag-grey-bgColor: var(--site-inset-borderColor);
+ --site-tag-grey-fgColor: var(--site-base-fgColor);
+ --site-tag-subtleGrey-bgColor: var(--site-raised-bgColor-translucent);
+ --site-tag-subtleGrey-borderColor: var(--site-inset-borderColor);
+ --site-tag-subtleGrey-fgColor: var(--site-base-fgColor);
+ --site-tag-purple-bgColor: #673AB7;
+ --site-tag-purple-fgColor: #fff;
+ --site-tag-teal-bgColor: #158477;
+ --site-tag-teal-fgColor: #fff;
+ --site-tag-magenta-bgColor: #9A2E6E;
+ --site-tag-magenta-fgColor: #fff;
+ --site-tag-red-bgColor: #B71C1C;
+ --site-tag-red-fgColor: #fff;
+ --site-tag-orange-bgColor: #C44F00;
+ --site-tag-orange-fgColor: #fff;
+ --site-tag-amber-bgColor: #A65A16;
+ --site-tag-amber-fgColor: #fff;
+
&:not(.dark-mode) .dark-mode-visible {
display: none !important;
}
@@ -162,6 +184,28 @@ body {
--site-alert-warning-color: #cea11f;
--site-alert-error-color: #ff5d5d;
+ --site-tag-blue-bgColor: #0553B1;
+ --site-tag-blue-fgColor: #fff;
+ --site-tag-green-bgColor: #188038;
+ --site-tag-green-fgColor: #fff;
+ --site-tag-grey-bgColor: var(--site-inset-borderColor);
+ --site-tag-grey-fgColor: var(--site-base-fgColor);
+ --site-tag-subtleGrey-bgColor: var(--site-raised-bgColor-translucent);
+ --site-tag-subtleGrey-borderColor: var(--site-inset-borderColor);
+ --site-tag-subtleGrey-fgColor: var(--site-base-fgColor);
+ --site-tag-purple-bgColor: #673AB7;
+ --site-tag-purple-fgColor: #fff;
+ --site-tag-teal-bgColor: #106c62;
+ --site-tag-teal-fgColor: #fff;
+ --site-tag-magenta-bgColor: #84255D;
+ --site-tag-magenta-fgColor: #fff;
+ --site-tag-red-bgColor: #B71C1C;
+ --site-tag-red-fgColor: #fff;
+ --site-tag-orange-bgColor: #C44F00;
+ --site-tag-orange-fgColor: #fff;
+ --site-tag-amber-bgColor: #A65A16;
+ --site-tag-amber-fgColor: #fff;
+
.opal,
.opal span {
color: var(--opal-dark-color) !important;
diff --git a/sites/docs/lib/_sass/components/_architecture-recommendations.scss b/sites/docs/lib/_sass/components/_architecture-recommendations.scss
new file mode 100644
index 00000000000..7487390e896
--- /dev/null
+++ b/sites/docs/lib/_sass/components/_architecture-recommendations.scss
@@ -0,0 +1,5 @@
+.tag-label.recommendation-status {
+ display: flex;
+ margin: 0.5rem;
+ text-wrap: wrap;
+}
diff --git a/sites/docs/lib/_sass/components/_filterable-index.scss b/sites/docs/lib/_sass/components/_filterable-index.scss
new file mode 100644
index 00000000000..99022c2e3cb
--- /dev/null
+++ b/sites/docs/lib/_sass/components/_filterable-index.scss
@@ -0,0 +1,203 @@
+// The markup comes from the `FiltersSidebar` and `FilterSearchGroup`
+// components in `filterable_index.dart`, so everything here is keyed off
+// classes. Each page styles its own results list separately, keyed off the
+// id of that list.
+
+.filterable-index {
+ $mobile-breakpoint: 839px;
+ $sidebar-width: 220px;
+
+ display: flex;
+ flex-direction: row;
+
+ .left-col {
+ margin-right: 1rem;
+ flex: 2;
+ }
+
+ .right-col {
+ width: $sidebar-width;
+ }
+
+ // The sidebar follows the page as it scrolls on wide screens and stays within
+ // the viewport. On narrow screens, it becomes an off-canvas drawer.
+ .filter-sidebar {
+ position: sticky;
+ top: calc(var(--site-header-height) + 1rem);
+ display: flex;
+ flex-direction: column;
+ max-height: calc(100vh - var(--site-header-height) - 2rem);
+ }
+
+ .filter-group-wrapper {
+ // Scroll expanded filters independently so any sidebar footer stays visible.
+ min-height: 0;
+ border: 1px solid var(--site-inset-borderColor);
+ background-color: var(--site-inset-bgColor);
+ border-radius: var(--site-radius);
+ overflow-x: hidden;
+ overflow-y: auto;
+ overscroll-behavior: none;
+ }
+
+ // Keep the title visible while the filters scroll. The close control is only
+ // shown while the sidebar is a drawer.
+ .filter-header {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ display: grid;
+ grid-template-columns: 2.25rem 1fr 2.25rem;
+ align-items: center;
+ background-color: var(--site-raised-bgColor);
+ border-bottom: 1px solid var(--site-inset-borderColor);
+
+ .table-title {
+ grid-column: 2;
+ padding: .5rem 0;
+ text-align: center;
+ color: var(--site-base-fgColor-alt);
+ font-family: var(--site-ui-fontFamily);
+ font-weight: 600;
+ font-size: .925rem;
+ }
+
+ .close-icon {
+ display: none;
+ grid-column: 3;
+ align-items: center;
+ justify-self: center;
+ cursor: pointer;
+ padding: 0.25rem;
+ border-radius: 4px;
+ background: none;
+ border: none;
+ color: var(--site-base-fgColor-alt);
+ transition: background-color 0.2s ease, color 0.2s ease;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+ color: var(--site-primary-color);
+ }
+
+ .material-symbols {
+ font-size: 20px;
+ }
+ }
+ }
+
+ .filter-group {
+ .table-content {
+ padding: 1rem;
+ }
+
+ ul {
+ padding-left: 0;
+ margin-bottom: .5rem;
+
+ li {
+ list-style: none;
+ padding-left: 0;
+ padding-bottom: .25rem;
+ display: flex;
+ align-items: center;
+
+ label {
+ padding-left: .35rem;
+ font-size: .9rem;
+ }
+ }
+ }
+
+ h4 {
+ margin: 0 0 .5rem;
+ padding: 0;
+ }
+
+ .filter-expansion-button {
+ margin-bottom: 1rem;
+ }
+
+ .hidden {
+ display: none;
+ }
+ }
+
+ .filter-search-group {
+ display: flex;
+ flex-direction: column;
+
+ button.show-filters-button {
+ @media (min-width: $mobile-breakpoint + 1) {
+ display: none;
+ }
+ }
+
+ .label-row {
+ display: flex;
+ justify-content: space-between;
+ font-size: .925rem;
+ margin-top: .25rem;
+
+ label {
+ font-family: var(--site-ui-fontFamily);
+ color: var(--site-base-fgColor-lighter);
+
+ margin: 0 .5rem 0 0;
+ text-align: end;
+ }
+ }
+ }
+
+ // On narrow screens the sidebar becomes a drawer that slides in from the
+ // right. Opening and closing is driven entirely by the hidden
+ // `.filter-drawer-toggle` checkbox that precedes it.
+ @media (max-width: $mobile-breakpoint) {
+ flex-direction: column;
+
+ .left-col {
+ margin-right: 0;
+ }
+
+ // The drawer itself is fixed-positioned, so this column no longer needs to
+ // reserve width for it in the flex layout.
+ .right-col {
+ width: auto;
+ }
+
+ .filter-header {
+ .close-icon {
+ display: flex;
+ }
+ }
+
+ .filter-sidebar {
+ position: fixed;
+ top: var(--site-header-height);
+ bottom: 0;
+ right: -$sidebar-width;
+ width: $sidebar-width;
+ max-height: none;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--site-inset-bgColor);
+ border-left: 1px solid var(--site-inset-borderColor);
+ transition: right 0.3s ease-in-out;
+ z-index: var(--site-z-top);
+ }
+
+ .filter-drawer-toggle:checked + .filter-sidebar {
+ right: 0;
+ }
+
+ // Fill the drawer rather than floating within it as a card,
+ // and scroll on its own so any footer stays in view.
+ .filter-group-wrapper {
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ border: none;
+ border-radius: 0;
+ }
+ }
+}
diff --git a/sites/docs/lib/_sass/components/_pill.scss b/sites/docs/lib/_sass/components/_pill.scss
deleted file mode 100644
index 0ab0453b731..00000000000
--- a/sites/docs/lib/_sass/components/_pill.scss
+++ /dev/null
@@ -1,55 +0,0 @@
-@use 'sass:color';
-
-.rrec-pill {
- border-radius: 8px;
- margin: 0.5rem;
- padding: 0.5rem;
- text-align: center;
-
- // Grey by default
- background: var(--site-inset-borderColor);
- color: var(--site-base-fgColor-lighter);
-
- // Green
- &.success {
- background: color.scale(#f1fbf9, $lightness: -10%);
- color: #155723;
- }
-
- // Blue
- &.info {
- $info-bg: #e7f8ff;
- background: color.scale($info-bg, $lightness: -10%);
- color: color.scale($info-bg, $lightness: -60%);
- }
-}
-
-.pill-sm {
- border-radius: 20px;
- padding: 0.35rem 0.5rem;
- text-align: center;
- font-size: .8rem;
-
-
- // Grey by default
- background: var(--site-inset-borderColor);
- color: var(--site-base-fgColor-lighter);
-
- // TODO(parlough): Consider if these colors can be shared and if
- // compatible with dark mode.
-
- &.flutter-blue {
- color: color.scale(#E7F8FF, $lightness: 10%);
- background: #0468D7;
- }
-
- &.teal {
- color: color.scale(#B8EDE1, $lightness: 10%);
- background: #158477;
- }
-
- &.purple {
- color: color.scale(#C6BAFA, $lightness: 10%);
- background: #6200EE;
- }
-}
diff --git a/sites/docs/lib/_sass/components/_platform-cards.scss b/sites/docs/lib/_sass/components/_platform-cards.scss
index ba690e2c842..4bc24290bf1 100644
--- a/sites/docs/lib/_sass/components/_platform-cards.scss
+++ b/sites/docs/lib/_sass/components/_platform-cards.scss
@@ -34,23 +34,7 @@
}
.platform-card-tags {
- display: flex;
- flex-flow: row wrap;
- align-items: center;
- gap: 0.5rem;
-
margin-top: 0.5rem;
-
- span {
- font-size: 0.875rem;
- line-height: 1;
-
- background-color: var(--site-raised-bgColor-translucent);
- border-radius: var(--site-radius);
- border: 1px solid var(--site-inset-borderColor);
-
- padding: 0.2rem 0.4rem;
- }
}
.platform-card-details {
diff --git a/sites/docs/lib/_sass/pages/_learning-resources-index.scss b/sites/docs/lib/_sass/pages/_learning-resources-index.scss
index 0e6581adbd5..031c44eb3d9 100644
--- a/sites/docs/lib/_sass/pages/_learning-resources-index.scss
+++ b/sites/docs/lib/_sass/pages/_learning-resources-index.scss
@@ -1,235 +1,6 @@
@use 'package:site_shared/_sass/base/mixins';
-#resource-filter-group-wrapper {
- border: 1px solid var(--site-inset-borderColor);
- background-color: var(--site-inset-bgColor);
- border-radius: var(--site-radius);
- overflow: hidden;
- position: sticky;
- top: calc(var(--site-header-height) + 1rem);
-}
-
-.filter-header {
- display: none;
- position: absolute;
- top: 0;
- right: 0;
- padding: 0.75rem 1rem;
- z-index: 10;
-}
-
-.close-icon {
- cursor: pointer;
- padding: 0.25rem;
- border-radius: 4px;
- background: none;
- border: none;
- color: var(--site-base-fgColor-alt);
- transition: all 0.2s ease;
- position: absolute;
- right: 1rem;
- top: 75%;
- transform: translateY(-50%);
-
- &:hover {
- background-color: rgba(0, 0, 0, 0.1);
- color: var(--site-primary-color);
- }
-
- .material-symbols {
- font-size: 20px;
- }
-}
-
-// Mobile screen customizations.
-@media (max-width: 839px) {
- .filter-header {
- display: block;
- }
-
- #resource-filter-group-wrapper {
- position: fixed;
- top: var(--site-header-height);
- bottom: 0;
- right: -220px;
- width: 220px;
- border-bottom: none;
- height: 100%;
- border-radius: 0;
- transition: right 0.3s ease-in-out;
- z-index: 1000;
- }
-
- #open-filter-toggle:not(:checked)+#resource-filter-group-wrapper {
- right: -220px;
- }
-
- #open-filter-toggle:checked+#resource-filter-group-wrapper {
- right: 0;
- }
-}
-
-//Desktop screens
-@media (min-width: 840px) {
- #resource-filter-group {
- position: static !important;
- right: auto !important;
- }
-
- .filter-header {
- display: none !important;
- }
-}
-
-#resource-index-content {
- display: flex;
- flex-direction: row;
-
- .left-col {
- margin-right: 1rem;
- flex: 2;
- }
-
- .right-col {
- width: 220px;
-
- @media (max-width: 840px) {
- position: fixed;
- top: var(--site-header-height);
- bottom: 0;
- right: -15rem;
- box-shadow: 0 6px 18px 0 rgba(0, 0, 0, 0.2);
- border-radius: 0.4rem;
- width: 220px;
-
- @keyframes slidein {
- 0% {
- right: -10rem;
- }
-
- 100% {
- right: 0;
- }
- }
-
- &.show {
- animation-duration: 500ms;
- animation-delay: 200ms;
- animation-name: slidein;
- animation-iteration-count: 1;
- animation-timing-function: ease;
- animation-fill-mode: forwards;
- }
- }
- }
-}
-
-#resource-filter-group {
- .table-title {
- text-align: center;
- color: var(--site-base-fgColor-alt);
- background-color: var(--site-raised-bgColor);
- font-family: var(--site-ui-fontFamily);
- font-weight: 600;
- font-size: .925rem;
- padding: .5rem;
- border-bottom: 1px solid var(--site-inset-borderColor);
- }
-
- .table-content {
- padding: 1rem;
- }
-
- ul {
- padding-left: 0;
- margin-bottom: .5rem;
-
- li {
- list-style: none;
- padding-left: 0;
- padding-bottom: .25rem;
- display: flex;
- align-items: center;
-
- label {
- padding-left: .35rem;
- font-size: .9rem;
- }
- }
- }
-
- h4 {
- margin: 0 0 .5rem;
- padding: 0;
- }
-
- button {
- color: var(--site-primary-color);
- margin: 0 0 2rem;
- padding: .5rem 0 1rem 1rem;
-
- &:hover {
- color: var(--site-onPrimary-color);
- }
- }
-
- @media (max-width: 840px) {
- border-bottom: none;
- height: 100%;
- border-radius: 0;
-
- .table-title {
- background-color: var(--site-raised-bgColor-translucent);
- }
- }
-
- .hidden {
- display: none;
- }
-}
-
-#resource-search-group {
- display: flex;
- flex-direction: column;
-
- button.show-filters-button {
- @media (min-width: 840px) {
- display: none;
- }
- }
-
- .label-row {
- display: flex;
- justify-content: space-between;
- font-size: .925rem;
-
- label {
- font-family: var(--site-ui-fontFamily);
- color: var(--site-base-fgColor-lighter);
-
- padding: .25rem 1rem 0 0;
- margin: 0;
- text-align: end;
- }
-
- button {
- padding: .25rem;
- color: var(--site-primary-color);
- display: flex;
- align-items: center;
-
- &:hover {
- color: var(--site-onPrimary-color-light);
- }
-
- &:disabled,
- &[disabled] {
- color: var(--site-inset-bgColor-translucent);
- cursor: default;
- }
- }
- }
-}
+// Shared styles live in `_filterable-index.scss`.
#all-resources-grid {
margin-block-start: 1rem;
diff --git a/sites/docs/lib/src/components/pages/architecture_recommendations.dart b/sites/docs/lib/src/components/pages/architecture_recommendations.dart
index 7ea371b7176..39bbd04db7d 100644
--- a/sites/docs/lib/src/components/pages/architecture_recommendations.dart
+++ b/sites/docs/lib/src/components/pages/architecture_recommendations.dart
@@ -5,6 +5,7 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';
+import 'package:site_shared/components/common/tags.dart';
import 'package:site_shared/markdown.dart';
class ArchitectureRecommendations extends CustomComponentBase {
@@ -67,15 +68,21 @@ class ArchitectureRecommendations extends CustomComponentBase {
td([
DashMarkdown(inline: true, content: rec.recommendation),
switch (rec.confidence) {
- 'strong' => const div(classes: 'rrec-pill success', [
- .text('Strongly recommend'),
- ]),
- 'recommend' => const div(classes: 'rrec-pill info', [
- .text('Recommend'),
- ]),
- _ => const div(classes: 'rrec-pill', [
- .text('Conditional'),
- ]),
+ 'strong' => const Tag(
+ 'Strongly recommend',
+ color: TagColor.green,
+ classes: ['recommendation-status'],
+ ),
+ 'recommend' => const Tag(
+ 'Recommend',
+ color: TagColor.blue,
+ classes: ['recommendation-status'],
+ ),
+ _ => const Tag(
+ 'Conditional',
+ color: TagColor.grey,
+ classes: ['recommendation-status'],
+ ),
},
]),
td([
diff --git a/sites/docs/lib/src/components/pages/filterable_index.dart b/sites/docs/lib/src/components/pages/filterable_index.dart
new file mode 100644
index 00000000000..5a96dab8d91
--- /dev/null
+++ b/sites/docs/lib/src/components/pages/filterable_index.dart
@@ -0,0 +1,172 @@
+// Copyright 2025 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Reusable shell components for filterable index pages.
+///
+/// The pages provide their own filter controls and results list,
+/// but the surrounding layout, search field, and sidebar are the same,
+/// and are styled by `_filterable-index.scss`.
+library;
+
+import 'package:jaspr/dom.dart';
+import 'package:jaspr/jaspr.dart';
+import 'package:site_shared/components/common/button.dart';
+import 'package:site_shared/components/common/material_icon.dart';
+import 'package:site_shared/components/common/search.dart';
+import 'package:site_shared/components/utils/global_event_listener.dart';
+import 'package:universal_web/js_interop.dart';
+import 'package:universal_web/web.dart' as web;
+
+/// The right-hand column of a filterable index page.
+///
+/// Renders [children] within a card titled 'Filter by', with [footer] pinned
+/// below it. On narrow screens the card and footer become a drawer that slides
+/// in from the right, toggled by the [FilterSearchGroup] search field.
+class FiltersSidebar extends StatelessComponent {
+ const FiltersSidebar({
+ required this.drawerToggleId,
+ required this.children,
+ this.footer = const [],
+ super.key,
+ });
+
+ /// The ID of the checkbox that toggles the drawer on narrow screens.
+ final String drawerToggleId;
+
+ /// The filter controls, rendered within the filter card.
+ final List children;
+
+ /// Optional page-specific content, rendered below the filter card.
+ final List footer;
+
+ @override
+ Component build(BuildContext context) {
+ return div(classes: 'right-col', [
+ input(
+ type: InputType.checkbox,
+ id: drawerToggleId,
+ classes: 'filter-drawer-toggle',
+ attributes: const {'hidden': 'true'},
+ ),
+ div(classes: 'filter-sidebar', [
+ div(classes: 'filter-group-wrapper', [
+ div(classes: 'filter-group', [
+ div(classes: 'filter-header', [
+ const div(classes: 'table-title', [.text('Filter by')]),
+ label(
+ attributes: {
+ 'for': drawerToggleId,
+ 'aria-hidden': 'true',
+ },
+ classes: 'close-icon',
+ const [MaterialIcon('close')],
+ ),
+ ]),
+ ...children,
+ ]),
+ ]),
+ ...footer,
+ ]),
+ ]);
+ }
+}
+
+/// The search field of a filterable index page,
+/// followed by the page-specific [children], such as a result count.
+///
+/// On narrow screens, the field also contains the button
+/// that opens the [FiltersSidebar] drawer.
+class FilterSearchGroup extends StatelessComponent {
+ const FilterSearchGroup({
+ required this.drawerToggleId,
+ required this.searchId,
+ required this.placeholder,
+ required this.label,
+ required this.value,
+ required this.onInput,
+ this.children = const [],
+ super.key,
+ });
+
+ /// The ID of the checkbox that toggles the [FiltersSidebar] drawer.
+ final String drawerToggleId;
+
+ /// The ID of the search field, so the page can label it.
+ final String searchId;
+
+ /// The placeholder text displayed in the search field.
+ final String placeholder;
+
+ /// The accessibility label for the search field.
+ final String label;
+
+ /// The current value of the search field.
+ final String value;
+
+ /// Callback triggered when the search field input changes.
+ final void Function(String) onInput;
+
+ /// Content rendered below the search field.
+ final List children;
+
+ @override
+ Component build(BuildContext context) {
+ return div(classes: 'filter-search-group', [
+ SearchBar(
+ placeholder: placeholder,
+ label: label,
+ value: value,
+ id: searchId,
+ onInput: onInput,
+ trailing: _DrawerToggleButton(drawerToggleId),
+ ),
+ ...children,
+ ]);
+ }
+}
+
+/// The button that opens the filter drawer on narrow screens.
+///
+/// Also closes the drawer again when anything outside of it is clicked.
+class _DrawerToggleButton extends StatelessComponent {
+ const _DrawerToggleButton(this._drawerToggleId);
+
+ /// The ID of the checkbox that this button toggles.
+ final String _drawerToggleId;
+
+ /// The checkbox that controls the drawer, if present.
+ web.HTMLInputElement? get _toggle =>
+ web.document.getElementById(_drawerToggleId) as web.HTMLInputElement?;
+
+ @override
+ Component build(BuildContext context) {
+ return GlobalEventListener(
+ onClick: (event) {
+ final toggle = _toggle;
+ if (toggle == null || !toggle.checked) return;
+
+ final target = event.target;
+ if (target == null || !target.isA()) return;
+
+ final element = target as web.Element;
+ // Keep the drawer open for clicks anywhere within it,
+ // including in the sidebar footer.
+ if (element.closest('.filter-sidebar') == null &&
+ element.closest('.show-filters-button') == null) {
+ toggle.checked = false;
+ }
+ },
+ Button(
+ icon: 'filter_list',
+ classes: const ['show-filters-button'],
+ attributes: const {'aria-label': 'Show filters'},
+ onClick: () {
+ if (_toggle case final toggle?) {
+ toggle.checked = !toggle.checked;
+ }
+ },
+ ),
+ );
+ }
+}
diff --git a/sites/docs/lib/src/components/pages/learning_resource_filters.dart b/sites/docs/lib/src/components/pages/learning_resource_filters.dart
index dcd726c134d..a6b5badfa3c 100644
--- a/sites/docs/lib/src/components/pages/learning_resource_filters.dart
+++ b/sites/docs/lib/src/components/pages/learning_resource_filters.dart
@@ -7,38 +7,47 @@ import 'dart:math';
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:site_shared/analytics.dart';
-import 'package:site_shared/components/common/material_icon.dart';
-import 'package:site_shared/components/common/search.dart';
-import 'package:site_shared/components/utils/global_event_listener.dart';
+import 'package:site_shared/components/common/button.dart';
import 'package:universal_web/js_interop.dart';
import 'package:universal_web/web.dart' as web;
import '../../models/learning_resource_model.dart';
+import 'filterable_index.dart';
import 'learning_resource_filters_sidebar.dart';
+/// The search controls and result summary for the learning resources index.
@client
class LearningResourceFilters extends StatefulComponent {
const LearningResourceFilters({super.key});
+ /// The ID of the checkbox that toggles the filter drawer on narrow screens.
+ static const String drawerToggleId = 'learning-resource-filter-toggle';
+
@override
State createState() =>
_LearningResourceFiltersState();
}
class _LearningResourceFiltersState extends State {
- String searchQuery = '';
+ /// The filters selected in the learning resource sidebar.
+ static LearningResourceFiltersNotifier get _filters =>
+ LearningResourceFiltersSidebar.filters;
+
+ /// The learning resources reconstructed from the rendered resource cards.
+ final List _resources = [];
- FiltersNotifier get filters => LearningResourceFiltersSidebar.filters;
+ /// The current search query.
+ String _searchQuery = '';
- final List resources = [];
- int filteredResourcesCount = 0;
+ /// The number of resources matching the active search and filters.
+ int _filteredResourcesCount = 0;
@override
void initState() {
super.initState();
if (kIsWeb) {
- filters.addListener(setFilters);
+ _filters.addListener(_setFilters);
final resourceGrid = web.document.getElementById('all-resources-grid');
if (resourceGrid == null) {
@@ -46,16 +55,17 @@ class _LearningResourceFiltersState extends State {
}
final resourceCards = resourceGrid.querySelectorAll('.card');
- recreateResources(resourceCards);
- shuffleCards(resourceGrid);
+ _recreateResources(resourceCards);
+ _shuffleCards(resourceGrid);
}
}
- void recreateResources(web.NodeList resourceCards) {
+ /// Populates [_resources] from [resourceCards] and registers click analytics.
+ void _recreateResources(web.NodeList resourceCards) {
for (var i = 0; i < resourceCards.length; i++) {
final element = resourceCards.item(i) as web.Element;
final info = LearningResource.fromElement(element);
- resources.add(info);
+ _resources.add(info);
element.addEventListener(
'click',
@@ -67,10 +77,11 @@ class _LearningResourceFiltersState extends State {
}).toJS,
);
}
- filteredResourcesCount = resources.length;
+ _filteredResourcesCount = _resources.length;
}
- void shuffleCards(web.Element container) {
+ /// Randomizes the order of the resource cards in [container].
+ void _shuffleCards(web.Element container) {
final r = Random();
final elements = container.childNodes;
for (var i = elements.length; i > 0; i--) {
@@ -91,12 +102,12 @@ class _LearningResourceFiltersState extends State {
/// searchQuery = '...';
/// });
/// ```
- void setFilters([void Function()? callback]) {
+ void _setFilters([void Function()? callback]) {
setState(callback ?? () {});
- final resourcesToShow = filters.filterResources(resources, searchQuery);
- filteredResourcesCount = resourcesToShow.length;
- for (final info in resources) {
+ final resourcesToShow = _filters.filterResources(_resources, _searchQuery);
+ _filteredResourcesCount = resourcesToShow.length;
+ for (final info in _resources) {
final element =
web.document.getElementById(info.name) as web.HTMLElement?;
if (element == null) {
@@ -114,78 +125,51 @@ class _LearningResourceFiltersState extends State {
@override
void dispose() {
if (kIsWeb) {
- filters.removeListener(setFilters);
+ _filters.removeListener(_setFilters);
}
super.dispose();
}
@override
Component build(BuildContext context) {
- return div(id: 'resource-search-group', classes: 'chip-filters-group', [
- SearchBar(
- placeholder: 'Try "button" or "networking"...',
- label: 'Search learning resources by name and category',
- value: searchQuery,
- id: 'resource-search',
- onInput: (value) {
- setFilters(() {
- searchQuery = value;
- });
- },
- trailing: GlobalEventListener(
- onClick: (event) {
- final target = event.target as web.Element?;
- // If clicking outside the filters or toggle, close the filters.
- if (target?.closest('#resource-filter-group-wrapper') == null &&
- target?.closest('.show-filters-button') == null) {
- final toggle = web.document.getElementById(
- 'open-filter-toggle',
- ) as web.HTMLInputElement?;
- toggle?.checked = false;
- }
- },
- button(
- classes: 'icon-button show-filters-button',
- onClick: () {
- final toggle = web.document.getElementById(
- 'open-filter-toggle',
- ) as web.HTMLInputElement?;
- toggle?.checked = !toggle.checked;
- },
+ return FilterSearchGroup(
+ drawerToggleId: LearningResourceFilters.drawerToggleId,
+ searchId: 'resource-search',
+ placeholder: 'Try "button" or "networking"...',
+ label: 'Search learning resources by name and category',
+ value: _searchQuery,
+ onInput: (value) {
+ _setFilters(() {
+ _searchQuery = value;
+ });
+ },
+ children: [
+ div(classes: 'label-row', [
+ label(
+ attributes: {'for': 'resource-search'},
[
- const MaterialIcon('filter_list'),
+ const .text('Showing '),
+ span([.text('$_filteredResourcesCount')]),
+ const .text(' / '),
+ span([.text('${_resources.length}')]),
],
),
- ),
- ),
- div(classes: 'label-row', [
- label(
- attributes: {'for': 'resource-search'},
- [
- const .text('Showing '),
- span([.text('$filteredResourcesCount')]),
- const .text(' / '),
- span([.text('${resources.length}')]),
- ],
- ),
- button(
- attributes: {
- if (searchQuery.isEmpty &&
- filters.selectedTags.isEmpty &&
- filters.selectedTypes.isEmpty)
- 'disabled': 'true',
- },
- onClick: () {
- // No setState needed, since resetting filters will trigger it.
- searchQuery = '';
- filters.reset();
- },
- [
- const MaterialIcon('close_small'),
- const span([.text('Clear filters')]),
- ],
- ),
- ]),
- ]);
+ Button(
+ icon: 'close_small',
+ content: 'Clear filters',
+ size: ButtonSize.compact,
+ disabled:
+ _searchQuery.isEmpty &&
+ _filters.selectedTags.isEmpty &&
+ _filters.selectedTypes.isEmpty,
+ onClick: () {
+ // No setState needed, since resetting filters will trigger it.
+ _searchQuery = '';
+ _filters.reset();
+ },
+ ),
+ ]),
+ ],
+ );
}
}
diff --git a/sites/docs/lib/src/components/pages/learning_resource_filters_sidebar.dart b/sites/docs/lib/src/components/pages/learning_resource_filters_sidebar.dart
index cf512193009..d662418fed5 100644
--- a/sites/docs/lib/src/components/pages/learning_resource_filters_sidebar.dart
+++ b/sites/docs/lib/src/components/pages/learning_resource_filters_sidebar.dart
@@ -5,12 +5,14 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:site_shared/analytics.dart';
-import 'package:site_shared/components/common/material_icon.dart';
+import 'package:site_shared/components/common/button.dart';
import 'package:site_shared/util.dart';
import '../../models/learning_resource_model.dart';
+import 'filterable_index.dart';
import 'learning_resource_filters.dart';
+/// The subject and type filters for the learning resources index.
@client
class LearningResourceFiltersSidebar extends StatelessComponent {
const LearningResourceFiltersSidebar({super.key});
@@ -19,103 +21,91 @@ class LearningResourceFiltersSidebar extends StatelessComponent {
///
/// This is static so that [LearningResourceFilters] can access it,
/// since both client components don't share a common ancestor.
- static FiltersNotifier filters = FiltersNotifier();
+ static final LearningResourceFiltersNotifier filters =
+ LearningResourceFiltersNotifier();
@override
Component build(BuildContext context) {
- return div(classes: 'right-col', [
- const input(
- type: InputType.checkbox,
- id: 'open-filter-toggle',
- attributes: {'hidden': 'true'},
- ),
- div(id: 'resource-filter-group-wrapper', [
- div(id: 'resource-filter-group', [
- const div(classes: 'filter-header', [
- label(
- attributes: {'for': 'open-filter-toggle', 'aria-hidden': 'true'},
- classes: 'close-icon',
- [MaterialIcon('close')],
- ),
- ]),
- const div(classes: 'table-title', [.text('Filter by')]),
- ListenableBuilder(
- listenable: filters,
- builder: (context) {
- return div(classes: 'table-content', [
- const h4([.text('Subject')]),
- ul(classes: filters.tagsExpanded ? '' : 'collapsed', [
- for (final (index, tag) in LearningResourceTag.values.indexed)
- li(
- classes: [
- if (!filters.tagsExpanded && index > 3) 'hidden',
- ].toClasses,
- [
- input(
- type: InputType.checkbox,
- attributes: {
- 'role': 'checkbox',
- 'name': 'filter-${tag.name}',
- },
- id: 'filter-${tag.name}',
- checked: filters.selectedTags.contains(tag),
- onChange: (checked) {
- filters.setTag(tag, checked as bool);
- },
- ),
- label(
- attributes: {'for': 'filter-${tag.name}'},
- [.text(tag.label)],
- ),
- ],
- ),
- ]),
- button(onClick: filters.toggleTagsExpanded, [
- span(classes: 'label', [
- .text(filters.tagsExpanded ? 'Less' : 'More'),
- ]),
- MaterialIcon(
- filters.tagsExpanded ? 'expand_less' : 'expand_more',
- ),
- ]),
- const h4([.text('Type')]),
- ul([
- for (final type in LearningResourceType.values)
- li([
+ return FiltersSidebar(
+ drawerToggleId: LearningResourceFilters.drawerToggleId,
+ children: [
+ ListenableBuilder(
+ listenable: filters,
+ builder: (context) {
+ return div(classes: 'table-content', [
+ const h4([.text('Subject')]),
+ ul([
+ for (final (index, tag) in LearningResourceTag.values.indexed)
+ li(
+ classes: [
+ if (!filters.tagsExpanded && index > 3) 'hidden',
+ ].toClasses,
+ [
input(
type: InputType.checkbox,
- attributes: {
- 'role': 'checkbox',
- 'name': 'filter-${type.name}',
- },
- id: 'filter-${type.name}',
- checked: filters.selectedTypes.contains(type),
+ attributes: {'name': 'resource-filter-${tag.name}'},
+ id: 'resource-filter-${tag.name}',
+ checked: filters.selectedTags.contains(tag),
onChange: (checked) {
- filters.setType(type, checked as bool);
+ filters.setTag(tag, checked as bool);
},
),
label(
- attributes: {'for': 'filter-${type.name}'},
- [.text(type.label)],
+ attributes: {'for': 'resource-filter-${tag.name}'},
+ [.text(tag.label)],
),
- ]),
- ]),
- ]);
- },
- ),
- ]),
- ]),
- ]);
+ ],
+ ),
+ ]),
+ Button(
+ content: filters.tagsExpanded ? 'Less' : 'More',
+ classes: const ['filter-expansion-button'],
+ size: ButtonSize.compact,
+ trailingIcon: filters.tagsExpanded
+ ? 'expand_less'
+ : 'expand_more',
+ onClick: filters.toggleTagsExpanded,
+ ),
+ const h4([.text('Type')]),
+ ul([
+ for (final type in LearningResourceType.values)
+ li([
+ input(
+ type: InputType.checkbox,
+ attributes: {'name': 'resource-filter-${type.name}'},
+ id: 'resource-filter-${type.name}',
+ checked: filters.selectedTypes.contains(type),
+ onChange: (checked) {
+ filters.setType(type, isSelected: checked as bool);
+ },
+ ),
+ label(
+ attributes: {'for': 'resource-filter-${type.name}'},
+ [.text(type.label)],
+ ),
+ ]),
+ ]),
+ ]);
+ },
+ ),
+ ],
+ );
}
}
-/// Notifier to manage the state of the filters.
-class FiltersNotifier extends ChangeNotifier {
- Set selectedTags = {};
- Set selectedTypes = {};
+/// Stores the selected learning resource filters and
+/// notifies listeners when they change.
+final class LearningResourceFiltersNotifier extends ChangeNotifier {
+ /// The currently selected subject tags.
+ final Set selectedTags = {};
+
+ /// The currently selected resource types.
+ final Set selectedTypes = {};
+ /// Whether all subject tags are visible.
bool tagsExpanded = false;
+ /// Updates whether [tag] is selected and notifies listeners.
void setTag(LearningResourceTag tag, bool isSelected) {
if (isSelected) {
selectedTags.add(tag);
@@ -133,7 +123,8 @@ class FiltersNotifier extends ChangeNotifier {
notifyListeners();
}
- void setType(LearningResourceType type, bool isSelected) {
+ /// Updates whether [type] is selected and notifies listeners.
+ void setType(LearningResourceType type, {required bool isSelected}) {
if (isSelected) {
selectedTypes.add(type);
@@ -150,17 +141,20 @@ class FiltersNotifier extends ChangeNotifier {
notifyListeners();
}
+ /// Toggles whether all subject tags are visible.
void toggleTagsExpanded() {
tagsExpanded = !tagsExpanded;
notifyListeners();
}
+ /// Clears all selected tags and resource types.
void reset() {
selectedTags.clear();
selectedTypes.clear();
notifyListeners();
}
+ /// Returns the resources matching [searchQuery] and the selected filters.
Set filterResources(
List resources,
String searchQuery,
diff --git a/sites/docs/lib/src/components/pages/learning_resource_index.dart b/sites/docs/lib/src/components/pages/learning_resource_index.dart
index 3141659ba0e..311ef620a49 100644
--- a/sites/docs/lib/src/components/pages/learning_resource_index.dart
+++ b/sites/docs/lib/src/components/pages/learning_resource_index.dart
@@ -5,7 +5,7 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';
-import 'package:site_shared/util.dart';
+import 'package:site_shared/components/common/tags.dart';
import '../../models/learning_resource_model.dart';
import 'learning_resource_filters.dart';
@@ -30,8 +30,8 @@ final class LearningResourceIndex extends StatelessComponent {
}
}
- return div(id: 'resource-index-content', [
- div(classes: 'left-col', id: 'resource-index-main-content', [
+ return div(classes: 'filterable-index', [
+ div(classes: 'left-col', [
const LearningResourceFilters(),
section(classes: 'card-grid', id: 'all-resources-grid', [
for (final item in learningResources) _ResourceCard(item),
@@ -65,21 +65,15 @@ final class _ResourceCard extends StatelessComponent {
img(src: imageUrl, alt: ''),
]),
div(classes: 'card-leading', [
- span(
- classes: [
- 'pill-sm',
- switch (resource.type) {
- 'codelab' || 'workshop' => 'flutter-blue',
- 'quickstart' || 'demo' => 'purple',
- _ => 'teal',
- },
- ].toClasses,
- [
- .text(
- resource.type.substring(0, 1).toUpperCase() +
- resource.type.substring(1),
- ),
- ],
+ Tag(
+ resource.type.substring(0, 1).toUpperCase() +
+ resource.type.substring(1),
+ color: switch (resource.type) {
+ 'codelab' || 'workshop' => TagColor.blue,
+ 'quickstart' || 'demo' => TagColor.purple,
+ _ => TagColor.teal,
+ },
+ size: TagSize.small,
),
_iconForLabel(resource.link?.label ?? ''),
]),
diff --git a/sites/docs/lib/src/components/pages/platforms_grid.dart b/sites/docs/lib/src/components/pages/platforms_grid.dart
index d41f065bd38..191d8f1203c 100644
--- a/sites/docs/lib/src/components/pages/platforms_grid.dart
+++ b/sites/docs/lib/src/components/pages/platforms_grid.dart
@@ -7,6 +7,7 @@ import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';
import 'package:site_shared/components/common/button.dart';
import 'package:site_shared/components/common/material_icon.dart';
+import 'package:site_shared/components/common/tags.dart';
import 'package:site_shared/markdown.dart';
class PlatformsGrid extends CustomComponentBase {
@@ -89,9 +90,17 @@ class PlatformCard extends CustomComponentBase {
href: deployToLink,
),
]),
- div(classes: 'platform-card-tags', [
- for (final a in arch) span([.text(a)]),
- ]),
+ Tags(
+ [
+ for (final architecture in arch)
+ Tag(
+ architecture,
+ color: .subtleGrey,
+ size: .small,
+ ),
+ ],
+ classes: const ['platform-card-tags'],
+ ),
div(classes: 'platform-card-details', [
span([
diff --git a/sites/docs/src/content/ai/agent-skills.md b/sites/docs/src/content/ai/agent-skills.md
deleted file mode 100644
index dd0dc4c9ea2..00000000000
--- a/sites/docs/src/content/ai/agent-skills.md
+++ /dev/null
@@ -1,85 +0,0 @@
----
-title: Agent skills for Flutter and Dart
-shortTitle: Agent skills
-description: >-
- Learn how to give AI agents new capabilities and expertise
- using agent skills.
----
-
-This guide covers how to enhance your AI agents and coding assistants
-with domain-specific capabilities using agent skills.
-
-## Overview
-
-AI agents can write Flutter and Dart code, but they sometimes are unaware of
-tools and best practices that professional developers use.
-
-[Agent skills](https://agentskills.io/) help solve this problem by providing a
-standardized way to give your AI agent a set of task-oriented blueprints to
-follow. By giving the agent actual domain expertise and repeatable workflows,
-you drastically reduce mistakes and can enforce consistent patterns.
-
-
-Skills use what we call "progressive disclosure," which is similar to deferred
-loading in Flutter. Instead of loading every single instruction into the context
-window up front, the agent only reads the metadata first. It pulls in the heavy,
-detailed instructions only when it actually needs them for the task at hand.
-
-## Official repositories
-
-The Dart and Flutter teams maintain official repositories packed with skills
-tailored specifically for our frameworks.
-
-* **[dart-lang/skills](https://github.com/dart-lang/skills)**: Provides skills
- for Dart development. Use these to generate unit tests, resolve package
- dependencies, and fix static analysis errors.
-* **[flutter/agent-plugins](https://github.com/flutter/agent-plugins)**:
- Provides skills for Flutter development.
- These skills help the AI build responsive layouts,
- set up declarative routing, and implement JSON serialization.
-
-## Install agent skills
-
-The recommended way to install skills for your project is by following the
-[Get started with AI](/ai/get-started) guide, which provides step-by-step
-instructions on how to install the official Flutter and Dart agent plugins for
-Claude Code, Codex, Antigravity, Cursor, and other tools. These plugins act as a
-complete package, bundling agent skills with the configuration for the Dart and
-Flutter MCP server.
-
-### Universal agent installation
-
-By default, compatible AI agents discover agent skills within the
-`.agents/skills` directory of your project workspace.
-
-To download and manage skills in that folder, you can use the `skills` CLI tool.
-It's distributed through npm, so you need [Node.js](https://nodejs.org/)
-installed to run it with `npx`.
-
-To install the official Flutter skills:
-
-```bash
-npx skills add flutter/agent-plugins --skill '*' --agent universal --yes
-```
-
-And to install the official Dart skills:
-
-```bash
-npx skills add dart-lang/skills --skill '*' --agent universal --yes
-```
-
-Running these commands automatically creates the `.agents/skills`
-directory and downloads the requested skills into your project.
-
-## Manage and verify agent skills
-
-For more details on available skills, updating, and contributing, see the
-[Dart skills repository](https://github.com/dart-lang/skills) and the
-[Flutter agent-plugins repository](https://github.com/flutter/agent-plugins).
-
-:::tip
-Once you've added skills to your project, try asking your AI agent to review
-your installed skills. You can ask, "Which of my installed skills
-can help me with [your current task]?" or "Summarize the capabilities of the
-skills I have available."
-:::
diff --git a/sites/docs/src/content/ai/ai-rules.md b/sites/docs/src/content/ai/ai-rules.md
deleted file mode 100644
index b99bd25de1c..00000000000
--- a/sites/docs/src/content/ai/ai-rules.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: AI rules
-shortTitle: AI rules
-description: >-
- Learn how to add AI rules to tools that accelerate your
- development workflow.
----
-
-This guide covers how you can leverage AI rules to
-streamline your Flutter and Dart development.
-
-:::note Agent skills
-While rules configure the default behavior for all tasks,
-you can use [Agent skills](/ai/agent-skills) to give the AI specific tools
-and instructions for discrete tasks.
-:::
-
-## Overview
-
-AI-powered editors use rules files to provide context and
-instructions to an underlying LLM. These files help you:
-
-* Customize AI behavior to your team's needs.
-* Enforce project best practices for code style and
- design.
-* Provide critical project context to the AI.
-
-The Flutter project provides several versions of the rules file to accommodate
-different tool limits:
-
-* [`rules.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules.md):
- The comprehensive master rule set.
-* [`rules_10k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_10k.md):
- A condensed version (<10k chars) for tools with stricter context limits.
-* [`rules_4k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_4k.md):
- A highly concise version (<4k chars) for limited contexts.
-* [`rules_1k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_1k.md):
- An ultra-compact version (<1k chars) for very strict limits.
-
-
-
- Download the Flutter and Dart rules template
-
-
-## Device and editor specific limits
-
-Different AI coding assistants and tools have varying limits for their "rules"
-or "custom instructions" files. *Last updated: 2026-01-05.*
-
-| Tool / Product | Rules file / Feature | Limit (soft / hard) | Documentation |
-|:---|:---|:---|:---|
-| Antigravity (Google) | `.agent/rules/.md` | 12,000 chars (Hard) | [Configure rules][antigravity] |
-| Claude Code | `CLAUDE.md` | No Hard Limit | [Claude Code Docs](https://code.claude.com/docs/en/memory) |
-| Cursor | `AGENTS.md` | No Hard Limit | [Cursor Docs](https://cursor.com/docs/context/rules) |
-| Gemini CLI | `GEMINI.md` | 1M+ Tokens (Context) | [Gemini CLI Docs](https://cloud.google.com/vertex-ai/generative-ai/docs/long-context) |
-| GitHub Copilot | `.github/copilot-instructions.md` | ~4k chars | [GitHub Copilot Docs](https://docs.github.com/en/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot) |
-| JetBrains AI (Junie) | `.junie/guidelines.md` | No Hard Limit | [JetBrains AI Docs](https://www.jetbrains.com/help/junie/get-started-with-junie.html) |
-| VS Code | `.instructions.md` | Unknown | [Configure instructions][vs-code] |
-
-{:.table .table-striped}
-
-:::note Support is evolving
-Support for rules files is still evolving.
-Please check the documentation for your specific development environment for
-the most up-to-date naming conventions and instructions.
-:::
-
-[copilot]: https://code.visualstudio.com/docs/copilot/customization/custom-instructions#_use-a-githubcopilotinstructionsmd-file
-[claude]: https://www.anthropic.com/engineering/claude-code-best-practices#1-customize-your-setup
-[cursor]: https://cursor.com/docs/context/rules
-[firebase]: https://firebase.google.com/docs/studio/set-up-gemini#custom-instructions
-[gemini-cli]: https://geminicli.com/docs/cli/gemini-md
-[antigravity]: https://antigravity.google/docs/rules-workflows
-[junie]: https://www.jetbrains.com/help/junie/customize-guidelines.html
-[vs-code]: https://code.visualstudio.com/docs/copilot/customization/custom-instructions#_use-instructionsmd-files
-[windsurf]: https://docs.windsurf.com/windsurf/cascade/memories#rules
-
-## Create rules for your editor
-
-You can adapt our Flutter and Dart rules template for your
-specific environment. To do so, follow these steps:
-
-1. Download the Flutter and Dart rules template:
- rules.md
-
-1. In an LLM like [Gemini][], attach the
- `rules.md` file that you downloaded in
- the last step.
-
-1. Provide a prompt to reformat the file for your desired
- editor.
-
- Example prompt:
-
- ```text
- Convert the attached rules.md file
- into a guidelines.md file for Gemini CLI. Make sure
- to use the styles required for a guidelines.md file.
- ```
-
-1. Review the LLM's output and make any necessary
- adjustments.
-
-1. Follow your environment's instructions to add the new
- rules file. This may involve adding to an existing file
- or creating a new one.
-
-1. Verify that your AI assistant is using the new rules to
- guide its responses.
-
-[Gemini]: https://gemini.google.com/
diff --git a/sites/docs/src/content/ai/antigravity-cli.md b/sites/docs/src/content/ai/antigravity-cli.md
deleted file mode 100644
index a4c6fa31a71..00000000000
--- a/sites/docs/src/content/ai/antigravity-cli.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-title: Antigravity CLI
-shortTitle: CLI
-description: Learn how to use the Antigravity CLI for Dart and Flutter.
----
-
-## Introduction
-
-[Antigravity CLI][] (using the executable command `agy`)
-is a terminal-based interface (TUI)
-for the **Antigravity 2.0** agentic coding assistant.
-It connects directly to your workspace
-and leverages the **Dart and Flutter MCP server**
-to help you build, modify, test, and release Flutter applications
-from the command line.
-
-The Antigravity CLI replaces the legacy Gemini CLI.
-
-[Antigravity CLI]: https://antigravity.google/docs/cli
-
-## Installation
-
-Install the Antigravity CLI on your machine by running the
-appropriate command for your platform:
-
-
-
-
-```bash
-curl -fsSL https://antigravity.google/install.sh | bash
-```
-
-
-
-
-```powershell
-irm https://antigravity.google/install.ps1 | iex
-```
-
-
-
-
-```cmd
-winget install Google.AntigravityCLI
-```
-
-
-
-
-After installation, verify that the tool is available on your path by running:
-
-```console
-$ agy --version
-agy version 2.0.0
-```
-
-## Migration from Gemini CLI
-
-If you previously used the Gemini CLI or Gemini CLI extension for Flutter,
-migrating to the Antigravity CLI is straightforward.
-
-### Automatic migration
-
-The first time you run `agy` in your terminal,
-the tool checks for existing Gemini configuration files
-(such as `~/.gemini/config/mcp_config.json`
-or legacy environment variables).
-If found, the tool asks if you would like to
-automatically migrate your settings,
-preferences, and API configuration.
-
-### Manual migration
-
-If you want to migrate your plugins and history manually,
-or if you skipped the automated setup,
-run the import plugin tool command:
-
-```bash
-agy plugin import gemini
-```
-
-This command parses your local Gemini configuration
-and copies its configurations over to your active Antigravity profile.
-
-## Workspace configuration and rules
-
-Like the legacy Gemini CLI tool,
-Antigravity CLI respects custom development guidelines
-and configurations stored in your workspace directory:
-
-- **Local rules**: You can place rules files in your project directory
- (such as `.agents/skills/` or `AGENTS.md`)
- to instruct the agent on specific coding style guidelines
- or architectural patterns.
- Note that Antigravity CLI also supports backward compatibility
- with the legacy `GEMINI.md` file;
- however, we recommend renaming it to `AGENTS.md`.
-- **Global configuration**: Antigravity CLI stores global settings
- and configured MCP servers in `~/.antigravity/`
- (e.g., `~/.antigravity/mcp_config.json`).
diff --git a/sites/docs/src/content/ai/antigravity.md b/sites/docs/src/content/ai/antigravity.md
deleted file mode 100644
index cb1935a616a..00000000000
--- a/sites/docs/src/content/ai/antigravity.md
+++ /dev/null
@@ -1,116 +0,0 @@
----
-title: Google Antigravity
-shortTitle: Antigravity
-description: Learn about Google Antigravity agentic coding tools.
----
-
-## Introduction
-
-Google Antigravity is a suite of agentic development tools
-for building apps, including Flutter apps.
-You can pair with Antigravity to solve coding tasks,
-create new codebases, modify existing ones,
-and answer questions.
-
-The Antigravity suite includes:
-
-* **Antigravity 2.0**: The core agentic assistant experience,
- driven via a Terminal User Interface (TUI)
- or Command-Line Interface (CLI).
-* **Antigravity IDE**: The focused editor experience
- featuring an integrated agent panel.
-
-This page describes the Antigravity IDE.
-For details on using the command-line tool,
-see the [Antigravity CLI](/ai/antigravity-cli) page.
-
-
-To learn some of what Antigravity is capable of,
-watch this talk from Google I/O 2026.
-
-
-
-## Installation and setup {: #setup}
-
-Install the latest version of Antigravity for your platform by visiting
-the [Antigravity site](https://antigravity.google/download).
-
- 1.
Open Antigravity
-
- When opening Antigravity for the first time,
- a **How do you want to use Antigravity** screen displays and
- provides some radio buttons and pulldown menus
- for customizing your use of the tool.
-
- We recommend that you select **Review-driven development**.
- This means that Antigravity asks you to approve each command
- that it wants to run.
-
- You can change this setting at any time to give Antigravity
- more or less control. Even if you select **Agent driven development**,
- which allows Antigravity to directly run commands without approval,
- you can specify certain commands that will _always_ ask
- for your approval, such as the `rm` command to remove files.
-
- 1.
Install the Dart and Flutter extensions
-
- 1. Open **Settings** (press Cmd/Ctrl + ,).
- 1. Click the **Customizations** tab.
- 1. In the **Build with Google Plugins** section, click **Customize**.
- 1. Click **Download** next to the **Dart and Flutter** integration.
-
- 1.
Set up any MCP servers that you use
-
- 1. Navigate to or open the **Agent** side panel.
-
- If it's closed, open it by either:
-
- * Pressing Cmd/Ctrl + L.
- * Going to **View**
- > **Open View...**
- > **Agent**.
-
- In the upper right of the **Agent** panel,
- click the **Additional options** (`...`) menu button.
-
- 1. Select **MCP Servers**.
-
- 1. In the upper right of the **Agent** panel,
- click **Manage MCP Servers**.
-
- The **MCP Store** screen appears and you can search for Dart,
- which is likely already in the list.
- Click **Install**.
-
- After installing any servers that you want,
- view them by clicking the **Manage MCP Servers** button
- and click **View raw config** to access your JSON manifest.
-
- 1.
Get started developing
-
- For these tips and more that show some of Antigravity's benefits,
- watch the following 10-minute [Flutter + Antigravity video][ag-video]:
-
-
-
- For a walkthrough on creating a new Flutter app in Antigravity,
- visit [Create a new Flutter app][].
- To learn more about the development features enabled by
- the Dart and Flutter extensions,
- check out [How to develop Flutter apps in VS Code][vs-code].
-
-{:.steps}
-
-## Agentic Hot Reload {: #agentic-hot-reload}
-
-If you are using Antigravity in Agent mode,
-the agent can automatically hot reload your running application
-when you prompt it to modify your app.
-This enables a hands-free, "prompt-to-reload" workflow
-that reduces context switching and development latency.
-
-[ag-video]: {{site.yt.watch}}?v=YY2w2JEX2xk&t=1s
-[Create a new Flutter app]: /reference/create-new-app#antigravity
-[vs-code]: /tools/vs-code
diff --git a/sites/docs/src/content/ai/best-practices/developer-experience.md b/sites/docs/src/content/ai/best-practices/developer-experience.md
deleted file mode 100644
index 7cea121968f..00000000000
--- a/sites/docs/src/content/ai/best-practices/developer-experience.md
+++ /dev/null
@@ -1,157 +0,0 @@
----
-title: Developer experience
-description: >
- Learn how to use spec-driven development and Gemini to plan, code, and
- iterate on high-quality Flutter applications.
-prev:
- title: Mode of interaction
- path: /ai/best-practices/mode-of-interaction
----
-
-
-Generative AI is not just useful for implementing features in your app; it's
-also useful for generating the code to implement those features.
-
-Unfortunately, it's just as easy as prompting an AI coding agent to "build a
-Flutter app that solves crossword puzzles." I'm sure that prompt would yield
-something, but I doubt very much that it would give us the powerful AI-assisted,
-user-validated combination the Crossword Companion provides.
-
-With better prompting, however, the sample app was implemented with Gemini 2.5
-Pro for the bulk of the functionality and Gemini 3 Pro Preview to add the final
-touches. The process to get the best results from both models was the same:
-
-- Plan
-- Code
-- Validate
-- Iterate
-
-### Plan
-
-The goal of the planning process is to kick off the coding process with enough
-detail to let the agent know what you have in mind. The Crossword Companion
-planning process was started with the following prompt:
-
-```plaintext
-I'd like to create a file called requirements.md in the plans folder at the root of the project. here's a description of the project:
-
-The application will be an open-source sample hosted on GitHub in the flutter/demos directory. It aims to demonstrate the use of Flutter, Firebase AI Logic, and Gemini to produce an agentic workflow that can solve a small crossword puzzle (one with a size under 10x10)....lots more description of the app along with a sample puzzle screenshot...
-Ask any questions you may have before you get started.
-```
-
-This prompt, with a little bit of Q&A, manual edits by a human, and some updates
-during the coding process, yielded [the requirements file][requirements].
-
-Before jumping into architectural design, the Gemini CLI was asked to initialize
-the GEMINI.md rules file and then to update it with a list of architectural
-principles:
-
-```plaintext
-DRY (Don't Repeat Yourself) – eliminate duplicated logic by extracting shared utilities and modules.
-
-Separation of Concerns – each module should handle one distinct responsibility.
-
-Single Responsibility Principle (SRP) – every class/module/function/file should have exactly one reason to change.
-
-Clear Abstractions & Contracts – expose intent through small, stable interfaces and hide implementation details.
-
-Low Coupling, High Cohesion – keep modules self-contained, minimize cross-dependencies.
-
-Scalability & Statelessness – design components to scale horizontally and prefer stateless services when possible.
-
-Observability & Testability – build in logging, metrics, tracing, and ensure components can be unit/integration tested.
-
-KISS (Keep It Simple, Sir) - keep solutions as simple as possible.
-
-YAGNI (You're Not Gonna Need It) – avoid speculative complexity or over-engineering.
-```
-
-The GEMINI.md file is loaded into every new prompt you create with Gemini; it
-provides the set of rules you want it to remember for any activity. Gemini was
-running inside of an empty Flutter app project, so the `/init` command
-documented how to build, test and run it, which was useful during coding.
-
-If you're building something more than a sample, I also recommend adding
-something for test-driven development:
-
-```markdown
-- **TDD (Test-Driven Development)** - write the tests first; the implementation
- code isn't done until the tests pass.
-```
-
-This helps to build guardrails to ensure the coding agent is writing solid code
-over time.
-
-With the requirements and rules in place, prompting for the design.md file was
-next:
-
-```plaintext
-great. i'd like to work on the design with you to be created in a design.md file to be stored in the plans folder. please use the @GEMINI.md and @requirements.md files as input. ask any questions you may have before you get started.
-```
-
-After inspecting and editing the generated app design, Gemini was prompted to
-break it down into [tasks][tasks-spec]:
-
-```plaintext
-please read the files in the @specs folder and create a corresponding tasks.md file in the same folder that lays out a set of tasks and subtasks representing the functionality of this app. lay out the top-level tasks as minimal new functionality that the user can see in the running app, step-by-step as each top-level task is completed. each top-level task should include sub-tasks for creating and running tests and updating the @README.md with a description of the current functionality of the app. ask any questions you may have before you get started.
-```
-
-All of this happens before any code is written. You don't have to split things
-into separate files, but by carefully considering the requirements, the design
-and the task breakdown, you're helping the agent to provide results that meet
-your expectations. This is called "Spec-Driven Development" and it's currently
-the best way we know of to upgrade your process from "vibe coding" to
-"AI-assisted software development."
-
-Also, the sentence that says "ask any questions you may have before you get
-started" is a great way for the agent to clarify anything that it doesn't
-understand instead of just making up the answers as it goes. It's also useful to
-help you to decide on details you might not otherwise have considered.
-
-### Code
-
-With the requirements, rules, design and tasks in place, kicking off the coding
-part is easy:
-
-```plaintext
-Read the @tasks.md file and implement the first milestone.
-```
-
-You can watch the coding agent at work, jumping in to correct it as it works, or
-just let it go. Either way, when it's done, it's time to check its work.
-
-### Validate
-
-At this point, you have some code and (in the world outside of samples) some
-tests. To validate, ask yourself some questions:
-
-- Does the analyzer show it to be free of errors? Of warnings?
-- Does the app run?
-- Does it have the features you asked for? Do they work?
-- Do the tests pass?
-- Does the code pass your review?
-
-The answers to these questions are the input for the next phase.
-
-### Iterate
-
-Gather the issues that need to be addressed and hand the ones that need fixing
-back to the coding agent, iterating between it coding and your validation until
-you get to a good place from a functional point of view.
-
-Now take another pass through validation from an architectural principles point
-of view, spinning up a new agent to check the code. By clearing out the agent's
-context, you remove the biases the original agent gathered choosing what code to
-write in the first place. To ground it on just the code changes the agent has
-just made, use a prompt like this:
-
-```plaintext
-Use git diff to find the new code and check it against the architectural principles listed here: @GEMINI.md. Make recommendations for important improvements.
-```
-
-Doing this a few times keeps the code in good shape for AI agents and humans
-alike.
-
-
-[requirements]: {{site.repo.demos}}/blob/main/crossword_companion/specs/requirements.md
-[tasks-spec]: {{site.repo.demos}}/blob/main/crossword_companion/specs/tasks.md
diff --git a/sites/docs/src/content/ai/coding-assistants.md b/sites/docs/src/content/ai/coding-assistants.md
deleted file mode 100644
index 6456cd145d2..00000000000
--- a/sites/docs/src/content/ai/coding-assistants.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: AI coding assistants
-description: >
- Learn how to use AI-powered coding assistants like Antigravity
- to accelerate your Flutter development.
----
-
-AI tools are not only features in your app,
-but can also be powerful assistants in your development workflow.
-
-To set up your preferred AI coding agent with official Flutter plugins,
-skills, and rules, check out the [Get started with AI](/ai/get-started) guide.
-
-Tools like Antigravity can help you write code faster,
-understand complex concepts, and reduce boilerplate.
-
-## Antigravity
-
-[Antigravity](https://antigravity.google/) is a suite of agentic development tools that includes:
-
-* **Antigravity 2.0**: The core agentic assistant experience (TUI/CLI-driven).
-* **Antigravity IDE**: The focused editor experience featuring an integrated agent panel.
-
-Some of Antigravity's capabilities include:
-
-* **Agentic capabilities**: Unlike chat-based assistants, Antigravity can proactively edit files and run terminal commands to complete tasks.
-* **Complex reasoning**: It can plan and execute multi-step workflows which makes it suitable for larger refactors or feature implementations.
-* **Verification**: It can run tests and verify its own changes to ensure correctness.
-
-To get started with the editor experience, see the [Antigravity IDE](/ai/antigravity) page.
-To get started with the command-line tool, see the [Antigravity CLI](/ai/antigravity-cli) page.
-
-## Gemini Code Assist
-
-[Gemini Code Assist](https://codeassist.google/) is an AI-powered collaborator
-available for IDEs like Visual Studio Code, JetBrains IDEs, and Android Studio.
-It has a deep understanding of your project's codebase and can help you with:
-
-* **Code completion and generation**: It suggests and generates entire blocks of
- code based on the context of what you're writing.
-* **In-editor chat**: You can ask questions about your code, Flutter concepts,
- or best practices directly within your IDE.
-* **Debugging and explanation**: If you encounter an error, you can ask Gemini
- Code Assist to explain it and suggest a fix.
-
-## Gemini CLI (Legacy)
-
-The [Gemini CLI](https://geminicli.com/) is a command-line AI workflow tool.
-For individual developers, it is superseded by the new [Antigravity CLI](/ai/antigravity-cli).
-It continues to be supported for Gemini Enterprise users.
-
-For more details about Gemini CLI, visit the [Gemini CLI](https://geminicli.com/) website.
-
-## Claude Code
-
-[Claude Code](https://code.claude.com/) is an agentic coding assistant from
-Anthropic that runs in your terminal.
-
-You can equip Claude Code with domain expertise and tools for Flutter and Dart
-by installing the official Flutter plugin, which bundles official
-[agent skills](/ai/agent-skills) and the
-[Dart and Flutter MCP server](/ai/mcp-server).
-
-To install the plugin, follow the Claude Code instructions in
-[Install agent skills](/ai/agent-skills#install-agent-skills).
diff --git a/sites/docs/src/content/ai/create-with-ai.md b/sites/docs/src/content/ai/create-with-ai.md
deleted file mode 100644
index 90eef4f9f3a..00000000000
--- a/sites/docs/src/content/ai/create-with-ai.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-title: Create with AI
-description: >
- Learn how to use AI to build Flutter apps, and how to build AI experiences
- into your apps.
----
-
-Use artificial intelligence to accelerate your Flutter development workflow and build intelligent, AI-powered features into your applications.
-
-## Develop with AI
-
-Configure your development environment with official Flutter and Dart plugins, rules, and tools.
-
-
-
- Set up your editor with official plugins, rules, and MCP configuration.
-
-
- Learn about supported AI editors, IDEs, and CLI tools.
-
-
- Enhance your AI assistant with domain-specific task blueprints.
-
-
- Connect your AI to Dart and Flutter developer tools.
-
-
- Configure project-wide guidelines and best practices for models.
-
-
-
-## Build AI-powered apps
-
-Integrate generative AI features directly into your Flutter applications.
-
-
-
- Use pre-built chat widgets and feature integrations.
-
-
- Orchestrate interactive conversational UIs in your app.
-
-
- Use Firebase to integrate Gemini and Vertex AI features.
-
-
- Build and debug AI flows with an open-source framework.
-
-
- Learn about prompting, tool calls, and interaction modes.
-
-
-
-## AI evaluations
-
-Measure the reliability and performance of your AI integrations.
-
-
-
- Learn how we use benchmarks and datasets to test and improve AI tooling.
-
-
diff --git a/sites/docs/src/content/ai/evals.md b/sites/docs/src/content/ai/evals.md
deleted file mode 100644
index b1d6b8b2d17..00000000000
--- a/sites/docs/src/content/ai/evals.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: AI evaluations
-description: >
- Learn about Dart and Flutter's evaluation frameworks for
- measuring AI tooling reliability.
----
-
-:::experimental
-Evaluation tooling and benchmarks are experimental and likely to change.
-:::
-
-To explore the evaluation strategy,
-view the open-source dataset and scoring rubrics,
-or get involved with community benchmark datasets,
-visit the [Flutter Evals repository](https://github.com/flutter/evals).
-
-Evaluating the capabilities and reliability of AI agents requires testing
-approaches that model actual developer tasks.
-Because LLMs are non-deterministic,
-standard unit testing is insufficient for verifying agentic behaviors like
-codebase navigation, plan execution, and code synthesis.
-
-To build developer confidence in AI tooling,
-Dart and Flutter use an evaluation system ("evals")
-to test critical user journeys (CUJs).
-Evals measure both deterministic code correctness
-(compilation, lints, automated tests) and qualitative performance
-(reasoning, safety, and conciseness) using automated model judges
-and expert human grading.
diff --git a/sites/docs/src/content/ai/get-started.md b/sites/docs/src/content/ai/get-started.md
index fc4a6d8bf97..c618114f1f2 100644
--- a/sites/docs/src/content/ai/get-started.md
+++ b/sites/docs/src/content/ai/get-started.md
@@ -2,40 +2,113 @@
title: Get started developing with AI
shortTitle: Get started
description: >-
- Learn how to set up and use AI agent plugins for Flutter and Dart using
- your preferred coding assistant.
+ Learn how to set up and use AI agent plugins for Flutter and Dart
+ using your preferred coding assistant.
---
AI coding assistants can accelerate your Flutter development workflow by
writing code, fixing errors, and building complete features.
-To get the best experience with AI coding assistants, install the official
-Flutter agent plugins, which bundle [agent skills](/ai/agent-skills) and
-configuration for the [Dart and Flutter MCP server](/ai/mcp-server).
+By default, general-purpose AI assistants might not have the latest context on
+Flutter patterns, project diagnostics, or third-party packages. Installing the
+official Flutter agent plugin equips your assistant with dedicated tools and
+recipes tailored for Flutter and Dart development.
+
+## How Flutter AI plugins work
+
+An **agent plugin** bundles the tools and knowledge that an AI assistant needs
+to understand and modify your Flutter codebase.
+
+When you install an official Flutter agent plugin, it connects your assistant
+to two core capabilities:
+
+* **[Agent skills](/ai/tools#agent-skills)**: On-demand procedural guides from
+ the official Flutter and Dart repositories that teach the assistant how to
+ perform specific tasks, such as creating responsive layouts, managing state,
+ or writing widget tests.
+* **[Dart and Flutter MCP server](/ai/tools#dart-and-flutter-mcp-server)**:
+ A Model Context Protocol (MCP) server that connects the assistant to the
+ Dart SDK, giving it real-time access to analyzer diagnostics, symbol
+ resolution, test runners, and runtime inspection.
+
+These tools work together automatically: the assistant uses MCP tools to query
+live project state and static analysis, while using agent skills to guide its
+coding strategies and best practices.
+
+In addition to core Flutter plugins, you can also equip your assistant with
+skills that are shipped directly by third-party `pub.dev`
+packages using the [skills](/ai/package-skills) package.
+
+To learn more about the underlying architecture and capabilities of each tool,
+check out [How Flutter AI tools work](/ai/tools).
## Choose your AI coding agent
-Select your agent below for instructions on how to install official plugins and
-set up rules for Flutter development.
+Select your agent below for instructions on how to install official plugins
+and configure tools for Flutter development.
-[Antigravity](https://antigravity.google/) is a suite of agentic development
-tools built by Google that includes the Antigravity IDE and Antigravity CLI.
+[Antigravity](
+https://antigravity.google/docs/build-with-google/#dart-and-flutter)
+is an agentic development platform by Google that includes the Antigravity
+IDE, IDE extensions (for VS Code and other editors), and the Antigravity CLI.
-**Install the official plugin**
+**Antigravity IDE and extensions**
-Equip Antigravity with official Dart and Flutter tools
-by installing the plugin from settings:
+Equip the Antigravity IDE or Antigravity extension (such as in VS Code)
+with official Dart and Flutter tools:
-1. Open **Settings** in Antigravity by clicking the gear icon
- or pressing Cmd/Ctrl + ,.
+1. Open **Settings** in Antigravity by clicking the gear icon or pressing
+ Cmd/Ctrl + ,.
1. Click the **Customizations** tab.
1. In the **Build with Google Plugins** section, click **Customize**.
1. Click **Download** next to the **Dart and Flutter** integration.
+To verify the installation, open the **Agent** panel
+(Cmd/Ctrl + L) and ask the agent:
+
+```text
+Summarize the Dart and Flutter tools and skills available in this project.
+```
+
+**Antigravity CLI**
+
+The Antigravity CLI runs as the `agy` command in your terminal.
+
+1. Configure the Dart and Flutter MCP server in your project's
+ `.agents/mcp_config.json` file (or globally in
+ `~/.gemini/config/mcp_config.json`):
+
+ ```json
+ {
+ "mcpServers": {
+ "dart": {
+ "command": "dart",
+ "args": ["mcp-server"]
+ }
+ }
+ }
+ ```
+
+1. Install official Flutter and Dart skills into your workspace:
+
+ ```bash
+ # Install Flutter skills
+ npx skills add flutter/agent-plugins --skill '*' --agent universal --yes
+
+ # Install Dart skills
+ npx skills add dart-lang/skills --skill '*' --agent universal --yes
+ ```
+
+1. Start an interactive session in your project root:
+
+ ```bash
+ agy
+ ```
+
@@ -45,39 +118,62 @@ Anthropic that runs in your terminal.
**Install the official plugin**
-Equip Claude Code with domain expertise and tools for Flutter and Dart by
-installing the official plugin from
-[flutter/agent-plugins](https://github.com/flutter/agent-plugins):
+Equip Claude Code with official Flutter and Dart skills and MCP configuration:
1. Add the marketplace for Claude Code plugins:
- ```console
- $ claude plugin marketplace add flutter/agent-plugins
+ ```bash
+ claude plugin marketplace add flutter/agent-plugins
```
1. Install the Flutter and Dart plugin:
- ```console
- $ claude plugin install dart-flutter@dart-flutter
+ ```bash
+ claude plugin install dart-flutter@dart-flutter
```
-1. Verify the installation:
+**Verify installation**
- ```console
- $ claude plugin marketplace list
+Verify that the plugin is active in Claude Code:
+
+```bash
+claude plugin marketplace list
+```
+
+
+
+
+
+[Cursor](https://cursor.com/) is an AI-powered code editor built on top of
+VS Code.
+
+**Install the local plugin**
+
+You can install the official plugin bundle locally by copying it to your Cursor
+plugins directory:
+
+1. Clone the repository:
+
+ ```bash
+ git clone https://github.com/flutter/agent-plugins.git
```
-**Rules setup**
+1. Copy the repository to your local Cursor plugins folder:
-Claude Code plugins currently cannot bundle rules files automatically.
-You can configure rules for your project by following [Rules for Flutter and Dart](/ai/ai-rules).
+ ```bash
+ mkdir -p ~/.cursor/plugins/local
+ cp -r agent-plugins ~/.cursor/plugins/local/dart-flutter
+ ```
+
+1. Restart Cursor. The editor automatically discovers the bundled skills and
+ configures the Dart and Flutter MCP server.
-[Codex](https://chatgpt.com/codex) is an agentic coding assistant
-designed for terminal and IDE workflows.
+[Codex](https://chatgpt.com/codex) is an agentic coding assistant designed for
+terminal and IDE workflows.
**Install the official plugin**
@@ -85,79 +181,113 @@ Equip Codex with official Flutter and Dart skills and MCP configuration:
1. Add the Dart and Flutter marketplace for Codex plugins:
- ```console
- $ codex plugin marketplace add flutter/agent-plugins
+ ```bash
+ codex plugin marketplace add flutter/agent-plugins
```
1. Install the Dart and Flutter plugin:
- ```console
- $ codex plugin add dart-flutter@dart-flutter
+ ```bash
+ codex plugin add dart-flutter@dart-flutter
```
-**Rules setup**
+**Verify installation**
-Codex plugins currently cannot bundle rules files automatically.
-You can configure rules for your project by following [Rules for Flutter and Dart](/ai/ai-rules).
+Verify that the plugin is active in Codex:
-
+```bash
+codex plugin list
+```
-
+
-[Cursor](https://cursor.com/) is an AI-first code editor built on VS Code.
+
-**Install the local plugin**
+[GitHub Copilot](https://github.com/features/copilot) in VS Code supports
+agentic coding, tool calling via MCP, and custom workspace instructions.
-You can install the plugin locally by copying it to your Cursor plugins directory:
+**Configure the MCP server**
+1. Ensure the **Model Context Protocol (MCP)** extension is enabled in VS Code.
+1. Create or open `.vscode/mcp.json` in your workspace and add the Dart MCP
+ server:
-1. Clone the repository:
- ```bash
- git clone https://github.com/flutter/agent-plugins.git
+ ```json
+ {
+ "servers": {
+ "dart": {
+ "command": "dart",
+ "args": ["mcp-server"]
+ }
+ }
+ }
```
-1. Copy the repository directory to your local Cursor plugins folder:
- ```bash
- mkdir -p ~/.cursor/plugins/local
- cp -r agent-plugins ~/.cursor/plugins/local/dart-flutter
- ```
+**Install agent skills**
+
+In your Flutter project root, install official skills using the universal flag:
-1. Restart Cursor. The editor automatically discovers and loads the skills
- under `skills/` and configures the MCP server defined in `.mcp.json`.
+```bash
+# Install Flutter skills
+npx skills add flutter/agent-plugins --skill '*' --agent universal --yes
+
+# Install Dart skills
+npx skills add dart-lang/skills --skill '*' --agent universal --yes
+```
-
+
-If you're using another compatible agentic assistant:
+If you're using another coding assistant (such as Windsurf, Zed, or Cline)
+that supports the Model Context Protocol (MCP) or Agent Skills specification,
+configure the tools manually.
-* **MCP Server**: To connect your agent to Dart and Flutter developer tools,
- see the [Dart and Flutter MCP server](/ai/mcp-server) setup guide.
-* **Agent Skills**: To manually install agent skills into your project's
- `.agents/skills` directory using the `skills` CLI tool, see
- [Install agent skills](/ai/agent-skills#install-agent-skills).
-* **Rules**: To configure rules and project best practices for your assistant,
- see [Rules for Flutter and Dart](/ai/ai-rules).
+**Configure the MCP server**
-
+Most MCP-compatible clients use a standard JSON configuration format. Add the
+following entry to your client's MCP configuration file:
-
+```json
+{
+ "mcpServers": {
+ "dart": {
+ "command": "dart",
+ "args": ["mcp-server"]
+ }
+ }
+}
+```
+
+For client-specific setup guides, command-line arguments, and troubleshooting,
+check out the [Dart and Flutter MCP server repository](
+https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp_server).
-## The AI tooling stack
+**Install agent skills**
-To get the most out of AI, it helps to understand how the different pieces work together:
+Use the `skills` CLI to download skills into your workspace's standard
+`.agents/skills` directory:
-| Component | What it is | How it helps |
-| :--- | :--- | :--- |
-| **Plugins** | Packages MCP and Skills together for your editor. | **Start here.** Recommended for quick setup. |
-| **MCP Server** | Connects the AI to Flutter developer tools (hot reload, widget tree). | Provides the raw machinery for advanced tools. |
-| **Agent Skills** | Step-by-step blueprints for specific tasks. | Provides the professional know-how to operate tools. |
-| **AI Rules** | General guidelines and best practices for your project. | Enforces consistent coding standards. |
+```bash
+# Install Flutter skills
+npx skills add flutter/agent-plugins --skill '*' --agent universal --yes
-{:.table .table-striped}
+# Install Dart skills
+npx skills add dart-lang/skills --skill '*' --agent universal --yes
+```
+
+
+
+
## Next steps
-* Learn more about [Agent skills](/ai/agent-skills) and how agents use them.
-* Explore the [Dart and Flutter MCP server](/ai/mcp-server) integration.
-* Check out [Rules for Flutter and Dart](/ai/ai-rules) to customize model behavior.
+* To learn more about how skills and tools interact, check out
+ [How Flutter AI tools work](/ai/tools).
+* To publish or consume skills from dependencies, refer to
+ [Package skills](/ai/package-skills).
+* To give your assistant search access to official Flutter and Dart
+ documentation, connect to the [Developer Knowledge MCP server](
+ https://developers.google.com/knowledge/mcp).
+* To explore the official plugins, check out the
+ [flutter/agent-plugins](https://github.com/flutter/agent-plugins) repository.
diff --git a/sites/docs/src/content/ai/mcp-server.md b/sites/docs/src/content/ai/mcp-server.md
deleted file mode 100644
index d0982613e5f..00000000000
--- a/sites/docs/src/content/ai/mcp-server.md
+++ /dev/null
@@ -1,461 +0,0 @@
----
-title: Dart and Flutter MCP server
-shortTitle: MCP server
-description: >
- Learn about the Dart and Flutter MCP server tool that
- exposes Dart and Flutter tools to compatible
- AI-assistant clients and agents.
----
-
-This guide discusses the Dart and Flutter MCP server.
-
-:::experimental
-The Dart and Flutter MCP server is experimental and likely to evolve quickly.
-The following instructions require Dart 3.9 or later.
-:::
-
-## Overview
-
-The [Dart and Flutter MCP server][]
-exposes Dart and Flutter development tool actions to
-compatible AI-assistant clients. MCP (model context protocol)
-is a protocol that enables communication between development tools
-and AI assistants, allowing the assistants to understand the
-context of the code and perform actions on behalf of the developer.
-
-The Dart and Flutter MCP server can work with any MCP client that
-supports standard I/O (stdio) as the transport medium.
-To access all the features of the Dart and Flutter MCP server,
-an MCP client must support [Tools][] and [Resources][].
-For the best development experience with the Dart and Flutter MCP server,
-an MCP client should also support [Roots][].
-
-If you are using a client that claims it
-supports roots but doesn't actually set them,
-pass `--force-roots-fallback` flag to enable tools for managing the roots.
-
-The Dart and Flutter MCP server provides a growing list of tools that
-grant AI assistants deep insights into your project.
-Here is an overview of a few things it can do:
-
-* Analyze and fix errors in your project's code.
-* Resolve symbols to elements to ensure their existence and
- fetch documentation and signature information for them.
-* Introspect and interact with your running application.
-* Search the [pub.dev site]({{site.pub}}) for the best package for a use case.
-* Manage package dependencies in your `pubspec.yaml` file.
-* Run tests and analyze the results.
-* Format code with the same formatter and config as
- [`dart format`][] and the Dart analysis server.
-
-[Tools]: https://modelcontextprotocol.io/docs/concepts/tools
-[Resources]: https://modelcontextprotocol.io/docs/concepts/resources
-[Roots]: https://modelcontextprotocol.io/docs/concepts/roots
-[Dart and Flutter MCP server]: https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp_server
-[`dart format`]: {{site.dart-site}}/tools/dart-format
-
-## Set up your MCP client
-
-Run the server with the `dart mcp-server` command,
-which must be configured in your preferred client.
-
-This section provides instructions for setting up the
-Dart and Flutter MCP server with popular tools such as
-Antigravity, Gemini CLI, Cursor, and GitHub Copilot.
-
-### Antigravity
-
-To configure Google [Antigravity][] to use the Dart and Flutter MCP server,
-you can either install it from the list of available servers or
-[connect it as a custom MCP server][antigravity-mcp].
-
-1. Navigate to or open the **Agent** side panel.
-
- If it's closed, open it by either:
-
- - Pressing Cmd/Ctrl + L.
- - Going to **View**
- > **Open View...**
- > **Agent**.
-
-1. In the upper right of the **Agent** panel,
- click the **Additional options** (`...`) menu button.
-1. Select **MCP Servers**.
-1. In the upper right of the **Agent** panel,
- click **Manage MCP Servers**.
-
-From here, you can choose to install the MCP server from
-[the built-in MCP store](#antigravity-mcp-store-install) or by
-[configuring it manually](#antigravity-mcp-manual-install).
-
-[Antigravity]: https://antigravity.google/
-[antigravity-mcp]: https://antigravity.google/docs/mcp#connecting-custom-mcp-servers
-
-#### Install from the MCP store {: #antigravity-mcp-store-install}
-
-1. In the list of available MCP servers,
- find or search for **Dart** and click **Install**.
-
-#### Connect manually {: #antigravity-mcp-manual-install}
-
-1. In the upper right of the **Manage MCPs** editor view,
- click **View raw config**.
-1. Add the following `dart-mcp-server` entry to the `mcpServers` map:
-
- ```json title="mcp_config.json" highlightLines=3-10
- {
- "mcpServers": {
- "dart-mcp-server": {
- "command": "dart",
- "args": [
- "mcp-server"
- ],
- "env": {}
- }
- }
- }
- ```
-
-#### Install extensions
-
-It is also recommended to install the Dart and Flutter extensions:
-
-1. Open the **Extensions** view by either:
-
- - Pressing Shift +
- Cmd/Ctrl +
- P.
- - Going to **View**
- > **Extensions**.
-
-1. In the **Search Extensions** input box, enter **Flutter**.
-1. From the list of extensions, select **Flutter**.
-1. In the **Extension: Flutter** view that opens,
- click the **Install** button.
-
- This installs both the Dart and Flutter extensions.
-
-To learn more about the Dart and Flutter extensions,
-check out [Develop Flutter apps in VS Code][].
-
-[Develop Flutter apps in VS Code]: /tools/vs-code
-
-### Gemini CLI
-
-To configure the [Gemini CLI][] to use the Dart and Flutter MCP server,
-add a Dart entry to the `mcpServers` section of the Gemini config.
-
-- To enable the server for all projects on your device,
- edit the `~/.gemini/settings.json` file in your home directory.
-- To enable the server for a specific project,
- edit the `.gemini/settings.json` file in the project's root directory.
-
-```json title=".gemini/settings.json"
-{
- "mcpServers": {
- "dart": {
- "command": "dart",
- "args": [
- "mcp-server"
- ]
- }
- }
-}
-```
-
-For more information, check out the official Gemini CLI
-documentation for [setting up MCP servers][].
-
-[Gemini CLI]: https://geminicli.com/
-[setting up MCP servers]: https://geminicli.com/docs/tools/mcp-server/#how-to-set-up-your-mcp-server
-
-### Gemini Code Assist in VS Code
-
-[Gemini Code Assist][]'s [Agent mode][] integrates the
-Gemini CLI to provide a powerful AI agent directly in your IDE.
-If you haven't set up Gemini Code Assist or its agent mode yet,
-follow its [Before you begin instructions][gca-setup] to get started.
-
-To configure Gemini Code Assist to use the Dart and Flutter MCP server,
-follow the instructions to [configure the Gemini CLI][].
-
-You can verify the MCP server has been configured
-properly by typing `/mcp` in the chat window in Agent mode.
-
-For more information see the official Gemini Code Assist
-documentation for [using agent mode][].
-
-[gca-setup]: https://developers.google.com/gemini-code-assist/docs/use-agentic-chat-pair-programmer#before-you-begin
-[Gemini Code Assist]: https://codeassist.google/
-[Agent mode]: https://developers.google.com/gemini-code-assist/docs/use-agentic-chat-pair-programmer
-[configure the Gemini CLI]: #gemini-cli
-[using agent mode]: https://developers.google.com/gemini-code-assist/docs/use-agentic-chat-pair-programmer#before-you-begin
-
-### GitHub Copilot in VS Code
-
-:::note
-Support for the Dart and Flutter MCP server in VS Code requires
-v3.116 or later of the [Dart Code extension][].
-:::
-
-By default, the Dart extension uses the
-[VS Code MCP API][] to register the Dart and Flutter MCP server, as well
-as a tool to provide the URI for the active Dart Tooling Daemon.
-
-Explicitly enable or disable the Dart and Flutter MCP server by
-configuring the `dart.mcpServer` setting in your VS Code settings.
-
-To change this globally, update your user settings:
-
-1. In VS Code, click **View > Command Palette** and then
- search for **Preferences: Open User Settings (JSON)**.
-
-1. Add the following setting:
-
- ```json
- "dart.mcpServer": true
- ```
-
-If you'd like this setting to apply only to a specific workspace,
-add the entry to your workspace settings:
-
-1. In VS Code, click **View > Command Palette** and then
- search for **Preferences: Open Workspace Settings (JSON)**.
-
-1. Add the following setting:
-
- ```json
- "dart.mcpServer": true
- ```
-
-For more information, see the official VS Code
-documentation for [enabling MCP support][].
-
-[Dart Code extension]: https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code
-[VS Code MCP API]: https://code.visualstudio.com/api/extension-guides/mcp
-[enabling MCP support]: https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_enable-mcp-support-in-vs-code
-
-### Cursor
-
-The easiest way to configure the Dart and Flutter MCP server with
-Cursor is by clicking the **Add to Cursor** button:
-
-[](cursor://anysphere.cursor-deeplink/mcp/install?name=dart&config=eyJjb21tYW5kIjoiZGFydCBtY3Atc2VydmVyIn0%3D){:.light-mode-visible}
-[](cursor://anysphere.cursor-deeplink/mcp/install?name=dart&config=eyJjb21tYW5kIjoiZGFydCBtY3Atc2VydmVyIn0%3D){:.dark-mode-visible}
-
-Alternatively, you can configure the server manually:
-
-1. Go to **Cursor > Settings > Cursor Settings > Tools & Integrations**.
-1. Click **Add Custom MCP** or **New MCP Server**
- depending on whether you already have other MCP servers configured.
-1. Edit the `.cursor/mcp.json` file in your local project
- (configuration will only apply to this project) or
- edit the global `~/.cursor/mcp.json` file in your home directory
- (configuration will apply for all projects) to
- configure the Dart and Flutter MCP server:
-
- ```json title=".cursor/mcp.json"
- {
- "mcpServers": {
- "dart": {
- "command": "dart",
- "args": [
- "mcp-server"
- ]
- }
- }
- }
- ```
-
-For more information, see the official Cursor
-documentation for [installing MCP servers][].
-
-[installing MCP servers]: https://docs.cursor.com/context/model-context-protocol#installing-mcp-servers
-
-### OpenCode
-
-To configure [OpenCode][] to use the Dart and Flutter MCP server
-for the current project, use the `opencode mcp add` CLI command:
-
-```console
-$ opencode mcp add dart-mcp-server -- dart mcp-server
-```
-
-[OpenCode]: https://opencode.ai/
-
-### Claude Code
-
-You can configure Claude Code to use the Dart and Flutter MCP server either
-by installing the official plugin or by configuring the server manually.
-
-#### Install via plugin (recommended)
-
-The easiest way to set up the Dart and Flutter MCP server in Claude Code is to
-install the official Flutter plugin, which bundles both the MCP server and
-official [agent skills](/ai/agent-skills).
-
-To install the plugin, follow the Claude Code instructions in
-[Install agent skills](/ai/agent-skills#install-agent-skills).
-
-#### Configure manually
-
-Alternatively, to configure Claude Code to use only the Dart and Flutter MCP
-server for the current project, use the `claude mcp add` CLI command:
-
-```console
-$ claude mcp add --transport stdio dart -- dart mcp-server
-```
-
-To learn more about configuring MCP servers in Claude Code,
-check out their documentation on [Installing MCP servers][claude-install].
-
-[claude-install]: https://code.claude.com/docs/en/mcp#installing-mcp-servers
-
-### Codex CLI
-
-To configure the Codex CLI to use the Dart and Flutter MCP server
-for the current project, use the `codex mcp add` CLI command:
-
-```console
-$ codex mcp add dart -- dart mcp-server --force-roots-fallback
-```
-
-To learn more about configuring MCP servers in the Codex CLI,
-check out their documentation on [Connecting to MCP servers][codex-connect].
-
-[codex-connect]: https://developers.openai.com/codex/mcp
-
-## Use your MCP client
-
-Once you've set up the Dart and Flutter MCP server with a client,
-the Dart and Flutter MCP server enables the client to not only reason
-about your project's context but also to take action with tools.
-
-The [Large Language Model (LLM)][LLM] decides which tools to use and when,
-so you can focus on describing your goal in natural language.
-Let's see this in action with a couple of examples using
-GitHub Copilot's Agent mode in VS Code.
-
-[LLM]: https://developers.google.com/machine-learning/resources/intro-llms
-
-### Fix a runtime layout error in a Flutter app
-
-We've all been there: you build a beautiful UI, run the app,
-and are greeted by the infamous yellow-and-black stripes of
-a RenderFlex overflow error.
-Instead of manually debugging the widget tree, you can now
-ask your AI assistant for help with a prompt similar to the following:
-
-> Check for and fix static and runtime analysis issues.
-> Check for and fix any layout issues.
-
-Behind the scenes, the AI agent uses the Dart and Flutter MCP server's tools to:
-
-* See the error: It uses a tool to get the current runtime errors
- from the running application.
-* Inspect the UI: It accesses the Flutter widget tree to understand
- the layout that is causing the overflow.
-* Apply a fix: Armed with this context, it applies a fix and
- checks once more for any remaining errors.
-
-You can then keep or undo the code changes.
-
-### Add new functionality with package search
-
-Imagine you need to add a chart to your app.
-Which package should you use? How do you add it and write the boilerplate?
-The Dart and Flutter MCP server can streamline this entire process with
-a prompt similar to the following:
-
-> Find a suitable package to add a line chart that
-> maps the number of button presses over time.
-
-The AI agent now acts as a true assistant:
-
-* Find the right tool: It uses the `pub_dev_search` tool to
- find popular and highly-rated charting libraries.
-* Manage dependencies: After you confirm its choice,
- such as [`package:fl_chart`][],
- it uses a tool to add the package as a dependency.
-* Generate the code: It generates the new widget code,
- complete with boilerplate for a line chart that it places in the UI.
- It even self-corrects syntax errors introduced during the process.
- You can customize further from there.
-
-What used to be a multi-step process of research,
-reading documentation, editing `pubspec.yaml`, and
-writing the appropriate code in your app,
-is now a single request.
-
-[`package:fl_chart`]: {{site.pub-pkg}}/fl_chart
-
-### Interact with a running app
-
-You can use the Dart and Flutter MCP server to drive a running
-Flutter app from your AI assistant—take screenshots, tap buttons,
-enter text, scroll, and hot reload.
-
-First, add the `flutter_driver` package:
-
-```console
-$ flutter pub add "flutter_driver:{sdk: flutter}"
-```
-
-On mobile and desktop, gate `enableFlutterDriverExtension()` behind a
-`--dart-define` flag in your app's `main()` so it stays out of
-production builds:
-
-```dart
-import 'package:flutter_driver/driver_extension.dart';
-
-void main() {
- if (const bool.fromEnvironment('ENABLE_FLUTTER_DRIVER')) {
- enableFlutterDriverExtension();
- }
- runApp(const MyApp());
-}
-```
-
-Launch your app with the flag turned on:
-
-```console
-$ flutter run -d --dart-define=ENABLE_FLUTTER_DRIVER=true
-```
-
-:::warning
-Enabling the Flutter Driver extension disables real keyboard input—typing
-is dropped and the on-screen keyboard might not appear. To type manually,
-use `enableFlutterDriverExtension(enableTextEntryEmulation: false)`, but
-then the agent's `enterText` command stops working.
-:::
-
-Then ask your assistant to connect:
-
-> Connect to my running Flutter app, take a screenshot, then tap "Sign In".
-
-The agent uses the `dtd` tool to discover the app and
-`flutter_driver_command` to drive its UI.
-
-:::note
-**Web**: the `flutter_driver` extension isn't supported on web builds, so
-finder-based commands like screenshots and taps aren't available there.
-Pair the Dart MCP server with a browser-driving MCP for those. Everything
-that flows through DTD—widget tree, runtime errors, and hot reload—still
-works in a normal `flutter run` web debug session. Prefer
-`flutter run -d web-server` so the browser the agent drives is the one DTD
-is connected to—with `-d chrome`, only the window Flutter spawned receives
-hot reload patches.
-
-See the [setup guide][flutter-driver-guide] for the web-safe conditional
-import, the `-d web-server` versus `-d chrome` modes, and common pitfalls.
-:::
-
-[flutter-driver-guide]: https://github.com/dart-lang/ai/blob/main/pkgs/dart_mcp_server/README.md#connect-to-a-running-flutter-app
-
-## Provide feedback
-
-If you encounter any issues or have feedback about the
-Dart and Flutter MCP server, file an issue on the
-[`dart-lang/ai` issue tracker][ai-issues].
-
-[ai-issues]: https://github.com/dart-lang/ai/issues
diff --git a/sites/docs/src/content/ai/package-skills.md b/sites/docs/src/content/ai/package-skills.md
new file mode 100644
index 00000000000..f0d0e9aa83f
--- /dev/null
+++ b/sites/docs/src/content/ai/package-skills.md
@@ -0,0 +1,94 @@
+---
+title: Package skills
+description: >-
+ Learn how to bundle and consume agent skills in Dart and Flutter packages.
+---
+
+Package skills allow library authors to distribute AI instructions alongside
+their code on [pub.dev](https://pub.dev). When developers add your package to
+their project, their AI coding assistant can discover and use your package's
+official skills.
+
+## For app developers: Consuming package skills
+
+When you add a package that includes skills, your coding assistant can access
+expert guidance tailored specifically to that library's APIs, patterns, and
+code generation workflows.
+
+### Discover and install skills from dependencies
+
+To scan your project dependencies and install available package skills, run:
+
+```bash
+dart run skills@ get
+```
+
+The `skills` CLI scans your dependencies for bundled `skills/` directories,
+presents the skills discovered, and lets you choose which skills to install.
+Subsequent runs are incremental—displaying only new, updated, or removed
+skills.
+
+To automatically install all discovered skills without interactive prompts, use
+the `--all` flag:
+
+```bash
+dart run skills@ get --all
+```
+
+### Install external skills
+
+You can also install standalone skills from external Git repositories using the
+`add` command:
+
+```bash
+dart run skills@ add https://github.com/my-org/custom-ai-skills.git
+```
+
+---
+
+## For package authors: Publishing skills
+
+Package authors can publish skills directly in their `pub.dev` package
+releases to teach AI assistants how to use their libraries correctly.
+
+### Add skills to your package repository
+
+1. Create a `skills/` directory in the root of your package repository.
+2. Inside `skills/`, create a subdirectory prefixed with your package name (for
+ example, `skills/my-package-network/`).
+3. Add a `SKILL.md` file containing YAML frontmatter (`name`, `description`)
+ and step-by-step markdown instructions.
+4. Verify that the `skills/` directory is not excluded in your `.pubignore`
+ file.
+5. Publish your package to `pub.dev`.
+
+### Example `SKILL.md` structure
+
+```markdown
+---
+name: my_package-error-handling
+description: >-
+ Use when making network requests with my_package to ensure safe patterns.
+---
+
+# Error handling guidelines
+
+## Best practices
+* Always wrap network operations in a try/catch block.
+* Catch `NetworkException` specifically to handle retries and diagnostics.
+* Provide fallback UI state in the event of a timeout.
+
+## Example
+1. Call `MyClient.fetchData()`.
+2. Catch `NetworkException` and log `e.context`.
+3. Return a fallback UI model.
+```
+
+---
+
+## Next steps
+
+* To explore the underlying specification, check out
+ [agentskills.io](https://agentskills.io/).
+* To file issues or give feedback on Dart skills tooling, visit the
+ [dart-lang/ai repository](https://github.com/dart-lang/ai).
diff --git a/sites/docs/src/content/ai/tools.md b/sites/docs/src/content/ai/tools.md
new file mode 100644
index 00000000000..333a9b4d5d7
--- /dev/null
+++ b/sites/docs/src/content/ai/tools.md
@@ -0,0 +1,129 @@
+---
+title: How Flutter AI tools work
+shortTitle: How AI tools work
+description: >-
+ Learn how agent skills, the Dart and Flutter MCP server, and package skills
+ interact to assist your development workflow.
+---
+
+Flutter and Dart provide an integrated suite of tools designed to help AI
+coding assistants understand your codebase, follow best practices, and execute
+development tasks accurately.
+
+## Tooling ecosystem overview
+
+The Flutter AI tooling ecosystem consists of four complementary components:
+
+1. **Agent skills**: Task-oriented blueprints that teach assistants how to
+ perform specific workflows (such as creating responsive layouts or writing
+ tests).
+2. **Dart and Flutter MCP server**: A Model Context Protocol (MCP) server
+ exposing real-time SDK diagnostics, symbol resolution, and runtime
+ introspection to assistants.
+3. **Developer Knowledge MCP server**: A cloud-hosted documentation search
+ server giving assistants direct access to official Flutter and Dart docs.
+4. **Package skills**: Skills published directly inside third-party `pub.dev`
+ packages, giving assistants instant expertise on specific libraries.
+
+---
+
+## Agent skills
+
+[Agent skills](https://agentskills.io/) provide a standardized way to equip AI
+assistants with procedural domain expertise.
+
+### Progressive disclosure
+
+Skills use **progressive disclosure** to conserve model context:
+
+* **Discovery phase**: The assistant initially reads only skill metadata (name
+ and short description) from the `.agents/skills` directory.
+* **Execution phase**: When the assistant determines a skill is relevant to
+ your prompt, it loads the complete instructions (`SKILL.md`) and supporting
+ reference scripts.
+
+### Official skills repositories
+
+* [**flutter/agent-plugins**](https://github.com/flutter/agent-plugins):
+ Provides skills for Flutter development, including widget construction,
+ declarative navigation, responsive design, and state management.
+* [**dart-lang/skills**](https://github.com/dart-lang/skills): Provides skills
+ for Dart development, including unit test authoring, dependency resolution,
+ and static analysis remediation.
+
+---
+
+## Dart and Flutter MCP server
+
+The [Dart and Flutter MCP server](
+https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp_server)
+implements the open Model Context Protocol (MCP), connecting AI assistants to
+live Dart SDK and Flutter tooling over standard I/O (stdio).
+
+Key capabilities provided by the MCP server include:
+
+* **Static analysis & diagnostics**: Inspect and fix analyzer errors and
+ warnings across your project.
+* **Symbol resolution**: Resolve symbols to their definitions, signatures, and
+ documentation.
+* **Runtime introspection**: Query the state of a running Flutter application
+ during active debugging sessions.
+* **Package management**: Search [pub.dev](https://pub.dev) and manage
+ dependencies in `pubspec.yaml`.
+* **Testing & formatting**: Execute unit and widget tests and format code using
+ `dart format` rules.
+
+---
+
+## Developer Knowledge MCP server
+
+While the local Dart and Flutter MCP server focuses on local codebase analysis
+and runtime debugging, you can also equip your assistant with search access to
+official online documentation.
+
+The [Developer Knowledge MCP server](
+https://developers.google.com/knowledge/mcp)
+connects AI assistants to Google's developer documentation corpus, including
+[docs.flutter.dev](https://docs.flutter.dev),
+[dart.dev](https://dart.dev), and official API references. This allows
+assistants to retrieve up-to-date guides, migration notes, and API references
+directly during conversation.
+
+---
+
+## Package skills
+
+In addition to core Flutter skills, library authors can bundle official skills
+directly inside their packages on [pub.dev](https://pub.dev).
+
+When you add a dependency to your project, you can discover and install its
+skills using the `skills` CLI:
+
+```bash
+dart run skills@ get
+```
+
+To learn how to install or publish package skills, check out
+[Package skills](/ai/package-skills).
+
+---
+
+## When to use what
+
+Use the following mental model to understand how different AI mechanisms
+interact:
+
+| Mechanism | Scope and behavior | Best suited for |
+| :--- | :--- | :--- |
+| **Agent skill** | On demand | Multi-step recipes, patterns |
+| **Dart MCP server** | Local SDK tools | Diagnostics, tests, runtime |
+| **Developer Knowledge MCP** | Online search | Live docs, API references |
+| **Package skill** | From dependencies | Library APIs, code generation |
+
+{:.table .table-striped}
+
+## Next steps
+
+* Follow the [Get started with AI](/ai/get-started) guide to install official
+ plugins in your editor.
+* Learn how to consume and publish [Package skills](/ai/package-skills).
diff --git a/sites/docs/src/content/deployment/android.md b/sites/docs/src/content/deployment/android.md
index 1f939067d3f..477a940bf81 100644
--- a/sites/docs/src/content/deployment/android.md
+++ b/sites/docs/src/content/deployment/android.md
@@ -37,7 +37,7 @@ substitute `[project]` with your app's directory.
## Add a launcher icon
When a new Flutter app is created, it has a default launcher icon.
-To customize this icon, you might want to check out the
+To learn about customizing this icon, check out the
[flutter_launcher_icons][] package.
Alternatively, you can do it manually using the following steps:
@@ -191,8 +191,8 @@ If not, create one using one of the following methods:
Create a file named `[project]/android/key.properties`
that contains a reference to your keystore.
-Don't include the angle brackets (`< >`).
-They indicate that the text serves as a placeholder for your values.
+Don't include the angle brackets (`< >`),
+which indicate that the text serves as a placeholder for your values:
```properties
storePassword=
@@ -222,7 +222,7 @@ To configure Gradle, edit the `/android/app/build.gradle.kts` file.
1. Define and load the keystore properties file before the `android`
property block.
-1. Set the `keystoreProperties` object to load the `key.properties` file.
+1. Set the `keystoreProperties` object to load the `key.properties` file:
@@ -272,7 +272,7 @@ To configure Gradle, edit the `/android/app/build.gradle.kts` file.
1. Add the signing configuration before the `buildTypes` property block
- inside the `android` property block.
+ inside the `android` property block:
@@ -283,10 +283,10 @@ To configure Gradle, edit the `/android/app/build.gradle.kts` file.
+ signingConfigs {
+ create("release") {
-+ keyAlias = keystoreProperties["keyAlias"] as String
-+ keyPassword = keystoreProperties["keyPassword"] as String
-+ storeFile = keystoreProperties["storeFile"]?.let { file(it) }
-+ storePassword = keystoreProperties["storePassword"] as String
++ keyAlias = keystoreProperties.getProperty("keyAlias")
++ keyPassword = keystoreProperties.getProperty("keyPassword")
++ storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) }
++ storePassword = keystoreProperties.getProperty("storePassword")
+ }
+ }
buildTypes {
@@ -340,7 +340,7 @@ You might need to run `flutter clean` after changing the Gradle file.
This prevents cached builds from affecting the signing process.
:::
-To learn more about signing your app, check out
+To learn about signing your app, check out
[Sign your app][] on the Android developer docs.
[Sign your app]: {{site.android-dev}}/studio/publish/app-signing.html#generate-key
@@ -365,8 +365,8 @@ attacks that make use of quantum computing.
Note that you must create a new classical key;
you cannot reuse the older one.
-For more information, check out the
-[Android documentation on PQC APK signing][android-doc]
+For details, consult the
+[Android documentation on PQC APK signing][android-doc].
[android-doc]: {{site.android-dev}}/about/versions/17/features#pqc-apk-signing
@@ -383,7 +383,7 @@ the compile time of an Android application.
The `--[no-]shrink` flag has no effect.
Code shrinking is always enabled in release builds.
-To learn more, check out [Shrink, obfuscate, and optimize your app][].
+For details, visit [Shrink, obfuscate, and optimize your app][].
:::
[R8]: {{site.android-dev}}/studio/build/shrink-code
@@ -423,7 +423,7 @@ The Flutter tool enables multidex support and retries the build:
-:::note
+:::version-note
Multidex support is natively included when targeting
Android SDK 21 or later.
:::
@@ -438,15 +438,15 @@ io/flutter/util/PathUtils.class
```
Also, include any other classes used in app startup.
-For more detailed guidance on adding multidex support manually,
-check out the official [Android documentation][multidex-docs].
+To learn about adding multidex support manually,
+consult the official [Android documentation][multidex-docs].
[multidex-keep]: {{site.android-dev}}/studio/build/multidex#keep
[multidex-docs]: {{site.android-dev}}/studio/build/multidex
## Review the app manifest
-Review the default [App Manifest][manifest] file.
+Review the default [App Manifest][manifest] file:
```xml title="[project]/android/app/src/main/AndroidManifest.xml"
@@ -476,8 +476,8 @@ Verify the following values:
To verify the Android build configuration,
review the `android` block in the default
-[Gradle build script][gradlebuild].
-The default Gradle build script is found at `[project]/android/app/build.gradle.kts`.
+[Gradle build script][gradlebuild]
+found at `[project]/android/app/build.gradle.kts`:
```kotlin title="[project]/android/app/build.gradle.kts"
android {
@@ -494,7 +494,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.[project]"
// You can update the following values to match your application needs.
- // For more information, see: https://flutter.dev/to/review-gradle-config.
+ // For details, see https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
@@ -541,9 +541,12 @@ For example:
The Flutter tooling sets default values for the Android SDK versions:
-* **`compileSdk`**: The version of the Android SDK used to compile the app.
-* **`minSdk`**: The minimum Android version that the app supports.
-* **`targetSdk`**: The Android version the app is designed and tested to run on.
+* **`compileSdk`**: The version of the Android SDK used to
+ compile the app.
+* **`minSdk`**: The minimum Android version that the
+ app supports.
+* **`targetSdk`**: The Android version the app is designed and
+ tested to run on.
These default values (`flutter.compileSdkVersion`, etc.) are managed by Flutter
to ensure compatibility with the framework and plugins.
@@ -572,7 +575,8 @@ publishing to the Play Store.
:::note
The Google Play Store prefers the app bundle format.
-To learn more, check out [About Android App Bundles][bundle].
+To learn about Android App Bundles, visit
+[About Android App Bundles][bundle].
:::
[bundle]: {{site.android-dev}}/guide/app-bundle
@@ -684,8 +688,8 @@ From the command line:
## Publish to the Google Play Store
-For detailed instructions on publishing your app to the Google Play Store,
-check out the [Google Play launch][play] documentation.
+To learn about publishing your app to the Google Play Store,
+consult the [Google Play launch][play] documentation.
## Update the app's version number
@@ -706,8 +710,8 @@ Both the version and the build number can be overridden in
Flutter's build by specifying `--build-name` and `--build-number`, respectively.
In Android, `build-name` is used as `versionName` while
-`build-number` used as `versionCode`. For more information,
-check out [Version your app][] in the Android documentation.
+`build-number` used as `versionCode`. For details,
+refer to [Version your app][] in the Android documentation.
When you rebuild the app for Android, any updates in
the version number from the pubspec file will
@@ -805,9 +809,9 @@ Run `apkanalyzer manifest print ` and look for a ``
tag with `android:name="flutterEmbedding"`.
The value can be `1` or `2`.
-Example:
-`apkanalyzer manifest print some-flutter-app.apk | grep flutterEmbedding -C 2`
-returns the following style string.
+This command,
+`apkanalyzer manifest print some-flutter-app.apk | grep flutterEmbedding -C 2`,
+returns the following output:
```
-
View the many videos on the Flutter YouTube channel.
-
+
Learn how to build with and integrate powerful AI tools.
diff --git a/sites/docs/src/content/learn/pathway/index.md b/sites/docs/src/content/learn/pathway/index.md
index 7469d90789e..21687640024 100644
--- a/sites/docs/src/content/learn/pathway/index.md
+++ b/sites/docs/src/content/learn/pathway/index.md
@@ -64,6 +64,12 @@ take full advantage of the framework.
+## Ready to start?
+
+Get started by installing Flutter and setting up your development environment:
+
+[Start with Step 1: Quick Install →](/learn/pathway/quick-install)
+
diff --git a/sites/docs/src/content/llms.txt b/sites/docs/src/content/llms.txt
index a0ab33e79cc..00b4ff2bf6f 100644
--- a/sites/docs/src/content/llms.txt
+++ b/sites/docs/src/content/llms.txt
@@ -98,8 +98,8 @@ This file provides a curated list of resources to help Large Language Models und
### AI-assisted development
-- [Create with AI](https://docs.flutter.dev/ai/create-with-ai): An overview of how to use AI to supercharge your Flutter development.
-- [The Dart and Flutter MCP Server](https://docs.flutter.dev/ai/mcp-server): Technical details on the server that enables AI features in the IDE.
+- [Get started with AI](https://docs.flutter.dev/ai/get-started): How to set up and use AI agent plugins for Flutter and Dart.
+- [How Flutter AI tools work](https://docs.flutter.dev/ai/tools): Technical details on the skills, MCP server, and tools for AI coding assistants.
- [Supercharge your Dart & Flutter development experience with the Dart and Flutter MCP server](https://blog.flutter.dev/supercharge-your-dart-flutter-development-experience-with-the-dart-mcp-server-2edcc8107b49): A blog post explaining the benefits of the MCP server.
- [Gemini in Android Studio now speaks fluent Flutter](https://blog.flutter.dev/gemini-in-android-studio-now-speaks-fluent-flutter-915dfec98274): A blog post about Gemini's Flutter capabilities in Android Studio.
diff --git a/sites/docs/src/content/packages-and-plugins/developing-packages.md b/sites/docs/src/content/packages-and-plugins/developing-packages.md
index 01751451f4c..93af492a2d9 100644
--- a/sites/docs/src/content/packages-and-plugins/developing-packages.md
+++ b/sites/docs/src/content/packages-and-plugins/developing-packages.md
@@ -1064,4 +1064,21 @@ file, like any other Dart package.
[test your plugin]: #testing-your-plugin
[unit tests]: /testing/overview#unit-tests
[`url_launcher`]: {{site.pub}}/packages/url_launcher
+## Bundle AI package skills
+
+You can help AI coding assistants use your package effectively by bundling
+custom instructions and recipes directly inside your repository.
+
+To distribute skills with your package:
+
+1. Create a `skills/` directory in the root of your package repo.
+2. Inside `skills/`, create a subdirectory named after your package (for
+ example, `skills//`).
+3. Add a `SKILL.md` file with guidelines and idiomatic code examples.
+4. Publish your package to `pub.dev`.
+
+When developers add your package as a dependency, their assistants can
+automatically discover your instructions. To learn more about authoring
+guidelines and best practices, check out [Package skills](/ai/package-skills).
+
[Writing a good plugin]: {{site.flutter-blog}}/writing-a-good-flutter-plugin-1a561b986c9c
diff --git a/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md b/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md
index 3d7a5077feb..dcbcaf9a115 100644
--- a/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md
+++ b/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md
@@ -39,8 +39,91 @@ yet support Swift Package Manager.
If you previously disabled SwiftPM, you might need to enable it with
`flutter config --enable-swift-package-manager`.
-If automatic migration works for you, that's it!
-You are done with this page.
+If automatic migration works for you and all of your dependencies
+support Swift Package Manager,
+you can [remove CocoaPods integration][removeCocoaPods].
+
+## How to remove CocoaPods integration
+
+**Flutter falls back to CocoaPods if any of your project dependencies
+do not support Swift Package Manager.**
+Ensure that all plugins in your project support Swift Package Manager
+before removing CocoaPods.
+
+:::note
+If you have manually edited your `Podfile` to add CocoaPods
+dependencies or logic other than Flutter plugins,
+you must manually remove those changes.
+:::
+
+
+
+
+To remove CocoaPods from your project, use the following steps.
+
+1. To de-integrate CocoaPods from your Xcode projects, run:
+
+ ```sh
+ cd ios
+ pod deintegrate
+ cd ..
+ ```
+
+2. Delete CocoaPods-related files and directories:
+
+ Delete `ios/Podfile`, `ios/Podfile.lock`,
+ the `ios/Pods/` directory, and
+ the `ios/.symlinks/` directory (if present).
+
+3. Check your configuration files:
+
+ In `ios/Flutter/Debug.xcconfig`
+ and `ios/Flutter/Release.xcconfig`,
+ remove any `#include` lines referencing `Pods/Target Support Files`
+ or CocoaPods `.xcconfig` files if they are still present.
+
+4. Clean and build your project to verify the migration:
+
+ ```sh
+ flutter clean
+ flutter pub get
+ flutter run
+ ```
+
+
+
+
+To remove CocoaPods from your project, use the following steps.
+
+1. To de-integrate CocoaPods from your Xcode projects, run:
+
+ ```sh
+ cd macos
+ pod deintegrate
+ cd ..
+ ```
+
+2. Delete CocoaPods-related files and directories:
+
+ Delete `macos/Podfile`, the `macos/Pods/` directory,
+ and the `macos/.symlinks/` directory (if present).
+
+3. Check your configuration files:
+
+ In `macos/Flutter/Flutter-Debug.xcconfig` and `macos/Flutter/Flutter-Release.xcconfig`
+ remove any `#include` lines referencing `Pods/Target Support Files`
+ or CocoaPods `.xcconfig` files if they are still present.
+
+4. Clean and build your project to verify the migration:
+
+ ```sh
+ flutter clean
+ flutter pub get
+ flutter run
+ ```
+
+
+
## How to add Swift Package Manager integration manually
@@ -223,4 +306,5 @@ This turns off Swift Package Manager for the current user.
If a project is incompatible with Swift Package Manager,
all contributors need to run this command.
+[removeCocoaPods]: #how-to-remove-cocoapods-integration
[removeSPM]: #how-to-remove-swift-package-manager-integration
diff --git a/sites/docs/src/content/packages-and-plugins/using-packages.md b/sites/docs/src/content/packages-and-plugins/using-packages.md
index 33e957c21ca..622defeee25 100644
--- a/sites/docs/src/content/packages-and-plugins/using-packages.md
+++ b/sites/docs/src/content/packages-and-plugins/using-packages.md
@@ -501,3 +501,17 @@ To use this plugin:
before adding the plugin). Click **Show Flutter homepage**.
You should see the default browser open on the device,
displaying the homepage for flutter.dev.
+## Install AI skills from packages
+
+Many `pub.dev` packages bundle official **package skills** that teach AI coding
+assistants how to use their APIs and follow recommended patterns.
+
+If your project dependencies include skills, you can discover and install them
+into your local workspace with a single command:
+
+```bash
+dart run skills@ get
+```
+
+To learn more about how skills help coding assistants understand your
+dependencies, check out [Package skills](/ai/package-skills).
diff --git a/sites/docs/src/content/platform-integration/android/local-network-permission.md b/sites/docs/src/content/platform-integration/android/local-network-permission.md
new file mode 100644
index 00000000000..d3d8af929e5
--- /dev/null
+++ b/sites/docs/src/content/platform-integration/android/local-network-permission.md
@@ -0,0 +1,154 @@
+---
+title: Request local network permissions on Android
+shortTitle: Local network permission
+description: >-
+ Learn how to declare and request Android local area network permissions
+ before opening Dart sockets in Flutter.
+---
+
+Starting in Android 17 (API level 37), Android blocks local network access
+by default. Apps targeting Android 17 or higher that discover, scan,
+or connect to devices on the local area network must declare and request
+the `ACCESS_LOCAL_NETWORK` runtime permission.
+
+:::note
+Although enforced for apps targeting Android 17 (API level 37) and higher,
+you can opt in to test this behavior on Android 16 (API level 36).
+:::
+
+## Understand the impact on Dart sockets
+
+The standard `dart:io` library provides low-level socket networking,
+including [`Socket`][socket-class], [`RawSocket`][raw-socket-class],
+and [`ServerSocket`][server-socket-class].
+These socket classes operate at the system level
+and do not interact with Android's application framework UI.
+
+Because Dart sockets cannot display an Android permission prompt,
+any attempt to connect to a local IP address without the required permission
+fails and throws a `SocketException`.
+To prevent connection failures,
+request the runtime permission in Flutter
+before you initialize or connect your Dart socket.
+
+## Declare the permission in Android
+
+Before you can request the permission at runtime,
+declare it in your Android manifest.
+
+1. Open `android/app/src/main/AndroidManifest.xml`.
+1. Add the `ACCESS_LOCAL_NETWORK` permission inside the `` tag:
+
+```xml
+
+
+
+
+```
+
+## Add the permission_handler package
+
+To request Android runtime permissions from Dart,
+use the [`permission_handler`][] plugin from [pub.dev][].
+
+Add `permission_handler` to your Flutter project dependencies:
+
+```bash
+flutter pub add permission_handler
+```
+
+## Request permission before connecting a socket
+
+Call `Permission.accessLocalNetwork.request()`
+before opening a Dart socket connection.
+
+The following example demonstrates how to check the platform,
+request local network permission,
+and open a socket connection:
+
+```dart
+import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+Future connectToLocalDevice({
+ required String host,
+ required int port,
+}) async {
+ // Check and request runtime permission on Android.
+ if (Platform.isAndroid) {
+ final status = await Permission.accessLocalNetwork.request();
+
+ if (status.isPermanentlyDenied) {
+ // The user opted not to grant permission and checked "Don't ask again".
+ // In a real app, show an explanation dialog before calling openAppSettings().
+ debugPrint('Permission permanently denied. Please enable it in settings.');
+ return;
+ }
+
+ if (!status.isGranted) {
+ // Handle the case where the user denied permission.
+ debugPrint('Local network permission was denied.');
+ return;
+ }
+ }
+
+ // Connect the Dart socket after the permission is granted.
+ try {
+ final socket = await Socket.connect(
+ host,
+ port,
+ timeout: const Duration(seconds: 5),
+ );
+
+ debugPrint(
+ 'Connected to ${socket.remoteAddress.address}:${socket.remotePort}',
+ );
+
+ // Use the socket...
+
+ await socket.close();
+ } on SocketException catch (e) {
+ debugPrint('Failed to connect: $e');
+ }
+}
+```
+
+## Handle permission status
+
+The `Permission.accessLocalNetwork.request()` method
+returns a `PermissionStatus` enum that indicates the outcome:
+
+* `PermissionStatus.granted`: The user granted local network access.
+ You can proceed to connect your Dart sockets.
+* `PermissionStatus.denied`: The user denied permission for this request.
+ Prompt the user with an explanation before requesting permission again.
+* `PermissionStatus.permanentlyDenied`: The user denied permission permanently.
+ Call `openAppSettings()` to help the user enable permission
+ in the system settings.
+
+:::tip
+You only need to obtain permission once during an app session,
+but checking the status before establishing a socket connection
+ensures reliable network calls.
+:::
+
+## More information
+
+To learn more about local network permissions and socket programming,
+consult the following resources:
+
+* Android developer guide on [Local network permission][android-local-network]
+* [`permission_handler`][] package on pub.dev
+* Dart [`Socket` class documentation][socket-class]
+* [Issue #184859 on GitHub][flutter-issue-184859]
+
+[android-local-network]: https://developer.android.com/privacy-and-security/local-network-permission#android-17-enforcement
+[`permission_handler`]: {{site.pub-pkg}}/permission_handler
+[pub.dev]: {{site.pub}}
+[socket-class]: {{site.dart.api}}/dart-io/Socket-class.html
+[raw-socket-class]: {{site.dart.api}}/dart-io/RawSocket-class.html
+[server-socket-class]: {{site.dart.api}}/dart-io/ServerSocket-class.html
+[flutter-issue-184859]: {{site.repo.flutter}}/issues/184859
diff --git a/sites/docs/src/content/platform-integration/index.md b/sites/docs/src/content/platform-integration/index.md
index 712d0384a68..aee1e3d8b97 100644
--- a/sites/docs/src/content/platform-integration/index.md
+++ b/sites/docs/src/content/platform-integration/index.md
@@ -165,6 +165,14 @@ Learn how to add custom integrations with Android to your Flutter app.
Learn how to launch a Jetpack Compose activity from your app.
+
### Integrate with iOS {:#ios}
diff --git a/sites/docs/src/content/reference/create-new-app.md b/sites/docs/src/content/reference/create-new-app.md
index c99a8ace287..703c09c06b2 100644
--- a/sites/docs/src/content/reference/create-new-app.md
+++ b/sites/docs/src/content/reference/create-new-app.md
@@ -304,7 +304,7 @@ check out the [IntelliJ for Flutter reference][ij-more].
To create a Flutter app with Antigravity, you first need
to install and set up Antigravity as described on the
-[Antigravity page](/ai/antigravity).
+[Get started with AI](/ai/get-started).
Then follow these steps:
1.
Open Antigravity and create a Workspace
diff --git a/sites/docs/src/content/release/breaking-changes/index.md b/sites/docs/src/content/release/breaking-changes/index.md
index ec95d837bcd..0ddde760623 100644
--- a/sites/docs/src/content/release/breaking-changes/index.md
+++ b/sites/docs/src/content/release/breaking-changes/index.md
@@ -37,8 +37,12 @@ They're sorted by release and listed in alphabetical order:
### Not yet released to stable
* [Added enabled property and made onChanged optional for DropdownButton][]
+* [Migrate to standalone `material_ui` and `cupertino_ui` packages][]
+* [Restrict command-line flags for prebuilt Android release binaries][]
[Added enabled property and made onChanged optional for DropdownButton]: /release/breaking-changes/dropdownbutton-enabled-property
+[Migrate to standalone `material_ui` and `cupertino_ui` packages]: /release/breaking-changes/material-ui-and-cupertino-ui
+[Restrict command-line flags for prebuilt Android release binaries]: /release/breaking-changes/restrict-command-line-flags-prebuilt-android-release-binaries.md
### Released in Flutter 3.47
diff --git a/sites/docs/src/content/release/breaking-changes/material-ui-and-cupertino-ui.md b/sites/docs/src/content/release/breaking-changes/material-ui-and-cupertino-ui.md
new file mode 100644
index 00000000000..2e66b1f7cee
--- /dev/null
+++ b/sites/docs/src/content/release/breaking-changes/material-ui-and-cupertino-ui.md
@@ -0,0 +1,244 @@
+---
+title: Migrate to standalone material_ui and cupertino_ui packages
+description: >-
+ The Material and Cupertino design libraries are now available as
+ standalone packages, decoupling design systems from the Flutter SDK.
+---
+
+{% render "docs/breaking-changes.md" %}
+
+## Summary
+
+The Material and Cupertino design libraries are now available as
+standalone packages: [`package:material_ui`][] and [`package:cupertino_ui`][].
+In Flutter 3.47, developers can opt in to these packages ahead of
+the formal deprecation of the in-framework design libraries.
+
+## Background
+
+Historically, Flutter's Material (`package:flutter/material.dart`)
+and Cupertino (`package:flutter/cupertino.dart`) widget libraries
+were bundled directly inside the core Flutter SDK.
+While this made it easy to get started with pixel-perfect widgets,
+bundling design systems inside the core framework presented several challenges:
+
+* Widget updates, bug fixes, and new components were tied to
+ quarterly Flutter SDK releases.
+* Contributions and iteration on design libraries moved slower
+ than standalone Dart packages.
+* Applications couldn't update design libraries independently of
+ the engine and framework.
+
+To resolve these challenges, the design systems are now decoupled
+from the core framework.
+Starting in Flutter 3.47, version 1.0 of the standalone
+`material_ui` and `cupertino_ui` packages is published on `pub.dev`.
+
+Decoupling the design systems provides several benefits:
+
+* **Independent release cycles**:
+ The packages can ship bug fixes, improvements, and new components
+ on their own release schedules (planned for weekly releases),
+ without requiring a full Flutter SDK upgrade.
+* **Faster contributions**:
+ Community members can contribute directly to the libraries in the
+ [`flutter/packages`][] repository.
+* **Style-neutral core**:
+ Decoupling lays the groundwork for a style-neutral Flutter core widget
+ catalog, making it easier to build and maintain custom design systems.
+
+Contributions to `package:flutter/material.dart` and
+`package:flutter/cupertino.dart` inside the core SDK were frozen
+starting in Flutter 3.44.
+The initial 1.0.0 versions of `package:material_ui` and `package:cupertino_ui`
+match the frozen framework code to provide a seamless transition.
+Future releases of the packages will use semantic versioning for major, minor,
+and patch updates.
+The in-framework design libraries are scheduled for formal deprecation
+in an upcoming stable release.
+
+## Migration guide
+
+To migrate your project from the in-framework design libraries to the
+standalone packages, you can use the automated `dart fix` tool
+or perform the migration manually.
+
+### Automated migration with dart fix
+
+The quickest way to migrate is using the `migrate_design_widgets` fix rule
+with `dart fix`:
+
+```console
+$ dart fix --apply --code=migrate_design_widgets
+```
+
+This command automatically updates your imports from
+`package:flutter/material.dart` and `package:flutter/cupertino.dart`
+to the corresponding standalone package imports.
+
+:::note
+If `dart fix` doesn't automatically add the new packages to your
+`pubspec.yaml` dependencies, manually add them by running
+`flutter pub add material_ui` or `flutter pub add cupertino_ui` as needed.
+:::
+
+Run `dart fix` once more to ensure all other automated migrations are applied,
+and lint warnings addressed, such as import sorting.
+
+### Manual migration
+
+To migrate manually, follow these steps:
+
+1. Add the standalone packages to your `pubspec.yaml` dependencies:
+
+ ```console
+ $ flutter pub add material_ui
+ $ flutter pub add cupertino_ui
+ ```
+
+1. Update the imports in your Dart files to reference the new packages.
+
+ Code before migration:
+
+ ```dart
+ import 'package:flutter/cupertino.dart';
+ import 'package:flutter/material.dart';
+ ```
+
+ Code after migration:
+
+ ```dart
+ import 'package:cupertino_ui/cupertino_ui.dart';
+ import 'package:material_ui/material_ui.dart';
+ ```
+
+### Compatibility bridge
+
+During the ecosystem transition, your application might depend on
+third-party packages that still import the legacy in-framework libraries
+(`package:flutter/material.dart` or `package:flutter/cupertino.dart`).
+
+To allow your application to migrate to the standalone packages immediately
+without waiting for all dependencies to update, `material_ui` and
+`cupertino_ui` provide compatibility bridge utilities.
+Wrap your app using `MaterialUiCompatibilityBridge` inside
+`MaterialApp.builder`:
+
+```dart
+import 'package:material_ui/material_ui.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(
+ seedColor: const Color(0xFF6750A4),
+ ),
+ ),
+ builder: (BuildContext context, Widget? child) {
+ return MaterialUiCompatibilityBridge(child: child!);
+ },
+ home: const HomeScreen(),
+ );
+ }
+}
+```
+
+#### Bridge capabilities and limitations
+
+`MaterialUiCompatibilityBridge` injects theme and localization data
+downward into the widget tree.
+This allows unmigrated child widgets and dependencies that read design state
+from context
+(such as `Theme.of(context)` or `MaterialLocalizations.of(context)`)
+to continue functioning properly.
+
+However, the compatibility bridge cannot resolve type mismatches
+when a dependency exposes, accepts, or returns in-framework SDK types
+in its public API signatures (such as passing a `FloatingActionButtonLocation`,
+`ColorScheme`, or `TextTheme` as a parameter or callback return value).
+Because Dart enforces static typing across distinct package imports,
+values from `package:flutter/material.dart` cannot be assigned to parameters
+expecting types from `package:material_ui`.
+Dependencies with API-signature coupling must be migrated to the standalone
+package before your application can pass modern types to them.
+
+### Decoupled localizations
+
+As part of this transition, `flutter_localizations` is unbundled
+for design components.
+Localization delegates and translated strings for Material and Cupertino
+widgets now reside directly in `package:material_ui` and
+`package:cupertino_ui`.
+
+Code before migration:
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+
+// ...
+MaterialApp(
+ localizationsDelegates: const >[
+ GlobalCupertinoLocalizations.delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ ],
+ // ...
+);
+```
+
+Code after migration:
+
+```dart
+import 'package:material_ui/material_ui.dart';
+
+// ...
+MaterialApp(
+ localizationsDelegates: GlobalMaterialLocalizations.delegates,
+ // ...
+);
+```
+
+Setting `localizationsDelegates` to `GlobalMaterialLocalizations.delegates`
+automatically includes the Cupertino and Widgets delegates as well,
+simplifying localization setup.
+
+### Guidance for package authors
+
+If you maintain an open source package or plugin in the Flutter ecosystem,
+migrating to `package:material_ui` or `package:cupertino_ui` introduces
+a breaking change for consumers who haven't updated their dependencies.
+While the symbols and class names remain the same,
+they are technically referenced from an entirely new library.
+Treat this migration as a major version bump for your package.
+
+## References
+
+Package documentation:
+
+* [`package:material_ui`][]
+* [`package:cupertino_ui`][]
+
+Relevant issues:
+
+* [Issue 191448][]
+
+Relevant blog posts and announcements:
+
+* [What's new in Flutter 3.47][]
+* [Flutter's Material and Cupertino code freeze][]
+
+[`flutter/packages`]: {{site.repo.packages}}
+[Issue 191448]: {{site.repo.flutter}}/issues/191448
+[`package:cupertino_ui`]: {{site.pub-pkg}}/cupertino_ui
+[`package:material_ui`]: {{site.pub-pkg}}/material_ui
+[Flutter's Material and Cupertino code freeze]: {{site.main-url}}/blog/flutters-material-and-cupertino-code-freeze
+[What's new in Flutter 3.47]: {{site.main-url}}/blog/whats-new-in-flutter-3-47
diff --git a/sites/docs/src/content/release/breaking-changes/restrict-command-line-flags-prebuilt-android-release-binaries.md b/sites/docs/src/content/release/breaking-changes/restrict-command-line-flags-prebuilt-android-release-binaries.md
new file mode 100644
index 00000000000..a4ad39e61ce
--- /dev/null
+++ b/sites/docs/src/content/release/breaking-changes/restrict-command-line-flags-prebuilt-android-release-binaries.md
@@ -0,0 +1,162 @@
+---
+title: Restrict command-line flags for prebuilt Android release binaries
+description: >-
+ Passing configuration flags to prebuilt Android release binaries with
+ `--use-application-binary` is no longer supported.
+---
+
+{% render "docs/breaking-changes.md" %}
+
+## Summary
+
+Previously, the Flutter CLI could pass engine configuration flags
+(such as `--dart-flags`) to a prebuilt Android release binary
+(`--use-application-binary --release`) at launch time
+using Android `Intent` extras,
+which the embedding accepted in all build modes.
+
+To protect production applications
+against `Intent`-based spoofing vulnerabilities,
+Flutter Android release builds now ignore `Intent` extras
+and read engine configuration strictly
+from the compiled `AndroidManifest.xml`.
+Because prebuilt binaries cannot have their manifests
+dynamically modified after compilation,
+the Flutter CLI now produces a fatal error
+if you pass engine configuration flags to a prebuilt release binary
+(preventing flags from being silently ignored).
+
+Standard release builds
+(where the CLI compiles the app and injects flags into the manifest)
+and all debug and profile workflows continue to work without changes.
+
+## Context
+
+The Flutter CLI allows passing flags
+(such as `--dart-flags` or tracing options)
+to configure the Flutter engine when running or driving an application.
+Historically, the Flutter Android embedding accepted these flags at runtime
+through [`Intent`][] extras.
+
+However, runtime `Intent` extras on Android
+can be spoofed or intercepted by other applications on a user's device.
+To harden production applications,
+Flutter Android release builds now read configuration strictly
+from a cryptographically signed `AndroidManifest.xml`
+and ignore runtime `Intent` flags.
+
+For release builds of standard Gradle-based projects,
+the Flutter CLI automatically injects command-line flags
+into `AndroidManifest.xml` during compilation.
+When you use a prebuilt release binary with `--use-application-binary`,
+the CLI cannot modify the compiled manifest,
+and the binary ignores runtime `Intent` flags.
+To prevent tests or scripts from running
+with unnoticed configuration failures,
+the CLI now reports a fatal error.
+
+Debug and profile builds intentionally maintain runtime flag support
+to preserve testing velocity and dynamic benchmarking workflows.
+
+[`Intent`]: https://developer.android.com/reference/android/content/Intent
+
+## Description of change
+
+The Flutter CLI enforces the following behavior
+when running Flutter apps on Android:
+
+| Build mode | Using `--use-application-binary` | CLI behavior | Notes |
+| :--- | :--- | :--- | :--- |
+| **Debug / Profile** | Yes | Passes flags to binary through `adb` | No rebuild required; flags apply at runtime. |
+| **Debug / Profile** | No | Builds and passes flags through `adb` | Standard development workflow. |
+| **Release** | Yes | **Fatal error** if configuration flags are provided | Prebuilt release binaries cannot be dynamically configured. |
+| **Release** | No | Injects flags into `AndroidManifest.xml` during compilation | Standard release build workflow. |
+
+## Migration guide
+
+:::note
+You are **not affected** and do not need to take action if:
+- You build and run standard release apps (`flutter run --release`,
+ `flutter build apk --release`, `flutter build appbundle`).
+- You run tests and benchmarks in **debug** or **profile** mode.
+- You use `--use-application-binary`
+ without passing engine configuration flags.
+:::
+
+If your CI/CD pipelines, automated scripts, or build systems
+pass flags to prebuilt release binaries,
+use one of the following migration paths:
+
+### Switch testing and benchmarking to profile mode
+
+If your automated test pipelines use `--use-application-binary`
+with `--release` to dynamically test different engine configurations:
+
+1. Switch your test target to **profile mode** (`--profile`).
+ Profile mode mirrors release performance characteristics
+ while retaining support for dynamic runtime flag configuration
+ without recompilation.
+
+### Build release binaries with flags directly
+
+If you must run tests against a release binary:
+
+1. Run `flutter build` or `flutter run` with your configuration flags
+ without `--use-application-binary`.
+ The CLI automatically embeds the flags into the compiled manifest.
+1. Alternatively, compile separate release binaries
+ for each required test configuration.
+
+### Configure non-Gradle or hermetic build systems
+
+If you build Flutter Android applications using hermetic build systems
+(such as Bazel) that separate compilation from execution:
+
+1. Statically declare any necessary engine flags in `AndroidManifest.xml`
+ before compiling the release APK.
+1. Use **profile mode** for test targets
+ that require dynamic configuration at launch time.
+
+### Declare engine flags in `AndroidManifest.xml`
+
+To configure engine flags statically in release builds,
+add `` elements under the `` tag in
+your `android/app/src/main/AndroidManifest.xml` file:
+
+```xml title="AndroidManifest.xml" highlightLines=6-12
+
+
+
+
+
+
+
+
+```
+
+## Timeline
+
+Landed in version: TBD
+In stable release: TBD
+
+## References
+
+Relevant issues:
+
+* [Issue 180686][]
+
+Relevant pull requests:
+
+* [PR 190870][]
+
+[Issue 180686]: https://github.com/flutter/flutter/issues/180686
+[PR 190870]: https://github.com/flutter/flutter/pull/190870
diff --git a/sites/docs/src/content/resources/faq.md b/sites/docs/src/content/resources/faq.md
index caa1f263e34..4b66150dc2f 100644
--- a/sites/docs/src/content/resources/faq.md
+++ b/sites/docs/src/content/resources/faq.md
@@ -170,7 +170,7 @@ of the many editors that support [editing Dart][].
[Android Studio]: {{site.android-dev}}/studio
[Android Studio/IntelliJ]: /tools/android-studio
-[Antigravity]: /ai/antigravity
+[Antigravity]: /ai/get-started
[editing Dart]: {{site.dart-site}}/tools
[editor configuration]: /tools/editors
[IntelliJ IDEA]: https://www.jetbrains.com/idea/
diff --git a/sites/docs/src/content/tools/hot-reload.md b/sites/docs/src/content/tools/hot-reload.md
index f99cf55c9c7..a56e01ff179 100644
--- a/sites/docs/src/content/tools/hot-reload.md
+++ b/sites/docs/src/content/tools/hot-reload.md
@@ -18,10 +18,10 @@ A demo of hot reload in DartPad
## How to perform a hot reload
-If you are using an AI coding assistant like [Google Antigravity](/ai/antigravity),
-you can use Agent mode to automatically hot reload your running application
-as soon as you prompt the agent to apply changes.
-For details, see [Agentic Hot Reload](/ai/antigravity#agentic-hot-reload).
+If you are using an AI coding assistant like Google Antigravity,
+the assistant can automatically hot reload your running application after
+making changes, or when asked to "Reload the app".
+To learn more, check out [Get started with AI](/ai/get-started).
To hot reload a Flutter app manually:
diff --git a/sites/docs/src/data/sidenav/default.yml b/sites/docs/src/data/sidenav/default.yml
index df3b6a28a8f..f3fc4564fe1 100644
--- a/sites/docs/src/data/sidenav/default.yml
+++ b/sites/docs/src/data/sidenav/default.yml
@@ -394,6 +394,8 @@
permalink: /platform-integration/android/chromeos
- title: Protect your app's sensitive content
permalink: /platform-integration/android/sensitive-content
+ - title: Request local network permissions
+ permalink: /platform-integration/android/local-network-permission
- title: iOS
permalink: /platform-integration/ios
children:
@@ -472,6 +474,8 @@
permalink: /packages-and-plugins/dependency-management
- title: Develop packages & plugins
permalink: /packages-and-plugins/developing-packages
+ - title: Package skills
+ permalink: /ai/package-skills
- title: Swift Package Manager
permalink: /packages-and-plugins/swift-package-manager
children:
@@ -639,35 +643,16 @@
- divider
- header: AI
-- title: Overview
- permalink: /ai/create-with-ai
- icon: psychology
-
- title: Develop with AI
permalink: /ai/get-started
icon: smart_toy
children:
- title: Get started
permalink: /ai/get-started
- - title: AI coding assistants
- permalink: /ai/coding-assistants
- children:
- - title: Overview
- permalink: /ai/coding-assistants
- - title: Antigravity
- permalink: /ai/antigravity
- - title: Antigravity CLI
- permalink: /ai/antigravity-cli
- - title: Agent skills
- permalink: /ai/agent-skills
- - title: Dart and Flutter MCP server
- permalink: /ai/mcp-server
- - title: AI rules
- permalink: /ai/ai-rules
- - title: Developer experience
- permalink: /ai/best-practices/developer-experience
- - title: "AI evaluations (experimental)"
- permalink: /ai/evals
+ - title: How AI tools work
+ permalink: /ai/tools
+ - title: Package skills
+ permalink: /ai/package-skills
- title: Build AI-powered apps
permalink: /ai/genui
diff --git a/sites/docs/src/data/tutorial.yml b/sites/docs/src/data/tutorial.yml
index 5d025e238f9..c543ba90bf9 100644
--- a/sites/docs/src/data/tutorial.yml
+++ b/sites/docs/src/data/tutorial.yml
@@ -7,6 +7,8 @@ units:
url: /learn/pathway/quick-install
- title: Introduction to Flutter UI
chapters:
+ - title: Tutorial introduction
+ url: /learn/pathway/tutorial
- title: Create a Flutter app
url: /learn/pathway/tutorial/create-an-app
- title: Widget fundamentals
diff --git a/sites/www/content/blog/a2ui-client-side-functions/images/A2UI.gif b/sites/www/content/blog/a2ui-client-side-functions/images/A2UI.gif
new file mode 100644
index 00000000000..b860fcf7fc3
Binary files /dev/null and b/sites/www/content/blog/a2ui-client-side-functions/images/A2UI.gif differ
diff --git a/sites/www/content/blog/a2ui-client-side-functions/images/A2UI_Still.png b/sites/www/content/blog/a2ui-client-side-functions/images/A2UI_Still.png
new file mode 100644
index 00000000000..cec4679b125
Binary files /dev/null and b/sites/www/content/blog/a2ui-client-side-functions/images/A2UI_Still.png differ
diff --git a/sites/www/content/blog/a2ui-client-side-functions/index.md b/sites/www/content/blog/a2ui-client-side-functions/index.md
new file mode 100644
index 00000000000..d4958e27102
--- /dev/null
+++ b/sites/www/content/blog/a2ui-client-side-functions/index.md
@@ -0,0 +1,250 @@
+---
+title: "Quick, reliable calculations with A2UI's Client-Side Functions"
+description: >-
+ Learn how client-side functions allow an agent to delegate local operations
+ directly to Dart code running on a user's device.
+publishDate: 2026-08-28
+author: redbrogdon
+image: images/A2UI.gif
+socialImage: images/A2UI_Still.png
+category: deep-dive
+layout: blog
+---
+
+
+
+
+Generative UI (GenUI) is transforming how I approach UI
+development in Flutter. Instead of hardcoding fixed screens
+for every scenario, GenUI enables an AI agent to dynamically
+generate and adjust user interface components at runtime.
+Using Agent-to-User Interface (A2UI) JSON messages and the
+[`genui`](https://pub.dev/packages/genui) package, a Flutter app
+can render dynamic AI-driven cards and surfaces on the fly.
+
+However, as I've started building real-world agentic apps,
+I've found myself looking for ways to reduce latency and decrease
+the number of opportunities my models have to make mistakes.
+When building with Large Language Models (LLMs), it's tempting
+to rely on the model for _everything_. But asking an LLM to take on
+tasks it wasn't necessarily designed for (such as arithmetic)
+can introduce latency and create more opportunity for errors.
+
+That's where A2UI's **client-side** functions come in.
+
+In this post, I'll walk through how client-side functions
+allow an agent to delegate local operations directly to Dart
+code running on a user's device, reducing the need for round-trips
+and delivering more predictable results in your Flutter GenUI apps.
+
+---
+
+## What client-side functions solve
+
+Sending raw prompts back and forth to an LLM for pure math is inefficient,
+adding round-trip latency and consuming extra tokens. Instead,
+client-side functions let the agent compute values like ingredient costs,
+tax totals, or unit conversions locally on the device.
+
+Client-side functions solve this by creating a clean division of labor:
+
+1. **The Flutter Client** declares available client-side functions
+ in the GenUI catalog, informing the agent of which local operations
+ can be invoked, what parameters they expect, and what format they return.
+1. **The LLM Agent** decides _when_ and _where_ a component should
+ be displayed and emits an A2UI expression calling the client function
+ with the necessary arguments (such as an item ID and quantity).
+1. **The Flutter Client** evaluates the expression locally and executes
+ the math synchronously in Dart code, rendering a clean,
+ formatted result on screen right away.
+
+By offloading calculations to the client device, your application avoids
+unnecessary network overhead, achieves consistent formatting,
+and lowers overall token usage.
+
+```dart
+sequenceDiagram
+ participant LLM as Gemini Agent
+ participant Client as Flutter App (genui)
+ participant Function as CalculateCost (Dart)
+ LLM->>Client: A2UI Payload with calculateCost(black_beans, 3)
+ Client->>Function: executeSync(args)
+ Function->>Client: "$2.97"
+ Client->>Client: Render Text widget ($2.97)
+```
+
+Let's look at how I implemented this pattern in a sample application
+called **Commis**, an intelligent assistant built for commercial kitchens and catering teams.
+
+---
+
+## Anatomy of the `CalculateCostFunction` class
+
+When preparing for an upcoming catering event, a chef might ask
+questions about ingredients used in the menu. To calculate and
+display ingredient costs reliably without waiting for server round-trips,
+I created a client-side function called `CalculateCostFunction`.
+
+In the `genui` package, synchronous client-side functions extend
+`SynchronousClientFunction`. Here is the complete implementation
+from `client_functions.dart`:
+
+```dart
+/// A client-side function that calculates the cost for an ingredient
+/// directly on the device using local Dart logic.
+class CalculateCostFunction extends SynchronousClientFunction {
+ const CalculateCostFunction();
+
+ // 1. The identifier referenced by the LLM in A2UI payloads.
+ @override
+ String get name => 'calculateCost';
+
+// 2. Clear description provided to the LLM so it knows when
+ // and why to use the function.
+ @override
+ String get description =>
+ 'Calculates the cost for a certain quantity of an ingredient. '
+ 'Returns a formatted dollar string (for example, \$4.50).';
+
+ // 3. The expected return type for the binding.
+ @override
+ ClientFunctionReturnType get returnType => ClientFunctionReturnType.string;
+
+ // 4. JSON Schema defining required input arguments.
+ @override
+ Schema get argumentSchema => S.object(
+ properties: {
+ 'ingredient_id': S.string(description: 'The ID of the ingredient.'),
+ 'quantity': S.number(description: 'The quantity of the ingredient.'),
+ },
+ required: ['ingredient_id', 'quantity'],
+ );
+
+ // 5. Synchronous Dart execution logic on the client
+ @override
+ Object? executeSync(JsonMap args, ExecutionContext context) {
+ final ingredientId = args['ingredient_id'].toString();
+ final quantity = num.tryParse(args['quantity'].toString())?.toDouble();
+
+ if (quantity == null || quantity < 1) {
+ return '\$0.00';
+ }
+
+ // Call the local cost service to fetch price and format as currency
+ final cost = CostService().fetchPrice(ingredientId, quantity);
+ return '\$${cost.toStringAsFixed(2)}';
+ }
+}
+```
+
+Let's break down the key parts of this class:
+
+1. `name`: The unique identifier (`calculateCost`) that the AI agent
+ uses when generating function calls inside A2UI payloads.
+1. `description`: A concise explanation sent to the LLM so it knows
+ _when_ and _why_ to invoke this function and what output format to expect.
+1. `returnType`: Specifies the data type returned by the function
+ (in this case, a string).
+1. `argumentSchema`: Built using
+ [`json_schema_builder`](https://pub.dev/packages/json_schema_builder),
+ this schema informs the LLM exactly which parameters are required
+ (`ingredient_id` and `quantity`).
+1. `executeSync`: The core Dart method executed on the user's device
+ when the UI renders. It parses the incoming arguments,
+ calls my local `CostService`, and returns the formatted dollar string.
+
+If you just noticed that this pattern looks very similar to the one used
+for catalog entries for UI components, you're spot on! Both provide
+metadata for the agent to use when reasoning, paired with Dart logic
+that does something useful: either create widgets or, in this case,
+calculate a value.
+
+---
+
+## Catalog registration and System prompt integration
+
+To make the agent aware of `calculateCost`, I register it in my app's
+GenUI `Catalog`.
+
+When instantiating `Catalog`, I pass `CalculateCostFunction()` into the
+`functions` list alongside my UI components:
+
+```dart
+// lib/ui/catalog/catalog.dart
+
+final commisCatalog = Catalog(
+ [
+ cateringJobItem,
+ recipeLineCatalogItem,
+ ingredientLineCatalogItem,
+ navigationCardCatalogItem,
+ simpleCardCatalogItem,
+ ],
+ functions: [
+ CalculateCostFunction(), // Here it is!
+ ],
+ catalogId: 'commis_catalog',
+);
+```
+
+When initializing the conversation session,
+`genui`'s `PromptBuilder` inspects the catalog and automatically
+extracts all client function declarations, incorporating their names,
+descriptions, and schemas into the system prompt provided to Gemini.
+
+With that in place, when a chef asks about recipe pricing, Gemini doesn't
+try to guess or compute the dollar total. Instead, it emits an A2UI message
+containing a call to calculateCost (in this case, for the price of three
+cans of beans):
+
+```json
+{
+ "id": "cost_val",
+ "component": "Text",
+ "text": {
+ "call": "calculateCost",
+ "args": {
+ "ingredient_id": "black_beans",
+ "quantity": 3
+ },
+ "returnType": "string"
+ },
+ "variant": "h2"
+}
+```
+
+Notice how the value for the `call` property in the A2UI message matches
+the function name (`calculateCost`) registered in the catalog. When the
+`SurfaceController` receives this message, it evaluates `calculateCost`
+locally on the device using `executeSync` and renders the accurate
+dollar string (such as `$2.97`) on screen right away.
+
+---
+
+## Fast developer iteration with Flutter Hot Reload
+
+Because client-side functions aren't locked behind a backend microservice
+or cloud function deployment, working with them still feels like regular
+old Dart.
+
+If I want to update currency formatting (for example, adding bulk discount
+logic or switching from `$4.50` to USD `4.50`), I can simply edit
+`executeSync` in Dart, save the file, and watch hot reload update
+my app with the new result.
+
+---
+
+## Summary and next steps
+
+Ready to try GenUI and client-side functions in your own apps?
+
+* Check out the official
+ [Intro to GenUI Codelab](https://codelabs.developers.google.com/codelabs/genui-intro#0)
+ to learn the basics of Generative UI.
+* Explore the [`genui` package on pub.dev](https://pub.dev/packages/genui)
+ for API details and catalog definitions.
+* Browse the
+ [**flutter/demos** repository on GitHub](https://github.com/flutter/demos)
+ to inspect the complete source code for Commis and other Dart GenUI samples.
+
+Happy building!
diff --git a/sites/www/content/blog/authors_images/1uLWpn1jOuAz8_lEu1M9Srw.webp b/sites/www/content/blog/author_images/abd99.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1uLWpn1jOuAz8_lEu1M9Srw.webp
rename to sites/www/content/blog/author_images/abd99.webp
diff --git a/sites/www/content/blog/authors_images/1beM676ZukZSi32yoyuhqxw.jpeg b/sites/www/content/blog/author_images/abdallahshaban557.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1beM676ZukZSi32yoyuhqxw.jpeg
rename to sites/www/content/blog/author_images/abdallahshaban557.jpg
diff --git a/sites/www/content/blog/authors_images/2AZjKy9ApAXsgYd9ElZSvlg.jpeg b/sites/www/content/blog/author_images/aguinis.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/2AZjKy9ApAXsgYd9ElZSvlg.jpeg
rename to sites/www/content/blog/author_images/aguinis.jpg
diff --git a/sites/www/content/blog/authors_images/1BgqwW36VvDjTOlmJk5VQ-A.jpeg b/sites/www/content/blog/author_images/amir_h.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1BgqwW36VvDjTOlmJk5VQ-A.jpeg
rename to sites/www/content/blog/author_images/amir_h.jpg
diff --git a/sites/www/content/blog/authors_images/19QBbBTP_oiuZGZf0I49AKA.webp b/sites/www/content/blog/author_images/anderdobo.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/19QBbBTP_oiuZGZf0I49AKA.webp
rename to sites/www/content/blog/author_images/anderdobo.webp
diff --git a/sites/www/content/blog/authors_images/0MWQEEWaslbr-PNFW.webp b/sites/www/content/blog/author_images/andremobilelabonte.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0MWQEEWaslbr-PNFW.webp
rename to sites/www/content/blog/author_images/andremobilelabonte.webp
diff --git a/sites/www/content/blog/authors_images/0Rcs4RmjLKoM8oYfZ.webp b/sites/www/content/blog/author_images/anjannarain.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0Rcs4RmjLKoM8oYfZ.webp
rename to sites/www/content/blog/author_images/anjannarain.webp
diff --git a/sites/www/content/blog/authors_images/1JXjnwMAb89ePR-FoV4-5Hg.jpeg b/sites/www/content/blog/author_images/antfitch.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1JXjnwMAb89ePR-FoV4-5Hg.jpeg
rename to sites/www/content/blog/author_images/antfitch.jpg
diff --git a/sites/www/content/blog/authors_images/0tNv4NL4miSZG0eeS.jpg b/sites/www/content/blog/author_images/arbullard.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0tNv4NL4miSZG0eeS.jpg
rename to sites/www/content/blog/author_images/arbullard.jpg
diff --git a/sites/www/content/blog/authors_images/2Iw2blcHT-LRD9lr8NTx8HQ.jpeg b/sites/www/content/blog/author_images/ayushbherwani1998.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/2Iw2blcHT-LRD9lr8NTx8HQ.jpeg
rename to sites/www/content/blog/author_images/ayushbherwani1998.jpg
diff --git a/sites/www/content/blog/authors_images/0Jzj4ct7CmcvOiR3g.webp b/sites/www/content/blog/author_images/bdero.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0Jzj4ct7CmcvOiR3g.webp
rename to sites/www/content/blog/author_images/bdero.webp
diff --git a/sites/www/content/blog/authors_images/1d1hf9rmd3uJL8yb1sPMIDA.webp b/sites/www/content/blog/author_images/bkonyi.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1d1hf9rmd3uJL8yb1sPMIDA.webp
rename to sites/www/content/blog/author_images/bkonyi.webp
diff --git a/sites/www/content/blog/authors_images/1nFmOI6gpDMxUgSzNIZ19Kg.webp b/sites/www/content/blog/author_images/brandonbadger.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1nFmOI6gpDMxUgSzNIZ19Kg.webp
rename to sites/www/content/blog/author_images/brandonbadger.webp
diff --git a/sites/www/content/blog/authors_images/0xM4OOQfKuCiHd5rI.webp b/sites/www/content/blog/author_images/chinmaygarde.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0xM4OOQfKuCiHd5rI.webp
rename to sites/www/content/blog/author_images/chinmaygarde.webp
diff --git a/sites/www/content/blog/authors_images/1fcJn7cFYnwInCzoug_PoDA.jpeg b/sites/www/content/blog/author_images/clocksmith.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1fcJn7cFYnwInCzoug_PoDA.jpeg
rename to sites/www/content/blog/author_images/clocksmith.jpg
diff --git a/sites/www/content/blog/authors_images/1xu-sgiT2AZrrzBVu1oYnoQ.jpeg b/sites/www/content/blog/author_images/craiglabenz.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1xu-sgiT2AZrrzBVu1oYnoQ.jpeg
rename to sites/www/content/blog/author_images/craiglabenz.jpg
diff --git a/sites/www/content/blog/authors_images/06LK838dGSbGJRtat.webp b/sites/www/content/blog/author_images/csells.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/06LK838dGSbGJRtat.webp
rename to sites/www/content/blog/author_images/csells.webp
diff --git a/sites/www/content/blog/authors_images/1Wio_YseYYApcaC6FSU52aQ.webp b/sites/www/content/blog/author_images/devoncarew.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1Wio_YseYYApcaC6FSU52aQ.webp
rename to sites/www/content/blog/author_images/devoncarew.webp
diff --git a/sites/www/content/blog/authors_images/0R7VUzyb9_HQm9afP.jpg b/sites/www/content/blog/author_images/domesticmouse.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0R7VUzyb9_HQm9afP.jpg
rename to sites/www/content/blog/author_images/domesticmouse.jpg
diff --git a/sites/www/content/blog/authors_images/1f-mpgT2KL1I-4NugTuv2Rg.jpeg b/sites/www/content/blog/author_images/efortuna.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1f-mpgT2KL1I-4NugTuv2Rg.jpeg
rename to sites/www/content/blog/author_images/efortuna.jpg
diff --git a/sites/www/content/blog/authors_images/1_vDVbsLisergcnpUiXcPRQ.webp b/sites/www/content/blog/author_images/ericwindmill.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1_vDVbsLisergcnpUiXcPRQ.webp
rename to sites/www/content/blog/author_images/ericwindmill.webp
diff --git a/sites/www/content/blog/authors_images/1X-6ifCWQoKYCZ-uLGdPa4w.webp b/sites/www/content/blog/author_images/esouthren.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1X-6ifCWQoKYCZ-uLGdPa4w.webp
rename to sites/www/content/blog/author_images/esouthren.webp
diff --git a/sites/www/content/blog/authors_images/1tTlm5zfgYWqtvLbIzUByLQ.webp b/sites/www/content/blog/author_images/ezra-sandzer-bell.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1tTlm5zfgYWqtvLbIzUByLQ.webp
rename to sites/www/content/blog/author_images/ezra-sandzer-bell.webp
diff --git a/sites/www/content/blog/authors_images/1dZx7Dxmr4VXmjc5U8yDf3w.jpeg b/sites/www/content/blog/author_images/filiph.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1dZx7Dxmr4VXmjc5U8yDf3w.jpeg
rename to sites/www/content/blog/author_images/filiph.jpg
diff --git a/sites/www/content/blog/authors_images/2wcnmZEJVlZsf2nNvDOziRA.jpeg b/sites/www/content/blog/author_images/fitzface.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/2wcnmZEJVlZsf2nNvDOziRA.jpeg
rename to sites/www/content/blog/author_images/fitzface.jpg
diff --git a/sites/www/content/blog/authors_images/0ALqtMy61v8ANAVWO.webp b/sites/www/content/blog/author_images/gaaclarke.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0ALqtMy61v8ANAVWO.webp
rename to sites/www/content/blog/author_images/gaaclarke.webp
diff --git a/sites/www/content/blog/authors_images/1QrcTxiCCIAj38rUnnPTryg.jpeg b/sites/www/content/blog/author_images/gskinner.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1QrcTxiCCIAj38rUnnPTryg.jpeg
rename to sites/www/content/blog/author_images/gskinner.jpg
diff --git a/sites/www/content/blog/authors_images/0gNIf0pPqSsP1oOcM.jpg b/sites/www/content/blog/author_images/haddadniaj.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0gNIf0pPqSsP1oOcM.jpg
rename to sites/www/content/blog/author_images/haddadniaj.jpg
diff --git a/sites/www/content/blog/authors_images/09ombi9JukiF7Z1-d.webp b/sites/www/content/blog/author_images/hansmuller.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/09ombi9JukiF7Z1-d.webp
rename to sites/www/content/blog/author_images/hansmuller.webp
diff --git a/sites/www/content/blog/authors_images/0ob8GIJbyVIHe-aQu.jpg b/sites/www/content/blog/author_images/harryterkelsen.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0ob8GIJbyVIHe-aQu.jpg
rename to sites/www/content/blog/author_images/harryterkelsen.jpg
diff --git a/sites/www/content/blog/authors_images/1EAeJ6vonuQM5daZCF9CfeQ.jpeg b/sites/www/content/blog/author_images/hellobrianjames.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1EAeJ6vonuQM5daZCF9CfeQ.jpeg
rename to sites/www/content/blog/author_images/hellobrianjames.jpg
diff --git a/sites/www/content/blog/authors_images/1HEfsynQuQpVrGR-qdGeSvg.jpeg b/sites/www/content/blog/author_images/itsjustkevin.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1HEfsynQuQpVrGR-qdGeSvg.jpeg
rename to sites/www/content/blog/author_images/itsjustkevin.jpg
diff --git a/sites/www/content/blog/authors_images/0SQ_vs0LMPjY6ejrP.webp b/sites/www/content/blog/author_images/ivan.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0SQ_vs0LMPjY6ejrP.webp
rename to sites/www/content/blog/author_images/ivan.webp
diff --git a/sites/www/content/blog/authors_images/109ekgauj46kjGlHoi94FwA.jpeg b/sites/www/content/blog/author_images/jayoung-lee.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/109ekgauj46kjGlHoi94FwA.jpeg
rename to sites/www/content/blog/author_images/jayoung-lee.jpg
diff --git a/sites/www/content/blog/authors_images/0Qp4aTK0f2iGxt-UF.webp b/sites/www/content/blog/author_images/jensjohansen.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0Qp4aTK0f2iGxt-UF.webp
rename to sites/www/content/blog/author_images/jensjohansen.webp
diff --git a/sites/www/content/blog/authors_images/1yJ9UamxmYZ_VwVDeM4mDzA.jpeg b/sites/www/content/blog/author_images/jjmutter.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1yJ9UamxmYZ_VwVDeM4mDzA.jpeg
rename to sites/www/content/blog/author_images/jjmutter.jpg
diff --git a/sites/www/content/blog/authors_images/1XqSzX8F7rKZnrHwIVIr71A.jpeg b/sites/www/content/blog/author_images/johnpryan.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1XqSzX8F7rKZnrHwIVIr71A.jpeg
rename to sites/www/content/blog/author_images/johnpryan.jpg
diff --git a/sites/www/content/blog/authors_images/0hPHb0U1EVsdSI-ZK.jpg b/sites/www/content/blog/author_images/josealba.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0hPHb0U1EVsdSI-ZK.jpg
rename to sites/www/content/blog/author_images/josealba.jpg
diff --git a/sites/www/content/blog/authors_images/16L6xLJHqC5vlGBrTFHc6xg.jpeg b/sites/www/content/blog/author_images/justinmc.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/16L6xLJHqC5vlGBrTFHc6xg.jpeg
rename to sites/www/content/blog/author_images/justinmc.jpg
diff --git a/sites/www/content/blog/authors_images/0VdlGNyd_YGGPFkrc.jpg b/sites/www/content/blog/author_images/jwren.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0VdlGNyd_YGGPFkrc.jpg
rename to sites/www/content/blog/author_images/jwren.jpg
diff --git a/sites/www/content/blog/authors_images/06siHzzkrp68l6H-V.jpg b/sites/www/content/blog/author_images/kenzieschmoll.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/06siHzzkrp68l6H-V.jpg
rename to sites/www/content/blog/author_images/kenzieschmoll.jpg
diff --git a/sites/www/content/blog/authors_images/1iUVmcyWC2Pj7ERaoSF-Onw.jpeg b/sites/www/content/blog/author_images/kevmoo.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1iUVmcyWC2Pj7ERaoSF-Onw.jpeg
rename to sites/www/content/blog/author_images/kevmoo.jpg
diff --git a/sites/www/content/blog/authors_images/2stkoPD1JQ5tooR-s76YSmw.jpeg b/sites/www/content/blog/author_images/kf6gpe.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/2stkoPD1JQ5tooR-s76YSmw.jpeg
rename to sites/www/content/blog/author_images/kf6gpe.jpg
diff --git a/sites/www/content/blog/authors_images/1Nmt27LItpgVqSBTY8WIPwg.jpeg b/sites/www/content/blog/author_images/khanhnwin.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1Nmt27LItpgVqSBTY8WIPwg.jpeg
rename to sites/www/content/blog/author_images/khanhnwin.jpg
diff --git a/sites/www/content/blog/authors_images/0IxpUINuAJhraX2My.jpg b/sites/www/content/blog/author_images/kkboateng.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0IxpUINuAJhraX2My.jpg
rename to sites/www/content/blog/author_images/kkboateng.jpg
diff --git a/sites/www/content/blog/authors_images/1vRMW7uCsV7VsqqC6dIghNA.jpeg b/sites/www/content/blog/author_images/ktjlee.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1vRMW7uCsV7VsqqC6dIghNA.jpeg
rename to sites/www/content/blog/author_images/ktjlee.jpg
diff --git a/sites/www/content/blog/authors_images/0z8A7a6gvtNuF-TxZ.jpg b/sites/www/content/blog/author_images/lamek.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0z8A7a6gvtNuF-TxZ.jpg
rename to sites/www/content/blog/author_images/lamek.jpg
diff --git a/sites/www/content/blog/authors_images/12P-T3BTzLSSPrvomuK_oRQ.webp b/sites/www/content/blog/author_images/leighajarett.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/12P-T3BTzLSSPrvomuK_oRQ.webp
rename to sites/www/content/blog/author_images/leighajarett.webp
diff --git a/sites/www/content/blog/authors_images/1T1lv65-OBsGrBSs6mvgvqg.jpeg b/sites/www/content/blog/author_images/liyuqian.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1T1lv65-OBsGrBSs6mvgvqg.jpeg
rename to sites/www/content/blog/author_images/liyuqian.jpg
diff --git a/sites/www/content/blog/author_images/louisehsu.jpg b/sites/www/content/blog/author_images/louisehsu.jpg
new file mode 100644
index 00000000000..1598b8b0963
Binary files /dev/null and b/sites/www/content/blog/author_images/louisehsu.jpg differ
diff --git a/sites/www/content/blog/authors_images/1osRrOD-rpUZlVPFwpn2_GQ.webp b/sites/www/content/blog/author_images/lukeaf.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1osRrOD-rpUZlVPFwpn2_GQ.webp
rename to sites/www/content/blog/author_images/lukeaf.webp
diff --git a/sites/www/content/blog/authors_images/1N3hr8E92Mseta1S7yCX2Mg.webp b/sites/www/content/blog/author_images/magder.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1N3hr8E92Mseta1S7yCX2Mg.webp
rename to sites/www/content/blog/author_images/magder.webp
diff --git a/sites/www/content/blog/authors_images/1TDttyOKULZENb7QG-0Wl3w.jpeg b/sites/www/content/blog/author_images/mariam_hasnany.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1TDttyOKULZENb7QG-0Wl3w.jpeg
rename to sites/www/content/blog/author_images/mariam_hasnany.jpg
diff --git a/sites/www/content/blog/authors_images/19Ub8P3iIx6Ji3hUUrSCZlg.webp b/sites/www/content/blog/author_images/maryx.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/19Ub8P3iIx6Ji3hUUrSCZlg.webp
rename to sites/www/content/blog/author_images/maryx.webp
diff --git a/sites/www/content/blog/authors_images/2j6fhR3Uq9ariRWaoB1MGPg.webp b/sites/www/content/blog/author_images/matthew-carroll.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/2j6fhR3Uq9ariRWaoB1MGPg.webp
rename to sites/www/content/blog/author_images/matthew-carroll.webp
diff --git a/sites/www/content/blog/author_images/mattkae.webp b/sites/www/content/blog/author_images/mattkae.webp
new file mode 100644
index 00000000000..1c1c417f28b
Binary files /dev/null and b/sites/www/content/blog/author_images/mattkae.webp differ
diff --git a/sites/www/content/blog/authors_images/1snI23_LOekhWjI-XVYtHmQ.jpeg b/sites/www/content/blog/author_images/mehmetf.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1snI23_LOekhWjI-XVYtHmQ.jpeg
rename to sites/www/content/blog/author_images/mehmetf.jpg
diff --git a/sites/www/content/blog/authors_images/1NC_dwnZV11nnMuK4D0mXYA.jpeg b/sites/www/content/blog/author_images/melyndahoover.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1NC_dwnZV11nnMuK4D0mXYA.jpeg
rename to sites/www/content/blog/author_images/melyndahoover.jpg
diff --git a/sites/www/content/blog/authors_images/1WKhF9u7jo7OkE26I5PjGiw.webp b/sites/www/content/blog/author_images/mhclin113.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1WKhF9u7jo7OkE26I5PjGiw.webp
rename to sites/www/content/blog/author_images/mhclin113.webp
diff --git a/sites/www/content/blog/authors_images/0Y_CFLc1qadgr3tPK.jpg b/sites/www/content/blog/author_images/mit-mit.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0Y_CFLc1qadgr3tPK.jpg
rename to sites/www/content/blog/author_images/mit-mit.jpg
diff --git a/sites/www/content/blog/authors_images/0cgL8XWJBpDSy2mHs.jpg b/sites/www/content/blog/author_images/mjohnsullivan.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0cgL8XWJBpDSy2mHs.jpg
rename to sites/www/content/blog/author_images/mjohnsullivan.jpg
diff --git a/sites/www/content/blog/authors_images/1HcMDVJ2lxSQDClP1IUCGzg.jpeg b/sites/www/content/blog/author_images/mravn.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1HcMDVJ2lxSQDClP1IUCGzg.jpeg
rename to sites/www/content/blog/author_images/mravn.jpg
diff --git a/sites/www/content/blog/authors_images/2G__H_oe3299HvlPy7YkpKg.webp b/sites/www/content/blog/author_images/nikkitagandhi.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/2G__H_oe3299HvlPy7YkpKg.webp
rename to sites/www/content/blog/author_images/nikkitagandhi.webp
diff --git a/sites/www/content/blog/authors_images/1Yh3FMfH210BvJATo22X4Aw.jpeg b/sites/www/content/blog/author_images/parlough.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1Yh3FMfH210BvJATo22X4Aw.jpeg
rename to sites/www/content/blog/author_images/parlough.jpg
diff --git a/sites/www/content/blog/authors_images/2d-UpSVU0saEV2On5JBvYaQ.jpeg b/sites/www/content/blog/author_images/perclasson.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/2d-UpSVU0saEV2On5JBvYaQ.jpeg
rename to sites/www/content/blog/author_images/perclasson.jpg
diff --git a/sites/www/content/blog/authors_images/00yA7OjxKWthRwjK2.webp b/sites/www/content/blog/author_images/piinks.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/00yA7OjxKWthRwjK2.webp
rename to sites/www/content/blog/author_images/piinks.webp
diff --git a/sites/www/content/blog/authors_images/0UfXdwppirgDYRNaj.webp b/sites/www/content/blog/author_images/redbrogdon.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0UfXdwppirgDYRNaj.webp
rename to sites/www/content/blog/author_images/redbrogdon.webp
diff --git a/sites/www/content/blog/authors_images/1w7Y-bThACd0V1YJ6N7QePg.webp b/sites/www/content/blog/author_images/rich_hall.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1w7Y-bThACd0V1YJ6N7QePg.webp
rename to sites/www/content/blog/author_images/rich_hall.webp
diff --git a/sites/www/content/blog/authors_images/rodydavis.jpg b/sites/www/content/blog/author_images/rodydavis.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/rodydavis.jpg
rename to sites/www/content/blog/author_images/rodydavis.jpg
diff --git a/sites/www/content/blog/authors_images/0m23OLfjD-pystWVb.jpg b/sites/www/content/blog/author_images/scheglov.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0m23OLfjD-pystWVb.jpg
rename to sites/www/content/blog/author_images/scheglov.jpg
diff --git a/sites/www/content/blog/authors_images/10pSgo-h67AMv_gytSDEiJg.webp b/sites/www/content/blog/author_images/sethladd.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/10pSgo-h67AMv_gytSDEiJg.webp
rename to sites/www/content/blog/author_images/sethladd.webp
diff --git a/sites/www/content/blog/authors_images/0LHz-9FgHd3doKgCM.jpg b/sites/www/content/blog/author_images/sfshaza.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0LHz-9FgHd3doKgCM.jpg
rename to sites/www/content/blog/author_images/sfshaza.jpg
diff --git a/sites/www/content/blog/authors_images/0vHty7_58DD6eW53D.webp b/sites/www/content/blog/author_images/shamiramarshall.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0vHty7_58DD6eW53D.webp
rename to sites/www/content/blog/author_images/shamiramarshall.webp
diff --git a/sites/www/content/blog/authors_images/0Qgrum-sTozr6lXCm.jpg b/sites/www/content/blog/author_images/swavkulinski.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0Qgrum-sTozr6lXCm.jpg
rename to sites/www/content/blog/author_images/swavkulinski.jpg
diff --git a/sites/www/content/blog/authors_images/0jbYlQ-K9Yh-HVUBp.jpg b/sites/www/content/blog/author_images/taodong.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0jbYlQ-K9Yh-HVUBp.jpg
rename to sites/www/content/blog/author_images/taodong.jpg
diff --git a/sites/www/content/blog/authors_images/0cNAKYVA08TeS6TVB.webp b/sites/www/content/blog/author_images/theaflowers.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0cNAKYVA08TeS6TVB.webp
rename to sites/www/content/blog/author_images/theaflowers.webp
diff --git a/sites/www/content/blog/authors_images/0xV6b-152TcEMSjUr.webp b/sites/www/content/blog/author_images/tianguang.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0xV6b-152TcEMSjUr.webp
rename to sites/www/content/blog/author_images/tianguang.webp
diff --git a/sites/www/content/blog/authors_images/1krC2R3xE1FCZTdIbXQDT_A.jpeg b/sites/www/content/blog/author_images/timothyhoang.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1krC2R3xE1FCZTdIbXQDT_A.jpeg
rename to sites/www/content/blog/author_images/timothyhoang.jpg
diff --git a/sites/www/content/blog/authors_images/14HRhyWLOwtZEN7MQ8zLuHA.jpeg b/sites/www/content/blog/author_images/timsneath.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/14HRhyWLOwtZEN7MQ8zLuHA.jpeg
rename to sites/www/content/blog/author_images/timsneath.jpg
diff --git a/sites/www/content/blog/authors_images/15Yq8VeVDQqM51HKi50phdg.jpeg b/sites/www/content/blog/author_images/tomayac.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/15Yq8VeVDQqM51HKi50phdg.jpeg
rename to sites/www/content/blog/author_images/tomayac.jpg
diff --git a/sites/www/content/blog/authors_images/0EUSBYlBzvqL9j_Xr.webp b/sites/www/content/blog/author_images/tvolkert.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/0EUSBYlBzvqL9j_Xr.webp
rename to sites/www/content/blog/author_images/tvolkert.webp
diff --git a/sites/www/content/blog/authors_images/0lw25USWxDjT-i0kV.jpg b/sites/www/content/blog/author_images/twerske.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0lw25USWxDjT-i0kV.jpg
rename to sites/www/content/blog/author_images/twerske.jpg
diff --git a/sites/www/content/blog/authors_images/1XUX_mTFnhm4BKyT9q9sOUQ.webp b/sites/www/content/blog/author_images/verygoodopensource.webp
similarity index 100%
rename from sites/www/content/blog/authors_images/1XUX_mTFnhm4BKyT9q9sOUQ.webp
rename to sites/www/content/blog/author_images/verygoodopensource.webp
diff --git a/sites/www/content/blog/authors_images/0c8OAb9nT35XgY56T.jpg b/sites/www/content/blog/author_images/wmleler.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0c8OAb9nT35XgY56T.jpg
rename to sites/www/content/blog/author_images/wmleler.jpg
diff --git a/sites/www/content/blog/authors_images/1h_lwkzhaAtW5WTkM750_Xw.jpeg b/sites/www/content/blog/author_images/xster.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/1h_lwkzhaAtW5WTkM750_Xw.jpeg
rename to sites/www/content/blog/author_images/xster.jpg
diff --git a/sites/www/content/blog/authors_images/0PSrFnoWOMMa0YZUM.jpg b/sites/www/content/blog/author_images/yanchenum.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/0PSrFnoWOMMa0YZUM.jpg
rename to sites/www/content/blog/author_images/yanchenum.jpg
diff --git a/sites/www/content/blog/authors_images/08pUBYXbTsN324CAB.jpg b/sites/www/content/blog/author_images/yegorj.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/08pUBYXbTsN324CAB.jpg
rename to sites/www/content/blog/author_images/yegorj.jpg
diff --git a/sites/www/content/blog/authors_images/2Cl8MkXie_Fr91CEmLGzQtw.jpeg b/sites/www/content/blog/author_images/zoeyfan.jpg
similarity index 100%
rename from sites/www/content/blog/authors_images/2Cl8MkXie_Fr91CEmLGzQtw.jpeg
rename to sites/www/content/blog/author_images/zoeyfan.jpg
diff --git a/sites/www/content/blog/authors.yaml b/sites/www/content/blog/authors.yaml
index 886bdf4c29d..51a3b0bce93 100644
--- a/sites/www/content/blog/authors.yaml
+++ b/sites/www/content/blog/authors.yaml
@@ -4,279 +4,283 @@
#
# Authors should have:
# - name: The author's display name (required).
-# - image: The author's image file, located in /content/blog/authors_images/ (optional).
+# - image: The author's image file, located in /content/blog/author_images/ (optional).
# - imageUrl: A url to an image for the author, as an alternative to a local image (optional).
# - link: A link to their GitHub, social profile, or personal website (optional).
abd99:
name: "Abdullah Deshmukh"
- image: "1uLWpn1jOuAz8_lEu1M9Srw.webp"
+ image: "abd99.webp"
link: "https://github.com/abd99"
abdallahshaban557:
name: "Abdallah W Shaban"
- image: "1beM676ZukZSi32yoyuhqxw.jpeg"
+ image: "abdallahshaban557.jpg"
link: "https://github.com/abdallahshaban557"
aguinis:
name: "Martin Aguinis"
- image: "2AZjKy9ApAXsgYd9ElZSvlg.jpeg"
+ image: "aguinis.jpg"
link: "https://github.com/maguinis"
amir_h:
name: "Amir Hardon"
- image: "1BgqwW36VvDjTOlmJk5VQ-A.jpeg"
+ image: "amir_h.jpg"
link: "https://twitter.com/AmirHardon"
anderdobo:
name: "Ander Dobo"
- image: "19QBbBTP_oiuZGZf0I49AKA.webp"
+ image: "anderdobo.webp"
link: "https://github.com/anderdobo"
andremobilelabonte:
name: "André Labonté"
- image: "0MWQEEWaslbr-PNFW.webp"
+ image: "andremobilelabonte.webp"
link: "https://github.com/andrelabonte"
anjannarain:
name: "Anjan Narain"
- image: "0Rcs4RmjLKoM8oYfZ.webp"
+ image: "anjannarain.webp"
link: null
antfitch:
name: "Amanda Fitch"
- image: "1JXjnwMAb89ePR-FoV4-5Hg.jpeg"
+ image: "antfitch.jpg"
link: "https://github.com/antfitch"
arbullard:
name: "Anthony Bullard"
- image: "0tNv4NL4miSZG0eeS.jpg"
+ image: "arbullard.jpg"
link: "https://github.com/gamebox"
ayushbherwani1998:
name: "Ayush Bherwani"
- image: "2Iw2blcHT-LRD9lr8NTx8HQ.jpeg"
+ image: "ayushbherwani1998.jpg"
link: "https://github.com/AyushBherwani1998"
bdero:
name: "Brandon DeRosier"
- image: "0Jzj4ct7CmcvOiR3g.webp"
+ image: "bdero.webp"
link: "https://github.com/bdero"
bkonyi:
name: "Ben Konyi"
- image: "1d1hf9rmd3uJL8yb1sPMIDA.webp"
+ image: "bkonyi.webp"
link: "https://github.com/bkonyi"
brandonbadger:
name: "Brandon Badger"
- image: "1nFmOI6gpDMxUgSzNIZ19Kg.webp"
+ image: "brandonbadger.webp"
link: null
chinmaygarde:
name: "Chinmay Garde"
- image: "0xM4OOQfKuCiHd5rI.webp"
+ image: "chinmaygarde.webp"
link: "https://github.com/chinmaygarde"
clocksmith:
name: "Anthony Robledo"
- image: "1fcJn7cFYnwInCzoug_PoDA.jpeg"
+ image: "clocksmith.jpg"
link: "https://github.com/clocksmith"
craiglabenz:
name: "Craig Labenz"
- image: "1PpHwzypsYF2ce75cfkt0_w.webp"
+ image: "craiglabenz.jpg"
link: "https://github.com/craiglabenz"
csells:
name: "Chris Sells"
- image: "06LK838dGSbGJRtat.webp"
+ image: "csells.webp"
link: "https://github.com/csells"
devoncarew:
name: "Devon Carew"
- image: "1Wio_YseYYApcaC6FSU52aQ.webp"
+ image: "devoncarew.webp"
link: "https://github.com/devoncarew"
domesticmouse:
name: "Brett Morgan"
- image: "0R7VUzyb9_HQm9afP.jpg"
+ image: "domesticmouse.jpg"
link: "https://github.com/domesticmouse"
efortuna:
name: "Emily Fortuna"
- image: "1f-mpgT2KL1I-4NugTuv2Rg.jpeg"
+ image: "efortuna.jpg"
link: "https://github.com/efortuna"
ericwindmill:
name: "Eric Windmill"
- image: "1_vDVbsLisergcnpUiXcPRQ.webp"
+ image: "ericwindmill.webp"
link: "https://github.com/ericwindmill"
esouthren:
name: "Eilidh Southren"
- image: "1X-6ifCWQoKYCZ-uLGdPa4w.webp"
+ image: "esouthren.webp"
link: "https://github.com/esouthren"
ezra-sandzer-bell:
name: "Ezra Sandzer-Bell"
- image: "1tTlm5zfgYWqtvLbIzUByLQ.webp"
+ image: "ezra-sandzer-bell.webp"
link: "https://twitter.com/EzraSandzer"
filiph:
name: "Filip Hracek"
- image: "1dZx7Dxmr4VXmjc5U8yDf3w.jpeg"
+ image: "filiph.jpg"
link: "https://github.com/filiph"
fitzface:
name: "Andrew Fitz Gibbon"
- image: "2wcnmZEJVlZsf2nNvDOziRA.jpeg"
+ image: "fitzface.jpg"
link: null
gaaclarke:
name: "Aaron Clarke"
- image: "0ALqtMy61v8ANAVWO.webp"
+ image: "gaaclarke.webp"
link: "https://github.com/gaaclarke"
gskinner:
name: "Grant Skinner"
- image: "1QrcTxiCCIAj38rUnnPTryg.jpeg"
+ image: "gskinner.jpg"
link: "https://github.com/gskinner"
guidez:
name: "Pierre-Louis Guidez"
link: "https://github.com/guidezpl"
haddadniaj:
name: "Justin Haddadnia"
- image: "0gNIf0pPqSsP1oOcM.jpg"
+ image: "haddadniaj.jpg"
link: "https://github.com/Haddadnia"
hansmuller:
name: "Hans Muller"
- image: "09ombi9JukiF7Z1-d.webp"
+ image: "hansmuller.webp"
link: "https://github.com/HansMuller"
harryterkelsen:
name: "Harry Terkelsen"
- image: "0ob8GIJbyVIHe-aQu.jpg"
+ image: "harryterkelsen.jpg"
link: "https://github.com/harryterkelsen"
hellobrianjames:
name: "Brian James"
- image: "1EAeJ6vonuQM5daZCF9CfeQ.jpeg"
+ image: "hellobrianjames.jpg"
link: "https://github.com/malloc-error"
itsjustkevin:
name: "Kevin Chisholm"
- image: "1HEfsynQuQpVrGR-qdGeSvg.jpeg"
+ image: "itsjustkevin.jpg"
link: "https://github.com/itsjustkevin"
ivan:
name: "Ivan Leider"
- image: "0SQ_vs0LMPjY6ejrP.webp"
+ image: "ivan.webp"
link: "https://github.com/ileider"
jayoung-lee:
name: "JaYoung Lee"
- image: "109ekgauj46kjGlHoi94FwA.jpeg"
+ image: "jayoung-lee.jpg"
link: "https://github.com/jayoung-lee"
jensjohansen:
name: "Jens Johansen"
- image: "0Qp4aTK0f2iGxt-UF.webp"
+ image: "jensjohansen.webp"
link: "https://github.com/jensjohansen"
jjmutter:
name: "John Mutter"
- image: "1yJ9UamxmYZ_VwVDeM4mDzA.jpeg"
+ image: "jjmutter.jpg"
link: null
johnpryan:
name: "John Ryan"
- image: "1XqSzX8F7rKZnrHwIVIr71A.jpeg"
+ image: "johnpryan.jpg"
link: "https://github.com/johnpryan"
josealba:
name: "Jose Alba"
- image: "0hPHb0U1EVsdSI-ZK.jpg"
+ image: "josealba.jpg"
link: "https://github.com/JoseAlba"
justinmc:
name: "Justin McCandless"
- image: "16L6xLJHqC5vlGBrTFHc6xg.jpeg"
+ image: "justinmc.jpg"
link: "https://github.com/justinmc"
jwren:
name: "Jaime Wren"
- image: "0VdlGNyd_YGGPFkrc.jpg"
+ image: "jwren.jpg"
link: "https://github.com/jwren"
kenzieschmoll:
name: "Kenzie Davisson"
- image: "06siHzzkrp68l6H-V.jpg"
+ image: "kenzieschmoll.jpg"
link: "https://github.com/kenzieschmoll"
kevmoo:
name: "Kevin Moore"
- image: "1iUVmcyWC2Pj7ERaoSF-Onw.jpeg"
+ image: "kevmoo.jpg"
link: "https://github.com/kevmoo"
kf6gpe:
name: "Ray Rischpater, KF6GPE"
- image: "2stkoPD1JQ5tooR-s76YSmw.jpeg"
+ image: "kf6gpe.jpg"
link: "https://github.com/kf6gpe"
khanhnwin:
name: "Khanh Nguyen"
- image: "1Nmt27LItpgVqSBTY8WIPwg.jpeg"
+ image: "khanhnwin.jpg"
link: "https://github.com/khanhnwin"
kkboateng:
name: "Kelvin Boateng"
- image: "0IxpUINuAJhraX2My.jpg"
+ image: "kkboateng.jpg"
link: "https://github.com/kkboateng"
ktjlee:
name: "Katie Lee"
- image: "1vRMW7uCsV7VsqqC6dIghNA.jpeg"
+ image: "ktjlee.jpg"
link: null
lamek:
name: "Kevin Lamenzo"
- image: "0z8A7a6gvtNuF-TxZ.jpg"
+ image: "lamek.jpg"
link: "https://github.com/lamek"
leighajarett:
name: "Leigha Jarett"
- image: "12P-T3BTzLSSPrvomuK_oRQ.webp"
+ image: "leighajarett.webp"
link: "https://github.com/leighajarett"
liyuqian:
name: "Yuqian Li"
- image: "1T1lv65-OBsGrBSs6mvgvqg.jpeg"
+ image: "liyuqian.jpg"
link: "https://github.com/liyuqian"
+louisehsu:
+ name: "Louise Hsu"
+ image: "louisehsu.jpg"
+ link: "https://github.com/LouiseHsu"
lukeaf:
name: "Luke Freeman"
- image: "1osRrOD-rpUZlVPFwpn2_GQ.webp"
+ image: "lukeaf.webp"
link: "https://twitter.com/lukeaf"
magder:
name: "Jenn Magder"
- image: "1N3hr8E92Mseta1S7yCX2Mg.webp"
+ image: "magder.webp"
link: "https://github.com/magder"
mariam_hasnany:
name: "Mariam Hasnany"
- image: "1TDttyOKULZENb7QG-0Wl3w.jpeg"
+ image: "mariam_hasnany.jpg"
link: "https://github.com/mariamhas"
maryx:
name: "Mary"
- image: "19Ub8P3iIx6Ji3hUUrSCZlg.webp"
+ image: "maryx.webp"
link: "https://github.com/maryx"
matthew-carroll:
name: "Matt Carroll"
- image: "2j6fhR3Uq9ariRWaoB1MGPg.webp"
+ image: "matthew-carroll.webp"
link: "https://github.com/matthew-carroll"
mattkae:
name: "Matthew Kosarek"
- image: "mattkae.png"
+ image: "mattkae.webp"
link: "https://github.com/mattkae"
mehmetf:
name: "Mehmet Fidanboylu"
- image: "1snI23_LOekhWjI-XVYtHmQ.jpeg"
+ image: "mehmetf.jpg"
link: "https://github.com/mehmetf"
melyndahoover:
name: "Mindy Hoover"
- image: "1NC_dwnZV11nnMuK4D0mXYA.jpeg"
+ image: "melyndahoover.jpg"
link: "https://github.com/melyndahoover"
mhclin113:
name: "Cheng Lin"
- image: "1WKhF9u7jo7OkE26I5PjGiw.webp"
+ image: "mhclin113.webp"
link: "https://x.com/chenglinlim"
mit-mit:
name: "Michael Thomsen"
- image: "0Y_CFLc1qadgr3tPK.jpg"
+ image: "mit-mit.jpg"
link: "https://github.com/mit-mit"
mjohnsullivan:
name: "Matt Sullivan"
- image: "0cgL8XWJBpDSy2mHs.jpg"
+ image: "mjohnsullivan.jpg"
link: "https://github.com/mjohnsullivan"
mravn:
name: "Mikkel Ravn"
- image: "1HcMDVJ2lxSQDClP1IUCGzg.jpeg"
+ image: "mravn.jpg"
link: "https://github.com/mravn"
nikkitagandhi:
name: "Nikita Gandhi"
- image: "2G__H_oe3299HvlPy7YkpKg.webp"
+ image: "nikkitagandhi.webp"
link: "https://twitter.com/Nikkitagandhi"
parlough:
name: "Parker Lougheed"
- image: "1Yh3FMfH210BvJATo22X4Aw.jpeg"
+ image: "parlough.jpg"
link: "https://github.com/parlough"
perclasson:
name: "Per Classon"
- image: "2d-UpSVU0saEV2On5JBvYaQ.jpeg"
+ image: "perclasson.jpg"
link: "https://github.com/perclasson"
piinks:
name: "Kate Lovett"
- image: "00yA7OjxKWthRwjK2.webp"
+ image: "piinks.webp"
link: "https://github.com/Piinks"
redbrogdon:
name: "Andrew Brogdon"
- image: "0UfXdwppirgDYRNaj.webp"
+ image: "redbrogdon.webp"
link: "https://github.com/redbrogdon"
rich_hall:
name: "Rich Hall"
- image: "1w7Y-bThACd0V1YJ6N7QePg.webp"
+ image: "rich_hall.webp"
link: "https://twitter.com/rich_hall_"
rodydavis:
name: "Rody Davis"
@@ -284,77 +288,77 @@ rodydavis:
link: "https://github.com/rodydavis"
scheglov:
name: "Konstantin Scheglov"
- image: "0m23OLfjD-pystWVb.jpg"
+ image: "scheglov.jpg"
link: "https://github.com/scheglov"
sethladd:
name: "Seth Ladd"
- image: "10pSgo-h67AMv_gytSDEiJg.webp"
+ image: "sethladd.webp"
link: "https://github.com/sethladd"
sfshaza:
name: "Shams Zakhour"
- image: "0LHz-9FgHd3doKgCM.jpg"
+ image: "sfshaza.jpg"
link: "https://github.com/sfshaza2"
shamiramarshall:
name: "Shamira Marshall"
- image: "0vHty7_58DD6eW53D.webp"
+ image: "shamiramarshall.webp"
link: "https://github.com/MiraMarshall"
swavkulinski:
name: "Swav Kulinski"
- image: "0Qgrum-sTozr6lXCm.jpg"
+ image: "swavkulinski.jpg"
link: "https://github.com/swavkulinski"
taodong:
name: "Tao Dong"
- image: "0jbYlQ-K9Yh-HVUBp.jpg"
+ image: "taodong.jpg"
link: "https://github.com/InMatrix"
theaflowers:
name: "Alethea K. Flowers"
- image: "0cNAKYVA08TeS6TVB.webp"
+ image: "theaflowers.webp"
link: "https://github.com/theacodes"
tianguang:
name: "Tianguang Zhang"
- image: "0xV6b-152TcEMSjUr.webp"
+ image: "tianguang.webp"
link: null
timothyhoang:
name: "Timothy Hoang"
- image: "1krC2R3xE1FCZTdIbXQDT_A.jpeg"
+ image: "timothyhoang.jpg"
link: null
timsneath:
name: "Tim Sneath"
- image: "14HRhyWLOwtZEN7MQ8zLuHA.jpeg"
+ image: "timsneath.jpg"
link: "https://github.com/timsneath"
tomayac:
name: "Thomas Steiner"
- image: "15Yq8VeVDQqM51HKi50phdg.jpeg"
+ image: "tomayac.jpg"
link: "https://github.com/tomayac"
tvolkert:
name: "Todd Volkert"
- image: "0EUSBYlBzvqL9j_Xr.webp"
+ image: "tvolkert.webp"
link: "https://github.com/tvolkert"
twerske:
name: "Emma Twersky"
- image: "0lw25USWxDjT-i0kV.jpg"
+ image: "twerske.jpg"
link: "https://github.com/twerske"
verygoodopensource:
name: "Very Good Ventures"
- image: "1XUX_mTFnhm4BKyT9q9sOUQ.webp"
+ image: "verygoodopensource.webp"
link: "https://github.com/VeryGoodOpenSource"
-wmleler1:
+wmleler:
name: "Wm Leler"
- image: "0c8OAb9nT35XgY56T.jpg"
+ image: "wmleler.jpg"
link: "https://github.com/wmleler"
xster:
name: "xster"
- image: "1h_lwkzhaAtW5WTkM750_Xw.jpeg"
+ image: "xster.jpg"
link: "https://github.com/xster"
yanchenum:
name: "Yan Chen"
- image: "0PSrFnoWOMMa0YZUM.jpg"
+ image: "yanchenum.jpg"
link: "https://twitter.com/yanchenum"
yegorj:
name: "Yegor Jbanov"
- image: "08pUBYXbTsN324CAB.jpg"
+ image: "yegorj.jpg"
link: "https://github.com/yjbanov"
zoeyfan:
name: "Zoey Fan"
- image: "2Cl8MkXie_Fr91CEmLGzQtw.jpeg"
+ image: "zoeyfan.jpg"
link: "https://github.com/zoeyfan"
diff --git a/sites/www/content/blog/authors_images/0cxUEIlx7f0R9rFrO.jpg b/sites/www/content/blog/authors_images/0cxUEIlx7f0R9rFrO.jpg
deleted file mode 100644
index 3b887981cae..00000000000
Binary files a/sites/www/content/blog/authors_images/0cxUEIlx7f0R9rFrO.jpg and /dev/null differ
diff --git a/sites/www/content/blog/authors_images/1PpHwzypsYF2ce75cfkt0_w.webp b/sites/www/content/blog/authors_images/1PpHwzypsYF2ce75cfkt0_w.webp
deleted file mode 100644
index 78fc46ea628..00000000000
Binary files a/sites/www/content/blog/authors_images/1PpHwzypsYF2ce75cfkt0_w.webp and /dev/null differ
diff --git a/sites/www/content/blog/authors_images/mattkae.png b/sites/www/content/blog/authors_images/mattkae.png
deleted file mode 100644
index 1b5d117f733..00000000000
Binary files a/sites/www/content/blog/authors_images/mattkae.png and /dev/null differ
diff --git a/sites/www/content/blog/desktop-windowing-apis/index.md b/sites/www/content/blog/desktop-windowing-apis/index.md
index 816456641de..6a13e055751 100644
--- a/sites/www/content/blog/desktop-windowing-apis/index.md
+++ b/sites/www/content/blog/desktop-windowing-apis/index.md
@@ -63,6 +63,14 @@ Our design centers around five window types: regular, dialog,
tooltip, popup, and satellite. Each window type has a specific,
cross-platform behavior and purpose.
+**Regular windows** are just that–an average window on a desktop.
+They have a toolbar and are resizable, maximizable, minimizable,
+and fullscreenable. They typically contain the main content of
+an application. Application developers often have at least one
+per application, but it is not uncommon to have more than one.
+For example, a web browser application can open multiple regular
+windows for the user to browse independently between them.
+
**Popup** windows provide functionality such as dropdown
diff --git a/sites/www/content/blog/how-flutter-stays-ahead-of-ios-releases/images/banner.jpg b/sites/www/content/blog/how-flutter-stays-ahead-of-ios-releases/images/banner.jpg
new file mode 100644
index 00000000000..4545a67abff
Binary files /dev/null and b/sites/www/content/blog/how-flutter-stays-ahead-of-ios-releases/images/banner.jpg differ
diff --git a/sites/www/content/blog/how-flutter-stays-ahead-of-ios-releases/index.md b/sites/www/content/blog/how-flutter-stays-ahead-of-ios-releases/index.md
new file mode 100644
index 00000000000..1e4dd61a824
--- /dev/null
+++ b/sites/www/content/blog/how-flutter-stays-ahead-of-ios-releases/index.md
@@ -0,0 +1,240 @@
+---
+title: "How Flutter stays ahead of iOS releases"
+description: >-
+ Learn how the Flutter team navigates WWDC, beta releases,
+ and proactive engineering to deliver Day 0 iOS support.
+publishDate: 2026-08-25
+author:
+ - craiglabenz
+ - louisehsu
+category: deep-dive
+layout: blog
+---
+
+
+
+As a Flutter developer, your relationship with each of Flutter's six primary
+supported platforms can vary (to say nothing of the extended universe platforms,
+like webOS). For the platform running the phone in your pocket,
+you might tune in to release events and get excited about each announcement.
+For other platforms,
+you might only cross your fingers and hope that annual changes won't disrupt
+your ability to run `flutter build [platform]` and release your app.
+Luckily, no matter who you are,
+you benefit from a band of platform-focused developers on the Flutter team
+whose job is to make sense of each announcement and deprecation so you don't have
+to.
+
+Each platform-specific sub-team's workflow is tailored to how that platform
+evolves and, in Apple's case, that tends to orbit a single week in June
+when the eyes of the tech world turn to Cupertino, California.
+
+## Apple's flagship event, WWDC
+
+Every June, Apple hosts an event called WWDC to preview how their software will
+evolve in the coming year.
+This is typically separate from hardware announcements,
+which trickle out during the rest of the year.
+At WWDC, Apple's many operating systems (iOS, macOS, watchOS, and more)
+are the main characters.
+
+If you've ever tuned in to WWDC,
+then you know its general post-COVID format: first, the keynote,
+then, the "Platform State of the Union",
+and lastly, a massive release of topical videos.
+In 2026, WWDC included over 145 technical sessions helping Apple's community
+of developers understand what to expect—an incredible resource, to be sure,
+but also far too much content to manually sift through.
+
+You already know where this is going.
+We don't even have to write the following sentence for you to know it's true,
+but we will anyway:
+
+_In 2026, the Flutter team began using AI to categorize each technical session._
+
+Beginning this year,
+a small piece of Dart code first extracted transcripts from each video.
+Then, Gemini ranked and categorized each session based on the following rubric:
+
+1. Importance to Flutter contributors (inside and outside of Google)
+1. Area of impact
+1. Recommended actions
+
+Instead of having to watch 145 (admittedly very high quality) technical
+sessions, by sundown on the day the videos were released,
+we had a Gemini-produced triage document with early signal on every session.
+For example, our system recognized that we could largely ignore a SwiftData
+session because Flutter handles persistence differently.
+Separately, it flagged a "Modernize your UIKit app" talk as critical [1]
+for the Flutter Engine team because it described mandatory changes
+to the `UIScene` [2] lifecycle API.
+
+> [1] To see everything important for Flutter,
+we do physically pop some popcorn and watch each session that Gemini flags
+in this way.
+> [2] More on the `UIScene` API later!
+
+By sun-up the next morning,
+Flutter's iOS team was officially in its crunch period.
+Three months separate WWDC in June from the eventual stable releases in
+September, so it's a race against the clock filled with sprints and bug bashes
+to ensure timely compliance.
+It's a lot of work, but the Flutter team is committed to always delivering Day 0
+support for every major iOS release. To follow our progress on supporting iOS
+27, [check out its project on GitHub][github-project].
+
+That system of Dart code and Gemini workflows processes all of WWDC,
+which accounts for the vast majority of changes from Apple.
+However, "vast majority" and "entirety" are different things;
+and sometimes, a critical breaking change comes from something as innocuous
+as a minor patch's release notes.
+
+## The beta releases heard 'round the Flutter team
+
+The year was 2024 and the iOS 18.2 developer beta had just arrived
+with a slew of updates around, amongst other things,
+gesture and pointer event handling.
+Days later, we received an alarming bug report:
+after clicking on a widget above a webview,
+no ensuing gestures or clicks to the underlying webview would trigger `onClick`
+events.
+
+This wasn't just bad. No, this was a Code Red, five alarm fire—because ads
+depend on webviews, and depriving developers of even a single dollar of revenue
+is absolutely a non-starter.
+
+The root cause of the bug was originally opaque,
+but in time-honored, tech-debugging tradition,
+a member of the team thought to try _turning it off and back on again_,
+so to speak.
+We discovered that swapping out one of the gesture recognizers
+in the webview stack on every click solved the problem.
+No one was terribly happy with the solution,
+but it was a solution, and an invisible one to end-users at that.
+
+Fast forward to August 2025 and the iOS 26 beta,
+and seemingly unrelated changes clashed with our gesture recognizer toggling
+trick which caused a more serious regression where Flutter's touch
+and gesture blocking system completely failed.
+With no other options, we reverted the previous workaround…
+_and were immediately reacquainted with the unresponsive webview bug_.
+
+Investigations into the anomaly from Flutter's end proved fruitless,
+so we had to simplify things.
+The team created a test project in pure Swift.
+With no Flutter, no Dart, just a standard UIKit parent view,
+a native gesture recognizer, and a standalone webview,
+we saw the same faulty behavior.
+
+This was a huge win!
+With a clean reproduction,
+we sent our bug report and a recommended fix to Apple,
+who wasted little time identifying and patching the issue.
+By iOS 26.4, webview clicks were back to behaving how you would expect,
+whether or not the project used Flutter.
+
+### Multiple fixes from multiple directions
+
+Complementing this happy ending was the fact that, at the same time,
+the Flutter team was busy
+[migrating all of our Dart code off of a separate "UI thread" and back on to the main "platform thread"][platform-thread-migration]
+(as Flutter calls them).
+This meant that Dart code could synchronously talk to platform code,
+like Swift and Objective-C for iOS,
+which opened the door to other modernizations,
+such as introducing a synchronous system for hit testing.
+These refactors further improved gesture behavior on iOS
+by removing microtask delays and other sources of chaos.
+
+Combined, these changes delivered major stability improvements
+for both Flutter apps running on iOS _and_ native iOS apps.
+
+## Sometimes we're even proactive, too
+
+WWDC announcements and bug reports are definitionally reactive maneuvers,
+but sometimes the Flutter team is actually ahead of the game.
+In the spring of 2025, Apple released iOS 18.4,
+which produced the following warning in Flutter apps:
+
+> CLIENT OF UIKIT REQUIRES UPDATE: This process does not adopt UIScene
+> lifecycle. This will become an assert in a future version.
+
+"A future version" is ambiguous,
+but the Flutter team wasn't about to procrastinate with your apps,
+so we dove in ahead of schedule.
+Unfortunately, the implied change would prove to be a big one.
+
+To understand why,
+one must consider how much the mobile landscape has changed
+since Flutter's inception in 2014.
+Once upon a time, Flutter was iOS and Android only,
+filled those screens completely, and enjoyed stable window sizes.
+Now, in 2026, Flutter runs on other platforms, can have multiple windows,
+is embedded into non-Flutter apps,
+and can have its geometry folded in half at a moment's notice.
+These changes complicated once simple questions like "How big is the window?",
+"Is the app backgrounded?", and also complicated the adoption of `UIScene`!
+
+Then, in 2025's WWDC,
+an individual technical session included the following all-important sentence:
+
+> In the release following iOS 26, any `UIKit` app built with the latest SDK
+> will be required to use the `UIScene` life cycle, otherwise it will not
+> launch.
+
+_Thank goodness our intake and our (then manual) triage process flagged that
+session!_
+
+We spent Q3 of 2025 designing and implementing,
+and were ultimately already beta testing our `UIScene` implementation by Q4.
+An experimental flag unlocked it for adventurous Flutter developers,
+and we owe a huge thank you to everyone who kicked the tires
+because you collectively helped us find critical edge cases,
+timing issues, and other quirks.
+
+By January of 2026,
+we shifted our focus to helping the ecosystem update iOS plugins,
+as their adoption was also required for app health.
+After migrating every plugin we directly maintain,
+we began filing issues against community plugins.
+Thankfully, plugin authors from around the world were highly responsive
+and we enjoyed a tremendous amount of buy-in and team spirit in getting
+everything updated!
+
+### Shipping the feature
+
+Full support for Apple's `UIScene` API landed in Flutter's 3.41 stable release
+in February of 2026.
+And if you've never heard of `UIScene` and basically don't know what we're
+talking about—good!
+Our definition of success was that most Flutter developers would run
+`flutter upgrade`, resume writing Dart code, and never have to think about this.
+(Some advanced users, like those with add-to-app scenarios,
+did have a short-but-manual migration guide to follow.)
+
+This proactive work paid _huge_ dividends,
+because Apple turned that warning into an assert in the iOS 27 beta
+just as they said they would.
+But, thanks to our early efforts,
+Flutter was ready for iOS 27 months before it was even announced.
+
+## We sweat in the desert; you get sweet desserts
+
+The Flutter team's goal remains the same as ever:
+to figure out the annoying parts of app development
+so you can focus on the fun parts: building features, making users happy,
+and shipping great releases.
+Whether it's iOS, Android, or any of the other many platforms where Flutter
+apps run, we want your build targets to remain just another implementation
+detail en route to creating great experiences that delight your users.
+
+For more information on Flutter,
+check out [the video version of this blog post][video-version],
+our [docs][], [YouTube channel][], or find us on socials.
+Until then, we can't wait to see what you build!
+
+[docs]: https://docs.flutter.dev
+[platform-thread-migration]: https://www.youtube.com/watch?v=miW7vCmQwnw
+[video-version]: https://www.youtube.com/watch?v=vFYxYjepaK8
+[YouTube channel]: https://youtube.com/flutterdev
+[github-project]: https://github.com/orgs/flutter/projects/212/views/1?sliceBy%5Bvalue%5D=27
diff --git a/sites/www/content/blog/perspective-on-flutter/index.md b/sites/www/content/blog/perspective-on-flutter/index.md
index 6f218151c51..4d7adf7660b 100644
--- a/sites/www/content/blog/perspective-on-flutter/index.md
+++ b/sites/www/content/blog/perspective-on-flutter/index.md
@@ -3,7 +3,7 @@ title: "Perspective on Flutter"
description: >-
Fun with 3D and the Transform widget
publishDate: 2018-06-18
-author: wmleler1
+author: wmleler
image: images/1WdpLQEC_L1PYDO1KgxxfMQ.webp
category: case-study
layout: blog
diff --git a/sites/www/content/blog/pitching-flutter-to-your-company-or-client/index.md b/sites/www/content/blog/pitching-flutter-to-your-company-or-client/index.md
index 77f84da112e..dd35498a40a 100644
--- a/sites/www/content/blog/pitching-flutter-to-your-company-or-client/index.md
+++ b/sites/www/content/blog/pitching-flutter-to-your-company-or-client/index.md
@@ -3,7 +3,7 @@ title: "Pitching Flutter to your company or client"
description: >-
The business case for Flutter
publishDate: 2018-11-01
-author: wmleler1
+author: wmleler
image: images/1OOmIZ2plXASJUl49v6TlDA.gif
category: deep-dive
layout: blog
diff --git a/sites/www/content/blog/whats-new-in-flutter-3-47/index.md b/sites/www/content/blog/whats-new-in-flutter-3-47/index.md
index 1d6825e0a81..548e088aa1a 100644
--- a/sites/www/content/blog/whats-new-in-flutter-3-47/index.md
+++ b/sites/www/content/blog/whats-new-in-flutter-3-47/index.md
@@ -558,4 +558,4 @@ with this new and improved version of Flutter!
[spm-blog-post]: /blog/saying-goodbye-to-cocoapods-swift-package-manager-is-soon-the-default-in-flutter
[uiscene-guide]: https://docs.flutter.dev/release/breaking-changes/uiscene-lifecycle-ios
[impeller-doc]: https://docs.flutter.dev/perf/impeller
-[opengles-breaking-change]: https://docs.flutter.dev/release/breaking-changes/opengles-render-to-texture
+[opengles-breaking-change]: https://docs.flutter.dev/release/breaking-changes/opengles-render-to-texture-top-down
diff --git a/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-1-6000x2834.webp b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-1-6000x2834.webp
new file mode 100644
index 00000000000..31e53c58f16
Binary files /dev/null and b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-1-6000x2834.webp differ
diff --git a/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-2-6000x2834.webp b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-2-6000x2834.webp
new file mode 100644
index 00000000000..dcd09ab19b9
Binary files /dev/null and b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-2-6000x2834.webp differ
diff --git a/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-3-6000x2834.webp b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-3-6000x2834.webp
new file mode 100644
index 00000000000..1a0be7fa3fa
Binary files /dev/null and b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-3-6000x2834.webp differ
diff --git a/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-4-6000x2834.webp b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-4-6000x2834.webp
new file mode 100644
index 00000000000..ea81074d077
Binary files /dev/null and b/sites/www/content/showcase/images/third_party/case_studies/maxint/flutter-showcase-4-6000x2834.webp differ
diff --git a/sites/www/content/showcase/images/third_party/case_studies/maxint/maxint_logo.webp b/sites/www/content/showcase/images/third_party/case_studies/maxint/maxint_logo.webp
new file mode 100644
index 00000000000..3df01f82f3c
Binary files /dev/null and b/sites/www/content/showcase/images/third_party/case_studies/maxint/maxint_logo.webp differ
diff --git a/sites/www/content/showcase/maxint.md b/sites/www/content/showcase/maxint.md
new file mode 100644
index 00000000000..d13f68a75c8
--- /dev/null
+++ b/sites/www/content/showcase/maxint.md
@@ -0,0 +1,168 @@
+---
+title: Maxint
+description:
+ Learn how Maxint deployed to six platforms simultaneously with Flutter, saving
+ 65% in codebase overhead.
+headline: Maxint
+summary: Building an autonomous financial OS across six platforms with Flutter
+appName: Maxint
+companyName: Maxint
+logo: images/third_party/case_studies/maxint/maxint_logo.webp
+card: images/third_party/case_studies/maxint/maxint_logo.webp
+locations:
+ - North America
+platforms:
+ - Mobile
+ - Desktop
+ - Web
+industries:
+ - Banking & Finance
+successMetrics:
+ - perc: 65
+ desc: code avoided vs native multi-platform
+ - text: "3-4x"
+ desc: fewer front-end engineers needed
+ - text: "6"
+ desc: platforms launched from day one
+tags:
+ - mobile
+ - desktop
+ - web
+ - ios
+ - android
+ - macos
+ - windows
+ - linux
+ - ai
+publishDate: 2026-08-21
+---
+
+[Maxint](https://maxint.com) is an autonomous financial operating system. Built
+with Flutter, it unifies enterprise accounting, multi-platform monetization, and
+AI-powered robo-advising into a single, precision-engineered workspace designed
+to provide the signal minus the noise.
+
+As a lean startup, Maxint needed to rapidly deploy an enterprise-grade financial
+operating system across iOS, Android, macOS, Windows, Linux, and Web
+simultaneously. By launching on every major app store from day one, the team
+leveraged organic search discovery across multiple platform ecosystems to drive
+consistent user growth without spending a dollar on paid marketing.
+
+
+
+**Trial by fire: From hackathon to production in three months**
+
+Rather than spending weeks evaluating frameworks in committee, the team tested
+Flutter during the TechCrunch Disrupt hackathon. Faced with tight time
+constraints after evaluating options like React Native, Ionic, Xamarin, and
+native platforms, they chose Flutter to build a fully functional proof of
+concept for Maxint in less than 24 hours.
+
+The success of this hackathon validated Flutter's developer velocity, responsive
+charting, and local state management across devices. Following the hackathon
+success, a lean team spent three months architecting the production-ready
+financial OS, deploying native applications across all six target platforms
+simultaneously. This was a timeline that likely would have required over a year
+using siloed native teams.
+
+**Why Flutter? Single codebase portability and rendering performance**
+
+Portability across desktop and mobile served as Maxint's foundational growth
+engine. By maintaining a single Dart codebase, complex mission-critical
+logic—such as their double-entry ledger, local encryption, and financial
+reporting engine—only had to be written and audited once, guaranteeing complete
+feature parity and data accuracy across all user devices.
+
+To meet the high UI performance standards of financial software, Maxint relies
+on Flutter's Impeller rendering engine on iOS and Android. By pre-compiling
+shaders ahead-of-time (AOT) and using modern native graphics APIs like Metal and
+Vulkan, Impeller renders complex, data-dense financial charts at 120 frames per
+second without blocking the UI thread. Additionally, Flutter's hot reload
+feature accelerated iteration, allowing the team to refine dense ledger layouts
+and state logic without restarting the app.
+
+
+
+**Building Finsight: Bidirectional conversational AI with Flutter**
+
+Maxint built Finsight, a fluid voice-driven financial assistant, directly within
+the Flutter ecosystem by orchestrating
+[`speech_to_text`](https://pub.dev/packages/speech_to_text) and
+[`flutter_tts`](https://pub.dev/packages/flutter_tts). The application uses
+continuous listening and partial transcription to stream user queries to
+language models while the user speaks, minimizing perceived latency.
+
+When generating spoken feedback, Finsight attaches context tags (such as urgent
+alerts or routine portfolio summaries) to dynamically adjust parameters like
+`setSpeechRate` and `setPitch` in `flutter_tts`. Using
+[Riverpod](https://riverpod.dev/) for state management, the on-screen visualizer
+pulses in synchronization with audio callbacks, maintaining a unified
+interactive state across mobile, desktop, and web platforms.
+
+
+
+**Overcoming multi-platform friction: Building Orca**
+
+Synchronizing user entitlements across the Apple App Store, Google Play,
+Microsoft Store, and Stripe presented a major technical hurdle due to isolated
+store silos. To solve multi-platform subscription management, Maxint developed
+an internal unified billing infrastructure and released it to the developer
+community as [Orca](https://orca.maxint.com/).
+
+Building for desktop also required investing in desktop-native UX paradigms,
+including information-dense layouts, robust keyboard shortcuts, right-click
+context menus, and window state management, while keeping double-entry ledger
+math completely isolated from UI rendering.
+
+**Key results and business impact**
+
+Building with Flutter transformed Maxint's resource efficiency and release
+cadence:
+
+- **65% less code:** Avoided writing and maintaining at least 65% more code
+ compared to managing separate native codebases (Swift, Kotlin, C\#, C++, and
+ TypeScript).
+- **3x to 4x team efficiency:** Operates with 3x to 4x fewer front-end engineers
+ than needed for native multi-platform parity.
+- **Day-one six-platform cadence:** Pushes simultaneous, feature-complete
+ updates to iOS, Android, Web, macOS, Windows, and Linux on a weekly basis.
+- **Ecosystem contribution:** Created and launched Orca to streamline
+ cross-platform billing for Flutter developers.
+
+
+
+
+**What's next for Maxint and Orca**
+
+Maxint's roadmap includes expanding Orca's SDK capabilities across web and
+desktop platforms for unified paywalls and revenue analytics. For the core
+platform, Maxint is building upon its current on-device receipt parsing, powered
+by Google’s ML Kit, by doubling down on privacy-first AI intelligence.
+
+
+
+The team is integrating small-footprint local LLMs (including custom
+135M-parameter transformer models) to execute highly advanced document parsing,
+cashflow forecasting, and transaction categorization entirely on the user's
+hardware. The team is also expanding their tax engine to support
+multi-jurisdiction calculations, local currency reconciliation, and native
+electronic tax filing with the IRS.
+
+
+