Repository navigation
Releases
Releases · anilcancakir/wind_kit
1.12.1
Fixed
llms.txtlinksPROJECT.md. The index now points an agent at the scope and the decisions already made. (llms.txt)- The
grid-cols-Ndocs no longer say the grid is a FlutterGridViewwithcrossAxisCount: N. Agridis aWrapwith computed column widths (or, withitems-stretch, a column of equal-height rows); the Quick Reference ondoc/layout/grid.md, the Display page and the gallery now read "N equal columns", and the page names theRowused with horizontal overflow scroll. (doc/layout/grid.md,doc/layout/display.md,example/lib/pages/layout/display.dart,example/lib/pages/layout/grid_cols.dart, #305) - The press-state advice is one answer everywhere. The README and the
WDivpage said to passstates: {'pressed'}, while the state guide,llms.txtand the skill saidstates: {'active'}; they now all sayactive, the state theactive:prefix reads. (README.md,doc/widgets/w-div.md, #305) - Eleven doc pages gain a
## Related Documentationsection (dark mode, debugging, theme binding, theming, utility-first, installation, upgrade guide and the colour, responsive, spacing and typography helpers), and the Z-Index, Opacity and WindRecipe pages use the same bare-list table of contents as every other page. (doc/core-concepts/,doc/getting-started/,doc/utilities/,doc/layout/z-index.md,doc/styling/opacity.md,doc/styling/wind-recipe.md, #305) - The Windows and macOS example runners carry
com.anilcancakir.wind_kitinstead of the oldcom.fluttersdk.windcompany string, and the stale AndroidMainActivityof the old package name is gone. (example/windows/runner/Runner.rc,example/macos/Runner/Configs/AppInfo.xcconfig,example/android/, #305) - The tables in the README and the skill are left-aligned. They used the unaligned
|---|separator, which the repo standard does not accept (every table is left-aligned with an explicit colon); they use|:---|like the docs. Thewind-uiskill's frontmatter listsversionbeforewhen_to_use, as the standard orders it, andexample/pubspec.lockrecordswind_kit1.12.0. (README.md,skills/wind-ui/SKILL.md,skills/wind-ui/references/{debug,dynamic,forms,layouts,tailwind-divergence,theme,tokens}.md,example/pubspec.lock, #307)
1.12.0
Changed
- Renamed from
fluttersdk_windtowind_kit. The package, its barrel (package:wind_kit/wind_kit.dart) and every test carry the new name, and the diagnostics dependency iswind_kit_diagnostics_contracts ^1.3.0(wasfluttersdk_wind_diagnostics_contracts ^1.2.0;WindDebugRegistry,WindDebugResolverandWindPerfResolverare unchanged). Migration: replace the dependency inpubspec.yamland the importpackage:fluttersdk_wind/fluttersdk_wind.dartwithpackage:wind_kit/wind_kit.dart. EveryW*andWind*class, every className token and the parser cache are unchanged by the rename; the Material types in the public signatures move tomaterial_uiin the next entry, so an app that passesColorsor aThemeDatafollows that migration too. Releasing both names in parallel was rejected because two packages with one codebase would split the ecosystem's dependants between them. The oldfluttersdk_windpackage receives no further feature releases. Do not depend on bothfluttersdk_windandwind_kitin one app: each has its ownWindThemeand its ownWindDebugRegistry, so aWDivfrom one finds no theme from the other. The packages that depend on it (magic, magic_starter, magic_notifications, magic_devtools) move to their wind_kit releases together, and tapir moves towind_kit_diagnostics_contractsin step, or its snapshots lose the Wind data.pubspec.yaml,lib/wind_kit.dart,test/,example/pubspec.yaml(#272) - BREAKING.
wind_kitbuilds onmaterial_ui, so an app usespackage:material_ui/material_ui.dartfor itsMaterialApp,ThemeData,ColorsandMaterialColor. Flutter 3.50 deprecatespackage:flutter/material.dartandpackage:flutter/cupertino.dart, and pana's static analysis score drops 10 points for a package that still imports them, so every file inlib/now importsmaterial_ui ^1.11.0instead. The new package defines its ownMaterialApp,ThemeData,MaterialColorand localisations, so an app that still importspackage:flutter/material.dartgets "No MaterialLocalizations found" from the default close button ofWKeyboardActionsand from the selection toolbar ofWText(selectable: true), aThemeDatatype mismatch fromWindThemeData.toThemeData()andWindThemeController.toThemeData(), and aMaterialColortype mismatch wherever it passes colours: thecolorsmaps ofWindThemeData(constructor andcopyWith),WindThemeController(colors,updateTheme) andwindColors, andinvertMaterialColor, take thematerial_uitype, soWindThemeData(colors: {'brand': Colors.indigo})with Flutter'sColorsno longer compiles. Runflutter pub add material_uiso the app declares the package it now imports, migrate its imports in the same upgrade and replace theflutter_localizationsdelegates (GlobalMaterialLocalizations,GlobalCupertinoLocalizations) withGlobalMaterialLocalizations.delegatesfrommaterial_ui. Keeping the deprecatedpackage:flutter/material.dartbarrels was rejected: Flutter 3.50 flags every import of them, pana takes 10 static analysis points off, and the barrels are slated for removal.pubspec.yaml,lib/,test/,example/pubspec.yaml,example/lib/,CLAUDE.md(#279) - BREAKING.
wind_kitneeds Dart 3.13 and Flutter 3.47.material_ui1.11 andcupertino_ui1.1 require that floor, so the old one (Dart 3.4, Flutter 3.27) no longer resolves with them; an app on an older SDK stays onfluttersdk_wind1.11.x, the last release under the previous name. Staying on the old floor was not an option, sincematerial_uiandcupertino_uirequire the new one. The floor also moves the package to the tall Dart formatter, solib/andtest/are reformatted with it, and the lints the new floor enables are satisfied without a change to any public signature.pubspec.yaml,CLAUDE.md,lib/,test/(#285) - The user docs, the
wind-uiskill (2.32.0) andARCHITECTURE.mdno longer promise behaviour the code does not have. An audit of 42 changed hunks againstlib/found the old text false in 26 of them: aWindControllerclass that does not exist,states: {'pressed'}where only'active'reachesactive:, an'input-border'theme key thatWInputnever reads, camelCasecustomIconskeys that never match, font stacks that were wrong, and a cache note that said the parser cache never needs clearing in production although its key leaves out the theme maps. Deleting the unverified claims instead of correcting them was rejected because pub.dev and the skill registry publish these pages as the reference.ARCHITECTURE.mdgains alias expansion, the cache bypass, invariants and a change guide, and the.claude/hook formats then analyses a Dart file in one script.doc/,skills/wind-ui/,llms.txt,ARCHITECTURE.md,.claude/settings.json,.claude/hooks/dart-format-analyze.sh(#273, #274, #275, #276, #277, #288, #292) - The gallery is
wind_kit_example, builds from the working tree and needs Dart 3.13 and Flutter 3.47. The example package is renamed from the previous name and depends onwind_kitwithpath: ../, so the gallery always shows the code it ships with instead of the last published release; its native ids arecom.anilcancakir.wind_kit(Android and the other platforms) andcom.anilcancakir.windKit(Apple, which takes no underscore); its SDK floor matches the package's; and the unusedcupertino_uidependency is dropped. Pinning the gallery to a hostedwind_kitrelease was rejected because the docs site embeds it as the live demo of the current code, and a hosted pin would lag every change until a publish.example/pubspec.yaml,example/lib/,example/android/,example/ios/,example/macos/,example/linux/,example/windows/,example/web/,.github/dependabot.yml(#278, #282, #286) - The pub.dev description is marketing-first and the lockfile is no longer tracked. The
pubspec.yamldescription is the 174-character one the GitHub repository carries, so pub.dev and GitHub show the same pitch, thedartdoc_options.yamlsource links point atanilcancakir/wind_kit, and the rootpubspec.lockis untracked and ignored, because a library does not commit its lockfile and an app resolves its own graph. Tracking the lockfile was rejected because it pins the CI to versions no consumer resolves and hides a breaking upper bound until a user hits it.pubspec.yaml,dartdoc_options.yaml,.gitignore,pubspec.lock(#283) - The package and the gallery lint with
flutter_lints ^6.0.0, and the 13 findings it adds are fixed. The Dart 3.13 floor lets the newer rule set resolve; it flagsif (x != null) x!list and map entries (use_null_aware_elements) and a double underscore parameter (unnecessary_underscores).dart fixrewrote them to?xentries and_, with identical behaviour: the 2037 tests and the 96.1% coverage are unchanged. Staying onflutter_lints ^5.0.0was rejected because it keeps the repo linting against a rule set that cannot see the null-aware syntax the new floor already allows, and turning the two rules off inanalysis_options.yamlwas rejected because it would hide the same findings in new code.pubspec.yaml,example/pubspec.yaml,lib/src/utils/wind_perf_counters.dart,lib/src/widgets/(w_breakpoint.dart,w_card.dart,w_div.dart,w_form_date_picker.dart,w_form_input.dart,w_form_select.dart) (#289) WInputdraws its own selection handles and selection toolbar, so the package no longer importscupertino_uiand a bareWidgetsApproot still gets handles and a copy, cut, paste and select-all menu. The Cupertino handles (cupertinoTextSelectionHandleControls), the Cupertino toolbar and one grey constant were the only use ofcupertino_ui, so they are replaced byWindTextSelectionHandleControls(a lollipop handle in the ambient cursor colour, blue-500 without one, still mixing inTextSelectionHandleControlssoMaterialLocalizationsis never read) andWindTextSelectionToolbar(a rounded row with a shadow, positioned byTextSelectionToolbarLayoutDelegate, white or slate-800 by the field's own brightness and refreshed while open when that brightness changes), both built onpackage:flutter/widgets.dartonly and not exported. The toolbar scrolls sideways instead of paging when its buttons are wider than the screen (seven iOS actions or German labels on a 320px phone), so every action stays reachable, and it draws no arrow. Look Up, Search Web and Share now carry theirWidgetsLocalizationslabels instead of rendering blank, and an action with no label at all (Live Text, whichWidgetsLocalizationsdoes not name) is left out.material_uistill pullscupertino_uiin transitively, so an app's resolved graph does not shrink.pubspec.yaml,lib/src/widgets/w_input.dart,lib/src/widgets/w_text_selection.dart,test/widgets/w_text_selection_test.dart,test/widgets/w_input/selection_test.dart,CLAUDE.md(#287)- The dartdoc on
WindTheme,WImage,WSvg,WPopover,WKeyboardActionsand the colour and context helpers now describes what the code does. Each claim was checked against the source:onThemeChangedalso fires forsetTheme,updateTheme(brightness:),resetToSystemand a newdata:from a parent rebuild when the brightness differs (never for an OS brightness change),WImageandWSvgignoreshadow-*,w-full,max-w-*andanimate-*, aWPopovermaxHeightcaps the overlay without scrolling its content, the keyboard bar's default button readsMaterialLocalizations.okButtonLabel, andwColorExttakesshade:. The examples in the comments carry theirdark:pairs anddebugPrintreplacesprint. The gallery pagez_index_hover.dartnow shows paint order and hover shadows instead of az-*class that no widget reads. Comments only, no behaviour change. Deleting the wrong claims instead of correcting them was ...
1.11.1
Fixed
- A horizontally scrolling
WDiv(overflow-x-*alone oroverflow-autoon both axes) paints its border, background and padding across at least the full viewport, so aw-full flex flex-row overflow-x-auto border-btab strip keeps its baseline edge to edge. The div's own box was built inside the horizontalSingleChildScrollViewand sized to its content, so with four tabs on a desktop the baseline stopped after the fourth one; thew-fullit carried sized the scroll view, not the box inside it. CSS paints those on the scroll container. The box is now wrapped in aWindMinWidthBoxin a newloosemode: at least the published viewport width, and free to be wider, so content wider than the viewport still scrolls. Reusing the tight modew-fullchildren get was rejected: it forced wide content into the viewport width and overflowed. The div's ownmin-w-*is not applied here, since it sizes the scroll container rather than what it scrolls. (#264) (lib/src/widgets/w_div.dart,lib/src/widgets/wind_min_width_scroll.dart,test/widgets/w_div/overflow_test.dart,test/flex/wind_min_width_scroll_test.dart,doc/layout/overflow.md)
1.11.0
Added
wWindowClassOf(context)returns aWindowSizeClass(compact,medium,expanded), so a shell chooses a bar or a rail by window width and not by platform. Wind maps iOS toisMobile(platform_service.dart), so an iPad reads as a phone by platform, andwIsTabletis only themdtolgband, which turns false again on a wide iPad or a desktop window; neither answers "does this window have room for a rail". The classes are cut at the theme's ownmdandlgscreens (compactbelowmd,mediumup to belowlg,expandedfromlg), so a customscreenstable moves them, and it reads the size aspect ofMediaQuerythroughwScreenIs, so a keyboard inset does not rebuild the caller. A platform or device-idiom check was rejected: split view puts an iPad in a phone-sized window.wIsTabletandisMobileare unchanged. (#259) (lib/src/utils/wind_extensions.dart,test/utils/wind_extensions_test.dart,doc/utilities/responsive-helpers.md,example/lib/pages/utilities/responsive_helpers_basic.dart,skills/wind-ui/SKILL.md,skills/wind-ui/references/theme.md)
1.10.0
Added
WImagesupports Tailwind's object-position classes, soobject-cover object-topkeeps the upper part of a backdrop in a short, wide banner.object-coveralways centred the crop, so faces and skylines in the top of a TV or film backdrop were cut off in aw-full h-fullhero, and there was no alignment prop to reach for.object-bottom,object-center,object-left,object-left-bottom,object-left-top,object-right,object-right-bottom,object-right-topandobject-topnow map to the matchingAlignmenton the renderedImage(Alignment.centerstays the default), with every fit. They follow the object-fit design rather than a new one: read straight off the className inWImage, never inWindStyle, first match wins, and a prefixed form such asmd:object-topis not applied (asmd:object-coveris not today). The nine tokens join the parser's known-unparsed set, so they no longer log the one-time unknown-className hint. (#257) (lib/src/widgets/w_image.dart,lib/src/parser/wind_parser.dart,test/widgets/w_image_test.dart,test/parser/wind_parser_test.dart,doc/widgets/w-image.md`)- A number
WInputorWFormInputaccepts the decimal mark the user's locale writes. The defaultInputType.numberfilter allowed only., and swallowed any other keystroke, so a Turkish user typing2,5got25, a silent 10x amount, and every app writing a comma locale had to replace the filter with a formatter of its own. A newdecimalSeparator(default'.', one character other than a digit,+or-, asserted) names the one mark the filter accepts;,keeps2,5and refuses a., and the default keeps today's behaviour. The field keeps the text as typed, so converting to the.a wire format expects stays with the caller. Accepting both marks at once was rejected: in English1,000is a thousand, and reading it as1.000would turn a grouping comma into a decimal one. CallerinputFormattersstill replace the filter entirely. (#253) (lib/src/widgets/w_input.dart,lib/src/widgets/w_form_input.dart,test/widgets/w_input_test.dart,test/widgets/w_form_input_test.dart,doc/widgets/w-input.md,doc/widgets/w-form-input.md,skills/wind-ui/SKILL.md)
1.9.4
Fixed
- A single-line
WInputcentres its text and placeholder vertically in a box taller than the line. Withmin-h-11orh-12the text and the placeholder were drawn at the top of the box, where a CSS input centres them. The box size lands on the outerContainer, and below it the editable sat directly in the contentPadding(or, with a placeholder, in aStackthat alignedtopStart), so the extra height stayed under the text. Measured on a 44 px box, the caret line centred at 7 px against a box centre of 22 px, and at 7 px against a box centre of 24 px on a 48 px box. The no-adornment path now wraps the field in anAlign(AlignmentDirectional.centerStart,heightFactor: 1) inside the content padding, so it stays one line tall and takes the box's min height through the constraints; the placeholder sits in that same aligned line. Horizontal position, content padding and the height of a field with no height class are unchanged (30 px for one 14 px line with 8 px of vertical padding), the whole box stays tappable, and a multiline field keeps its top alignment. The editable (and its text-field semantics node) is now one line tall inside the box, as it already was with a placeholder, so an accessibility focus rect or a dusk bound for a field with no placeholder covers the line rather than the padded box. The prefix and suffix path already centred through itsRowand is untouched. (lib/src/widgets/w_input.dart,test/widgets/w_input_test.dart,doc/widgets/w-input.md)
1.9.3
Fixed
- A closed
WSelectpublishes one button node with the tap action, so a screen reader announces it once. The trigger was aSemantics(container: true, button: true)node around aMergeSemantics, and a container is a semantics boundary, so the merge did not fold into it: it became a second boundary. The platform received two nested nodes with the same name, an outer button with the label and no action, and an inner one with the merged trigger text, the tap and the focus. Neither isisMergedIntoParent, so dusk listed the control twice and an agent could pick the action-less node. Measured on the default single trigger, the multi trigger with chips and a customtriggerBuilder: each had two platform nodes, now one. The outer node now carries the label, the button flag, the enabled state and the tap that opens or closes the menu (absent when disabled), and anExcludeSemanticsdrops the trigger subtree below it, asWAnchordoes for an explicit label.Focusstays outside the exclusion so the node keepsfocusable,focusedand the focus action. Nothing reachable is lost: the chips' remove taps and a custom trigger's content were already merged into the inner node, and the search field lives in the overlay, which this does not touch. (lib/src/widgets/w_select.dart,test/widgets/w_select_test.dart,doc/widgets/w-select.md)
1.9.2
Fixed
- A
WSelectis named by its selection, and by its placeholder only while it is empty. The closed trigger's accessible name was theplaceholderwhenever one was set, and the default is the English "Select an option", so a select showing "Apple" was announced as "Select an option" and, in a Turkish app, in the wrong language. The name now follows the trigger's visible text: the selected option's label, the labels joined with ', ' for a multi select, and the placeholder (none when it is empty) when nothing is selected, as Flutter'sDropdownButtonnames itself by its visible child. Visible to dusk and tests: a script that found a selected select by its placeholder text now finds it by the selected label. (lib/src/widgets/w_select.dart,test/widgets/w_select_test.dart,doc/widgets/w-select.md,doc/widgets/w-form-select.md)
1.9.1
Fixed
- Every
WButtonis a button to assistive technology, including one with no callback, and it is not enabled while disabled, loading or without a callback. A button with noonTap,onLongPressoronDoubleTap(the commondisabled: !canSubmit, onTap: canSubmit ? submit : null, oronTap: nullalone) gaveWAnchornothing to gesture on, and a gestureless anchor publishes no node, so its label read as plain text. It now publishes one button node of its own withenabled: false, labelled by its child text or itssemanticLabel, and no tap action; asemanticLabelon such a button used to publish an enabled button. An enabled button keeps the single nodeWAnchorgives it, and a gesturelessWAnchorused for styling still publishes nothing. (lib/src/widgets/w_button.dart,test/widgets/w_button_test.dart) - A disabled or loading
WButtonis still a button, announced as disabled. It handedWAnchorno callbacks while inert, and a gestureless anchor is a styling wrapper that publishes no button node, so screen readers lost the control and Playwright'sgetByRole('button')found nothing. Visible to dusk: a disabled or loading button now appears in a snapshot as a button withenabled: falseand its label (theloadingTextwhile loading), where it used to be absent. The callbacks now reachWAnchorwithisDisabledset for loading as well as disabled, soWAnchor's own guard keeps tap, long press, double tap, the activation key and the lifted semantics actions from firing; loading still activates onlyloading:, notdisabled:. A loading button is no longer a Tab stop and no longer lightshover:orfocus:, as Material's buttons behave with a null callback. (lib/src/widgets/w_button.dart,test/widgets/w_button_test.dart) - A
WPopovertrigger is one button. The trigger'sSemanticsnow merges its subtree, so the documentedWButton(onTap: null)trigger (which now publishes its own disabled button node) folds into the popover's single enabled button named by its label instead of nesting a second one. (lib/src/widgets/w_popover.dart,test/widgets/w_popover_test.dart) - A
WSelectshows keyboard focus, and a disabled one is skipped by Tab. The trigger litfocus:only while its menu was open, so a keyboard user tabbing through a form saw nothing on the select they had landed on. It now also lights while the trigger holds focus in the traditional (keyboard) highlight mode, so a tap does not ring it. ItsFocusnode takescanRequestFocus: !disabled, andfocusis never active on a disabled select. The trigger publishes its state throughWindAnchorStateProvider, asWPopoverdoes, so theWAnchorafocus:class wraps around the trigger div inherits instead of being a second Tab stop that a disabled select kept. (lib/src/widgets/w_select.dart,test/widgets/w_select_test.dart)
1.9.0
Changed
- BREAKING (behaviour):
tracking-*is now em, as in Tailwind, so letter spacing scales with the font size. It ships in 1.9.0, a minor release, as the behavioural break in 1.4.0 did, so a^1.8constraint picks it up on the nextpub get. Every app that uses atracking-*class renders differently, and a customWindThemeData.trackingmap is now read as em multipliers: a value written as pixels ('tight': -1) now means -1em, so convert it (-1pxon 14px text is about-0.07). The defaults were -2/-1/0/1/2/4 logical pixels under comments that said -0.05em through 0.1em, and Flutter'sTextStyle.letterSpacingis pixels, sotracking-tightwas a flat -1px: -0.07em on 14px text, nearly three times Tailwind's -0.025em, and a 14px semibold title rendered with its letters colliding. The theme map now holds Tailwind's values (-0.05, -0.025, 0, 0.025, 0.05, 0.1).WindStyle.letterSpacingcarries a newletterSpacingUnit(WindLetterSpacingUnit.emor.px) andtoTextStyle({inheritedFontSize})converts to pixels against the size the text renders at: thetext-*size in the same className (responsive and state variants included), else the inheritedDefaultTextStylesize, elsekDefaultFontSize(14), the size unsized text paints at (the theme'sbaseis not used, because no text paints at it unless aDefaultTextStylesets it). The conversion happens at build inWText,WDiv,WButtonandWInput, because the parser cannot know the inherited size and its cache key does not carry it; resolving to a fixed 16 in the parser was rejected for exactly that reason.WTextresolves against atextStyle:font size when one is given.WDivandWButtonpublish the resolved pixels to their children, which is how CSS inherits a computedletter-spacing.WInputwith notext-*resolves againstkDefaultFontSize(14), sinceEditableTextreads noDefaultTextStyle. The newwInheritedFontSize(context)helper exposes the same fallback chain for custom widgets. The em is taken from the unscaled size: Flutter scalesfontSizeby the accessibilityTextScalerand leavesletterSpacingas written. (lib/src/theme/defaults/tracking.dart,lib/src/theme/wind_theme_data.dart,lib/src/parser/wind_style.dart,lib/src/parser/parsers/text_parser.dart,lib/src/utils/wind_helpers.dart,lib/src/widgets/w_text.dart,lib/src/widgets/w_div.dart,lib/src/widgets/w_button.dart,lib/src/widgets/w_input.dart)
Fixed
WButtonpaints itsopacity-*, so adisabled:opacity-50button looks disabled. It built its box without the parsed opacity, sodisabled:,loading:andhover:opacity classes parsed and painted nothing, and a disabled button looked enabled. It now wraps the box in anOpacity, or anAnimatedOpacityunder aduration-*, asWDivdoes. (lib/src/widgets/w_button.dart)- A
ring-*is an outline, so a focused element with no background is no longer a solid block. Wind drew the ring as a spreadBoxShadow. A browser clips a box-shadow to the outside of its element, so Tailwind's ring reads as an outline; Flutter does not clip it, and an element with no background filled with the ring colour. A host app's rail drew every keyboard-focused icon button as a solid blue block with the glyph lost inside it.WDivnow paintsWindStyle.ringOutline, a stroke over the box on its own corner radius: outside the edge, inside it forring-inset(which never showed over a background before), and moved out byring-offset-*with the gap left unpainted (the offset used to be covered by the ring itself). The ring no longer joins the box shadows. A focusedWInputpaints the same outline instead of its ring shadow (it still promotes the ring to its border), so a transparent field no longer fills with the ring colour. The docs'ring-offset-whiteexample is gone: Wind has no ring-offset colour and the class did nothing. (lib/src/parser/wind_style.dart,lib/src/widgets/w_div.dart,lib/src/widgets/w_input.dart,test/pixel/ring_outline_pixel_test.dart,test/widgets/w_input_test.dart,test/pixel/radius_ring_border_pixel_test.dart,doc/borders/ring.md) WButtonpaints itsring-*andshadow-*. It built its box from the parsed decoration alone, so both parsed and painted nothing: a host app's dialog buttons showed no focus while Tab moved between them. It now foldsboxShadowinto the decoration and paintsringOutlineover the box. (lib/src/widgets/w_button.dart,test/widgets/w_button_test.dart)- A
WPopoveropens from the keyboard and shows where focus is. Its trigger took focus on Tab and then answered nothing: Enter and Space did not open it, Escape did not close it, and nofocus:class on the trigger could light, so a host app's notification bell was a Tab stop a keyboard user could neither see nor use. The trigger now answersActivateIntent(Enter, Space, the Android TV D-pad centre), asWAnchordoes, only where a tap would toggle; Escape closes an open popover from the trigger or from inside the panel, returns focus to the trigger and consumes the key, so a popover in a dialog closes alone; and the popover publishes its trigger's hover, focus and disabled state throughWindAnchorStateProvider, so a plainWDivtrigger answersfocus:and its own styling wrapper stops being a second Tab stop. Escape is a key handler on the focus tree, above the portal. In a host app's web walk it first went deaf: the browser drops the trigger's focus as the panel mounts, so after a keyboard open Escape reached nothing and the next Tab started over from the top of the page. The first focus change after a keyboard open, when it lands on a scope rather than a control, now hands focus back to the trigger, once. AHardwareKeyboardhandler was rejected for Escape: returning true from one does not stop the key reaching the focus tree, so a popover open in a dialog closed together with the dialog. The trigger and the panel are oneFocusTraversalGroupin widget order, so Tab goes from the trigger into the panel: reading order had ranked a control beside the trigger before the panel below it, and a panel flipped above its trigger before the trigger itself, so the walk measured a phone shell that never reached the panel and a rail bell whose panel was reachable only with Shift+Tab. ADismissIntentaction was rejected becauseShortcutManagerasks only the nearest action for an intent, so a disabled one would have swallowed Escape on a closed popover and a dialog holding it would have stopped closing. A disabled popover is no longer a Tab stop. (lib/src/widgets/w_popover.dart,test/widgets/w_popover/keyboard_test.dart,doc/widgets/w-popover.md,skills/wind-ui/SKILL.md) - Arbitrary
tracking-[...]honours its unit and its sign.tracking-[0.1em]stripped the unit and set 0.1 logical pixels, and a negative value such astracking-[-0.02em]did not match the pattern at all, so it was dropped. Anemvalue is now em, apxvalue or a bare number is logical pixels (as inleading-[24]andtext-[20]), and both take a negative sign. (lib/src/parser/parsers/text_parser.dart,test/parser/parsers/text_parser_test.dart,doc/typography/letter-spacing.md)