diff --git a/AGENTS.md b/AGENTS.md index 07e225c..2fe9bfc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ Operating manual for coding agents working in this repository. Read this fully b Two products in one monorepo: - **`flutterwindcss`** — Tailwind CSS's *design system and styling vocabulary* for Flutter. A real pub dependency. It provides design tokens (spacing, radius, semantic colors) and a utility authoring API over Flutter's **primitive** widgets. This is the "Tailwind" layer. -- **`flutterbits`** — shadcn/ui for Flutter. **Copy-paste** components (the developer owns the source, fetched via a CLI from a registry), styled entirely through `flutterwindcss` and semantic tokens. This is the "shadcn/ui" layer. +- **`flutterbits`** — shadcn/ui for Flutter, **plus an opinionated app-structure layer**. **Copy-paste** components (the developer owns the source, fetched via a CLI from a registry), styled entirely through `flutterwindcss` and semantic tokens. Two things in one: (1) shadcn's familiar primitives (parity), and (2) an **intention-revealing "structure" layer** — `Layout`, `Screen`, and typed routing that model how a Next.js/Expo dev thinks, replacing `MaterialApp`/`Scaffold`/`Navigator` ceremony while staying Material-free. This second part is the differentiator. **flutterbits is mobile-first** (portable to web/desktop, matured by demand). Full design: `docs/superpowers/specs/2026-06-10-flutterbits-charter.md` and its two companions (structure+routing, registry+CLI). The headline feature is **theme portability**: a developer pastes any [tweakcn](https://tweakcn.com)/shadcn theme into a web generator and gets a working Flutter `theme.dart`. No other Flutter UI library does this — protect it. @@ -32,11 +32,12 @@ packages/ registry/ # SOURCE OF TRUTH for components: .dart files + JSON manifests apps/ docs/ # Fumadocs (Next.js) site: docs + the tweakcn -> theme.dart generator + registry endpoint - example/ # Flutter showcase app; ALSO the golden-test + compile target for the registry + example/ # flutterwindcss ENGINE showcase (pure-path WidgetsApp + FwAnimatedTheme) + engine golden/smoke target + gallery/ # flutterbits COMPONENT showcase; the golden-test + compile target for the registry (planned) tooling/ # registry builder, melos config, CI scripts ``` -> **This is the *target* layout.** Today `packages/flutterwindcss/`, `tooling/bake_palette.dart`, `apps/example/`, and `apps/docs/` exist — `apps/example` is a runnable, pure-path **engine** showcase (`WidgetsApp` + `FwAnimatedTheme`), not yet the **component** golden target (no `flutterbits` components yet); `apps/docs` is a **Fumadocs (Next.js/TS)** site whose tweakcn→`theme.dart` generator is **complete (G0–G5 all shipped)** — the color core (`src/lib/generator/color/`, G1), the CSS parser (`src/lib/generator/parse/`, G2 — `:root`/`.dark` → `RawTheme`), the emitter (`src/lib/generator/emit/`, G3 — `RawTheme` → `ResolvedTheme` → `theme.json` → `theme.dart`, end-to-end golden vs `themes.dart`), the web UI (`src/app/(home)/theme-generator/`, G4 — paste → preview → copy, logic in the tested `src/lib/generator/preview.ts`), and the docs page (`content/docs/theme-generator.mdx`, G5). `flutterbits_cli/`, `registry/`, and the melos config are still planned. CI covers `packages/flutterwindcss` (analyze + format + tests + floor-compat + arch-guard), `apps/example` (analyze + format + widget smoke tests), **and** `apps/docs` (the `docs-generator` job: eslint + scoped `tsc` on `src/lib/generator` *and* the `theme-generator` route + Vitest). Don't go looking for dirs that aren't there yet. +> **This is the *target* layout.** Today `packages/flutterwindcss/`, `tooling/bake_palette.dart`, `apps/example/`, and `apps/docs/` exist — `apps/example` is a runnable, pure-path **engine** showcase (`WidgetsApp` + `FwAnimatedTheme`) and stays **engine-only**; the **component** golden + compile target is a **separate `apps/gallery`** app (planned — does not exist yet, created with the first component slice), NOT `apps/example` (decision 2026-06-10: the engine showcase and the component showcase are distinct apps); `apps/docs` is a **Fumadocs (Next.js/TS)** site whose tweakcn→`theme.dart` generator is **complete (G0–G5 all shipped)** — the color core (`src/lib/generator/color/`, G1), the CSS parser (`src/lib/generator/parse/`, G2 — `:root`/`.dark` → `RawTheme`), the emitter (`src/lib/generator/emit/`, G3 — `RawTheme` → `ResolvedTheme` → `theme.json` → `theme.dart`, end-to-end golden vs `themes.dart`), the web UI (`src/app/(home)/theme-generator/`, G4 — paste → preview → copy, logic in the tested `src/lib/generator/preview.ts`), and the docs page (`content/docs/theme-generator.mdx`, G5). `flutterbits_cli/`, `registry/`, and the melos config are still planned. CI covers `packages/flutterwindcss` (analyze + format + tests + floor-compat + arch-guard), `apps/example` (analyze + format + widget smoke tests), **and** `apps/docs` (the `docs-generator` job: eslint + scoped `tsc` on `src/lib/generator` *and* the `theme-generator` route + Vitest). Don't go looking for dirs that aren't there yet. - **Toolchain floor (hard): Flutter ≥ 3.29 / Dart ≥ 3.7.** The wide-gamut `Color` API (`Color.withValues` per §3.8, and the `Color.a/.r/.g/.b` accessors), the `Row`/`Column`/`Flex` `spacing` parameter, and pub workspaces require 3.27/3.6 — below that, the code will not compile. We floor one minor higher at **Dart 3.7** so `dart format` uses the modern "tall" style (Dart 3.7+) rather than the legacy short style; mixing the two fails the format check. This floor is set in every `pubspec.yaml` `environment:` and **verified by a CI job pinned to it** (separate from the golden job, which pins a newer version for determinism). Keep the floor identical in the pubspecs and the README. - Dependency resolution: **pub workspaces** (`resolution: workspace` in each `pubspec.yaml`). Task running / versioning / publishing: **Melos** — the intended cross-package runner, adopted once the workspace holds multiple packages. While `flutterwindcss` is the only package, use plain `flutter`/`dart` per-package (see §10). @@ -70,7 +71,7 @@ These encode hard-won decisions. Violating any of them is a review failure. ## 4. Coding conventions (Dart) -- Public types are prefixed `Fw` (`FwStyle`, `FwButton`, `FwColors`, `FwButtonVariant`). +- **`flutterwindcss` (engine) public types are prefixed `Fw`** (`FwStyle`, `FwColors`, `FwTokens`, `FwBreakpoint`), and so are the routing/structure **library** base types that ship as engine-style code (`FwRoute`, `FwRoutePattern`, `FwPresentation`, `FwStatusBar`). **flutterbits *components* are UNprefixed** — `Button`, `Card`, `Screen`, `Layout` — because they are copy-paste source the developer owns (shadcn-style) and need no namespace. (Material name clashes, only possible in the rare Material-interop case, are resolved by namespacing the barrel `import '.../ui/ui.dart' as ui;`, not by prefixing — see the charter §5.) When you see `Fw` inside a component, that is the engine showing through. - Prefer `const` constructors wherever the analyzer allows; leaf widgets that never change should be `const`. - Variants are **typed enums + exhaustive `switch`** (the cva equivalent). No stringly-typed variant maps. The `switch` must be exhaustive so the compiler catches a missing case — do not add a `default:` that papers over new enum values. - Every file passes `dart format` (100-col) and `flutter analyze` with **zero** warnings before you call a task done. @@ -109,7 +110,7 @@ Until the first component lands, follow the component checklist below and the en - [ ] Directional layout throughout (§3.3). - [ ] A registry manifest entry (§8) listing pub deps and `registryDeps`. - [ ] Golden tests for **every variant × size × brightness** (§9). -- [ ] Imported and rendered in `apps/example` so CI compiles it. +- [ ] Imported and rendered in `apps/gallery` (the flutterbits component target — **not** `apps/example`, which is the engine showcase) so CI compiles it. If a desired behavior genuinely cannot be done in Flutter, do not fake it — add it to the **Won't-do list** (§11) and note it in the component's docs. @@ -137,10 +138,12 @@ If a desired behavior genuinely cannot be done in Flutter, do not fake it — ad ## 8. Registry & CLI -- Registry manifest per component (JSON): `{ name, description, pubDeps: [...], registryDeps: [...], files: [{ path, content }] }`. `registryDeps` resolves inter-component needs (e.g. `dialog` pulls in `button`). -- `flutterbits add ` writes files into the host project (default `lib/components/ui/`), installs `pubDeps`, recursively adds `registryDeps`, then runs `dart format`. -- `flutterbits diff ` shows upstream changes vs the developer's copy (the copy-paste survival mechanism — analogous to shadcn's diff). -- The registry build step (`tooling/`) generates manifests FROM the `registry/*.dart` source. Never hand-edit a manifest's `content`. +- Registry manifest per item (JSON): `{ name, type, description, exported, pubDeps: [...], registryDeps: [...], files: [{ path, type, target, content }] }`. **Install-types** (shadcn's `registry:ui`/`lib`/`hook` analog): `component` → `lib/components/ui/.dart`; `util` → `lib/components/ui/_utils/.dart` (shared lib utilities — the `anchor`/overlay substrate, `showConfirm`/`showSheet`/`showToast`; Flutter has no native "hooks", so these are plain utilities). `registryDeps` resolves inter-item needs recursively (e.g. `dialog` pulls in `button`; `popover` pulls in `anchor`). +- `flutterbits init` scaffolds a project: adds `flutterwindcss`, creates `lib/components/ui/` + `_utils/`, writes a starter `theme.dart`, an initial barrel, an optional starter `Layout`, and `flutterbits.json` (the config — `components.json` analog: registry URL + target paths + theme path). +- `flutterbits add ` writes files into the host project (default `lib/components/ui/`), installs `pubDeps`, recursively adds `registryDeps`, **regenerates the barrel**, then runs `dart format`. +- **The barrel `lib/components/ui/ui.dart`** is CLI-**generated, never hand-edited** — it re-exports every `exported` item so devs import the whole set from one place (and `import … as ui;` namespaces it for the rare Material-interop clash). Internal utils (e.g. `anchor`) are imported directly and stay out of the barrel. +- `flutterbits diff ` shows upstream changes vs the developer's copy (the copy-paste survival mechanism — analogous to shadcn's diff); it **shows, never auto-merges** (the dev owns their edits). +- The registry build step (`tooling/build_registry.dart`) generates manifests FROM the `registry/*.dart` source. Never hand-edit a manifest's `content`. Full design: `docs/superpowers/specs/2026-06-10-flutterbits-registry-cli-design.md`. --- @@ -148,11 +151,11 @@ If a desired behavior genuinely cannot be done in Flutter, do not fake it — ad This is the safety net that makes a solo, wave-by-wave rollout survivable. -- **Golden tests** use `matchesGoldenFile`. **`flutterwindcss` engine goldens live in-package** (`packages/flutterwindcss/test/golden/`) — a library tests its own widgets. **Component goldens** (every variant × size × brightness) live in `apps/example`, which is also the registry compile target. +- **Golden tests** use `matchesGoldenFile`. **`flutterwindcss` engine goldens live in-package** (`packages/flutterwindcss/test/golden/`) — a library tests its own widgets. **Component goldens** (every variant × size × brightness) live in **`apps/gallery`**, which is also the registry compile target. (`apps/example` is the *engine* showcase and hosts only engine smoke tests — the two apps are deliberately separate, decision 2026-06-10.) - CI pins a **fixed font** and platform so goldens are deterministic across machines, and **CI (Linux) is the authoritative golden platform** — goldens are generated/verified there, and a local `--update-goldens` on a dev box is *not* authoritative. A golden diff on CI is a failing build, not a nuisance. - Update goldens only intentionally: `flutter test --update-goldens`, and review the image diff before committing. - `flutterwindcss` gets unit tests for `FwStyle` resolution — especially **last-wins conflict behavior** and that chaining produces a single resolved widget. -- Before marking any task done: `flutter analyze` (zero warnings) AND `flutter test` (green) AND the registry compiles in `apps/example`. +- Before marking any task done: `flutter analyze` (zero warnings) AND `flutter test` (green) AND the registry compiles in `apps/gallery`. --- @@ -178,6 +181,8 @@ Adjust paths if the layout drifts; keep this section current. | Task | Command | |---|---| | Build registry manifests | `dart run tooling/build_registry.dart` | +| Run the flutterbits component gallery | `cd apps/gallery && flutter run` (any device; e.g. `-d chrome`) | +| Test/golden the gallery | `cd apps/gallery && flutter test` (`--update-goldens` locally is non-authoritative; CI Linux is the source of truth) | | Run docs site / generator | `cd apps/docs && pnpm dev` | Once the workspace holds multiple packages, **Melos** wraps the per-package commands (`melos bootstrap`, `melos run analyze`, `melos run test`); adopt it then, not before. @@ -217,7 +222,7 @@ Everything **not** in 11a or 11b is fair game and must not be refused on cost gr - **Verdict before "won't" (MUST).** Before declaring anything a Non-Goal or limitation, write a one-paragraph feasibility verdict: the mechanism that *would* implement it, its rough cost, and the honest call. Attach it to the spec. Default to building; only the §11 bar excuses not building. - **Read before you edit.** Open the canonical sources (`lib/src/style/`, `lib/src/theme/`, `lib/src/tokens/`, and once it exists `registry/button.dart`) and match their patterns before writing new code. - **Small, focused changes.** One component or one utility group per change. Don't refactor unrelated code in passing. -- **No new dependencies without justification.** Prefer the framework's widgets layer. Known sanctioned deps: `lucide_icons_flutter` (icons), `flutter_animate` (animation). Anything else needs a reason in the PR description. +- **No new dependencies without justification.** Prefer the framework's widgets layer. Known sanctioned deps: `lucide_icons_flutter` (icons), `flutter_animate` (animation), and **`go_router`** (the routing engine the flutterbits structure layer *wraps* — not forks — declared as a `pubDep` of the structure components only; never a `flutterwindcss` dep). `flutter_svg` is sanctioned **by-demand** for blocks that render real SVG illustrations. Anything else needs a reason in the PR description. Deps are declared **per-component in the manifest `pubDeps`**, never globally: a copied `Button` drags in nothing; `add toast` pulls `flutter_animate`; `add layout` pulls `go_router`. - **Don't invent APIs.** If unsure whether a Flutter symbol exists in the widgets layer, verify before using it. Do not assume Material symbols are available. - **Surface assumptions.** If a task is ambiguous (which variant set? which platforms?), state the assumption you made inline rather than guessing silently. - **Never weaken the rules in §3 to make a task pass.** If a rule blocks you, stop and flag it — don't import Material, don't hardcode a color, don't nest wrappers to ship faster. diff --git a/README.md b/README.md index c42ab98..741be12 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Two products in one monorepo, built so that **any [tweakcn](https://tweakcn.com)/shadcn theme you paste into a web generator becomes a working Flutter `theme.dart`** — theme portability no other Flutter UI library offers. - **`flutterwindcss`** — Tailwind CSS v4's *design system and styling vocabulary* for Flutter. Design tokens (spacing, radius, semantic colors, typography, shadows) and a typed, compile‑time utility API (`.tw`) over Flutter's primitive widgets. This is the **Tailwind** layer. -- **`flutterbits`** — shadcn/ui for Flutter. **Copy‑paste** components you own, fetched via a CLI from a registry, styled entirely through `flutterwindcss` and semantic tokens. This is the **shadcn/ui** layer. +- **`flutterbits`** — shadcn/ui for Flutter. **Copy‑paste** components you own, fetched via a CLI from a registry, styled entirely through `flutterwindcss` and semantic tokens — **plus** an opinionated, intention‑revealing **structure layer** (`Layout`, `Screen`, typed routing) that models app‑building the Next.js/Expo way. Mobile‑first, portable. This is the **shadcn/ui** layer (and a bit more). ## Install & docs @@ -88,7 +88,7 @@ Flutter has no structure/style split and no CSS cascade — the widget tree *is* **🚧 Next on the roadmap:** -1. **`flutterbits` components** and the **registry + CLI** (`flutterbits add` / `diff`). *(The engine is ready — ~96% of daily-driver Tailwind is covered and a full-catalog audit found no engine blocker.)* +1. **`flutterbits` components** and the **registry + CLI** (`flutterbits init` / `add` / `diff`) — primitives (shadcn parity), the **structure layer** (`Layout`/`Screen`/routing over `go_router`), blocks, and templates. Design specs: [`flutterbits-charter`](docs/superpowers/specs/2026-06-10-flutterbits-charter.md), [structure + routing](docs/superpowers/specs/2026-06-10-flutterbits-structure-and-routing-design.md), [registry + CLI](docs/superpowers/specs/2026-06-10-flutterbits-registry-cli-design.md). *(The engine is ready — ~96% of daily-driver Tailwind is covered and a full-catalog audit found no engine blocker.)* 2. **Remaining engine long-tail (by demand only)** — niche/feasible items (`inset-shadow`, `mask-*`, backdrop color filters, `columns`, negative margins, `scroll-margin/padding`, decoration styling) built when a real component needs one; the rest is the [coverage & roadmap](docs/superpowers/specs/2026-06-07-flutterwindcss-coverage-and-roadmap.md)'s delegated (animation → [`flutter_animate`](https://pub.dev/packages/flutter_animate); forms/prose/tables/SVG → flutterbits) / impossible sets. See [`docs/superpowers/specs`](docs/superpowers/specs) for the full engine design and [`docs/superpowers/plans`](docs/superpowers/plans) for the implementation plans. @@ -99,7 +99,8 @@ See [`docs/superpowers/specs`](docs/superpowers/specs) for the full engine desig packages/ flutterwindcss/ # pub package: tokens, FwTheme, FwStyle accumulator, .tw utilities apps/ - example/ # flutterwindcss engine showcase (pure path, runnable); component golden target (planned) + example/ # flutterwindcss ENGINE showcase (pure path, runnable) + engine golden/smoke target + gallery/ # flutterbits COMPONENT showcase + golden/compile target (planned) docs/ # Fumadocs site + the tweakcn→theme.dart generator (shipped: parse→color→emit pipeline, web UI at /theme-generator, docs) registry/ # (planned) source-of-truth copy-paste components tooling/ # registry builder + the Tailwind palette baker diff --git a/apps/docs/content/docs/flutterbits/index.mdx b/apps/docs/content/docs/flutterbits/index.mdx index 8984489..d72b477 100644 --- a/apps/docs/content/docs/flutterbits/index.mdx +++ b/apps/docs/content/docs/flutterbits/index.mdx @@ -3,12 +3,18 @@ title: Overview description: flutterbits is shadcn/ui for Flutter — copy-paste components you own, styled entirely through flutterwindcss semantic tokens. --- -**flutterbits** is *[shadcn/ui](https://ui.shadcn.com) for Flutter*: a registry of **copy-paste -components** you own. You don't +**flutterbits** is *[shadcn/ui](https://ui.shadcn.com) for Flutter* — a registry of **copy-paste +components** you own — **plus an opinionated, intention-revealing app-structure layer**. You don't add a versioned widget library — you pull a component's source into your project and it's yours to read and edit. Every component is styled entirely through [flutterwindcss](/docs/flutterwindcss) semantic tokens, so a theme reskins them all. +Beyond shadcn's primitives, flutterbits ships a **structure layer** — `Layout`, `Screen`, and typed +routing that model how a Next.js/Expo developer thinks (layouts wrap screens, screens compose blocks, +blocks compose primitives), so the code reads top-down as the rendered artifact instead of as +`MaterialApp`/`Scaffold`/`Navigator` plumbing. flutterbits is **mobile-first** (portable to web and +desktop, maturing there by demand). + The styling engine ([flutterwindcss](/docs/flutterwindcss)) and the [Theme generator](/theme-generator) are shipped and stable. The component registry and CLI @@ -21,10 +27,12 @@ semantic tokens, so a theme reskins them all. - **Copy-paste, not a dependency.** Components are source you own — fetched from a registry, written into your project (default `lib/components/ui/`). You can change anything; nothing is locked behind a package version. This is the model shadcn/ui pioneered. -- **A CLI to fetch and update.** `flutterbits add ` writes a component's files into your app, - installs its package dependencies, and pulls in any other components it needs. `flutterbits diff - ` shows what changed upstream versus your copy — the survival mechanism for code you've since - edited. +- **A CLI to scaffold, fetch, and update.** `flutterbits init` sets up your project (the styling + engine, a `theme.dart`, the components folder, and a generated barrel). `flutterbits add ` + writes a component's files into your app, installs its package dependencies, pulls in any other + components it needs, and regenerates the barrel so you import the whole set from one place + (`components/ui/ui.dart`). `flutterbits diff ` shows what changed upstream versus your copy — + the survival mechanism for code you've since edited (it shows, it never overwrites your edits). - **Styled through tokens.** Components reference only semantic roles (`primary`, `muted`, `border`) via `context.fw` — never raw colors. So a `theme.dart` generated from any tweakcn/shadcn theme reskins every component at once. diff --git a/docs/superpowers/plans/2026-06-10-flutterbits-button-and-gallery.md b/docs/superpowers/plans/2026-06-10-flutterbits-button-and-gallery.md new file mode 100644 index 0000000..ef5a355 --- /dev/null +++ b/docs/superpowers/plans/2026-06-10-flutterbits-button-and-gallery.md @@ -0,0 +1,833 @@ +# flutterbits `Button` + `apps/gallery` — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Stand up the flutterbits component app (`apps/gallery`) and author the canonical `Button` component — the template every later component imitates — with full behavioral tests and golden coverage. + +**Architecture:** `Button` is a Material-free `StatefulWidget` that sources its own interaction states (hover/focus/pressed/disabled) via `FocusableActionDetector` + a tap detector (AGENTS.md §6 — action-bearing components own their states), resolves a `(variant, size, states)` → visual treatment with exhaustive `switch`es in plain Dart, and renders a single styled box through the `flutterwindcss` `.tw` chain using only semantic tokens (`context.fw.colors.*`). `apps/gallery` is a new pub-workspace member (mirroring `apps/example`) that hosts the component, its widget smoke test, and its goldens. + +**Tech Stack:** Dart/Flutter (≥3.29/3.7), `flutterwindcss` (path dep), `package:flutter/widgets.dart` + `package:flutter/services.dart` (keyboard), `flutter_test` goldens. No Material, no other deps. + +**Scope note (no silent reduction):** This plan delivers `Button` + `apps/gallery` end-to-end. The **registry source-of-truth file (`registry/button.dart`), the manifest JSON, and `tooling/build_registry.dart` are deliberately moved to the next plan** (the registry + CLI slice), because the manifest's metadata-source (where `description`/`pubDeps`/`registryDeps` come from when "generated from the .dart") is a registry-spec decision best made alongside the CLI — not invented here. For this plan, `Button` is authored at its consumer-shaped home `apps/gallery/lib/components/ui/button.dart`; the registry/CLI plan promotes it to `registry/` and adds the install path. This is an explicit, recorded adjustment to the original "manifest in plan 1" scope. + +**Gallery root is TEMPORARY (becomes `Layout`):** the gallery's job is to showcase flutterbits, and `Layout` (the structure layer) is the flagship — so the gallery's real root **will be `Layout`**. `Layout` is the (larger) structure+routing plan, and Button is built first to de-risk the component pattern and give `Layout` something real to host. Therefore the `WidgetsApp` host in Tasks 0/6 is an **explicitly-temporary bootstrap** (commented as such), and the structure plan **replaces it with `Layout` and makes the gallery the `Layout` demo**. The `WidgetsApp` here is throwaway dev scaffolding for an internal app, not shipped product code — it must be clearly labeled so it is never mistaken for the intended root. + +**Reconciliation this plan performs (no-drift):** `default` is a Dart reserved word and cannot be an enum constant, so the shadcn `default` variant/size map to `primary` (variant) and `md` (size). Task 7 updates the charter §3.2 + AGENTS.md §where-named to record this. Every other shadcn variant/size name is used verbatim. + +--- + +## File Structure + +| File | Responsibility | +|---|---| +| `apps/gallery/pubspec.yaml` | New workspace member `flutterbits_gallery`; deps `flutter` + `flutterwindcss` (path); `uses-material-design: false`. | +| `pubspec.yaml` (root) | Add `apps/gallery` to the `workspace:` list. | +| `apps/gallery/analysis_options.yaml` | Lints (mirror `apps/example`). | +| `apps/gallery/lib/components/ui/button.dart` | The `Button` component + `ButtonVariant`/`ButtonSize` enums. The canonical template. | +| `apps/gallery/lib/main.dart` | Material-free `WidgetsApp` host rendering a Button gallery (so CI compiles the component). | +| `apps/gallery/test/button_behavior_test.dart` | Widget tests: renders label, tap fires, disabled blocks, keyboard activates, Semantics flags. | +| `apps/gallery/test/button_golden_test.dart` | Goldens: every variant × size × brightness grid, plus focused-ring and RTL. | +| `apps/gallery/test/gallery_smoke_test.dart` | The app builds + renders without exceptions (light/dark/LTR/RTL). | + +--- + +## Task 0: Scaffold `apps/gallery` and register it in the workspace + +**Files:** +- Create: `apps/gallery/pubspec.yaml` +- Modify: `pubspec.yaml` (root, `workspace:` list) +- Create: `apps/gallery/analysis_options.yaml` +- Create: `apps/gallery/lib/main.dart` (placeholder for now) + +- [ ] **Step 1: Create `apps/gallery/pubspec.yaml`** + +```yaml +name: flutterbits_gallery +description: >- + flutterbits COMPONENT gallery — a Material-free Flutter app showcasing the + copy-paste components, and the golden-test + compile target for the registry. +publish_to: none +version: 1.0.0+1 + +# Joins the repo's pub workspace (root pubspec.yaml `workspace:`). +resolution: workspace + +environment: + # Match the toolchain floor (AGENTS.md §2): Flutter 3.29 / Dart 3.7. + sdk: '>=3.7.0 <4.0.0' + flutter: '>=3.29.0' + +dependencies: + flutter: + sdk: flutter + # The styling engine every component styles through. + flutterwindcss: + path: ../../packages/flutterwindcss + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + # Material-free: components run on the pure path (WidgetsApp + FwTheme). + uses-material-design: false +``` + +- [ ] **Step 2: Add `apps/gallery` to the root workspace list** + +In `pubspec.yaml` (root), under `workspace:`, add the line so it reads: + +```yaml +workspace: + - packages/flutterwindcss + - packages/flutterwindcss/example + - apps/example + - apps/gallery +``` + +- [ ] **Step 3: Create `apps/gallery/analysis_options.yaml`** + +```yaml +include: package:flutter_lints/flutter.yaml + +analyzer: + language: + strict-casts: true + strict-raw-types: true +``` + +- [ ] **Step 4: Create a placeholder `apps/gallery/lib/main.dart`** (replaced in Task 6) + +```dart +import 'package:flutter/widgets.dart'; +import 'package:flutterwindcss/flutterwindcss.dart'; + +void main() => runApp(const GalleryApp()); + +/// TEMPORARY bootstrap root for the gallery. The structure plan REPLACES this +/// raw `WidgetsApp` with the flutterbits `Layout` (the gallery's intended root, +/// which it then demos). Do not entrench this — it is throwaway dev scaffolding. +class GalleryApp extends StatelessWidget { + const GalleryApp({super.key}); + + @override + Widget build(BuildContext context) { + return FwTheme( + tokens: FwTokens.light, + child: WidgetsApp( + title: 'flutterbits gallery', + color: const Color(0xFF2563EB), + debugShowCheckedModeBanner: false, + pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) { + return PageRouteBuilder( + settings: settings, + pageBuilder: (context, _, _) => builder(context), + ); + }, + home: Builder( + builder: (context) => ColoredBox( + color: context.fw.colors.background, + child: const Center(child: Text('flutterbits gallery')), + ), + ), + ), + ); + } +} +``` + +- [ ] **Step 5: Resolve and analyze** + +Run: `flutter pub get` (from repo root) then `cd apps/gallery && flutter analyze --fatal-infos --fatal-warnings` +Expected: deps resolve; analyze reports **No issues found**. + +- [ ] **Step 6: Commit** + +```bash +git add apps/gallery pubspec.yaml +git commit -m "feat(gallery): scaffold apps/gallery (flutterbits component target)" +``` + +--- + +## Task 1: `Button` enums + skeleton + first behavior test + +**Files:** +- Create: `apps/gallery/lib/components/ui/button.dart` +- Create: `apps/gallery/test/button_behavior_test.dart` + +- [ ] **Step 1: Write the failing test** (`button_behavior_test.dart`) + +```dart +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutterwindcss/flutterwindcss.dart'; +import 'package:flutterbits_gallery/components/ui/button.dart'; + +Widget _host(Widget child) => FwTheme( + tokens: FwTokens.light, + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: Center(child: child), + ), + ), +); + +void main() { + testWidgets('renders its label and reports button semantics', (t) async { + await t.pumpWidget(_host(Button(onPressed: () {}, child: const Text('Save')))); + expect(find.text('Save'), findsOneWidget); + + final semantics = t.getSemantics(find.text('Save')); + expect(semantics.hasFlag(SemanticsFlag.isButton), isTrue); + expect(semantics.hasFlag(SemanticsFlag.isEnabled), isTrue); + }); +} +``` + +- [ ] **Step 2: Run it to verify it fails** + +Run: `cd apps/gallery && flutter test test/button_behavior_test.dart` +Expected: FAIL — `button.dart` / `Button` does not exist (compile error). + +- [ ] **Step 3: Write the minimal skeleton** (`button.dart`) + +```dart +import 'package:flutter/widgets.dart'; +import 'package:flutterwindcss/flutterwindcss.dart'; + +/// shadcn's button variants. `primary` is shadcn's `default` (`default` is a +/// Dart reserved word, so it cannot be an enum constant). +enum ButtonVariant { primary, secondary, destructive, outline, ghost, link } + +/// shadcn's button sizes. `md` is shadcn's `default` size (same reserved-word +/// reason as above). +enum ButtonSize { sm, md, lg, icon } + +/// A Material-free, themeable button — shadcn parity. Copy-paste source you own. +/// +/// Sources its own interaction states (hover/focus/pressed/disabled) and styles a +/// single box through `.tw` using semantic tokens only (AGENTS.md §3.1/§6). +class Button extends StatefulWidget { + const Button({ + super.key, + required this.child, + this.onPressed, + this.variant = ButtonVariant.primary, + this.size = ButtonSize.md, + this.semanticLabel, + }); + + /// The button's content (a `Text`, an icon widget, or a row of both). + final Widget child; + + /// Tapped/activated callback. `null` disables the button. + final VoidCallback? onPressed; + + final ButtonVariant variant; + final ButtonSize size; + + /// Optional accessibility label (defaults to the child's own semantics). + final String? semanticLabel; + + /// Whether the button is interactive. + bool get enabled => onPressed != null; + + @override + State