You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
BREAKING
MagicVaultService.macOsUsesDataProtectionKeychain is now bool?, and an absent security.vault.macos_data_protection_keychain means "probe" rather than true. Code that read the field as a bool no longer compiles. The config key stays as the explicit override, and true or false is honoured without a probe.
Added
A route can open as a bottom sheet with its own URL.RouteDefinition.overlay(base:, canDismiss:) makes to() always push the route as a ModalBottomSheetRoute on the root navigator over the page on screen (a deep link or post-login redirect through to() included); back(), the system back button and browser Back return to that page with its state, and the address bar shows the sheet's URL. A replacing arrival (the initial location, platform route information such as a reload, a go_router go()) is redirected to base and the sheet is pushed over it in the next frame, only if the navigation actually landed on base; MagicRoute.replace() to an overlay is not such an entry. base must name another registered page route, and may declare parameters, filled from the overlay location, that the overlay path must also declare; otherwise the router throws a StateError when it builds. canDismiss is asked before every close (scrim, drag, system back, browser Back, back(), a to() that replaces the stack, through GoRoute.onExit) and turns the scrim and the drag off while it answers no; a refused browser Back keeps the sheet and the address bar on the sheet's URL. A second to() for a sheet already on top (a double click) is ignored. On the web a cold load waits until the browser holds an entry for base before it pushes the sheet, so the history is [base, sheet] and the first browser Back closes the sheet instead of leaving the app; a reload with the sheet open leaves base in the history twice (measured in Chrome through Playwright). This is best effort until the user has interacted with the page: Chrome's history intervention lets its real Back button skip an entry added without a user gesture, which Playwright's goBack does not exercise. A guarded sheet takes a go_router onExit on every platform, so Navigator.popUntil over it does not terminate, and a navigation issued while it is on top runs one event later so a Navigator.pop() just before it cannot drop it; a second pop of the same sheet in one event is refused instead of completing it twice. On the web, closing the sheet with MagicRoute.back(), a scrim tap or a drag steps the browser history back under the same rule as a stacked() page (see Fixed), so a later browser Back does not reopen it; a panel that refuses the pop itself (PopScope(canPop: false), a refusing Form) keeps a scrim tap or Escape from moving the browser at all, a Navigator.pop from the panel closes in the app and adds an entry, and a DraggableScrollableSheet that closes by shrinking to its minimum does too (measured in Chrome). The frame is the one showModalBottomSheet(backgroundColor: Colors.transparent, isScrollControlled: true, useRootNavigator: true) builds, with no drag handle, no safe area and no constraints; the route's widget is the panel, and it inherits the app-level themes rather than a layout's local ones. An overlay declared in a layout group keeps its prefix and middleware, opens above the shell, and is matched before the layout's pages, so its path must not also match a page path. While an overlay is on top, the title, currentPath, Request.route() and the query readers answer for the sheet, not for the page a layout builds beneath it. (lib/src/routing/magic_overlay_page.dart, lib/src/routing/magic_router.dart, lib/src/routing/route_definition.dart)
MagicVaultService.usesDataProtectionKeychain reports the keychain in use.null until the first operation has resolved it.
MagicVaultException.statusCode and isMissingEntitlement name the cause of a failed operation.statusCode is the OSStatus the keychain returned (taken from the wrapped PlatformException.details, since the exception code is the same string for every status), and isMissingEntitlement is true for -34018. A write that fails for want of the entitlement now says so in its message.
Changed
The macOS Vault chooses its keychain on first use instead of assuming the data protection one. With no security.vault.macos_data_protection_keychain config, MagicVaultService writes and deletes a throwaway item in the data protection keychain once. It keeps that keychain when the write works and falls back to the legacy login keychain, with one Log.warning, when the keychain answers errSecMissingEntitlement (-34018), which is what an ad-hoc build or one without a provisioning profile gets. Before this, every Vault.put on such a build failed and a sign-in showed only a generic error. The probe writes because a read of the data protection keychain on such a build finds nothing instead of failing. Any other probe failure surfaces as a MagicVaultException and the next operation probes again. A signed build that was already on the data protection keychain keeps it. An app that fell back to the legacy keychain and is later signed with a profile moves to the data protection keychain and does not see its old items (there is no migration between the two); pin macos_data_protection_keychain: false for such an app. A release build with no provisioning profile now stores in the legacy keychain, with one warning, instead of failing every write; set the key to true to have it fail loudly instead. The legacy keychain ties an item to the signature that wrote it, so an ad-hoc build that is rebuilt can raise a system keychain prompt per stored item on its next launch: sign local builds with a stable identity.
The fluttersdk_wind floor moves ^1.9.0 to ^1.11.0. The old range already admitted 1.11.0, so a fresh pub get resolves nothing differently; what changes is that the floor names the release magic is verified against. wind 1.9.1 to 1.11.0 add and fix without a break a consumer inherits (WButton, WPopover and WSelect semantics, WInput vertical centring and a locale decimal mark, WImage object position, wWindowClassOf), and magic needed no change for them. The stack lines in CLAUDE.md and .github/copilot-instructions.md quote the new floor. (pubspec.yaml, CLAUDE.md, .github/copilot-instructions.md)
Fixed
to() on a stacked() route decides "the screen already showing" from the top of the page stack. It read the location of the page whose builder ran last, which over a page inside a layout is the page beneath (the shell builds after the pushed page). The documented query swap (to('/monitors/42', queryParameters: {'tab': 'checks'}) on /monitors/42) then pushed a second copy of the screen instead of swapping its tab, and two to() calls for the same stacked route in one frame (a double click) pushed it twice. Both now behave as documented on every platform. (lib/src/routing/magic_router.dart)
On the web, MagicRoute.back() from a stacked() page no longer leaves the page behind the browser's Back. go_router reports every change of the page stack as a new history entry, so back() added the page underneath as a NEW entry, and the next browser Back reopened the page just closed. back() now closes a page pushed by MagicRoute.to() or MagicRoute.push() through history.back() when the browser's current entry is the one that push created over the stack beneath (compared by the encoded stack, page keys included); the browser answers as it does its own Back button, so no entry is added and Forward reopens the page. A second back() before the browser answers does nothing, and a to(), push() or replace() issued meanwhile runs after it. A step the browser does not answer within a second is given up and the page stays; nothing closes in the app then, so a late answer closes it like a browser Back. A replace(), including the query swap to() does on the screen already showing, gives up the claim, since go_router reports it as a new entry with the same page key. Every other close happens in the app as before and adds an entry: Navigator.pop and popUntil from app code (their synchronous pop is kept: no route gains a go_router onExit for this), a go_router context.push, two pushes in one frame, and any page after the app closed a page in the app. Native platforms are unchanged. Measured in Chrome; Safari, Firefox and mobile browsers were not measured. (lib/src/routing/magic_router.dart, lib/src/routing/browser_history_web.dart, lib/src/routing/browser_history_stub.dart)