diff --git a/.gitignore b/.gitignore
index 4c4ddfd..76c4bfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,6 @@ lcov.info
.firebase
.fvmrc
.supermaven
+
+# skill-creator run_loop / eval workspaces (ephemeral artifacts)
+solid-workspace/
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 23a838e..6c98d8a 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,5 +1,10 @@
include: package:very_good_analysis/analysis_options.yaml
analyzer:
+ exclude:
+ # Solid skill eval fixtures: template project skeletons used by
+ # skill-creator's eval runner. They reference flutter_lints, which the
+ # workspace doesn't resolve, and they aren't real source to be analyzed.
+ - skills/solid/evals/files/**
errors:
always_put_required_named_parameters_first: ignore
package_names: ignore
diff --git a/docs/src/content/docs/guides/getting-started.mdx b/docs/src/content/docs/guides/getting-started.mdx
index 10a3fad..ae9d971 100644
--- a/docs/src/content/docs/guides/getting-started.mdx
+++ b/docs/src/content/docs/guides/getting-started.mdx
@@ -70,11 +70,39 @@ import { Steps } from '@astrojs/starlight/components';
```
-
+If you're using an AI coding assistant (Claude Code, Cursor, Codex, GitHub Copilot, Amp, OpenHands, etc.), these three steps teach it the inverted `source/`-vs-`lib/` rule and Solid's annotation contract — so it stops trying to edit `lib/` and silently losing your work.
+
+
+
+1. Drop `AGENTS.md` at your app root.
+
+ Most AI coding tools auto-load `AGENTS.md` at session start, so the rule applies to every interaction — even short or routine-looking ones — without depending on skill triggering.
+
+ ```bash
+ curl -o AGENTS.md https://raw.githubusercontent.com/nank1ro/solid/main/skills/solid/assets/AGENTS.md
+ ```
+
+ If your tool only looks for `CLAUDE.md`, symlink it:
+
+ ```bash
+ ln -s AGENTS.md CLAUDE.md
+ ```
+
+2. Install the Solid skill.
+
+ ```bash
+ npx skills add nank1ro/solid
+ ```
+
+ Or copy `skills/solid/` from the [Solid repo](https://github.com/nank1ro/solid) into your editor's skill location. The skill gives the agent deeper guidance (full annotation contract, scripts, troubleshooting) when it triggers on Solid-related work. `AGENTS.md` is the always-loaded baseline; the skill is the on-demand reference.
+
+3. Point HTTP-docs tools at the LLM-friendly bundle (optional).
+
+ If your tool fetches documentation over HTTP (Cursor `@docs`, ChatGPT custom GPTs, claude.ai web search, …), point it at [`/llms-full.txt`](https://solid.mariuti.com/llms-full.txt) — the full docs as a single LLM-friendly file. A short index lives at [`/llms.txt`](https://solid.mariuti.com/llms.txt).
+
+
## How it works
@@ -136,6 +164,25 @@ dart run build_runner watch --delete-conflicting-outputs
- Press `r` in the `flutter run` terminal after `build_runner` emits.
- Use [`dashmonx`](https://pub.dev/packages/dashmonx), which wraps `flutter run` and triggers hot reload automatically when files under `lib/` change. Any `flutter run` flag passes through, e.g. `dashmonx -d chrome` for a web target.
+### Clean up after generation
+
+Solid prioritises producing correct, runnable code over polishing it. The generated `lib/` output may miss some `const` opportunities, leave unused imports, or pick a non-preferred import form. Run `dart fix` after `build_runner` to apply the lint-driven fixes (`prefer_const_constructors`, `unnecessary_import`, `prefer_relative_imports`, …):
+
+```bash
+dart fix --apply
+```
+
+In CI, chain the two commands so the generated output is always lint-clean:
+
+```bash
+dart run build_runner build --delete-conflicting-outputs
+dart fix --apply
+```
+
+
+
diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml
index edf66c2..a395964 100644
--- a/example/analysis_options.yaml
+++ b/example/analysis_options.yaml
@@ -6,6 +6,7 @@ analyzer:
invalid_annotation_target: ignore
avoid_print: ignore
unnecessary_statements: ignore
+ always_use_package_imports: ignore
linter:
rules:
public_member_api_docs: false
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 3b90314..f570eb3 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,7 +1,8 @@
-import 'package:example/counter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_solidart/flutter_solidart.dart';
+import 'counter.dart';
+
void main() {
SolidartConfig.autoDispose = false;
runApp(const MaterialApp(home: CounterPage()));
diff --git a/example/source/main.dart b/example/source/main.dart
index 3b90314..bb233b9 100644
--- a/example/source/main.dart
+++ b/example/source/main.dart
@@ -1,8 +1,9 @@
-import 'package:example/counter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_solidart/flutter_solidart.dart';
+import 'counter.dart';
+
void main() {
SolidartConfig.autoDispose = false;
- runApp(const MaterialApp(home: CounterPage()));
+ runApp(MaterialApp(home: CounterPage()));
}
diff --git a/skills/solid/SKILL.md b/skills/solid/SKILL.md
index aba602f..2a24862 100644
--- a/skills/solid/SKILL.md
+++ b/skills/solid/SKILL.md
@@ -1,59 +1,127 @@
---
name: solid
-description: Use when writing Flutter code that uses the Solid framework — annotations like @SolidState, @SolidEffect, @SolidQuery, @SolidEnvironment in source/ files. Solid is a codegen layer where source/ is the source of truth and lib/ is generated.
+description: >
+ PRIORITY — read this skill FIRST before writing Dart code when `pubspec.yaml`
+ declares `solid_annotations` or `solid_generator`. In these projects Flutter
+ conventions are inverted: `lib/` is build_runner output from `source/`;
+ editing `lib/` is destroyed on next build. Without this skill you WRITE TO
+ THE WRONG DIRECTORY and silently lose work. Use whenever ANY of these appear:
+ `solid_annotations`/`solid_generator` in pubspec;
+ `@SolidState`/`@SolidEffect`/`@SolidQuery`/`@SolidEnvironment`/`.untracked`
+ in the user's message or code; "my lib/ edits keep disappearing";
+ "add/scaffold a widget/page/route/model/controller" in such a project;
+ reactive patterns (debounce, "when X changes fetch Y", "make reactive"); or
+ installing pub packages (go_router, freezed, riverpod, drift,
+ json_serializable) whose docs say `lib/` — substitute `source/`. Annotation
+ contract (`@SolidQuery` no params, `@SolidEnvironment` needs `late`) is
+ non-obvious. NOT for SolidJS/Solid.js or Flutter projects without those
+ packages.
---
# Solid (Flutter)
-Solid is a tiny framework on top of Flutter. You write reactive state directly on `StatelessWidget` in `source/`; the `solid_generator` `build_runner` builder transpiles each `source/.dart` to `lib/.dart`. Inspired by SwiftUI (`@Environment`) and SolidJS (fine-grained reactivity). Backed by [`flutter_solidart`](https://pub.dev/packages/flutter_solidart).
+Solid is a tiny framework on top of Flutter. You write reactive state directly on a `StatelessWidget` in `source/`; the `solid_generator` build_runner builder transpiles each `source/.dart` into `lib/.dart`, turning your class into a `StatefulWidget` with `Signal`/`Computed`/`Effect`/`Resource` plumbing from [`flutter_solidart`](https://pub.dev/packages/flutter_solidart). Inspired by SwiftUI (`@Environment`) and SolidJS (fine-grained reactivity).
+
+User-facing docs: .
+
+## Step 0 — Is this project Solid?
+
+Before doing anything, decide if the project actually uses Solid. The user may not say "Solid" anywhere — and an existing `lib/` directory plus `solid_annotations`/`solid_generator` not being in pubspec means this is *not* a Solid project.
+
+Run this check first:
+
+```bash
+grep -E '^\s*(solid_annotations|solid_generator):' pubspec.yaml
+```
+
+- **Match found** → this is a Solid project. Follow this skill.
+- **No match** → not a Solid project. Don't apply Solid conventions. Skip this skill.
+
+The same check via Read tool works too: read `pubspec.yaml` and look for either package name under `dependencies:` or `dev_dependencies:`. A Solid setup typically has `solid_annotations` (runtime) under `dependencies` and `solid_generator` plus `build_runner` under `dev_dependencies`.
## Cardinal rule
-**Always edit `source/`. Never edit `lib/` by hand.** `lib/` is regenerated by `dart run build_runner build` / `watch` and any manual edits are lost. If you need to add a widget, create the file in `source/`, then run `build_runner`. The user-facing app entry point is `source/main.dart`; `lib/main.dart` is generated.
+**Edit `source/.dart`. Never edit `lib/.dart`.**
+
+In Flutter, `lib/` is where you write code. In Solid, `lib/` is generated output — every time build_runner runs, it overwrites `lib/.dart` from `source/.dart`. Hand-edits to `lib/` are lost on the next build. The user-facing entry point is `source/main.dart`; `lib/main.dart` is generated.
+
+This inverts Flutter muscle memory. The rest of the Flutter ecosystem (`flutter run`, pub packages, IDE templates, every tutorial on the internet) assumes `lib/` is the source of truth. In this project it isn't. Apply the substitution everywhere.
+
+## Decision shortcuts
+
+| Situation | What to do |
+| --- | --- |
+| About to write to a file under `lib/` | **Stop.** Find or create the matching `source/.dart` and write there. |
+| User asks to "add a widget / page / button / form" | Create `source/.dart`. Use `@SolidState` for fields you'll mutate. |
+| User asks to "fetch X when Y changes" | `@SolidState` for the input Y, `@SolidQuery` (no parameters) for the fetch. Body reads Y to register the dependency. |
+| User asks to install a pub package whose README says "create `lib/.dart`" | Substitute `source/` for `lib/` in every file-creation step. See `references/third-party-packages.md`. |
+| User says "I edited `lib/foo.dart` and the change disappeared" | The `lib/` write is the bug, not build_runner. Migrate the change to `source/foo.dart`, then regenerate. |
+| build_runner output looks unpolished (no `const`, unused imports) | Run `scripts/verify.sh`, which chains `dart fix --apply` after build_runner. |
+| build_runner fails | Run `scripts/verify.sh` from the package root — it surfaces the first `[SEVERE]` line. |
+
+## Third-party packages: substitute `source/` for `lib/`
+
+When the user (or another AI) installs a new pub package, the package's README, examples, and any AI-generated setup instructions will all assume `lib/` is the source of truth. In a Solid project that assumption is wrong.
-**Same-package imports must be relative.** Inside a `source/` file, reference other source files via relative paths (`../controllers/foo.dart`), never via `package:/foo.dart`. The `package:` form resolves to `lib/` (the generated realm), pointing a source file at the lowered Signal types — and the generator now rejects it at build time. Cross-package imports (`flutter`, `solid_annotations`, `provider`, third-party) keep the `package:` form as usual.
+**Rule of thumb**: the package itself stays in `pubspec.yaml` as the docs describe. Only the *files you write that import it* move from `lib/` to `source/`. Examples:
+
+- `go_router` README says "create `lib/router.dart`" → create `source/router.dart` instead, and import it from `source/main.dart` via a relative path.
+- `freezed` says "create `lib/models/user.dart`" → create `source/models/user.dart`. Freezed's own `*.freezed.dart` generated output still lands wherever `build.yaml` puts it (typically next to the source file under `lib/`, since freezed reads from `lib/`); **but** in a Solid project you want freezed to read from `source/` too. Add `source/**` to freezed's `build.yaml` `sources` list (Solid's setup already does this — keep it).
+- `riverpod` generator says "create `lib/providers/...`" → create `source/providers/...`.
+- `drift` says "create `lib/database.dart`" → create `source/database.dart`.
+
+For the full list and per-package gotchas, read `references/third-party-packages.md`.
+
+**The key thing to tell yourself**: "the docs say `lib/`. In this project, that means `source/`."
+
+## Same-package imports must be relative
+
+Inside a `source/` file, reference other source files via relative paths (`../controllers/foo.dart`), never via `package:/foo.dart`. The `package:` form resolves to `lib/` (the *generated* realm), pointing your source file at lowered Signal types — the generator now rejects it at build time. Cross-package imports (`flutter`, `solid_annotations`, `provider`, third-party) keep the `package:` form as usual.
## Annotation cheat sheet
Each annotation goes on a class member of a `StatelessWidget` (or any class — `@SolidEnvironment` also works in `State`). The generator turns the widget into a `StatefulWidget` under the hood.
- **`@SolidState()`** — reactive state. Docs: .
- - Valid on: instance field with initializer, `late` non-nullable instance field, instance getter (derived state).
+ - Valid on: instance field with initializer, `late` non-nullable instance field, nullable instance field, instance getter (derived state).
- Invalid: `final`, `const`, `static`, setter, method, top-level.
- - Example: `@SolidState() int counter = 0;` or `@SolidState() int get doubleCounter => counter * 2;`
+ - Example: `@SolidState() int counter = 0;` or `@SolidState() int get doubleCounter => counter * 2;`.
- **`@SolidEffect()`** — side effect that re-runs whenever its tracked dependencies change. Docs: .
- Valid on: instance method returning `void`.
- - Example: `@SolidEffect() void logCounter() { print('Counter: $counter'); }`
+ - Example: `@SolidEffect() void logCounter() { print('Counter: $counter'); }`.
- **`@SolidQuery()`** — reactive async/stream resource. Docs: .
- Valid on: instance method returning `Future` or `Stream`. **No parameters.**
- Call site `fetchData()` returns a `Resource` exposing `.when(ready:, loading:, error:)`, `.maybeWhen(...)`, `.isRefreshing`, `.refresh()`.
- Options: `debounce: Duration(...)`, `useRefreshing: false`.
- - Example: `@SolidQuery() Future fetchData() async { ... }` then `fetchData().when(ready: Text.new, loading: CircularProgressIndicator.new, error: (e, _) => Text('$e'))`.
+ - Read `@SolidState` fields from the body to make the query react to them.
-- **`@SolidEnvironment()`** — inject a value from the widget tree (SwiftUI `@Environment`-style). Docs: .
+- **`@SolidEnvironment()`** — inject a value from the widget tree (SwiftUI-style). Docs: .
- Valid on: `late` field on a `StatelessWidget` or `State`.
- - Bound on first access to the nearest ancestor `Provider`. Reactive — `@SolidState` fields on the injected type stay reactive.
- - Provide via `.environment()` extension or `Provider` from `package:provider`.
- - Example: `@SolidEnvironment() late Counter counter;` then `child: CounterDisplay().environment((_) => Counter())`.
+ - Bound on first access to the nearest ancestor `Provider`.
+ - Provide via `.environment()` extension shipped by `solid_annotations`, or `Provider` from `package:provider`.
+
+For full target rules per annotation, read `references/annotation-contract.md`. For canonical idioms, read `references/patterns.md`.
## Untracked reads
-By default every read of a `@SolidState` field inside `build`, `@SolidEffect`, or `@SolidQuery` registers a dependency on the surrounding computation — `build` re-renders the read site, effects re-fire, queries re-execute. Two ways to read without registering a dependency:
+By default every read of a `@SolidState` field inside `build`, `@SolidEffect`, or `@SolidQuery` registers a dependency. Two opt-outs:
+
+- **Automatic**: reads inside callback parameters whose name starts with `on` (`onPressed`, `onTap`, `onChanged`, …) are untracked — Solid recognizes user-interaction handlers and doesn't subscribe.
+- **Manual**: append `.untracked` to the field. Common use: `key: ValueKey(counter.untracked)`, or reading the same signal you're writing to inside an effect to avoid a self-dependency loop.
-- **Automatic**: reads inside callback parameters whose name starts with `on` (`onPressed`, `onTap`, `onChanged`, …) are untracked — Solid treats them as gesture handlers. You don't write anything special.
-- **Manual**: append `.untracked` to the field for a one-off untracked read. Common case is `key: ValueKey(counter.untracked)` (build the key once, don't rebuild on later changes), or reading the same signal you're writing to inside an effect to avoid a self-dependency loop: `history = [...history.untracked, counter];`.
+In string interpolations, only the long form works: `'${counter.untracked}'`. The short form `'$counter.untracked'` parses as `${counter}` followed by a literal suffix (still tracked).
-In string interpolations use the long form `'${counter.untracked}'` — the short form `'$counter.untracked'` parses as `${counter}` followed by a literal suffix (still tracked). Docs: .
+Docs: .
-For full target rules (every valid/invalid form with rationale) see `references/annotation-contract.md`. For canonical idioms see `references/patterns.md`. For error messages and fixes see `references/troubleshooting.md`.
+## Setup checklist (fresh project)
-## Setup checklist
+If `pubspec.yaml` doesn't yet declare Solid, install it:
1. `flutter pub add solid_annotations flutter_solidart provider`
2. `dart pub add --dev solid_generator build_runner`
-3. Add `source/**` to `build.yaml`:
+3. Create `build.yaml` at the project root (or extend the existing one):
```yaml
targets:
$default:
@@ -62,32 +130,46 @@ For full target rules (every valid/invalid form with rationale) see `references/
- lib/**
- $package$
```
-4. In `source/main.dart`, set `SolidartConfig.autoDispose = false` before `runApp(...)`. (Temporary — will become the default in a future `flutter_solidart` major release.)
-5. Run `dart run build_runner watch --delete-conflicting-outputs` during development.
+4. In `source/main.dart`, set `SolidartConfig.autoDispose = false;` before `runApp(...)`. (Temporary — will become the default in a future `flutter_solidart` major release.)
+5. In `analysis_options.yaml`, add `analyzer.errors.must_be_immutable: ignore` (your source widgets are mutable; the generated ones are immutable).
+6. Run `dart run build_runner watch --delete-conflicting-outputs` during development.
+
+## Verify your changes
+
+After writing or editing `source/`, regenerate `lib/` and apply lint fixes:
+
+- **`scripts/verify.sh`** — run from any package root. Runs `dart run build_runner build --delete-conflicting-outputs`, then `dart fix --apply` on the package (adds `const`, removes unused imports, applies relative-import lints). Prints PASS/FAIL plus the first `[SEVERE]` error on failure. Exit code reflects build_runner success; `dart fix` failure is non-fatal.
+
+Why `dart fix --apply` matters: the generator prioritises correct, runnable code over polish. The emitted `lib/` may miss `const` opportunities, leave unused imports, or pick a non-preferred import form. `dart fix --apply` cleans this up using the project's lint rules (`prefer_const_constructors`, `unnecessary_import`, `prefer_relative_imports`, …). Always run it after generation — in CI too.
## Hot reload
-`flutter run` does not auto-reload when `build_runner` rewrites `lib/`. Two workflows:
-- Press `r` in the `flutter run` terminal after `build_runner` emits.
-- Use [`dashmonx`](https://pub.dev/packages/dashmonx) — it wraps `flutter run` and triggers reload on `lib/` changes.
+`flutter run` does not auto-reload when build_runner rewrites `lib/` (no IDE save event fires for filesystem changes). Two workflows:
+
+- Press `r` in the `flutter run` terminal after build_runner emits.
+- Use [`dashmonx`](https://pub.dev/packages/dashmonx) — wraps `flutter run` and triggers hot reload on `lib/` changes. Any `flutter run` flag passes through, e.g. `dashmonx -d chrome`.
## Common mistakes
-- Editing `lib/` files. They are regenerated; your edits are lost.
+- Writing to a file under `lib/`. Always under `source/`. (The single hardest rule to internalise.)
+- Following a pub-package README literally when it says `lib/`. Substitute `source/`. See `references/third-party-packages.md`.
- Adding `final` or `static` to a `@SolidState` field. The generator rejects it.
-- Forgetting `SolidartConfig.autoDispose = false` — atoms are not disposed and tests / long sessions leak.
-- Giving `@SolidQuery` parameters. Use `@SolidState` fields as inputs instead — the query re-runs when they change.
-- Expecting `flutter run` to pick up `build_runner` output without `r` or `dashmonx`.
-- Treating `must_be_immutable` lint as a real error. The widgets you write are mutable; the generated ones are immutable. Set `must_be_immutable: ignore` in `analysis_options.yaml`.
+- Giving `@SolidQuery` parameters. Use `@SolidState` fields as inputs — the query re-runs when they change.
+- Forgetting `SolidartConfig.autoDispose = false` in `source/main.dart` — atoms leak in tests and long sessions.
+- Importing same-package files via `package:/...` from inside `source/`. Use relative paths.
+- Expecting `flutter run` to pick up build_runner output without `r` or `dashmonx`.
+- Treating `must_be_immutable` lint as a real error — your widgets are mutable; the generated ones are immutable. Set `must_be_immutable: ignore`.
## Helper scripts
-The skill ships two scripts under `scripts/`:
-
-- **`scripts/verify.sh`** — run from any package root. Runs `dart run build_runner build --delete-conflicting-outputs`, prints PASS/FAIL plus the first error. Exit code reflects success.
-- **`scripts/scaffold-widget.sh [--state|--query|--env]`** — writes a starter `source/.dart` with the right boilerplate for the chosen annotation. Refuses to overwrite existing files.
+- **`scripts/verify.sh`** — described above.
+- **`scripts/scaffold-widget.sh [--state|--query|--env]`** — writes a starter `source/.dart` with the right boilerplate. Refuses to overwrite.
## Where to read more
- Canonical docs:
-- Working example on GitHub:
+- Annotation valid/invalid targets: `references/annotation-contract.md`
+- Canonical idioms (counter, computed, effect, query, environment, untracked): `references/patterns.md`
+- Error symptom → cause → fix: `references/troubleshooting.md`
+- Third-party package redirect catalogue: `references/third-party-packages.md`
+- Working example:
diff --git a/skills/solid/assets/AGENTS.md b/skills/solid/assets/AGENTS.md
new file mode 100644
index 0000000..64418c1
--- /dev/null
+++ b/skills/solid/assets/AGENTS.md
@@ -0,0 +1,91 @@
+# AGENTS.md — Solid (Flutter) project
+
+> Copy this file to the root of your Flutter app when you install Solid.
+> Most AI coding tools (Claude Code, Cursor, Codex, GitHub Copilot, Amp, etc.) auto-load `AGENTS.md` at session start.
+> Tools that look for `CLAUDE.md` instead can symlink: `ln -s AGENTS.md CLAUDE.md`.
+
+This Flutter app uses [Solid](https://solid.mariuti.com), a reactive framework built on `flutter_solidart`. Flutter conventions are **inverted** in this project. Read this file carefully before editing.
+
+## Cardinal rule
+
+**Edit `source/.dart`. Never edit `lib/.dart`.**
+
+- `source/` is the code you write.
+- `lib/` is regenerated by `dart run build_runner build` from `source/`. Any hand-edit to `lib/` is silently destroyed on the next build.
+- The app entry point is `source/main.dart`. `lib/main.dart` is generated.
+
+If you catch yourself about to write to `lib/`, stop and write to the matching `source/.dart` instead.
+
+## Reactive annotations
+
+You write a `StatelessWidget` with these annotations on members; the generator rewrites it into a `StatefulWidget` with `Signal`/`Computed`/`Effect`/`Resource` plumbing.
+
+- `@SolidState()` — reactive field, getter (derived state). **Not** valid on `final`, `static`, or methods.
+- `@SolidEffect()` — `void` method that re-runs when its tracked reads change.
+- `@SolidQuery()` — `Future` / `Stream` method. **No parameters** — read `@SolidState` fields from the body to make it react. Options: `debounce: Duration(...)` waits N after the last input change before re-running; `useRefreshing: true` (default) keeps the resource on its previous value while refetching with `.isRefreshing == true` for smoother UX, `useRefreshing: false` drops back to the `loading` state on every re-execution.
+- `@SolidEnvironment()` — `late` field bound to the nearest ancestor `Provider`. **`late` is required.**
+- `.untracked` — read a `@SolidState` field without registering a dependency. In string interpolation, only `'${x.untracked}'` works (not `'$x.untracked'`).
+
+## Same-package imports inside `source/` must be relative
+
+`import 'package:/foo.dart'` inside a `source/` file resolves to `lib/` (the generated realm) and is rejected by the generator. Use relative paths: `import '../path/to/foo.dart'`.
+
+Cross-package imports (`package:flutter/...`, `package:solid_annotations/...`, `package:provider/...`, third-party) stay normal.
+
+## Third-party packages
+
+When you install a pub package whose docs say "create `lib/.dart`" (go_router, freezed, riverpod, drift, json_serializable, get_it, flutter_bloc, …), substitute `source/.dart`. The package itself stays in `pubspec.yaml` as the docs describe; only the files **you write that import it** move from `lib/` to `source/`. If the package is itself a code generator, ensure `source/**` is in `build.yaml` `sources` (Solid's setup checklist puts it there).
+
+## Workflow
+
+After editing anything under `source/`:
+
+```bash
+dart run build_runner build --delete-conflicting-outputs
+dart fix --apply
+```
+
+The first regenerates `lib/`. The second cleans up `const` placement, unused imports, and prefers relative imports. Both are mandatory; the [Solid skill](https://github.com/nank1ro/solid/tree/main/skills/solid) ships a `scripts/verify.sh` that chains them.
+
+`flutter run` doesn't auto-pick-up build_runner output — press `r` in the terminal after the generator emits, or use [`dashmonx`](https://pub.dev/packages/dashmonx).
+
+## Required `analysis_options.yaml` rules
+
+```yaml
+analyzer:
+ errors:
+ must_be_immutable: ignore # source/ widgets are mutable by design
+linter:
+ rules:
+ public_member_api_docs: false
+ always_use_package_imports: false
+ prefer_relative_imports: true
+```
+
+## Required `source/main.dart` setup
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false; // Solid manages disposal manually
+ runApp(/* ... */);
+}
+```
+
+## Common bugs you might be asked about
+
+| Symptom | Diagnosis |
+| --- | --- |
+| "my edits to `lib/.dart` keep disappearing" | They are. Move the change to `source/.dart` and regenerate. |
+| build_runner: "requires assignable target" on `@SolidState() final int x = 0` | Drop `final`. `@SolidState` rewrites reads through a setter; the field must be assignable. |
+| `@SolidQuery()` rejected with parameter list | Queries cannot have parameters. Move inputs to `@SolidState` fields. |
+| `@SolidEnvironment` field is null at first read | Mark it `late`. The lookup is lazy and `late` defers initialization. |
+| `must_be_immutable` lint everywhere | Add `must_be_immutable: ignore` to `analysis_options.yaml`. |
+
+## Where to read more
+
+- User docs:
+- Skill for AI assistants (deeper guidance, scripts, evals):
+- LLM-friendly full docs (for tools that fetch over HTTP):
diff --git a/skills/solid/evals/evals.json b/skills/solid/evals/evals.json
new file mode 100644
index 0000000..7589830
--- /dev/null
+++ b/skills/solid/evals/evals.json
@@ -0,0 +1,86 @@
+{
+ "skill_name": "solid",
+ "evals": [
+ {
+ "id": 1,
+ "prompt": "Add a counter widget to my Flutter app — tap + and the number bumps. Make it a fresh page.",
+ "expected_output": "A new widget file created under source/counter.dart (or source/counter_page.dart) using @SolidState for the counter field. No lib/ files created or edited by the agent. The user is told to run build_runner (or scripts/verify.sh) afterward.",
+ "files": [
+ "evals/files/eval-1/pubspec.yaml",
+ "evals/files/eval-1/build.yaml",
+ "evals/files/eval-1/analysis_options.yaml",
+ "evals/files/eval-1/source/main.dart"
+ ],
+ "expectations": [
+ "The agent created a new file under source/ (path matches source/*.dart) — not under lib/.",
+ "The new file uses @SolidState on at least one mutable field (e.g. `@SolidState() int counter = 0;`).",
+ "The new file imports 'package:solid_annotations/solid_annotations.dart'.",
+ "The new file does NOT define an empty `void dispose() {}` override on the StatelessWidget (the generator synthesizes dispose for reactive fields).",
+ "No file under lib/ was created or modified by the agent.",
+ "The agent recommends running `dart run build_runner build` (or `scripts/verify.sh`, or `build_runner watch`) so the new widget is transpiled into lib/."
+ ]
+ },
+ {
+ "id": 2,
+ "prompt": "Fetch posts whenever the selected userId changes. Debounce 500ms.",
+ "expected_output": "A widget (or modification to an existing widget) that has a @SolidState field for the userId and a @SolidQuery method (no parameters) that fetches based on that field, with debounce: Duration(milliseconds: 500). Rendered with .when(ready:, loading:, error:). All edits under source/, none under lib/.",
+ "files": [
+ "evals/files/eval-2/pubspec.yaml",
+ "evals/files/eval-2/build.yaml",
+ "evals/files/eval-2/analysis_options.yaml",
+ "evals/files/eval-2/source/main.dart",
+ "evals/files/eval-2/source/posts_page.dart"
+ ],
+ "expectations": [
+ "The agent extends source/posts_page.dart (or replaces it) so the displayed posts react to the dropdown's selected userId.",
+ "The widget declares a @SolidState field (typically String? userId or similar) that the query reads from its body.",
+ "The query method is annotated @SolidQuery(...) with debounce: Duration(milliseconds: 500).",
+ "The @SolidQuery method takes no parameters (the body reads @SolidState fields directly).",
+ "The query is rendered with a `.when(ready:, loading:, error:)` block (or `.maybeWhen`).",
+ "No lib/ file was created or modified by the agent."
+ ]
+ },
+ {
+ "id": 3,
+ "prompt": "I keep editing lib/counter.dart to change the AppBar title from 'Counter' to 'My Counter' but the change keeps disappearing on save. What's going on, and how do I fix it?",
+ "expected_output": "The agent diagnoses that lib/counter.dart is generated from source/counter.dart and explains why edits to lib/ are overwritten. The agent then makes the change in source/counter.dart (changing the AppBar title string from 'Counter' to 'My Counter') and tells the user to re-run build_runner. The agent must NOT edit lib/counter.dart.",
+ "files": [
+ "evals/files/eval-3/pubspec.yaml",
+ "evals/files/eval-3/build.yaml",
+ "evals/files/eval-3/analysis_options.yaml",
+ "evals/files/eval-3/source/main.dart",
+ "evals/files/eval-3/source/counter.dart",
+ "evals/files/eval-3/lib/main.dart",
+ "evals/files/eval-3/lib/counter.dart"
+ ],
+ "expectations": [
+ "The agent explains that lib/ is generated from source/ and that build_runner overwrites lib/ edits — using language the user can act on, not jargon.",
+ "The agent edits source/counter.dart to change the AppBar title from 'Counter' to 'My Counter'.",
+ "The agent does NOT modify or write to lib/counter.dart.",
+ "The agent recommends re-running `dart run build_runner build` (or `scripts/verify.sh`) to regenerate lib/counter.dart from the updated source."
+ ]
+ },
+ {
+ "id": 4,
+ "prompt": "Add go_router and set up two routes: `/` → HomePage, `/settings` → SettingsPage. Keep my existing app structure.",
+ "expected_output": "go_router added to pubspec.yaml. A new source/router.dart created with the GoRouter configuration referencing the existing source/home_page.dart and source/settings_page.dart. source/main.dart edited to use MaterialApp.router with the new router config. NO lib/ files created or edited by the agent — even though the go_router README would normally direct the agent to create lib/router.dart.",
+ "files": [
+ "evals/files/eval-4/pubspec.yaml",
+ "evals/files/eval-4/build.yaml",
+ "evals/files/eval-4/analysis_options.yaml",
+ "evals/files/eval-4/source/main.dart",
+ "evals/files/eval-4/source/home_page.dart",
+ "evals/files/eval-4/source/settings_page.dart"
+ ],
+ "expectations": [
+ "go_router was added as a dependency in pubspec.yaml.",
+ "A new file `source/router.dart` (or similar under source/) exists and contains a `GoRouter` configuration declaring the `/` and `/settings` routes that point to the existing HomePage and SettingsPage classes.",
+ "source/main.dart was edited to use `MaterialApp.router` (or equivalent) wired to the GoRouter config.",
+ "Any imports between source/ files use relative paths (e.g. `import 'router.dart';`), NOT `package:/router.dart`.",
+ "The agent did NOT recreate or overwrite source/home_page.dart or source/settings_page.dart — they already existed.",
+ "No file under lib/ was created or modified by the agent — even though go_router's README normally instructs creating lib/router.dart.",
+ "The agent recommends running build_runner (or `scripts/verify.sh`) after the source/ edits so lib/ is regenerated."
+ ]
+ }
+ ]
+}
diff --git a/skills/solid/evals/files/eval-1/analysis_options.yaml b/skills/solid/evals/files/eval-1/analysis_options.yaml
new file mode 100644
index 0000000..1659294
--- /dev/null
+++ b/skills/solid/evals/files/eval-1/analysis_options.yaml
@@ -0,0 +1,9 @@
+include: package:flutter_lints/flutter.yaml
+analyzer:
+ errors:
+ must_be_immutable: ignore
+linter:
+ rules:
+ public_member_api_docs: false
+ always_use_package_imports: false
+ prefer_relative_imports: true
diff --git a/skills/solid/evals/files/eval-1/build.yaml b/skills/solid/evals/files/eval-1/build.yaml
new file mode 100644
index 0000000..673bd1d
--- /dev/null
+++ b/skills/solid/evals/files/eval-1/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ sources:
+ - source/**
+ - lib/**
+ - $package$
diff --git a/skills/solid/evals/files/eval-1/pubspec.yaml b/skills/solid/evals/files/eval-1/pubspec.yaml
new file mode 100644
index 0000000..ab77fbb
--- /dev/null
+++ b/skills/solid/evals/files/eval-1/pubspec.yaml
@@ -0,0 +1,23 @@
+name: my_app
+description: Sample Flutter app using Solid.
+publish_to: none
+version: 0.1.0
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_solidart: ^2.0.0
+ solid_annotations: ^2.0.0
+ provider: ^6.1.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ build_runner: ^2.4.0
+ solid_generator: ^2.0.0
+
+flutter:
+ uses-material-design: true
diff --git a/skills/solid/evals/files/eval-1/source/main.dart b/skills/solid/evals/files/eval-1/source/main.dart
new file mode 100644
index 0000000..b593477
--- /dev/null
+++ b/skills/solid/evals/files/eval-1/source/main.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false;
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'My App',
+ home: Scaffold(
+ appBar: AppBar(title: const Text('My App')),
+ body: const Center(child: Text('Hello, world!')),
+ ),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-2/analysis_options.yaml b/skills/solid/evals/files/eval-2/analysis_options.yaml
new file mode 100644
index 0000000..1659294
--- /dev/null
+++ b/skills/solid/evals/files/eval-2/analysis_options.yaml
@@ -0,0 +1,9 @@
+include: package:flutter_lints/flutter.yaml
+analyzer:
+ errors:
+ must_be_immutable: ignore
+linter:
+ rules:
+ public_member_api_docs: false
+ always_use_package_imports: false
+ prefer_relative_imports: true
diff --git a/skills/solid/evals/files/eval-2/build.yaml b/skills/solid/evals/files/eval-2/build.yaml
new file mode 100644
index 0000000..673bd1d
--- /dev/null
+++ b/skills/solid/evals/files/eval-2/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ sources:
+ - source/**
+ - lib/**
+ - $package$
diff --git a/skills/solid/evals/files/eval-2/pubspec.yaml b/skills/solid/evals/files/eval-2/pubspec.yaml
new file mode 100644
index 0000000..ab77fbb
--- /dev/null
+++ b/skills/solid/evals/files/eval-2/pubspec.yaml
@@ -0,0 +1,23 @@
+name: my_app
+description: Sample Flutter app using Solid.
+publish_to: none
+version: 0.1.0
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_solidart: ^2.0.0
+ solid_annotations: ^2.0.0
+ provider: ^6.1.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ build_runner: ^2.4.0
+ solid_generator: ^2.0.0
+
+flutter:
+ uses-material-design: true
diff --git a/skills/solid/evals/files/eval-2/source/main.dart b/skills/solid/evals/files/eval-2/source/main.dart
new file mode 100644
index 0000000..101ce34
--- /dev/null
+++ b/skills/solid/evals/files/eval-2/source/main.dart
@@ -0,0 +1,18 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+import 'posts_page.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false;
+ runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(title: 'My App', home: PostsPage());
+ }
+}
diff --git a/skills/solid/evals/files/eval-2/source/posts_page.dart b/skills/solid/evals/files/eval-2/source/posts_page.dart
new file mode 100644
index 0000000..17aaaf8
--- /dev/null
+++ b/skills/solid/evals/files/eval-2/source/posts_page.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/material.dart';
+
+/// Placeholder posts page. The user picks a `userId` via the dropdown,
+/// then the body is supposed to show that user's posts — but right now
+/// it just shows a static "No user selected" message.
+///
+/// Extend this widget so that picking a user fetches and displays their
+/// posts. Debounce 500ms.
+class PostsPage extends StatelessWidget {
+ PostsPage({super.key});
+
+ // Available users for the dropdown. Hard-coded for now.
+ static const List userIds = ['alice', 'bob', 'charlie'];
+
+ String? selectedUserId;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Posts')),
+ body: Column(
+ children: [
+ DropdownButton(
+ value: selectedUserId,
+ hint: const Text('Pick a user'),
+ items: userIds
+ .map((id) => DropdownMenuItem(value: id, child: Text(id)))
+ .toList(),
+ onChanged: (id) => selectedUserId = id,
+ ),
+ const Expanded(child: Center(child: Text('No user selected'))),
+ ],
+ ),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-3/analysis_options.yaml b/skills/solid/evals/files/eval-3/analysis_options.yaml
new file mode 100644
index 0000000..1659294
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/analysis_options.yaml
@@ -0,0 +1,9 @@
+include: package:flutter_lints/flutter.yaml
+analyzer:
+ errors:
+ must_be_immutable: ignore
+linter:
+ rules:
+ public_member_api_docs: false
+ always_use_package_imports: false
+ prefer_relative_imports: true
diff --git a/skills/solid/evals/files/eval-3/build.yaml b/skills/solid/evals/files/eval-3/build.yaml
new file mode 100644
index 0000000..673bd1d
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ sources:
+ - source/**
+ - lib/**
+ - $package$
diff --git a/skills/solid/evals/files/eval-3/lib/counter.dart b/skills/solid/evals/files/eval-3/lib/counter.dart
new file mode 100644
index 0000000..87984d5
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/lib/counter.dart
@@ -0,0 +1,41 @@
+// GENERATED FILE — DO NOT EDIT BY HAND.
+// Source: source/counter.dart
+// The user attempted to change the AppBar title from 'Counter' to 'My Counter'
+// directly in this file. build_runner's next run will overwrite their change.
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+class CounterPage extends StatefulWidget {
+ const CounterPage({super.key});
+
+ @override
+ State createState() => _CounterPageState();
+}
+
+class _CounterPageState extends State {
+ final counter = Signal(0, name: 'counter');
+
+ @override
+ void dispose() {
+ counter.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('My Counter'),
+ ), // user's mis-edit, will be overwritten
+ body: Center(
+ child: SignalBuilder(
+ builder: (context, _) => Text('Counter is ${counter.value}'),
+ ),
+ ),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () => counter.value++,
+ child: const Icon(Icons.add),
+ ),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-3/lib/main.dart b/skills/solid/evals/files/eval-3/lib/main.dart
new file mode 100644
index 0000000..8026c8b
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/lib/main.dart
@@ -0,0 +1,24 @@
+// GENERATED FILE — DO NOT EDIT BY HAND.
+// Source: source/main.dart
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false;
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'My App',
+ home: Scaffold(
+ appBar: AppBar(title: const Text('My App')),
+ body: const Center(child: Text('Hello, world!')),
+ ),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-3/pubspec.yaml b/skills/solid/evals/files/eval-3/pubspec.yaml
new file mode 100644
index 0000000..ab77fbb
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/pubspec.yaml
@@ -0,0 +1,23 @@
+name: my_app
+description: Sample Flutter app using Solid.
+publish_to: none
+version: 0.1.0
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_solidart: ^2.0.0
+ solid_annotations: ^2.0.0
+ provider: ^6.1.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ build_runner: ^2.4.0
+ solid_generator: ^2.0.0
+
+flutter:
+ uses-material-design: true
diff --git a/skills/solid/evals/files/eval-3/source/counter.dart b/skills/solid/evals/files/eval-3/source/counter.dart
new file mode 100644
index 0000000..55f4462
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/source/counter.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+import 'package:solid_annotations/solid_annotations.dart';
+
+class CounterPage extends StatelessWidget {
+ CounterPage({super.key});
+
+ @SolidState()
+ int counter = 0;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Counter')),
+ body: Center(child: Text('Counter is $counter')),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () => counter++,
+ child: const Icon(Icons.add),
+ ),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-3/source/main.dart b/skills/solid/evals/files/eval-3/source/main.dart
new file mode 100644
index 0000000..b593477
--- /dev/null
+++ b/skills/solid/evals/files/eval-3/source/main.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false;
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'My App',
+ home: Scaffold(
+ appBar: AppBar(title: const Text('My App')),
+ body: const Center(child: Text('Hello, world!')),
+ ),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-4/analysis_options.yaml b/skills/solid/evals/files/eval-4/analysis_options.yaml
new file mode 100644
index 0000000..1659294
--- /dev/null
+++ b/skills/solid/evals/files/eval-4/analysis_options.yaml
@@ -0,0 +1,9 @@
+include: package:flutter_lints/flutter.yaml
+analyzer:
+ errors:
+ must_be_immutable: ignore
+linter:
+ rules:
+ public_member_api_docs: false
+ always_use_package_imports: false
+ prefer_relative_imports: true
diff --git a/skills/solid/evals/files/eval-4/build.yaml b/skills/solid/evals/files/eval-4/build.yaml
new file mode 100644
index 0000000..673bd1d
--- /dev/null
+++ b/skills/solid/evals/files/eval-4/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ sources:
+ - source/**
+ - lib/**
+ - $package$
diff --git a/skills/solid/evals/files/eval-4/pubspec.yaml b/skills/solid/evals/files/eval-4/pubspec.yaml
new file mode 100644
index 0000000..ab77fbb
--- /dev/null
+++ b/skills/solid/evals/files/eval-4/pubspec.yaml
@@ -0,0 +1,23 @@
+name: my_app
+description: Sample Flutter app using Solid.
+publish_to: none
+version: 0.1.0
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_solidart: ^2.0.0
+ solid_annotations: ^2.0.0
+ provider: ^6.1.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ build_runner: ^2.4.0
+ solid_generator: ^2.0.0
+
+flutter:
+ uses-material-design: true
diff --git a/skills/solid/evals/files/eval-4/source/home_page.dart b/skills/solid/evals/files/eval-4/source/home_page.dart
new file mode 100644
index 0000000..7d43d88
--- /dev/null
+++ b/skills/solid/evals/files/eval-4/source/home_page.dart
@@ -0,0 +1,13 @@
+import 'package:flutter/material.dart';
+
+class HomePage extends StatelessWidget {
+ const HomePage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Home')),
+ body: const Center(child: Text('Welcome home')),
+ );
+ }
+}
diff --git a/skills/solid/evals/files/eval-4/source/main.dart b/skills/solid/evals/files/eval-4/source/main.dart
new file mode 100644
index 0000000..632b2c2
--- /dev/null
+++ b/skills/solid/evals/files/eval-4/source/main.dart
@@ -0,0 +1,18 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+import 'home_page.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false;
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(title: 'My App', home: HomePage());
+ }
+}
diff --git a/skills/solid/evals/files/eval-4/source/settings_page.dart b/skills/solid/evals/files/eval-4/source/settings_page.dart
new file mode 100644
index 0000000..b3ec9b6
--- /dev/null
+++ b/skills/solid/evals/files/eval-4/source/settings_page.dart
@@ -0,0 +1,13 @@
+import 'package:flutter/material.dart';
+
+class SettingsPage extends StatelessWidget {
+ const SettingsPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Settings')),
+ body: const Center(child: Text('Settings go here')),
+ );
+ }
+}
diff --git a/skills/solid/evals/trigger_queries.json b/skills/solid/evals/trigger_queries.json
new file mode 100644
index 0000000..d2bfd1c
--- /dev/null
+++ b/skills/solid/evals/trigger_queries.json
@@ -0,0 +1,82 @@
+[
+ {
+ "query": "In my flutter app i need to add a counter widget — tap + and it counts up. I have solid_annotations in my pubspec.yaml. give me a fresh page for it",
+ "should_trigger": true
+ },
+ {
+ "query": "ok so im trying to fetch user posts whenever the selected user id changes, with a 500ms debounce so we dont hammer the api. this is the flutter app where i'm already using @SolidQuery elsewhere",
+ "should_trigger": true
+ },
+ {
+ "query": "weird bug — i keep editing lib/counter.dart to change a string and it keeps reverting after a few seconds. why?? my pubspec has build_runner and solid_generator listed",
+ "should_trigger": true
+ },
+ {
+ "query": "i want to add go_router to this flutter app and set up 2 routes (/ and /settings). just looked at pubspec and we already use solid_annotations so i'm not sure where router.dart should go",
+ "should_trigger": true
+ },
+ {
+ "query": "Add a SolidEffect that prints to console whenever the counter changes. The widget is in source/counter_page.dart and the counter is an @SolidState int field",
+ "should_trigger": true
+ },
+ {
+ "query": "the build_runner is failing with 'requires assignable target' on my @SolidState field. heres the snippet: @SolidState() final int counter = 0;",
+ "should_trigger": true
+ },
+ {
+ "query": "Make this widget reactive — when toggle is true the colour changes, when it's false the colour reverts. the file is at source/colour_picker.dart in a flutter project with solid_generator in dev_deps",
+ "should_trigger": true
+ },
+ {
+ "query": "scaffold a new page that fetches `/api/profile` and shows a loading spinner while it resolves. flutter project, solid_annotations is in pubspec already",
+ "should_trigger": true
+ },
+ {
+ "query": "I want to inject an AuthService into a bunch of widgets via @SolidEnvironment. how do I provide it from main()? this is a Flutter app already set up with the Solid build_runner",
+ "should_trigger": true
+ },
+ {
+ "query": "Help me wire up freezed in this flutter project — i need a User model with email, name, age. pubspec already has solid_generator so i'm not sure if i write the model under source/ or lib/",
+ "should_trigger": true
+ },
+ {
+ "query": "fix this Solid.js component — when the signal changes the JSX doesn't re-render properly. it's a vite + typescript project",
+ "should_trigger": false
+ },
+ {
+ "query": "explain how Riverpod scoping works in flutter. when does a Provider get re-created? this is a pure riverpod app, no codegen",
+ "should_trigger": false
+ },
+ {
+ "query": "im setting up GitHub Actions for my Flutter project. how do i make `flutter test` run on PRs? pubspec is a vanilla flutter app, no solid",
+ "should_trigger": false
+ },
+ {
+ "query": "json_serializable is throwing 'no JsonKey for field email'. where do i put the @JsonKey annotation? this is a standard flutter project, no solid_annotations",
+ "should_trigger": false
+ },
+ {
+ "query": "in vanilla flutter, when should I use ChangeNotifier vs ValueListenable for state management? we're not using any codegen libs",
+ "should_trigger": false
+ },
+ {
+ "query": "what's the difference between StatefulWidget and StatelessWidget? im new to flutter and starting a fresh project, pubspec is empty besides flutter",
+ "should_trigger": false
+ },
+ {
+ "query": "convert this React class component to React hooks. its a Next.js page using class component lifecycle methods",
+ "should_trigger": false
+ },
+ {
+ "query": "explain how to use setState properly to avoid extra rebuilds in flutter. our codebase is standard flutter, no codegen libs in pubspec",
+ "should_trigger": false
+ },
+ {
+ "query": "I keep getting 'Provider not found' in my flutter app — pubspec has `provider: ^6.0` and i wrapped in Provider. what am i missing? no other state libs",
+ "should_trigger": false
+ },
+ {
+ "query": "configure my flutter project's launch.json in VS Code to start with --device web-server. flutter app, no solid packages installed",
+ "should_trigger": false
+ }
+]
diff --git a/skills/solid/references/annotation-contract.md b/skills/solid/references/annotation-contract.md
index 469d22d..00eb002 100644
--- a/skills/solid/references/annotation-contract.md
+++ b/skills/solid/references/annotation-contract.md
@@ -1,6 +1,6 @@
# Solid annotation contract
-Per-annotation valid/invalid targets. Distilled from the user docs at .
+Per-annotation valid/invalid targets and rationale. Distilled from the user docs at .
## `@SolidState()` — reactive state
@@ -23,7 +23,7 @@ Docs: .
| `static` field | State is per-widget-instance, not per-class. |
| Setter | A `@SolidState` write goes through the generated setter; you don't write your own. |
| Method | Use `@SolidEffect` for side effects or `@SolidQuery` for async values. |
-| Top-level / library variable | Annotation only applies to class members. |
+| Top-level / library variable | The annotation only applies to class members. |
## `@SolidEffect()` — side effect
@@ -43,7 +43,7 @@ The body's reads of `@SolidState` fields are tracked automatically. The effect r
- Methods with parameters.
- Static or top-level functions.
-## `@SolidQuery()` — async/stream resource
+## `@SolidQuery()` — async / stream resource
Docs: .
@@ -60,7 +60,7 @@ Docs: .
- Return type must be `Future` or `Stream`.
- The call site `fetchData()` does **not** return a `Future`/`Stream` — it returns a `Resource` exposing:
- `.when(ready: ..., loading: ..., error: ...)`
- - `.maybeWhen(...orElse: ...)`
+ - `.maybeWhen(..., orElse: ...)`
- `.isRefreshing` (true while a re-execution is in flight)
- `.refresh()` to manually re-run
@@ -69,7 +69,7 @@ Docs: .
| Option | Effect |
| --- | --- |
| `debounce: Duration(...)` | Wait this long after the last input change before re-running. |
-| `useRefreshing` (default `true`) | On re-execution from a dependency change, the resource stays on the current value while refetching, and `.isRefreshing` becomes `true` (smoother UX, no loading flash). Pass `useRefreshing: false` to drop back into the `loading` state on each re-execution instead. |
+| `useRefreshing` (default `true`) | On re-execution from a dependency change, the resource stays on the current value while refetching, and `.isRefreshing` becomes `true` (smoother UX, no loading flash). Pass `useRefreshing: false` to drop back into the `loading` state on each re-execution. |
## `@SolidEnvironment()` — inject from widget tree
@@ -84,13 +84,13 @@ Docs: .
### Behavior
- Lookup is lazy: the field initializer runs the first time the field is read.
-- Resolves the nearest ancestor `Provider` where `T` is the field's declared type.
+- Resolves the nearest ancestor `Provider` where `T` is the declared type.
- Reading a `@SolidState` member of the injected instance stays reactive — fine-grained reactivity is preserved across the boundary.
- Works inside `build`, `@SolidEffect`, `@SolidQuery` bodies, or any other context.
### Providing the instance
-Two equivalent ways:
+Two equivalent forms:
```dart
// 1. .environment() extension shipped by solid_annotations
@@ -100,7 +100,7 @@ home: CounterDisplay().environment((_) => Counter()),
home: Provider(create: (_) => Counter(), child: CounterDisplay()),
```
-For multiple providers chain `.environment(...)` calls or use `MultiProvider` from `package:provider`.
+For multiple providers, chain `.environment(...)` calls or use `MultiProvider` from `package:provider`.
The type argument is inferred from the closure's return type. Pass it explicitly only when consumers should read by a supertype: `.environment((_) => RealAuthService())`.
@@ -121,7 +121,7 @@ extension UntrackedExtension on T {
}
```
-`counter.untracked` typechecks identically to `counter` and is a no-op at runtime. The generator detects the pattern at source level and rewrites it to the underlying `untrackedValue` primitive and excludes the read from the dependency set — `SignalBuilder` doesn't wrap it inside `build`, and an enclosing `@SolidEffect` / `@SolidQuery` won't re-fire on changes to it.
+`counter.untracked` typechecks identically to `counter` and is a no-op at runtime. The generator detects the pattern at source level and rewrites it to the underlying `untrackedValue` primitive, excluding the read from the dependency set — `SignalBuilder` doesn't wrap it inside `build`, and an enclosing `@SolidEffect` / `@SolidQuery` won't re-fire on changes to it.
### Two ways reads become untracked
@@ -132,9 +132,9 @@ extension UntrackedExtension on T {
### Hard rules
-- **String interpolation form**: only `'${counter.untracked}'` works. The short form `'$counter.untracked'` parses as `${counter}` followed by a literal `.untracked` suffix and is still a tracked read.
+- **String interpolation form**: only `'${counter.untracked}'` works. The short form `'$counter.untracked'` parses as `${counter}` followed by a literal `.untracked` suffix (still tracked).
- **Shadowing**: a local variable that shadows the field disables the rewrite for that scope (the analyzer's identifier resolution wins).
-- **No-op on non-reactive types**: applied to a non-`@SolidState` value the extension is identity at compile time *and* runtime — safe to leave in code that may or may not target a reactive field.
+- **No-op on non-reactive types**: applied to a non-`@SolidState` value, the extension is identity at compile time and at runtime — safe to leave in code that may or may not target a reactive field.
### When to reach for it
diff --git a/skills/solid/references/patterns.md b/skills/solid/references/patterns.md
index 5c7eb7b..e45cdb3 100644
--- a/skills/solid/references/patterns.md
+++ b/skills/solid/references/patterns.md
@@ -1,6 +1,6 @@
# Solid patterns
-Six canonical idioms, each with a complete `source/` snippet. All examples lifted from the user docs at .
+Canonical idioms, each with a complete `source/` snippet. All examples lifted from the user docs at .
## 1. Counter with `@SolidState` field
@@ -211,7 +211,7 @@ import 'package:provider/provider.dart';
home: Provider(create: (_) => Counter(), child: CounterDisplay()),
```
-For multiple providers chain `.environment(...)` calls or use `MultiProvider`:
+For multiple providers, chain `.environment(...)` calls or use `MultiProvider`:
```dart
HomePage()
@@ -251,8 +251,8 @@ List history = [];
@SolidEffect()
void recordHistory() {
- history = [...history.untracked, counter]; // counter is tracked, history is not
+ history = [...history.untracked, counter]; // counter tracked, history not
}
```
-Reads inside `on*` callback parameters (`onPressed`, `onTap`, `onChanged`, …) are auto-untracked — no `.untracked` needed. In string interpolations only the long form `'${counter.untracked}'` works; `'$counter.untracked'` is still tracked.
+Reads inside `on*` callback parameters (`onPressed`, `onTap`, `onChanged`, …) are auto-untracked — no `.untracked` needed. In string interpolations, only the long form `'${counter.untracked}'` works; `'$counter.untracked'` is still tracked.
diff --git a/skills/solid/references/third-party-packages.md b/skills/solid/references/third-party-packages.md
new file mode 100644
index 0000000..240548c
--- /dev/null
+++ b/skills/solid/references/third-party-packages.md
@@ -0,0 +1,116 @@
+# Third-party packages in a Solid project
+
+Every pub package's README, every Stack Overflow answer, every AI-generated setup instruction assumes `lib/` is where you write code. In a Solid project that assumption is inverted: `source/` is where you write code, `lib/` is generated.
+
+This reference catalogues the redirect pattern for common packages.
+
+## The general rule
+
+When a package's docs say:
+
+- *"Create `lib/.dart` and put this in it..."* → create `source/.dart` instead.
+- *"Register this in `lib/main.dart`..."* → edit `source/main.dart` instead.
+- *"Import via `package:/.dart`..."* → from inside `source/`, use a **relative** import (`../path/to/x.dart`). The `package:/...` form resolves to `lib/` (the generated realm) and the generator rejects it.
+
+The package itself stays in `pubspec.yaml` exactly as the docs describe. Only the *Dart files you write that import it* move from `lib/` to `source/`.
+
+## When a new code generator is added
+
+If the new package is itself a `build_runner` builder (freezed, json_serializable, drift, riverpod_generator, isar_generator, mockito with `@GenerateMocks`, …), it reads from `lib/` by default. To make it read from `source/` too, ensure your `build.yaml` `targets.$default.sources` includes `source/**`:
+
+```yaml
+targets:
+ $default:
+ sources:
+ - source/**
+ - lib/**
+ - $package$
+```
+
+The Solid setup already puts `source/**` first — keep it there and other generators will pick up your source files automatically.
+
+## Per-package gotchas
+
+### `go_router`
+
+The README says: *"Create `lib/router.dart` with your `GoRouter` config and import it from `lib/main.dart`."*
+
+In a Solid project:
+- Create `source/router.dart` with the `GoRouter` config.
+- Import it from `source/main.dart` via a relative path: `import 'router.dart';`.
+- No `lib/` files created or edited by you. build_runner produces `lib/router.dart` and `lib/main.dart` from your source.
+
+```dart title="source/router.dart"
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+import 'home_page.dart';
+import 'settings_page.dart';
+
+final appRouter = GoRouter(
+ routes: [
+ GoRoute(path: '/', builder: (_, __) => const HomePage()),
+ GoRoute(path: '/settings', builder: (_, __) => const SettingsPage()),
+ ],
+);
+```
+
+```dart title="source/main.dart"
+import 'package:flutter/material.dart';
+import 'package:flutter_solidart/flutter_solidart.dart';
+
+import 'router.dart';
+
+void main() {
+ SolidartConfig.autoDispose = false;
+ runApp(MaterialApp.router(routerConfig: appRouter));
+}
+```
+
+### `freezed`
+
+The README tells you to create `lib/models/user.dart` and run `build_runner`. In a Solid project:
+- Create `source/models/user.dart`.
+- Freezed generates `*.freezed.dart` siblings. Where those land depends on `build.yaml`. If `source/**` is in your sources list (Solid's setup ensures this), freezed reads your `source/models/user.dart` and emits `source/models/user.freezed.dart`. Solid then transpiles both `source/` files into `lib/`.
+- Inside `source/`, import the freezed-generated file via a relative `.freezed.dart` path: `part 'user.freezed.dart';`.
+
+### `json_serializable`
+
+Same redirect as freezed — create the annotated class under `source/`, let the generator produce the `.g.dart` sibling under `source/`, and Solid copies the whole thing to `lib/` on build.
+
+### `riverpod` / `flutter_riverpod` + `riverpod_generator`
+
+Riverpod docs say: *"Define providers in `lib/providers/`."* In Solid:
+- Create `source/providers/.dart`.
+- `riverpod_generator` emits `*.g.dart` siblings — same deal as freezed.
+- Providers read in widgets via `ref.watch(...)` work without modification, because by the time the code runs, it's the generated `lib/` version.
+
+That said: if you're using Riverpod, you may not need Solid at all (both are reactive state libraries). Mixing them in one project is unusual but supported — Solid handles the widget-local state, Riverpod handles app-global.
+
+### `drift`
+
+Drift docs say create `lib/database.dart`. In Solid: `source/database.dart`. Drift's generated `*.g.dart` lands as a sibling.
+
+### `isar`
+
+Same — `source/.dart` for the annotated classes.
+
+### `get_it`
+
+`get_it` doesn't generate code; it's runtime DI. The README still tells you to call `GetIt.I.registerSingleton(...)` in `lib/main.dart`. Substitute `source/main.dart`.
+
+### `flutter_bloc`
+
+The pattern guides write `lib/blocs/_bloc.dart` and `lib/blocs/_event.dart` etc. Substitute `source/blocs/_bloc.dart` etc.
+
+### `provider`
+
+`provider` is already a Solid dependency (it backs `@SolidEnvironment`). Use it directly when you need multiple providers or when the `.environment(...)` chain gets long.
+
+### `dio` / `http` / `chopper` / API client packages
+
+These are runtime libraries with no code generation — `lib/`-vs-`source/` doesn't apply to the package itself. But the *Dart files you write to consume them* (your `ApiClient` class, your DTOs) go under `source/`.
+
+## What if the new package isn't listed here?
+
+Apply the rule of thumb: anywhere the docs say `lib/`, substitute `source/`. The package itself stays normal in `pubspec.yaml`. If it's a code generator, make sure `source/**` is in `build.yaml` sources.
diff --git a/skills/solid/references/troubleshooting.md b/skills/solid/references/troubleshooting.md
index 9d5f487..e7bb41f 100644
--- a/skills/solid/references/troubleshooting.md
+++ b/skills/solid/references/troubleshooting.md
@@ -8,9 +8,10 @@ Common errors and fixes. Source: plus the "commo
| --- | --- | --- |
| Edits to a file under `lib/` keep disappearing on save | `lib/` is generated; `build_runner watch` rewrites it from `source/`. | Move the change to the matching `source/.dart`. |
| `lib/.dart` not produced | `source/**` not in `build.yaml` `targets.$default.sources`. | Add `- source/**` to the sources list. |
-| Generator errors complain about stale outputs | Old `.g.dart` / `lib/` files conflict with regeneration. | `dart run build_runner build --delete-conflicting-outputs` (or `watch ...`). |
+| Generator errors complain about stale outputs | Old `.g.dart` / `lib/` files conflict with regeneration. | `dart run build_runner build --delete-conflicting-outputs` (or `watch ...`). Or run `scripts/verify.sh`. |
| `flutter run` doesn't pick up a `build_runner` rewrite | No IDE save event fires for filesystem changes. | Press `r` in the `flutter run` terminal, or use [`dashmonx`](https://pub.dev/packages/dashmonx) (`dashmonx -d chrome` etc.). |
| Working with another generator (`freezed`, `json_serializable`, …) and only Solid runs | `build.yaml` `sources` list missing `lib/**` or `$package$`. | Use the full block: `- lib/**`, `- $package$`, `- source/**`. Run `dart run build_runner watch --delete-conflicting-outputs` once. |
+| Generated `lib/` files are missing `const`, have unused imports, or use absolute `package:` imports | The generator prioritises correctness over polish; lint-driven fixes aren't applied. | Run `dart fix --apply` after `build_runner` — or use `scripts/verify.sh` which chains them. Apply in CI too. |
## Annotation rejections
@@ -21,6 +22,7 @@ Common errors and fixes. Source: plus the "commo
| `late` field with `@SolidState` never gets a value | A `late` `@SolidState` field needs an initializer site (or to be assigned before first read). | Either initialize at declaration or assign before read; or make the type nullable. |
| `@SolidQuery() Future fetchData(int id) async {...}` rejected | Queries cannot have parameters. | Move the input into a `@SolidState` field; the query auto-re-runs when it changes. See `references/patterns.md` §5. |
| `@SolidEnvironment() Counter counter;` errors at first read | Lookup is lazy and needs `late`. | Mark the field `late`: `@SolidEnvironment() late Counter counter;`. |
+| Generator rejects `import 'package:/foo.dart';` from a `source/` file | Same-package imports inside `source/` must be relative — `package:` resolves to the generated `lib/` realm. | Use a relative import: `import '../path/to/foo.dart';`. |
## Runtime
@@ -37,3 +39,4 @@ Common errors and fixes. Source: plus the "commo
| --- | --- | --- |
| `must_be_immutable` lint fires on every Solid widget | The `StatelessWidget` you write holds mutable fields. The generated widget is immutable. | In `analysis_options.yaml`: `analyzer.errors.must_be_immutable: ignore`. |
| `public_member_api_docs` lint fires everywhere in `source/` | Solid's recommended setup disables it. | In `analysis_options.yaml`: `linter.rules.public_member_api_docs: false`. |
+| Lints complain about `package:/...` imports inside `source/` | Same-package imports must be relative. | Set `linter.rules.always_use_package_imports: false` and `linter.rules.prefer_relative_imports: true`. |
diff --git a/skills/solid/scripts/scaffold-widget.sh b/skills/solid/scripts/scaffold-widget.sh
index eb38248..35c06d4 100755
--- a/skills/solid/scripts/scaffold-widget.sh
+++ b/skills/solid/scripts/scaffold-widget.sh
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# Scaffold a starter Solid widget under source/.dart.
+#
# Usage: scaffold-widget.sh [--state|--query|--env]
+#
# Run from the package root. Refuses to overwrite an existing file.
set -eu
@@ -34,7 +36,7 @@ fi
mkdir -p source
-# PascalCase -> snake_case (e.g. CounterPage -> counter_page)
+# PascalCase -> snake_case (CounterPage -> counter_page).
snake="$(printf '%s' "$name" \
| sed -E 's/([a-z0-9])([A-Z])/\1_\2/g; s/([A-Z]+)([A-Z][a-z])/\1_\2/g' \
| tr '[:upper:]' '[:lower:]')"
diff --git a/skills/solid/scripts/verify.sh b/skills/solid/scripts/verify.sh
index c798fb0..0401168 100755
--- a/skills/solid/scripts/verify.sh
+++ b/skills/solid/scripts/verify.sh
@@ -1,8 +1,14 @@
#!/usr/bin/env bash
-# Run the Solid (build_runner) code generator and report PASS/FAIL.
-# Run from the package root (the directory with pubspec.yaml).
-# Exits 0 on success, non-zero on failure. On failure, prints the first
-# [SEVERE] line so the agent can act on it without parsing the full log.
+# Verify a Solid package: regenerate lib/ from source/, then apply lint-driven
+# fixes. Run from the package root (the directory with pubspec.yaml).
+#
+# Exit code:
+# 0 = build_runner succeeded (dart fix failure is non-fatal, reported as WARN)
+# 1 = build_runner failed
+# 2 = misuse (not a package root)
+#
+# On build_runner failure, prints the first [SEVERE] line so the caller can act
+# on it without parsing the full log.
set -u
@@ -14,17 +20,26 @@ fi
log="$(mktemp -t solid-verify.XXXXXX)"
trap 'rm -f "$log"' EXIT
-if dart run build_runner build --delete-conflicting-outputs >"$log" 2>&1; then
- echo "PASS: build_runner generated lib/ from source/."
- exit 0
+# Step 1: build_runner. This is the hard requirement.
+if ! dart run build_runner build --delete-conflicting-outputs >"$log" 2>&1; then
+ echo "FAIL: build_runner returned non-zero." >&2
+ first_severe="$(grep -m1 '\[SEVERE\]' "$log" || true)"
+ if [[ -n "$first_severe" ]]; then
+ echo "First error: $first_severe" >&2
+ else
+ echo "Last 20 lines of output:" >&2
+ tail -n 20 "$log" >&2
+ fi
+ exit 1
fi
-echo "FAIL: build_runner returned non-zero." >&2
-first_severe="$(grep -m1 '\[SEVERE\]' "$log" || true)"
-if [[ -n "$first_severe" ]]; then
- echo "First error: $first_severe" >&2
-else
- echo "Last 20 lines of output:" >&2
- tail -n 20 "$log" >&2
+# Step 2: dart fix. This is polish; failure here doesn't fail the script.
+if dart fix --apply >>"$log" 2>&1; then
+ echo "PASS: build_runner generated lib/ from source/, dart fix applied."
+ exit 0
fi
-exit 1
+
+echo "PASS: build_runner generated lib/ from source/."
+echo "WARN: dart fix --apply failed (non-fatal). Last 10 lines of log:" >&2
+tail -n 10 "$log" >&2
+exit 0