Skip to content

feat: WindRecipe + 5 design-system primitives#120

Open
anilcancakir wants to merge 6 commits into
masterfrom
feat/design-first-component-system
Open

feat: WindRecipe + 5 design-system primitives#120
anilcancakir wants to merge 6 commits into
masterfrom
feat/design-first-component-system

Conversation

@anilcancakir

@anilcancakir anilcancakir commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Adds WindRecipe/WindSlotRecipe (tailwind-variants tv() equivalent, strict emission order, no dedupe/sort/twMerge) and 5 className-driven primitives WBadge/WCard/WSwitch/WRadio/WTabs, all additive. Full five-surface sync (doc/, example pages, wind-ui skill 22->27 widgets, CHANGELOG, README). analyze 0, 1538 tests, 91.3% coverage.

Cross-repo: this design-first component system spans 5 repos. Land in dependency order: wind (WindRecipe + 5 primitives) first, then magic (design:sync/lint, previews:refresh, make:component) and magic_devtools (preview catalog) which consume wind, then magic_starter (component library + view rewrite), then magic_example (consumer app). Branches are all feat/design-first-component-system. Verified locally end-to-end (analyze 0, full suites green, real-browser e2e of the /preview catalog, release-strip confirmed).

Summary by CodeRabbit

  • New Features

    • Added new widgets: WBadge, WCard, WSwitch, WRadio<T>, and WTabs.
    • Introduced WindRecipe / WindSlotRecipe for strict, ordered variant and slot-based class composition.
    • Added example pages demonstrating the new widgets and recipe styling.
  • Bug Fixes

    • Fixed WSelect dropdown behavior on web so it doesn’t dismiss itself on the same click that opens it.
  • Documentation

    • Added and expanded widget and recipe docs, plus updated README, changelog, and skill materials.
  • Tests

    • Added recipe and widget test coverage, including interaction, accessibility, and semantics.

Add WindRecipe + WindSlotRecipe (tailwind-variants tv() equivalent:
base + variants + compoundVariants + defaultVariants resolved in strict
emission order, no dedupe/sort/twMerge) and 5 className-driven primitives
WBadge, WCard, WSwitch, WRadio, WTabs. All exported from the barrel and
covered by widget/recipe tests; additive only (no existing API touched).
…ives

Five-surface post-change sync for the Wave 1 APIs: per-widget doc/ pages
with x-previews, demo gallery pages, wind-ui skill roster (22 to 27
widgets + WindRecipe), CHANGELOG [Unreleased] Added entry, README roster.
Replace em-dashes with ':' / '-' across the WindRecipe + primitives docs,
the wind-ui skill token catalog, and the README WindRecipe row to satisfy
the English-only, no-em-dash repo convention. Content unchanged.
Copilot AI review requested due to automatic review settings June 25, 2026 22:53
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@anilcancakir, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 20 minutes and 47 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 817f9266-b6f8-46cc-b82c-19ec8ddcccd8

📥 Commits

Reviewing files that changed from the base of the PR and between 50c3bed and dc14a03.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • lib/src/widgets/w_select.dart
  • test/interaction/animation_overlay_test.dart
  • test/interaction/tap_callbacks_test.dart
  • test/widgets/w_select_test.dart
📝 Walkthrough

Walkthrough

Adds WindRecipe/WindSlotRecipe, new WBadge, WCard, WSwitch, WRadio, and WTabs widgets, plus matching docs, examples, tests, exports, routes, guidance, and a WSelect tap handling fix.

Changes

Wind UI expansion

Layer / File(s) Summary
Wind recipe engine
lib/src/recipe/wind_recipe.dart, doc/styling/wind-recipe.md, example/lib/pages/styling/wind_recipe_basic.dart, test/recipe/wind_recipe_test.dart, skills/wind-ui/SKILL.md
WindRecipe and WindSlotRecipe add class composition rules, slot outputs, docs, examples, and tests for variant resolution and compound matching.
Badge and card widgets
lib/src/widgets/w_badge.dart, lib/src/widgets/w_card.dart, doc/widgets/w-badge.md, doc/widgets/w-card.md, example/lib/pages/widgets/w_badge_basic.dart, example/lib/pages/widgets/w_card_basic.dart, test/widgets/w_badge_test.dart, test/widgets/w_card_test.dart
WBadge and WCard add pill and card composition with docs, examples, and widget tests for className threading and slot rendering.
Switch, radio, and tabs controls
lib/src/widgets/w_switch.dart, lib/src/widgets/w_radio.dart, lib/src/widgets/w_tabs.dart, doc/widgets/w-switch.md, doc/widgets/w-radio.md, doc/widgets/w-tabs.md, example/lib/pages/widgets/w_switch_basic.dart, example/lib/pages/widgets/w_radio_basic.dart, example/lib/pages/widgets/w_tabs_basic.dart, test/widgets/w_switch_test.dart, test/widgets/w_radio_test.dart, test/widgets/w_tabs_test.dart
WSwitch, WRadio, and WTabs add controlled interactions, semantic state handling, docs, examples, and widget tests.
Select outside-tap handling
lib/src/widgets/w_select.dart, CHANGELOG.md
WSelect adds shared tap-region grouping and one-frame suppression so the open gesture does not dismiss the dropdown immediately.
Public catalog, routes, and guidance
CHANGELOG.md, README.md, example/lib/routes.dart, lib/fluttersdk_wind.dart, skills/wind-ui/SKILL.md, .gitignore
Top-level docs, package exports, example routes, skill guidance, and ignore rules update the listed Wind surface and counts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

I hopped through Wind with a whisker-twitching grin,
🐇 Recipes, badges, tabs, and switches all came in.
The class strings danced neatly; the panels slid by,
While docs and tests sparkled beneath the sky.
Thump-thump, the burrow hums—new widgets to try!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding WindRecipe plus five new design-system primitives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/design-first-component-system

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.21267% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/src/widgets/w_badge.dart 71.42% 4 Missing ⚠️
lib/src/widgets/w_tabs.dart 89.47% 4 Missing ⚠️
lib/src/widgets/w_radio.dart 89.28% 3 Missing ⚠️
lib/src/widgets/w_switch.dart 88.00% 3 Missing ⚠️
lib/src/widgets/w_card.dart 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Wind’s public surface with a tv()-style variant composition API (WindRecipe / WindSlotRecipe) and five new className-driven design-system primitives (WBadge, WCard, WSwitch, WRadio, WTabs), along with the expected ecosystem sync (tests, docs, examples, skill, changelog, README, exports).

Changes:

  • Added WindRecipe / WindSlotRecipe (+ compound-variant models) for strict-order className composition without dedupe/sort/twMerge.
  • Introduced 5 new public widgets (WBadge, WCard, WSwitch, WRadio, WTabs) and exported them from the barrel.
  • Synced docs (doc/), example gallery pages + routes, skill metadata, README, and CHANGELOG; added targeted widget/recipe tests.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
test/widgets/w_tabs_test.dart Adds widget tests for controlled tabs behavior, className threading, and selection state.
test/widgets/w_switch_test.dart Adds widget tests for switch interaction, state styling, and semantics.
test/widgets/w_radio_test.dart Adds widget tests for radio interaction, state styling, grouping, and semantics.
test/widgets/w_card_test.dart Adds widget tests for card slots and default/className behavior.
test/widgets/w_badge_test.dart Adds widget tests for badge rendering and className pass-through.
test/recipe/wind_recipe_test.dart Adds unit tests for recipe ordering, defaults/null-clear, compounds, and debug lint.
skills/wind-ui/SKILL.md Updates skill surface summary to include new widgets + recipes and bumps skill version.
README.md Updates widget count and highlights WindRecipe / WindSlotRecipe.
lib/src/widgets/w_tabs.dart Implements the new WTabs controlled tabs widget.
lib/src/widgets/w_switch.dart Implements the new WSwitch controlled toggle widget with semantics.
lib/src/widgets/w_radio.dart Implements the new WRadio<T> controlled radio widget with semantics.
lib/src/widgets/w_card.dart Implements the new WCard slot-based container widget.
lib/src/widgets/w_badge.dart Implements the new WBadge display pill widget.
lib/src/recipe/wind_recipe.dart Implements WindRecipe / WindSlotRecipe and compound-variant support + debug lint.
lib/fluttersdk_wind.dart Exports the new widgets and recipes from the package barrel.
example/lib/routes.dart Registers new example routes for the widgets and WindRecipe page.
example/lib/pages/widgets/w_tabs_basic.dart Adds the WTabs example page (underline + pill styles).
example/lib/pages/widgets/w_switch_basic.dart Adds the WSwitch example page (basic, list, disabled).
example/lib/pages/widgets/w_radio_basic.dart Adds the WRadio example page (vertical, horizontal, nullable groupValue).
example/lib/pages/widgets/w_card_basic.dart Adds the WCard example page (basic/header/footer patterns).
example/lib/pages/widgets/w_badge_basic.dart Adds the WBadge example page (tones, inline usage, unstyled).
example/lib/pages/styling/wind_recipe_basic.dart Adds a WindRecipe/WindSlotRecipe example page demonstrating variants/compounds/overrides.
doc/widgets/w-tabs.md New widget documentation page for WTabs with x-preview + examples.
doc/widgets/w-switch.md New widget documentation page for WSwitch with x-preview + examples.
doc/widgets/w-radio.md New widget documentation page for WRadio with x-preview + examples.
doc/widgets/w-card.md New widget documentation page for WCard with x-preview + examples.
doc/widgets/w-badge.md New widget documentation page for WBadge with x-preview + examples.
doc/styling/wind-recipe.md New documentation page for WindRecipe/WindSlotRecipe with x-preview + rules.
CHANGELOG.md Adds Unreleased entries documenting the new public APIs/widgets.

Comment on lines +108 to +112
final Set<String> activeStates = {
if (value) 'checked',
if (disabled) 'disabled',
...?states,
};
Comment thread lib/src/widgets/w_switch.dart Outdated
Comment on lines +136 to +140
enabled: !disabled,
child: MergeSemantics(
child: WAnchor(
onTap: disabled ? null : () => onChanged?.call(!value),
isDisabled: disabled,
Comment on lines +107 to +111
final Set<String> activeStates = {
if (_isSelected) 'selected',
if (disabled) 'disabled',
...?states,
};
Comment on lines +150 to +168
final Widget anchor = WAnchor(
onTap: disabled || _isSelected ? null : () => onChanged?.call(value),
isDisabled: disabled,
states: activeStates,
semanticLabel: semanticLabel,
child: shell,
);

// 6. Wrap with Semantics so Playwright / assistive tech sees a radio role.
//
// `inMutuallyExclusiveGroup: true` surfaces the radio group relationship
// in the accessibility tree alongside the checked state.
return Semantics(
container: true,
checked: _isSelected,
enabled: !disabled,
inMutuallyExclusiveGroup: true,
child: MergeSemantics(child: anchor),
);
Comment on lines +165 to +167
return WAnchor(
onTap: () => onChanged?.call(index),
states: tabStates,
Comment thread example/lib/pages/widgets/w_badge_basic.dart Outdated
Comment thread example/lib/pages/styling/wind_recipe_basic.dart Outdated
Comment thread example/lib/pages/styling/wind_recipe_basic.dart Outdated
Comment thread example/lib/pages/styling/wind_recipe_basic.dart Outdated
Comment thread example/lib/pages/styling/wind_recipe_basic.dart Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (1)
example/lib/routes.dart (1)

209-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the new widget imports alphabetized within this block.

These five imports were appended after wind_animation_wrapper_basic.dart, but this file's routing rule asks for new page imports to be alphabetized within their category block. Reordering them next to the other w_* imports will keep future route updates predictable. As per coding guidelines, example/lib/routes.dart: "add an import statement (alphabetically within its category block)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/lib/routes.dart` around lines 209 - 214, The new widget imports in
the routes import block are not alphabetized, so reorder the w_* page imports in
routes.dart to keep the category block sorted consistently with the existing
import ordering. Use the existing route import section around
wind_animation_wrapper_basic.dart and the
w_badge_basic/w_card_basic/w_radio_basic/w_switch_basic/w_tabs_basic symbols as
the reference point, and place the new imports in alphabetical order within that
block.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@doc/styling/wind-recipe.md`:
- Around line 153-156: The caption above the WindRecipe example has the
shorthand/longhand relationship reversed, so update the prose to match the
actual snippet. In the affected markdown section, adjust the text around
WindRecipe so it describes base as p-* and the variant as px-* rather than “base
px-* vs variant p-*”, keeping the explanation aligned with the example.
- Around line 133-135: The fenced code block in the documentation is missing a
language tag, which is triggering markdownlint. Update the affected fenced block
in the wind-recipe markdown so it uses a plain text language identifier, keeping
the content unchanged while satisfying the linter.

In `@doc/widgets/w-badge.md`:
- Around line 28-32: The widget docs props table is using the wrong shape by
folding requiredness into the Default column instead of matching the standard
required/default/description format used in doc/widgets/*.md. Update the Props
table in the badge documentation so each prop follows the usual columns, with
label marked required in its own Required field and className showing its
default separately, keeping the existing descriptions intact.

In `@doc/widgets/w-card.md`:
- Around line 28-33: The props table for w-card is missing the requiredness
column used by widget docs. Update the table near the
child/header/footer/className entries to use the standard widget-doc columns
from the guidelines, including a separate Required column and a Default column,
and mark each prop’s required status explicitly instead of overloading Default
for that purpose.

In `@doc/widgets/w-radio.md`:
- Around line 38-48: The WRadio documentation table uses the wrong prop-table
shape by mixing requiredness into the Default column. Update the props table in
WRadio’s docs to match the standard widget-doc contract by adding a dedicated
Required column and moving required/optional status there, while keeping
defaults only in Default. Make sure the table remains consistent with the
repository’s widget docs format and still reflects the WRadio props like value,
groupValue, onChanged, className, indicatorClassName, disabled, states, and
semanticLabel.

In `@doc/widgets/w-tabs.md`:
- Around line 37-47: The props table in w-tabs.md is using the wrong shape and
is putting requiredness into the Default column instead of the repo’s standard
Required column. Update the table to match the widget-doc template used
elsewhere: include separate Name/Type/Required/Default/Description columns, mark
required props explicitly in Required, and keep default values only in Default.
Make this consistent for the tabs-related props in the documented widget
section.

In `@example/lib/pages/styling/wind_recipe_basic.dart`:
- Around line 10-16: The `_button` intent token map is missing required
dark-mode counterparts for light-only color classes, so update the entries in
this recipe to ensure every color token has a matching `dark:` pair. Focus on
the `intent` mapping inside `_button` and adjust the `primary`, `destructive`,
and `ghost` variants so their className strings follow the same light/dark token
pattern used elsewhere in the examples.

In `@example/lib/pages/widgets/w_radio_basic.dart`:
- Around line 131-168: The `_buildRadioRow` UI in `WRadioBasic` makes the entire
plan card look selectable, but only the inner `WRadio` updates `_plan`; wire the
outer row/card to the same `setState` change so taps anywhere on the row select
the option, or remove the selected-card styling if the row is not meant to be
interactive. Use the existing `_buildRadioRow`, `WRadio<String>`, and `_plan`
state update as the place to apply the fix.

In `@example/lib/pages/widgets/w_switch_basic.dart`:
- Around line 45-49: The example switch thumb styles are missing explicit
dark-mode color pairs, so update each thumbClassName block in the switch basic
examples to include a matching dark: background token alongside bg-white. Apply
the same fix to all three thumbClassName instances in the widget examples so the
styling stays consistent with the rest of the example pages.

In `@lib/fluttersdk_wind.dart`:
- Around line 105-110: The main barrel export list in fluttersdk_wind.dart is
missing one public widget, so the package surface does not match the documented
27-widget count. Review the existing widget exports in fluttersdk_wind.dart and
restore the missing public export, likely WFormMultiSelect, or update the
README/CHANGELOG counts if that export is intentionally absent. Keep the fix
centered on the barrel file so package users importing fluttersdk_wind.dart can
access the full advertised API.

In `@lib/src/recipe/wind_recipe.dart`:
- Around line 279-304: _validateSelection currently only checks axis names, so
_resolveSelection in WindRecipe can accept invalid variant values and carry them
into resolved. Add value validation against the allowed options for each known
axis before merging selection/defaultVariants into resolved, and reject bad
values with ArgumentError using the existing axis/value lookup flow. Apply the
same validation path where WindRecipe and WindSlotRecipe build defaults so
invalid defaultVariants are also caught instead of silently dropping or matching
incorrect classes.

In `@lib/src/widgets/w_radio.dart`:
- Around line 150-166: The radio in WRadio is still actionable when onChanged is
null because WAnchor.onTap is only gated by disabled/_isSelected and
Semantics.enabled only tracks disabled, not whether the control can change.
Update WRadio so a null onChanged makes the radio non-interactive: disable tap
handling in the WAnchor setup and make the Semantics state reflect that
read-only radios are not enabled/clickable, using the existing WRadio/WAnchor/
Semantics block to keep the checked state intact.

In `@lib/src/widgets/w_switch.dart`:
- Around line 133-142: The switch semantics and tap handling in WSwitch are
still interactive when onChanged is null because the current WAnchor onTap and
Semantics enabled state only check disabled. Update the WSwitch build logic so
onChanged != null is required alongside !disabled before wiring the tap callback
and before reporting enabled in Semantics, keeping toggled/value behavior
unchanged.

In `@lib/src/widgets/w_tabs.dart`:
- Around line 133-135: Validate selectedIndex in WTabs before calling
panelBuilder(selectedIndex): if tabs is empty or selectedIndex is outside 0 ..<
tabs.length, fail fast at the widget boundary instead of building the panel.
Update the panel construction path in WTabs to guard this case before creating
the WDiv panel so invalid state is caught early and panelBuilder is only invoked
with a valid index.

In `@skills/wind-ui/SKILL.md`:
- Around line 107-123: The new WindRecipe and button examples are inconsistent
with the dark-pair rule taught earlier because they include light-only color
tokens like text-white and bg-blue-600 without matching dark: peers. Update the
example snippets in WindRecipe and the empty-state button usage so every color
token has a corresponding dark variant, keeping the examples aligned with the
rule described in SKILL.md and the related reference snippets.
- Around line 107-116: The compound variant example in WindRecipe is unreachable
because the size axis in the button recipe only defines sm and md, while the
WindCompoundVariant uses size = lg. Update the example so the compound condition
matches an existing size value, or extend the size variants in the same button
definition to include lg, keeping the base, variants, and compoundVariants in
sync.

---

Nitpick comments:
In `@example/lib/routes.dart`:
- Around line 209-214: The new widget imports in the routes import block are not
alphabetized, so reorder the w_* page imports in routes.dart to keep the
category block sorted consistently with the existing import ordering. Use the
existing route import section around wind_animation_wrapper_basic.dart and the
w_badge_basic/w_card_basic/w_radio_basic/w_switch_basic/w_tabs_basic symbols as
the reference point, and place the new imports in alphabetical order within that
block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8197e163-b910-455b-8e4f-73d251f49ba0

📥 Commits

Reviewing files that changed from the base of the PR and between bdcc572 and 980fbe1.

📒 Files selected for processing (29)
  • CHANGELOG.md
  • README.md
  • doc/styling/wind-recipe.md
  • doc/widgets/w-badge.md
  • doc/widgets/w-card.md
  • doc/widgets/w-radio.md
  • doc/widgets/w-switch.md
  • doc/widgets/w-tabs.md
  • example/lib/pages/styling/wind_recipe_basic.dart
  • example/lib/pages/widgets/w_badge_basic.dart
  • example/lib/pages/widgets/w_card_basic.dart
  • example/lib/pages/widgets/w_radio_basic.dart
  • example/lib/pages/widgets/w_switch_basic.dart
  • example/lib/pages/widgets/w_tabs_basic.dart
  • example/lib/routes.dart
  • lib/fluttersdk_wind.dart
  • lib/src/recipe/wind_recipe.dart
  • lib/src/widgets/w_badge.dart
  • lib/src/widgets/w_card.dart
  • lib/src/widgets/w_radio.dart
  • lib/src/widgets/w_switch.dart
  • lib/src/widgets/w_tabs.dart
  • skills/wind-ui/SKILL.md
  • test/recipe/wind_recipe_test.dart
  • test/widgets/w_badge_test.dart
  • test/widgets/w_card_test.dart
  • test/widgets/w_radio_test.dart
  • test/widgets/w_switch_test.dart
  • test/widgets/w_tabs_test.dart

Comment thread doc/styling/wind-recipe.md Outdated
Comment on lines +153 to +156
// Incorrect: base px-* vs variant p-* (same family, different granularity)
WindRecipe(
base: 'p-4',
variants: {'size': {'sm': 'px-2'}}, // px-2 does NOT override p-4; p-4 wins silently

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the reversed shorthand/longhand caption.

The prose says “base px-* vs variant p-*”, but the example below is base: 'p-4' with variant px-2. Swap the wording so the explanation matches the code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/styling/wind-recipe.md` around lines 153 - 156, The caption above the
WindRecipe example has the shorthand/longhand relationship reversed, so update
the prose to match the actual snippet. In the affected markdown section, adjust
the text around WindRecipe so it describes base as p-* and the variant as px-*
rather than “base px-* vs variant p-*”, keeping the explanation aligned with the
example.

Comment thread doc/widgets/w-badge.md
Comment on lines +28 to +32
| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `label` | `String` | **Required** | The text displayed inside the badge. |
| `className` | `String?` | `null` | Caller-supplied utility classes appended after the default layout classes (`inline-flex items-center rounded-full px-2 py-0.5`). Use this to supply `bg-*`, `text-*`, and their `dark:` pairs. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Match the standard widget-doc props table shape.

This table folds requiredness into the Default column, which makes the new doc inconsistent with the required doc/widgets/*.md format.

Suggested fix
-| Prop | Type | Default | Description |
-|:-----|:-----|:--------|:------------|
-| `label` | `String` | **Required** | The text displayed inside the badge. |
-| `className` | `String?` | `null` | Caller-supplied utility classes appended after the default layout classes (`inline-flex items-center rounded-full px-2 py-0.5`). Use this to supply `bg-*`, `text-*`, and their `dark:` pairs. |
+| Prop | Type | Required | Default | Description |
+|:-----|:-----|:---------|:--------|:------------|
+| `label` | `String` | Yes | - | The text displayed inside the badge. |
+| `className` | `String?` | No | `null` | Caller-supplied utility classes appended after the default layout classes (`inline-flex items-center rounded-full px-2 py-0.5`). Use this to supply `bg-*`, `text-*`, and their `dark:` pairs. |

As per coding guidelines, new widget documentation should include a Props table with Required/default/description columns.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `label` | `String` | **Required** | The text displayed inside the badge. |
| `className` | `String?` | `null` | Caller-supplied utility classes appended after the default layout classes (`inline-flex items-center rounded-full px-2 py-0.5`). Use this to supply `bg-*`, `text-*`, and their `dark:` pairs. |
| Prop | Type | Required | Default | Description |
|:-----|:-----|:---------|:--------|:------------|
| `label` | `String` | Yes | - | The text displayed inside the badge. |
| `className` | `String?` | No | `null` | Caller-supplied utility classes appended after the default layout classes (`inline-flex items-center rounded-full px-2 py-0.5`). Use this to supply `bg-*`, `text-*`, and their `dark:` pairs. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/widgets/w-badge.md` around lines 28 - 32, The widget docs props table is
using the wrong shape by folding requiredness into the Default column instead of
matching the standard required/default/description format used in
doc/widgets/*.md. Update the Props table in the badge documentation so each prop
follows the usual columns, with label marked required in its own Required field
and className showing its default separately, keeping the existing descriptions
intact.

Source: Coding guidelines

Comment thread doc/widgets/w-card.md
Comment on lines +28 to +33
| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `child` | `Widget` | **Required** | The main body content of the card. |
| `header` | `Widget?` | `null` | Optional widget rendered above the body. Typical use: a title row, a card image, or a section label. |
| `footer` | `Widget?` | `null` | Optional widget rendered below the body. Typical use: action buttons or status/metadata rows. |
| `className` | `String?` | `null` | Utility classes for the outer container. When omitted, defaults to `'w-full flex flex-col'` (no colors, no spacing). |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the required widget-doc props table columns here too.

The props table is missing the Required column, and Default is doing double duty for both requiredness and runtime fallback.

Suggested fix
-| Prop | Type | Default | Description |
-|:-----|:-----|:--------|:------------|
-| `child` | `Widget` | **Required** | The main body content of the card. |
-| `header` | `Widget?` | `null` | Optional widget rendered above the body. Typical use: a title row, a card image, or a section label. |
-| `footer` | `Widget?` | `null` | Optional widget rendered below the body. Typical use: action buttons or status/metadata rows. |
-| `className` | `String?` | `null` | Utility classes for the outer container. When omitted, defaults to `'w-full flex flex-col'` (no colors, no spacing). |
+| Prop | Type | Required | Default | Description |
+|:-----|:-----|:---------|:--------|:------------|
+| `child` | `Widget` | Yes | - | The main body content of the card. |
+| `header` | `Widget?` | No | `null` | Optional widget rendered above the body. Typical use: a title row, a card image, or a section label. |
+| `footer` | `Widget?` | No | `null` | Optional widget rendered below the body. Typical use: action buttons or status/metadata rows. |
+| `className` | `String?` | No | `'w-full flex flex-col'` | Utility classes for the outer container. |

As per coding guidelines, new widget documentation should include a Props table with Required/default/description columns.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `child` | `Widget` | **Required** | The main body content of the card. |
| `header` | `Widget?` | `null` | Optional widget rendered above the body. Typical use: a title row, a card image, or a section label. |
| `footer` | `Widget?` | `null` | Optional widget rendered below the body. Typical use: action buttons or status/metadata rows. |
| `className` | `String?` | `null` | Utility classes for the outer container. When omitted, defaults to `'w-full flex flex-col'` (no colors, no spacing). |
| Prop | Type | Required | Default | Description |
|:-----|:-----|:---------|:--------|:------------|
| `child` | `Widget` | Yes | - | The main body content of the card. |
| `header` | `Widget?` | No | `null` | Optional widget rendered above the body. Typical use: a title row, a card image, or a section label. |
| `footer` | `Widget?` | No | `null` | Optional widget rendered below the body. Typical use: action buttons or status/metadata rows. |
| `className` | `String?` | No | `'w-full flex flex-col'` | Utility classes for the outer container. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/widgets/w-card.md` around lines 28 - 33, The props table for w-card is
missing the requiredness column used by widget docs. Update the table near the
child/header/footer/className entries to use the standard widget-doc columns
from the guidelines, including a separate Required column and a Default column,
and mark each prop’s required status explicitly instead of overloading Default
for that purpose.

Source: Coding guidelines

Comment thread doc/widgets/w-radio.md
Comment on lines +38 to +48
| Prop | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| `value` | `T` | **Required** | The value this radio represents. |
| `groupValue` | `T?` | **Required** | The currently selected value for the group. When `value == groupValue`, `selected:` state activates. |
| `onChanged` | `ValueChanged<T>?` | **Required** | Called with `value` when this radio is tapped and not already selected. Pass `null` for a non-interactive radio. |
| `className` | `String?` | `null` | Utility classes for the outer ring shell. Defaults to `'w-5 h-5 rounded-full border border-gray-300 items-center justify-center selected:border-blue-500'` when `null`. |
| `indicatorClassName` | `String?` | `null` | Utility classes for the inner filled dot (visible only when selected). Defaults to `'w-2.5 h-2.5 rounded-full bg-blue-500 selected:opacity-100'` when `null`. |
| `disabled` | `bool` | `false` | Blocks tap and activates the `disabled:` prefix. |
| `states` | `Set<String>?` | `null` | Extra custom states merged with built-in `selected` and `disabled`. |
| `semanticLabel` | `String?` | `null` | Accessible label for the Semantics node. Required on icon-only radios. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the standard props-table shape.

This table folds requiredness into Default, but the widget-doc format here requires a dedicated Required column. Split that field out so the new WRadio doc matches the repository’s doc contract. As per coding guidelines, "New widget documentation should create doc/widgets/<w-name>.md with: one # title, ToC, <x-preview> tag, Props table (Required/default/description columns), Constructor signature, Styling Examples, and Related Documentation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/widgets/w-radio.md` around lines 38 - 48, The WRadio documentation table
uses the wrong prop-table shape by mixing requiredness into the Default column.
Update the props table in WRadio’s docs to match the standard widget-doc
contract by adding a dedicated Required column and moving required/optional
status there, while keeping defaults only in Default. Make sure the table
remains consistent with the repository’s widget docs format and still reflects
the WRadio props like value, groupValue, onChanged, className,
indicatorClassName, disabled, states, and semanticLabel.

Source: Coding guidelines

Comment thread lib/src/widgets/w_radio.dart
Comment thread lib/src/widgets/w_switch.dart
Comment thread lib/src/widgets/w_tabs.dart
Comment thread skills/wind-ui/SKILL.md
Comment on lines +107 to +116
```dart
final button = WindRecipe(
base: 'inline-flex items-center rounded-lg font-medium',
variants: {
'intent': {'primary': 'bg-blue-600 dark:bg-blue-500 text-white', 'ghost': 'bg-transparent text-blue-600 dark:text-blue-400'},
'size': {'sm': 'px-3 py-1.5 text-sm', 'md': 'px-4 py-2 text-base'},
},
compoundVariants: [
WindCompoundVariant(conditions: {'intent': 'primary', 'size': 'lg'}, className: 'shadow-lg'),
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make this compound example reachable.

The size axis only declares sm and md, so conditions: {'intent': 'primary', 'size': 'lg'} can never match. Either add lg to the axis or change the compound to one of the declared values.

🧰 Tools
🪛 SkillSpector (2.2.3)

[warning] 467: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.

(Excessive Agency (EA2))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/wind-ui/SKILL.md` around lines 107 - 116, The compound variant example
in WindRecipe is unreachable because the size axis in the button recipe only
defines sm and md, while the WindCompoundVariant uses size = lg. Update the
example so the compound condition matches an existing size value, or extend the
size variants in the same button definition to include lg, keeping the base,
variants, and compoundVariants in sync.

Comment thread skills/wind-ui/SKILL.md
Comment on lines +107 to +123
```dart
final button = WindRecipe(
base: 'inline-flex items-center rounded-lg font-medium',
variants: {
'intent': {'primary': 'bg-blue-600 dark:bg-blue-500 text-white', 'ghost': 'bg-transparent text-blue-600 dark:text-blue-400'},
'size': {'sm': 'px-3 py-1.5 text-sm', 'md': 'px-4 py-2 text-base'},
},
compoundVariants: [
WindCompoundVariant(conditions: {'intent': 'primary', 'size': 'lg'}, className: 'shadow-lg'),
],
defaultVariants: {'intent': 'primary', 'size': 'md'},
);

button() // uses defaults
button(variants: {'intent': 'ghost'}) // override one axis
button(variants: {'size': null}) // null clears the default (no size classes)
button(className: 'w-full') // caller suffix, appended last

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the new examples consistent with the dark-pair rule they teach.

Section 1 says every color token needs a dark: peer, but the new recipe example still uses text-white without one, and the empty-state button example uses bg-blue-600 hover:bg-blue-700 text-white with no dark equivalents. That makes the skill internally inconsistent for readers copying these snippets. As per coding guidelines, skills/wind-ui/**/*.md: “Keep skills/wind-ui/SKILL.md and matching skills/wind-ui/references/<topic>.md files self-consistent”.

Also applies to: 368-372

🧰 Tools
🪛 SkillSpector (2.2.3)

[warning] 467: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.

(Excessive Agency (EA2))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/wind-ui/SKILL.md` around lines 107 - 123, The new WindRecipe and
button examples are inconsistent with the dark-pair rule taught earlier because
they include light-only color tokens like text-white and bg-blue-600 without
matching dark: peers. Update the example snippets in WindRecipe and the
empty-state button usage so every color token has a corresponding dark variant,
keeping the examples aligned with the rule described in SKILL.md and the related
reference snippets.

Source: Coding guidelines

…rap, docs)

- WSwitch/WRadio/WTabs: onChanged == null now means non-interactive +
  disabled (no gesture handler, disabled: styling activates, Semantics
  enabled:false), matching the documented contract.
- WTabs: assert tabs.isNotEmpty and selectedIndex in range (drops the
  const constructor since a const assert cannot read tabs.length).
- WindRecipe shorthand/longhand lint: key the dedupe on the actual
  offending token set so distinct recipes each warn once.
- Examples: replace the no-op 'flex-wrap' with Wind's 'wrap' utility.
- Docs: WCard example uses onTap (not onPressed); WBadge example adds
  dark: pairs; wind-recipe.md fenced block gets a language (MD040).
@anilcancakir

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Pushed cef63c2 addressing the findings:

Behavior (Copilot)

  • WSwitch / WRadio / WTabs: onChanged == null now means non-interactive and disabled: no gesture handler is attached, the disabled: className state activates, and Semantics reports enabled: false. Matches the documented contract. Added tests for each.
  • WTabs: added a constructor assert for tabs.isNotEmpty and selectedIndex in range. This drops the const constructor (a const assert cannot read tabs.length); the only const WTabs sites were in this PR's own tests, now updated. No sibling-repo const WTabs usage exists.

Recipe lint (Copilot)

  • The shorthand/longhand debug lint cache now keys on the actual offending token set (sorted), so two distinct offending recipes each warn once instead of one global warning per family pair. Added tests for distinct-offense + idempotency.

Examples / docs

  • Replaced the no-op flex-wrap with Wind's wrap utility in the two example pages this PR adds (wind_recipe_basic.dart, w_badge_basic.dart). Note: several pre-existing example pages (responsive/, backgrounds/, etc.) also use flex-wrap; those predate this PR and are left for a separate cleanup to keep this diff scoped.
  • WCard dartdoc uses onTap (not onPressed); WBadge dartdoc examples now include dark: pairs; doc/styling/wind-recipe.md fenced block now has a text language (markdownlint MD040).

CI: Assert WASM-ready (pana)
This step (deploy.yml) globally activates the latest pana and asserts is:wasm-ready. The new code is pure package:flutter/widgets.dart + foundation.dart (no dart:io/html/ffi/js), so it cannot remove wasm-readiness; master passed the same assertion on an earlier pana build, so this looks like pana-version drift rather than a regression from this PR. It is not a required/branch-protected check. Happy to pin pana or dig further if you'd prefer.

Gates after the push: flutter analyze 0, dart format clean, flutter test 1548 pass (1 pre-existing skip), coverage 91.3%, cd example && flutter analyze 0.

WSwitch drops `relative` from the track: a single-child WDiv with
`relative` renders as a Stack (thumb pinned top-left) before the flex
branch runs, defeating `justify-*`. The track is now a flex Row, so
`justify-start` -> `checked:justify-end` slides the thumb. translate-x
(a no-op in Wind) is purged from the widget doc, example page, and docs.

WSelect swallows the opening gesture's own pointer-up via a one-shot
post-frame guard (mirroring the WPopover 1.1.2 fix), so the dropdown no
longer dismisses itself on the same web click that opens it; the shared
TapRegion group id alone was insufficient.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/src/widgets/w_select.dart`:
- Around line 650-653: The custom trigger builders are not participating in the
shared tap region, so taps on them can be treated as outside taps and close the
menu early. Update the `multiTriggerBuilder` and `triggerBuilder` paths in
`w_select.dart` to wrap their `GestureDetector` output in the same `TapRegion`
group used by the default trigger, reusing `_tapGroupId` so `onTapOutside`
ignores the opening tap before `_toggleMenu` runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba4312d7-4287-4652-b964-9840acf12027

📥 Commits

Reviewing files that changed from the base of the PR and between 980fbe1 and 50c3bed.

⛔ Files ignored due to path filters (1)
  • example/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • .gitignore
  • CHANGELOG.md
  • doc/styling/wind-recipe.md
  • doc/widgets/w-switch.md
  • example/lib/pages/styling/wind_recipe_basic.dart
  • example/lib/pages/widgets/w_badge_basic.dart
  • example/lib/pages/widgets/w_switch_basic.dart
  • lib/src/recipe/wind_recipe.dart
  • lib/src/widgets/w_badge.dart
  • lib/src/widgets/w_card.dart
  • lib/src/widgets/w_radio.dart
  • lib/src/widgets/w_select.dart
  • lib/src/widgets/w_switch.dart
  • lib/src/widgets/w_tabs.dart
  • test/recipe/wind_recipe_test.dart
  • test/widgets/w_radio_test.dart
  • test/widgets/w_switch_test.dart
  • test/widgets/w_tabs_test.dart
✅ Files skipped from review due to trivial changes (2)
  • .gitignore
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • example/lib/pages/widgets/w_switch_basic.dart
  • example/lib/pages/styling/wind_recipe_basic.dart
  • test/widgets/w_switch_test.dart
  • doc/widgets/w-switch.md
  • example/lib/pages/widgets/w_badge_basic.dart
  • lib/src/widgets/w_tabs.dart
  • test/widgets/w_radio_test.dart
  • lib/src/widgets/w_card.dart
  • doc/styling/wind-recipe.md
  • test/recipe/wind_recipe_test.dart
  • lib/src/widgets/w_badge.dart
  • lib/src/widgets/w_radio.dart
  • lib/src/recipe/wind_recipe.dart

Comment on lines +650 to +653
// Default trigger rendering. Tag it with the shared tap group so the open
// menu's onTapOutside ignores the tap that opened it.
return TapRegion(
groupId: _tapGroupId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and inspect relevant sections around the reported lines.
wc -l lib/src/widgets/w_select.dart
ast-grep outline lib/src/widgets/w_select.dart --view expanded | sed -n '1,220p'
sed -n '560,720p' lib/src/widgets/w_select.dart | cat -n

Repository: fluttersdk/wind

Length of output: 6178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "_tapGroupId|TapRegion|onTapOutside|onTapUpOutside|onTapInside" lib/src/widgets/w_select.dart

Repository: fluttersdk/wind

Length of output: 919


🏁 Script executed:

rg -n "_tapGroupId|TapRegion|onTapOutside|onTapUpOutside|onTapInside" lib/src/widgets/w_select.dart

Repository: fluttersdk/wind

Length of output: 919


Wrap the custom trigger builders in the shared TapRegion group.

multiTriggerBuilder / triggerBuilder still return bare GestureDetectors, while the overlay closes via onTapOutside on _tapGroupId. Tapping a custom trigger can therefore be treated as an outside tap and close the menu before _toggleMenu runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/widgets/w_select.dart` around lines 650 - 653, The custom trigger
builders are not participating in the shared tap region, so taps on them can be
treated as outside taps and close the menu early. Update the
`multiTriggerBuilder` and `triggerBuilder` paths in `w_select.dart` to wrap
their `GestureDetector` output in the same `TapRegion` group used by the default
trigger, reusing `_tapGroupId` so `onTapOutside` ignores the opening tap before
`_toggleMenu` runs.

Two independent self-close paths fired on the opening click (confirmed
with real browser mouse events via CDP, which dusk's synthetic taps did
not reproduce):

1. The overlay's TapRegion.onTapOutside received the opening tap's own
   pointer-up, because OverlayPortal mounts synchronously on the frame it
   opens. Fixed by deferring the overlay mount one frame via
   addPostFrameCallback, so the pointer-up is fully dispatched before the
   overlay's TapRegion exists. (A shared groupId and a one-frame suppress
   guard, as used for WPopover, were both insufficient here.)
2. _onFocusChange closed the menu when the trigger lost focus, which web
   does transiently (~150ms) right after the opening click. Removed the
   focus-loss auto-close: the tap-only trigger cannot be opened by
   keyboard, so it added no real dismissal path while firing spuriously.

Dismissal is now driven by an outside tap and option selection only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants