diff --git a/.agents/skills/contribute-blog/SKILL.md b/.agents/skills/contribute-blog/SKILL.md new file mode 100644 index 0000000000..a1ea5c693e --- /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 0000000000..6bd6327c4f --- /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 0000000000..fc0fb55d21 --- /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 0000000000..19b010852a --- /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 af90ef5579..0000000000 --- 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/author_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.