Skip to content

Latest commit

 

History

History
90 lines (71 loc) · 5.07 KB

File metadata and controls

90 lines (71 loc) · 5.07 KB

Wind UI — Architecture

How the package is organized internally. Companion to README.md (the user-facing overview) and CLAUDE.md (the contributor process rules).

Directory layout

lib/src/
├── widgets/          # 20 user-facing W-prefix widgets (see .claude/rules/widgets.md)
├── parser/
│   ├── wind_parser.dart      # Orchestrator: first-match-wins routing to 17 parsers
│   ├── wind_style.dart       # Immutable value object (parse output)
│   ├── wind_context.dart     # Theme + breakpoint + brightness + platform + states
│   └── parsers/              # 17 domain parsers (see .claude/rules/parsers.md)
├── theme/
│   ├── wind_theme.dart       # WindTheme StatefulWidget + WindThemeController
│   ├── wind_theme_data.dart  # 23 configurable fields; merges with defaults/
│   └── defaults/             # 16 default token scales
├── dynamic/          # WDynamic JSON renderer (see .claude/rules/dynamic.md)
├── state/            # WindAnchorStateProvider (hover/focus/press via InheritedWidget)
├── utils/            # color_utils, wind_logger, extensions, helpers
├── core/             # platform_service singleton
├── debug_resolver.dart   # implements contracts package's WindDebugResolver
└── wind_facade.dart      # Wind.installDebugResolver() entry point

Data flow

className: String
    ↓
WindParser.parse(className, context, states:)
    ↓
17 domain parsers (first match wins)
    ↓
WindStyle (immutable value object)
    ↓
Widget.build()  // composes the Flutter tree from style fields

Parser cache

Key: className + activeBreakpoint + brightness + platform + sorted(activeStates). Stable across rebuilds; near-100% hit rate in production. Invalidate via WindParser.clearCache() (test setUp only — never in production code).

WindTheme integration

Wrap the app once at the top:

WindTheme(
  data: WindThemeData(/* 23 fields, all optional, merged with defaults */),
  builder: (context, controller) => MaterialApp(
    theme: controller.toThemeData(),
    home: ...,
  ),
);

WindTheme lives BELOW MaterialApp in the actual widget tree (the builder pattern inverts the apparent order). Overlay contexts cannot reach WindTheme via ancestor walk — use the State's context when calling WindParser.parse from inside OverlayEntry.builder.

Diagnostics bridge

Call Wind.installDebugResolver() inside kDebugMode to expose 6 fields (className, breakpoint, brightness, platform, states, bgColor, textColor) to any consumer of WindDebugRegistry.current (e.g., fluttersdk_dusk for E2E and any runtime inspector). Idempotent; gated by kDebugMode so release builds tree-shake it. There is no WindDuskIntegration class in v1.0 — replaced by this bridge via the fluttersdk_wind_diagnostics_contracts contract.

Widget API surface (invariants locked at 1.0.0)

  • child XOR children — never both. Asserts at construction.
  • className: String? is the styling API; explicit props (backgroundColor, foregroundColor) are escape hatches that override className.
  • states: Set<String>? activates prefixed classes (hover:, selected:, loading:, custom).
  • WIcon(Icons.X) — use the *_outlined variant; Wind icons are outlined by convention.
  • WAnchor auto-wraps WDiv / WButton when className contains hover: / focus: / active:.

Wind ≠ Tailwind cheat sheet

A contributor fluent in Tailwind will trip over these:

Tailwind expectation Wind reality
flex-wrap enables wrapping Use wrap instead. flex-wrap is a no-op (Flutter Wrap is a separate widget).
text-* means font-size Overloaded: font-size (text-xl), color (text-red-500), alignment (text-center). Resolved by ordered regex; do not assume one meaning.
Font sizes go to 9xl Stop at 6xl (60px). 7xl / 8xl / 9xl silently no-op.
text-7xl typo fails loudly Every unknown token fails silently — parser ignores it. Spell-check tokens by hand.
Spacing in rem Logical pixels (4 px per unit; p-4 = 16 px).
w-full inside a row works Causes overflow. Use flex-1 for row children.
h-full inside a scrollable parent works Infinite-height layout error. Use min-h-screen.
overflow-y-auto enables iOS tap-to-top Pass the constructor prop scrollPrimary: true as well — there is no className for it.
dark: is optional Every bg-*, text-*, border-*, ring-*, fill-* carries a dark: pair in the same className. Missing dark pair is a bug.
divide-*, cursor-*, filter, backdrop-blur, container queries, group-* / peer-*, @apply Not implemented. No silent no-op intent — these tokens simply do not exist.

Wind-only additions Tailwind lacks: ios: / android: / web: / mobile: platform prefixes; axis-min / axis-max for MainAxisSize control; WDynamic JSON-driven widget trees; state prefixes (hover:, focus:, disabled:, loading:, custom) as first-class className citizens.