diff --git a/CHANGELOG.md b/CHANGELOG.md index d3b7f92..2cae02e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * `setOptions(List)` on `AdvancedSingleSelectFieldController` and `AdvancedMultiSelectFieldController`: the list of options can change while the form is open. A selected value that is not on the new list is cleared as a program write (no user edit, no error on an untouched field); widgets rebuild even when the value stayed the same. * The single-select controller now copies the `options` you pass, as the multi-select always did. `options` is an unmodifiable list on both. +* The Agent Skill is a [package skill](https://dart.dev/tools/pub/package-skills): `dart run skills@ get` installs it into your agent's skills folder. The folder moved from `skills/advanced_forms/` to `skills/advanced_forms-build-forms/`, the name the CLI requires; the skill now adds the package with `flutter pub add advanced_forms` when the project lacks it. ## 0.2.1+1 diff --git a/README.md b/README.md index a8519a6..0985b31 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ - **Granular rebuilds** — one builder per field, so a keystroke rebuilds one subtree and nothing else. > [!TIP] -> **Using Claude Code (or another agent that supports Agent Skills)?** This package ships an [Agent Skill](https://advanced-forms.leancode.co/docs/agent-skill) that teaches your agent the full API, so it generates fields, validation, cross-field logic, and subforms idiomatically. Installing it is one command: +> **Using Claude Code (or another agent that supports Agent Skills)?** This package ships an [Agent Skill](https://advanced-forms.leancode.co/docs/agent-skill) that teaches your agent the full API, so it generates fields, validation, cross-field logic, and subforms idiomatically. With `advanced_forms` in your `pubspec.yaml`, one command installs it into your agent's skills folder: > > ```sh -> mkdir -p .claude/skills/advanced_forms && curl -fsSL https://advanced-forms.leancode.co/skill.md -o .claude/skills/advanced_forms/SKILL.md +> dart run skills@ get > ``` > -> Or copy the file from [the docs](https://advanced-forms.leancode.co/docs/agent-skill) and paste it into `.claude/skills/advanced_forms/SKILL.md`. Then say *"build me a sign-up form"* — the agent adds the package if it is missing and writes the form. +> Not using the package yet? Copy the file from [the docs](https://advanced-forms.leancode.co/docs/agent-skill) into `.claude/skills/advanced_forms-build-forms/SKILL.md`, then say *"build me a sign-up form"* — the agent adds the package and writes the form. ## Website: interactive examples and docs @@ -150,7 +150,7 @@ Own the controller wherever you like — it's a `ChangeNotifier`, so any DI pack - [MIGRATION.md](https://github.com/leancodepl/advanced_forms/blob/main/MIGRATION.md) — coming from 0.1.x. - `example/` — a runnable app where every pattern in these docs has a working screen. See [example/example.md](https://github.com/leancodepl/advanced_forms/blob/main/example/example.md) for the screen guide. - [API reference](https://pub.dev/documentation/advanced_forms/latest/) — the generated dartdoc, for every member and its edge cases. -- [Agent Skill](https://advanced-forms.leancode.co/docs/agent-skill) — `skills/advanced_forms/SKILL.md`, a skill that teaches your agent the full API, so it generates fields, validation, cross-field logic, and subforms idiomatically. One `curl` or one copy-paste puts it into your project's `.claude/skills/`. +- [Agent Skill](https://advanced-forms.leancode.co/docs/agent-skill) — `skills/advanced_forms-build-forms/SKILL.md`, a skill that teaches your agent the full API, so it generates fields, validation, cross-field logic, and subforms idiomatically. `dart run skills@ get` installs it into your project's agent skills folder. ## Rendering fields diff --git a/docs/agent-skill.mdx b/docs/agent-skill.mdx index e7ceb4a..de8d101 100644 --- a/docs/agent-skill.mdx +++ b/docs/agent-skill.mdx @@ -14,8 +14,9 @@ The package ships an [Agent Skill](https://agentskills.io): a single Markdown fi a task looks like form work, so it generates fields, validation, cross-field logic and subforms the way these docs describe them rather than the way a generic Flutter tutorial would. -Copy the file into your project and say *"build me a sign-up form"*. The agent adds the package to `pubspec.yaml` if -it is not there yet, writes the controller and the widgets, and wires the validation — you do not open the docs. +Copy the file below into your project, or install it with one command — then say *"build me a sign-up form"*. The +agent adds the package to `pubspec.yaml` if it is not there yet, writes the controller and the widgets, and wires the +validation — you do not open the docs. @@ -25,69 +26,72 @@ it is not there yet, writes the controller and the widgets, and wires the valida hardened by having agents generate real forms from it alone, compiling those forms, and feeding every defect back. -## Install it +Get this skill with one command line + +The skill ships inside the package, so Dart's [skills CLI](https://dart.dev/tools/pub/package-skills) can install it — +and every other skill your dependencies carry — into the folder your agent reads. -### Create the file - - - +### Have the package in your project -Press **Copy SKILL.md** above, create this file in your project and paste: +The CLI reads `pubspec.yaml`, so `advanced_forms` has to be a dependency first: -```text -.claude/skills/advanced_forms/SKILL.md +```sh +flutter pub add advanced_forms ``` -Commit it, and every contributor's agent picks it up. To have it in every project you work on instead, put the same -file under `~/.claude/skills/advanced_forms/SKILL.md`. - - - + + -The file is served raw at [`/skill.md`](/skill.md), so the terminal can fetch it into place: +### Run the skills CLI -```sh title="Into the project" -mkdir -p .claude/skills/advanced_forms && curl -fsSL https://advanced-forms.leancode.co/skill.md -o .claude/skills/advanced_forms/SKILL.md +```sh +dart run skills@ get ``` -```sh title="For every project you work on" -mkdir -p ~/.claude/skills/advanced_forms && curl -fsSL https://advanced-forms.leancode.co/skill.md -o ~/.claude/skills/advanced_forms/SKILL.md -``` +`skills@` runs the CLI straight from pub.dev — nothing to install, nothing added to your pubspec. It detects the agent +from the project (`.claude/skills/` for Claude Code, `.cursor/skills/`, `.github/skills/` for Copilot, `.agents/skills/` +for the rest; `--agent claude` to force one) and asks which skills to install; `--all` skips the question. -Run it again whenever you upgrade the package: the URL always serves the skill for the current release. + + - - +### Check it is loaded -`.claude/skills/` is where Claude Code looks. Other agents that support Agent Skills read the same file from their own -skills folder — check the agent's docs for the path. +`dart run skills@ list` shows what the CLI manages. In Claude Code, `/skills` lists the skills in scope. The skill's +description mentions forms, fields, validation and subforms, so it triggers on a request like *"add a shipping address +section to the checkout"* even when the word "form" never appears. -### Check it is loaded +### Ask for a form -In Claude Code, `/skills` lists the skills in scope. The skill's description mentions forms, fields, validation and -subforms, so it triggers on a request like *"add a shipping address section to the checkout"* even when the word -"form" never appears. +*"Build me a sign-up form with email, password and terms."* That is the whole prompt. The skill is enough on its own: +the agent does not need the docs, the README or the package source in context. It knows the two rules that prevent +most bugs — call `registerFields` once with every field, and bind text widgets to `field.textController` — and it knows +the API surface well enough not to invent parameters. -### Ask for a form +### Keep it current -*"Build me a sign-up form with email, password and terms."* That is the whole prompt: if `advanced_forms` is not in -`pubspec.yaml` yet, the agent runs `flutter pub add advanced_forms` first, then writes the controller, the widgets and -the validation. The skill is enough on its own: the agent does not need the docs, the README or the package source in -context. It knows the two rules that prevent most bugs — call `registerFields` once with every field, and bind text widgets to -`field.textController` — and it knows the API surface well enough not to invent parameters. +After a package upgrade, run `dart run skills@ get` again: it offers to update the skill to the one the new version +ships. `dart run skills@ prune` removes skills of dependencies you dropped. + + The file is the skill. Paste what **Copy SKILL.md** gives you into `.claude/skills/advanced_forms-build-forms/SKILL.md` + (or `~/.claude/skills/…` for every project), or fetch it raw: + `curl -fsSL https://advanced-forms.leancode.co/skill.md -o .claude/skills/advanced_forms-build-forms/SKILL.md`. Other agents + read the same file from their own skills folder. + + ## What it teaches - Setup: if `pubspec.yaml` does not list `advanced_forms`, the agent runs `flutter pub add advanced_forms` before @@ -108,7 +112,7 @@ context. It knows the two rules that prevent most bugs — call `registerFields` ## The file This is the skill, whole — the same bytes the copy button and `/skill.md` give you. It lives in the repository at -[`skills/advanced_forms/SKILL.md`](https://github.com/leancodepl/advanced_forms/blob/main/skills/advanced_forms/SKILL.md) +[`skills/advanced_forms-build-forms/SKILL.md`](https://github.com/leancodepl/advanced_forms/blob/main/skills/advanced_forms-build-forms/SKILL.md) and this page is built from it, so what you read here is what ships. diff --git a/docs/index.mdx b/docs/index.mdx index e3b672d..f743bd1 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -58,8 +58,8 @@ Why the package makes the choices it makes — no widgets, no `Form`, no codegen [Design decisions](./internals/decisions.mdx). - The repository ships an [Agent Skill](./agent-skill.mdx) that teaches your agent the full API, so it generates fields, - validation, cross-field logic and subforms idiomatically. + The package ships an [Agent Skill](./agent-skill.mdx) that teaches your agent the full API, so it generates fields, + validation, cross-field logic and subforms idiomatically. `dart run skills@ get` installs it. `advanced_forms` is maintained by [LeanCode](https://leancode.co/?utm_source=advanced-forms-docs&utm_medium=referral&utm_campaign=advanced-forms) diff --git a/docs/installation.mdx b/docs/installation.mdx index 1a6f0b9..959fbcb 100644 --- a/docs/installation.mdx +++ b/docs/installation.mdx @@ -41,7 +41,7 @@ None are required. Two are worth knowing about: disposed for you. The example app uses it; a `StatefulWidget` works just as well. See [Lifecycle and ownership](./lifecycle/index.mdx). - **An [Agent Skill](./agent-skill.mdx)** — if you write forms with Claude Code or another agent that supports - skills, one copy-paste (or one `curl`) puts `SKILL.md` into your project and the agent generates idiomatic + skills, `dart run skills@ get` installs it into your agent's skills folder and the agent generates idiomatic `advanced_forms` code. ## Coming from `leancode_forms` 0.1.x diff --git a/docs_app/components/mdx.tsx b/docs_app/components/mdx.tsx index 36f230e..b396f1e 100644 --- a/docs_app/components/mdx.tsx +++ b/docs_app/components/mdx.tsx @@ -11,7 +11,7 @@ import { TypeTable } from "fumadocs-ui/components/type-table" import type { MDXComponents } from "mdx/types" import type { ImgHTMLAttributes } from "react" import { AdvancedFormsExample } from "./advanced-forms-example" -import { SkillCopy, SkillSource } from "./skill-install" +import { SkillBanner, SkillCopy, SkillSource } from "./skill-install" import { cn } from "@/lib/cn" function toPixel(value: ImgHTMLAttributes["width"]): number | undefined { @@ -37,6 +37,7 @@ export function getMDXComponents(components?: MDXComponents) { Tabs, TypeTable, AdvancedFormsExample, + SkillBanner, SkillCopy, SkillSource, ...components, diff --git a/docs_app/components/skill-install.tsx b/docs_app/components/skill-install.tsx index 3b027ba..36ac498 100644 --- a/docs_app/components/skill-install.tsx +++ b/docs_app/components/skill-install.tsx @@ -4,7 +4,8 @@ * harness: Claude Code */ import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock" -import { ExternalLink, FileText } from "lucide-react" +import { ExternalLink, FileText, Terminal } from "lucide-react" +import type { ReactNode } from "react" import { CopySkillButton } from "./copy-skill-button" import { readSkill } from "@/lib/skill" import { skillRoute, urls } from "@/lib/shared" @@ -61,3 +62,22 @@ export function SkillSource() { ) } + +/** + * The title over the install steps. A quiet accent — a soft tint and a rule + * in the accent colour — so the one-command path reads as the main road + * without shouting over the copy card above it. + */ +export function SkillBanner({ children }: { children: ReactNode }) { + return ( +
+ + {/* The docs' heading rules add a top margin and padding to every h2; this one sits in a box. */} +

+ {children} +

+
+ ) +} diff --git a/docs_app/lib/shared.ts b/docs_app/lib/shared.ts index 88f5460..9e03a19 100644 --- a/docs_app/lib/shared.ts +++ b/docs_app/lib/shared.ts @@ -28,7 +28,7 @@ export const docsContentRoute = "/llms.mdx" /** The Agent Skill, served raw so one `curl` (or an agent) can fetch it. */ export const skillRoute = "/skill.md" /** Where the skill goes in a project; `~/` in front makes it global. */ -export const skillInstallPath = ".claude/skills/advanced_forms/SKILL.md" +export const skillInstallPath = ".claude/skills/advanced_forms-build-forms/SKILL.md" export const gitConfig = { user: "leancodepl", @@ -46,7 +46,7 @@ export const urls = { changelog: `${repo}/blob/${gitConfig.branch}/CHANGELOG.md`, migration: `${repo}/blob/${gitConfig.branch}/MIGRATION.md`, license: `${repo}/blob/${gitConfig.branch}/LICENSE`, - skill: `${repo}/blob/${gitConfig.branch}/skills/advanced_forms/SKILL.md`, + skill: `${repo}/blob/${gitConfig.branch}/skills/advanced_forms-build-forms/SKILL.md`, exampleApp: `${repo}/tree/${gitConfig.branch}/example`, exampleGuide: `${repo}/blob/${gitConfig.branch}/example/example.md`, exampleWidgets: `${repo}/tree/${gitConfig.branch}/example/lib/widgets`, diff --git a/docs_app/lib/skill.ts b/docs_app/lib/skill.ts index fd2cc4b..e4d3a64 100644 --- a/docs_app/lib/skill.ts +++ b/docs_app/lib/skill.ts @@ -7,11 +7,11 @@ import { readFileSync, statSync } from "node:fs" import path from "node:path" /** - * The Agent Skill is one Markdown file in the repo root (`skills/advanced_forms/`), + * The Agent Skill is one Markdown file in the repo root (`skills/advanced_forms-build-forms/`), * the same one the package ships. Read at build time, like `../docs`, so the * site can never serve a stale copy of it. */ -const skillFile = path.join(process.cwd(), "..", "skills", "advanced_forms", "SKILL.md") +const skillFile = path.join(process.cwd(), "..", "skills", "advanced_forms-build-forms", "SKILL.md") export interface Skill { /** The file, verbatim. */ diff --git a/landing/lib/app.dart b/landing/lib/app.dart index 49b1fb8..9b42d76 100644 --- a/landing/lib/app.dart +++ b/landing/lib/app.dart @@ -25,11 +25,13 @@ class App extends StatelessComponent { classes: 'af-landing', children: [ Hero(version: version, demo: loadExample('hero')), + // Right under the fold: the fastest way to a working form comes + // before the explanation of how forms work. + const SkillBand(), ModelSection(example: loadExample('signup')), ValidationSection(example: loadExample('modes')), AsyncSection(example: loadExample('async')), const Features(), - const SkillBand(), ], ), SiteFooter(version: version), diff --git a/landing/lib/components/hero.dart b/landing/lib/components/hero.dart index 7a86adc..1c51812 100644 --- a/landing/lib/components/hero.dart +++ b/landing/lib/components/hero.dart @@ -1,6 +1,6 @@ import 'package:advanced_forms_landing/components/button.dart'; import 'package:advanced_forms_landing/components/example_frame.dart'; -import 'package:advanced_forms_landing/components/icons.dart'; +import 'package:advanced_forms_landing/components/install_command.dart'; import 'package:advanced_forms_landing/components/pill.dart'; import 'package:advanced_forms_landing/components/section.dart'; import 'package:advanced_forms_landing/examples.dart'; @@ -51,28 +51,7 @@ class Hero extends StatelessComponent { .text(' your app already uses.'), ]), div(classes: 'af-install', [ - div(classes: 'af-install-command', [ - const span( - classes: 'af-prompt', - attributes: {'aria-hidden': 'true'}, - [.text(r'$')], - ), - const code([.text(installCommand)]), - button( - classes: 'af-copy-button', - attributes: const { - 'type': 'button', - 'data-copy-text': installCommand, - 'aria-label': 'Copy to clipboard', - 'aria-live': 'polite', - }, - [ - span(classes: 'af-copy-idle', [Icon.copy.build(size: 15)]), - span(classes: 'af-copy-done', [Icon.check.build(size: 15)]), - const span(classes: 'af-copy-label', [.text('Copy')]), - ], - ), - ]), + const InstallCommand(installCommand), p(classes: 'af-install-alt', [ const .text('Coming from '), const code([.text('leancode_forms')]), diff --git a/landing/lib/components/install_command.dart b/landing/lib/components/install_command.dart new file mode 100644 index 0000000..b1eacff --- /dev/null +++ b/landing/lib/components/install_command.dart @@ -0,0 +1,40 @@ +import 'package:advanced_forms_landing/components/icons.dart'; +import 'package:jaspr/dom.dart'; +import 'package:jaspr/jaspr.dart'; + +/// A shell command in a box with a copy button: `flutter pub add …` in the +/// hero, `dart run skills@ get` in the skill band. The button is wired by +/// `[data-copy-text]` in web/landing.js. +/// +/// Styled by `.af-install-command*` in web/landing.css. +class InstallCommand extends StatelessComponent { + const InstallCommand(this.command, {super.key}); + + final String command; + + @override + Component build(BuildContext context) { + return div(classes: 'af-install-command', [ + const span( + classes: 'af-prompt', + attributes: {'aria-hidden': 'true'}, + [.text(r'$')], + ), + code([.text(command)]), + button( + classes: 'af-copy-button', + attributes: { + 'type': 'button', + 'data-copy-text': command, + 'aria-label': 'Copy to clipboard', + 'aria-live': 'polite', + }, + [ + span(classes: 'af-copy-idle', [Icon.copy.build(size: 15)]), + span(classes: 'af-copy-done', [Icon.check.build(size: 15)]), + const span(classes: 'af-copy-label', [.text('Copy')]), + ], + ), + ]); + } +} diff --git a/landing/lib/components/sections.dart b/landing/lib/components/sections.dart index d9da5f6..c5bf1c2 100644 --- a/landing/lib/components/sections.dart +++ b/landing/lib/components/sections.dart @@ -1,6 +1,7 @@ import 'package:advanced_forms_landing/components/button.dart'; import 'package:advanced_forms_landing/components/example_frame.dart'; import 'package:advanced_forms_landing/components/icons.dart'; +import 'package:advanced_forms_landing/components/install_command.dart'; import 'package:advanced_forms_landing/components/pill.dart'; import 'package:advanced_forms_landing/components/section.dart'; import 'package:advanced_forms_landing/examples.dart'; @@ -364,21 +365,24 @@ class SkillBand extends StatelessComponent { 'The package ships an Agent Skill that teaches Claude ' 'Code — or any agent that supports skills — the full ' '`advanced_forms` API, so it generates fields, validation, ' - 'cross-field logic and subforms idiomatically. One file, ' - 'one copy — then say "build me a sign-up form" and it adds ' - 'the package and writes the form.', + 'cross-field logic and subforms idiomatically. It ships ' + "in the package: one command puts it into your agent's " + 'skills folder, then say "build me a sign-up form".', ), ), ]), - const div(classes: 'af-hero-actions', [ - Button('Get the skill', href: skillDocsPath, leading: .bot), - Button( - 'Read SKILL.md', - href: skillUrl, - variant: .secondary, - external: true, - trailing: .arrowRight, - ), + const div([ + InstallCommand(skillInstallCommand), + div(classes: 'af-hero-actions', [ + Button('How it works', href: skillDocsPath, leading: .bot), + Button( + 'Read SKILL.md', + href: skillUrl, + variant: .secondary, + external: true, + trailing: .arrowRight, + ), + ]), ]), ]), ]), diff --git a/landing/lib/site.dart b/landing/lib/site.dart index 318237a..e149fe8 100644 --- a/landing/lib/site.dart +++ b/landing/lib/site.dart @@ -46,7 +46,8 @@ const apiReferenceUrl = 'https://pub.dev/documentation/advanced_forms/latest/'; const changelogUrl = '$repoUrl/blob/main/CHANGELOG.md'; const migrationUrl = '$repoUrl/blob/main/MIGRATION.md'; const issuesUrl = '$repoUrl/issues'; -const skillUrl = '$repoUrl/blob/main/skills/advanced_forms/SKILL.md'; +const skillUrl = + '$repoUrl/blob/main/skills/advanced_forms-build-forms/SKILL.md'; /// The docs page that installs the Agent Skill: one copy, one file. const skillDocsPath = '$docsPath/agent-skill'; @@ -61,6 +62,9 @@ const patrolUrl = 'https://patrol.leancode.co/?$_utm'; const installCommand = 'flutter pub add advanced_forms'; +/// Installs the package's Agent Skill into the agent's skills folder. +const skillInstallCommand = 'dart run skills@ get'; + /// The package version, read from the repository's own pubspec so the site /// can never announce a stale number. The build number is not shown. String packageVersion() { diff --git a/landing/web/landing.css b/landing/web/landing.css index 7727888..cdcfc11 100644 --- a/landing/web/landing.css +++ b/landing/web/landing.css @@ -857,6 +857,10 @@ a.af-pill-accent:hover { margin-top: 0; } +.af-band .af-install-command + .af-hero-actions { + margin-top: 1rem; +} + /* ---------- Footer ---------- */ .af-footer { diff --git a/skills/advanced_forms/SKILL.md b/skills/advanced_forms-build-forms/SKILL.md similarity index 99% rename from skills/advanced_forms/SKILL.md rename to skills/advanced_forms-build-forms/SKILL.md index 8a57ef6..09ddcb0 100644 --- a/skills/advanced_forms/SKILL.md +++ b/skills/advanced_forms-build-forms/SKILL.md @@ -1,5 +1,5 @@ --- -name: advanced_forms +name: advanced_forms-build-forms description: Build Flutter forms with the advanced_forms package (AdvancedFormController, AdvancedTextFieldController, AdvancedFieldBuilder). Use whenever the user creates a form, adds or edits form fields, wires validation (sync, async, or cross-field), chooses when errors appear (ValidationMode), builds dropdowns/checkboxes/multi-selects/sliders bound to field controllers, handles submit buttons or server-side errors, or works with subforms in any Flutter project — whether or not it depends on advanced_forms yet (add it with `flutter pub add advanced_forms`) — even if they never say the word "form". ---