dartvel_flutter 0.9.2
dartvel_flutter: ^0.9.2 copied to clipboard
The Flutter runtime for Dartvel apps, with pages, DVBox/DVText UI primitives, signals, typed routing, Studio, and SEO and PWA on the web.
0.9.2 #
-
DVHostedPage,dvOnGenerateRoute,dvPageFor: Dartvel's routes in a host on Navigator 1.0, Navigator 2.0 or auto_route, in a router of their own with their guards, back through Dartvel's stack first, and the web address bar following the page. -
A loading bar across the top instead of a spinner in the middle.
DvDefaultLoading, which a deferred page arriving and a page's data loading both show, is now the same thin bar the web page shows before its first frame, on every platform.DV.progress.track(future)andDV.progress.start()show it for an application's own work;DV.progress.colorandDV.progress.enabledtheme and turn it off. Themed from the colour scheme, still under reduced motion.
0.9.1 #
- Studio's sign-in is a Flutter page of the Studio app,
DVStudioSignInScreen, shown byDVStudioAppat<mount>/login. - Signing in with
from=a path the router does not serve loads it from the server instead of drawing the application's not-found page.
0.9.0 #
DVBox.threePane: three panes, one per panel of a tri-fold. Each pane fills a panel between two folds or hinges, left to right, right to left in an RTL language, or top to bottom when the folds run across. Across one fold the first pane has a side and the others share the other, or withoneFold: DVThreePaneFold.lastAlonethe first two share; with no fold, three columns on a desktop, two on a tablet, stacked on a phone. Screen readers read the panes first to last.
0.8.0 #
-
Studio: a formula bar and a command palette. The formula bar runs across the page editor: pick a field of the selected element, type a formula of its kind (text, arithmetic, a colour, an option, TRUE/FALSE,
Navigate(...)), with highlighting, completions and inline errors; Enter applies it as one undoable edit and Esc puts back what was there. Ctrl+K (Cmd+K) opens a palette of every section, page, element and action; Ctrl+D duplicates the selected element; Ctrl+/ lists the shortcuts. -
Password managers save and fill the prebuilt sign-in and sign-up pages. The email and password fields are one
AutofillGroup, hinted as a username and a password (a new password on sign-up), Enter in the password field submits, and the platform is asked to save only after the server accepts the password. Leaving the page cancels, so a refused password is never offered for saving. -
A link to a heading opens the page at that heading. An address ending in
#heading-idscrolls aDVPageShellpage to that heading on its first frame and whenever the address changes, on the web and in native apps. Every heading gets an id from its words withdvHeadingSlug(a repeated heading is numbered from 2), and on the web the document copy of the page carries the same ids. A page that loads its content later is looked at again for a few frames. Text fragments (#:~:text=) and hash-strategy routes are left alone. Nothing to add to a page.
0.7.1 #
-
Fixed: Esc on a page is left to the browser when the page has nothing open. The selection area every
DVPageShellpage carries reported every Esc handled, so on the web the engine calledpreventDefaulton it and the browser never saw it: with the find bar open and the page focused, Esc did not close the find bar. The page now answers Esc beneath the selection area. It closes the selection menu when one is showing, passes theDismissIntenton to whatever answers it above the page (a dismissible route, the application's own action), and otherwise leaves the key unhandled. Dialogs, menus and text fields keep their own Esc, and a text field that is composing keeps it for the input method. -
Fixed: a dialog opened over a page keeps focus. Keyboard scrolling took focus back whenever it rested on any focus scope, a dialog's that had just opened included, so the dialog's keys, Esc among them, went to the page beneath. It now takes focus only from the scopes the page itself sits in, and from the selection area after a click into the text, which leaves Page Down and Esc working after the click.
0.7.0 #
-
Breaking:
DV.Platform.Tray.show(icon:)takes a generated asset. The icon is aDVAssetRef, theDVAssetvaluedartvel routesgenerates, instead of a path string, so a renamed or unlisted icon is a compile error rather than an empty tray slot. An asset that is not an image is refused with anArgumentErrornaming it. Rewriteicon: 'assets/tray.png'asicon: DVAsset.tray. -
Fixed: a shared-store value spilled to file storage that fails to open reads as unreadable (
null), like any other unreadable value, instead of throwing. -
The browser's own find reaches a page on the web. Ctrl+F, and "Find in page" on a phone, find text on a
DVPageShellpage and scroll the Flutter page to it. The shell keeps the page-text block in the document up to date with what the page on top has drawn. On the route the build wrote the block for, it is kept and topped up; after a navigation it is rewritten. Each paragraph is ahidden="until-found"section, and the block isaria-hiddenonce the app runs. When the browser firesbeforematchon a section, the page scrolls to the paragraph it mirrors withScrollable.ensureVisible, highlights it for a moment, and hides the section again on the next task. A section the browser revealed before the app loaded, from a#:~:text=link, is scrolled to once the page settles. Nothing for an application to add.DVPageScaffoldSpec(findable: false)opts a page out. -
Printing after a navigation prints the page on screen. The block now follows the reader instead of being removed after the first navigation, so print no longer falls back to the canvas.
-
DV.Cacheis dartvel_core'sDVCache, so a page and a backend function share one cache and one API:get,set,hasanddelete, with read-through (compute:,staleFor:) andtags:as options, and bulk deletes asdelete(DVCacheTag('products'))anddelete(DVCache.all). Breaking:remember,tag,revalidateTag,clear,lock,configure,adapterand theglobal*helpers are gone fromDV.Cache; see dartvel_core's changelog for the rewrite of each.DVCacheLockis gone too. -
The cache adapters are exported:
DVCacheAdapter,DVMemoryCacheAdapter,DVDatabaseCacheAdapter,DVRedisCacheAdapter,DVMemcachedCacheAdapter,DVDistributedCacheAdapterandDVCacheView.dartvel.cachesets the storeDV.Cacheuses, andDV.Cache.withAdapter(adapter)switches store in code.DVCacheTags, the Redis client andDVCacheConfigstay out. -
Signing out empties the device's offline data.
DV.Auth.signOut()sends whatever queued offline writes can reach the server, then empties every offline data model's copy and queue, so the next person to sign in on the device sees none of it. -
Breaking: the SDK floor is Dart 3.13.0 and Flutter 3.47.0. Dart 3.13 is the first release with primary constructors (
class Point(final int x, final int y);), which thedartvel createscaffold, the samples and generated data models are written with; on Dart 3.12 they are a compile error. Flutter 3.47.0 is the first stable release that ships Dart 3.13.0. Raiseenvironment: sdk:to">=3.13.0 <4.0.0"and upgrade Flutter before taking this release.
0.6.2 #
- Documentation only: install the CLI with
dart pub global activate dartvel_clifirst, then Homebrew, the release binaries, dev_dependencies and npm.
0.6.1 #
- Declares the platforms it supports in pubspec.yaml, so pub.dev lists them instead of inferring them from platform-specific imports. No code changes.
0.6.0 #
-
Studio image export shares the box modifier chain. An exported image is emitted as
.backgroundImage(...)inside the singleDVModifierchain rather than onDVBox.image, so subsequent sizing and box styling modifiers do not replace the image. -
Studio follows dark mode.
DVStudioAppreads the system's setting,prefers-color-schemein a browser, and redraws when it changes. EachDVStudioStylecolour is aDVStudioColorwith a light and a dark value, stillconst, so sections written against the palette change with it and keep compiling. The artboard stays the page's white. -
A web app served by its own server shows the pages Studio published there.
DVPageStorereads its documents from the server's/_dartvel/pageswhenDVAuth.servedByOwnServer, rather than fromDV.Databasein the browser where nothing is published, so a published page takes over its route on the next load without a rebuild and a reverted one gives the route back to the compiled page.DVPageStore.sourcesets where documents come from, andDVPageStore.reload()reads them again. -
Studio's record form edits more than text. An enum is chosen from its values, a reference from the related model's records, a list or map is edited as JSON and not sent while it does not parse, and a nullable field has Empty. A mounted module's models are listed with the module's name.
-
Studio has Queues and Cache sections. Queues lists every queue the build declares with its waiting jobs and dead letters, each with the error it died of and Retry and Discard. Cache lists each tag with the keys it covers and revalidates one, saying how many keys went.
-
Studio's Access section grants and revokes. An account is granted by its sign-in address or id, a refusal says why, each grant shows the account's address, the caller's own row is highlighted, and revoking your own grant or the last one asks before it goes (
DVStudioClient.grant,DVStudioClient.revoke). -
The sign-in page leaves once signed in. Opened without a
from, the generated/loginstayed on its form after the server accepted the sign-in, as if nothing had happened; it now goes to/. Afromis still followed when it is a path in the application. -
Studio's button element is a button, and a page card shows the page. A Button from the palette is filled, padded and rounded through ordinary properties the inspector shows, a stored button with no styling is drawn the same way, and its semantics survive a style (a second modifier used to replace them, on the page and in exported source). The overview's page thumbnails draw the page at a legible scale on a sheet that fills the card.
-
Studio's model tables read properly. A heading stays on one line and a narrow table scrolls sideways; a
DateTime, or anintnamed for a moment holding epoch milliseconds, is shown as a date; and the builder's page list includes and selects the page being edited before its first publish. -
A web-server build signs in through its own server, even over the development auth provider.
DVAuth.servedByOwnServer, set byDARTVEL_WEB_SERVER, makes a configuredDVLocalAuthProvideryield to the generated session provider, so/loginand everyDV.Authcall reach the accounts the server keeps. A provider the application wrote is still used. -
A link with a
semanticLabelcan be activated from a screen reader. The label excludes the child's semantics, and the tap action went with them, so a labelled link was announced as a link with nothing to activate. -
A deep link over a page with a scroll view no longer asserts. The page underneath is built covered and never laid out, and its scroll view made a selection scope of its own under the inert registrar, then sorted its text by screen position through an overscroll transform with no size. A page that has never been on top now has no selection registrar until it is.
-
A global can be registered again after the scope that read it is disposed. Starting an application twice in one process, as every integration test of the real entrypoint does, made
DV.globalwrite to the first run's disposedProviderContainerand throw. A global also no longer keeps the first value it was registered with in a scope created later. -
The dev client pairs over TLS, and on iOS, macOS and Linux. The bundle loader trusts only a server presenting the pairing key, and the session hands the VM service to the native tunnel over FFI on iOS, macOS and Linux as it does over JNI on Android. A
dartvel-dev://pairlink is never treated as a route. -
A page with a grid can be covered by another route. Selection beneath a covering route was switched off by removing the selection registrar, which rebuilt every child of a lazy grid or list into a different widget outside its layout, and the sliver asserted as soon as a link was followed. A covered page now selects into a registrar that keeps nothing.
-
DV.Updatesworks on a Shorebird build.updates.check,updates.applyandupdates.rollbackare bound to the Shorebird updater's C API over FFI (DynamicLibrary.process(), on Android and iOS alike), each call on its own isolate because the updater blocks on the network.checkreports a downloadable patch, or one downloaded and waiting for a restart;applyinstalls it for the next launch and throws with the updater's message on a failed download or a patch that does not verify;rollbackruns the check that applies a rollback published for the release and says so when none was. On an engine without the updater nothing is registered andDVShorebirdUpdates.lastFailuresays why. Until now nothing registered these names, soDV.Updates.check()threw in every application. -
DVDevClientSession(inpackage:dartvel_flutter/dev_client.dart) hands a development build's Dart VM service URI to the tunneldartvel build android --profile developmentwrites. It never throws, and reports why when there is nothing to pair. -
A form's edited model saves at the version its record was read.
DVFormrebuilt the model it returns from JSON, which carried no read version, so a save of it replaced the row whatever had changed since. It now callscarryDVModelRead, which the generated client registers. -
DVModelAdminasks the model's policy before it offers an action, and again before it writes. New is drawn only whenT.createallows it, Delete only whenT.deleteallows it on the open record, and Save only whenT.update(orT.createfor a new record) does; a record that may not be saved is shown as a read-only form saying so. Each action is asked again at the moment it writes, so a button drawn before a role was removed deletes or saves nothing. An update is asked about the stored record rather than the edit.DVModelAdmin(as:)names who the policy is asked about -- the application's own user -- and without itDV.Auth.currentUseris asked. A caller the policy cannot take is refused rather than cast, and a model no policy answers offers no action, as every unanswered policy is refused. -
Each account page names itself with a level 1 heading, and sign-in goes back where the gate was sending the person.
ProfilePage,SecurityPage,SessionsPage("Devices"),SignUpPage,DeletePageandSignInWithEmailAndPasswordPageeach carry one, so a screen reader can name the page anddartvel build web's audit accepts it.SignInWithEmailAndPasswordPage(from:)goes tofromonce signed in, when it is a path in this application, and home otherwise.DVAccountPage.signInis the sign-in page's entry. -
A deletion that waits, on the device.
DV.Auth.deleteAccount(andDVSessionClient.deleteAccount) answers when a scheduled deletion erases the account, or null for one erased at once.DeletePagesays when, and that signing in before then keeps the account, instead of saying it has been deleted.DVSessionClient.deletionCancelledsays whether the last sign-in cancelled a deletion, and the sign-in page says so;DVAccountDeletedis a sign-in refused because the window had closed, and the page shows it as that. -
Changing the password from
DV.Auth.SecurityPage.DV.Auth.changePassword(andDVSessionClient.changePassword) sends the current and new password, with a code or recovery code when given, adopts the rotated session and answers how many other devices were signed out. The page asks for both passwords, and a code when the account has an authenticator; says that changing the password signs out every other device; clears every field once the change is made and keeps what was typed after a refusal; and names a wrong current password, a breached or weak new one, and a missing code. -
DVAccountPages.requireSession, and navigation entries for the account pages. The redirect the generated router puts in front of an account page: nobody signed in goes todvSignInRoutewith where they were going asfrom, and a browser that has not yet asked which session its cookie is asks before refusing.DVAccountPagenames each page with its label and whether it needs a session;DVAccountPageEntryis a page and its route target;DVAccountPages.visible(entries, signedIn:)is what a menu offers, so a signed-out person is offered sign-up and not a page that only sends them to sign in. -
DV.Session.watch(context), and a device on every session. A widget reading the session in its build withwatchrebuilds on sign-in, a completed second factor, sign-out and revocation, with one subscription per element that goes when the element does.dvSessionDeviceLabel()is what a session from this install is recorded as -- the platform and the kind of client,Android apporWeb browser-- and never a host name, a user name or a hardware identifier, which would follow the person across sign-outs. -
The prebuilt account pages.
DV.Auth.SecurityPage()shows whether an authenticator is on, enrolls one from a QR code drawn in Dart with the key beside it, shows new recovery codes once with a copy button and keeps none after they are dismissed, and removes the authenticator only with a code.SessionsPage()lists every device with its last use and signs out one or all the others.SignUpPage()creates an account, and an address that has one reads exactly like any other failure.ProfilePage()shows the address and changes it only once the code sent to the new one is entered.DeletePage()sends nothing untilDELETEis typed and the password -- and the code, when there is a second factor -- is given. Each is built fromDVBoxandDVText.DV.Auth.account(),requestEmailChange,confirmEmailChangeanddeleteAccount(confirmed:)are what they call. -
The second-factor challenge, for pages and calls.
DVPageMfais what the generated router asks for a page declaringmfa:: nobody signed in goes todvSignInRoute, a session without a recent enough factor goes todvSecondFactorRoutewith where it was going, and a browser that has not yet asked the server which session it is on asks before refusing.DV.Auth.SecondFactorPage({from})takes a code or a recovery code, keeps the rotated session, and goes tofromonly when it is a path in this application --//host,https://hostand backslash forms go to/, so the challenge is not an open redirect.DVAuth.installStepUp()makes a refused backend call present the page over the current screen and resume. -
QR codes in Dart.
DVQrCode.encodebuilds a byte-mode, level-M symbol in the smallest version that holds the text, andDVQrImagedraws it dark on white with a quiet zone. The tests read every symbol back with a reader written from the standard's tables that shares no code with the encoder. -
DV.Authenrolls and removes a second factor.DV.Auth.secondFactors()says whether an authenticator is active and how many recovery codes are left;enrollTotp()answers the secret andotpauth://URI to show;confirmTotp(code)activates it;regenerateRecoveryCodes()answers a new set once;removeSecondFactor(code:|recoveryCode:)removes the authenticator. The server rotates the session on each change and the device keeps only the rotated token -- a recovery code or a secret never reaches the token store. A refusal asking for a fresher factor throwsDVMfaRequiredand keeps the session, rather than reading as a revoked one. -
DV.Session, andDV.Auth.sessions,revokeandrevokeOthers.DV.Sessionis this device's session as a read-only signal (DVSessionSignal, aDVLifecycleSignal<DVSession?>):current,idandclaims, withchangesandlisten, and no setter -- only the server's answers change it.DV.Auth.sessions()lists the signed-in person's sessions newest first with this one marked current,DV.Auth.revoke(id)revokes one and signs this device out when it is this device's, andDV.Auth.revokeOthers()revokes the rest and answers how many, each through the generated endpoints, which answer for the signed-in person's own sessions only. A revoke the server refused throws and changes nothing. -
DV.Authsigns in through the application's own backend.DVSessionClientcalls the generated/auth/sign-in,/auth/sign-up,/auth/second-factorand/auth/sign-out, andDVSessionAuthProviderisDV.Auth's provider over it; the generated runtime installs both withDVAuth.installDefaultProvider, andDV.Auth.configurestill replaces it. A native client asks for thedvs_token, hands it toDartvelClient.setAuthTokenand keeps it in aDVSealedSessionTokenStore: AES-256-GCM under the application keydartvel keymanages, in the platform keyring, before a byte reaches the file. With no key custody the token lasts for the process and is never written down; an unreadable file is discarded rather than sent. A browser asks for nothing and stores nothing, because the server'sHttpOnlycookie is the session. Sign-out forgets the session only once the server has revoked it -- one it did not confirm throwsDVSessionRequestFailedand leaves the device signed in. A sign-in to an account with a second factor throwsDVMfaRequiredand holds the pending session in memory only;DV.Auth.completeSecondFactor(code: ...)presents it, and only the rotated token is stored and handed out. At launch a stored session is checked with the server and forgotten when refused (DV-SESSION-002). Refusals are rebuilt from their code with the client's own wording.DV.Auth.SignInWithEmailAndPasswordPageshows a refusal and asks for the second factor when the account has one. -
DV.Auth.apiKeysandDV.Auth.oauthClients. The platform API's management surface,DVPlatformApiAuthoverDV.Auth.authorization, so a policy registered throughDV.Auth.registerPolicyforDVApiKeyResourceorDVOAuthClientResourcedecides who may issue, list, rotate and revoke the current organization's keys and register, list and revoke its OAuth clients. -
DV.Auth.OAuthConsentPage. The screen a person answers a partner's OAuth request on, built fromDVBoxandDVText. It asks the backend to describe the request, shows the client and each scope in the wording its declaration gave it, and approves or denies nothing until a button is pressed. The answer is a form POST with the person's own headers and a CSRF token, and the redirect the backend answers is opened throughDVLinkOpener. A request the backend will not describe offers nothing to approve, and a refused answer is shown and followed nowhere; neither shows the server's error text. -
DV.Crashes.installApplicationnever takes the application down. The generated runtime calls it from the router's constructor, before the first frame, so an exception there was an application that never drew. A failure while installing is now taken back -- no hook is left chained to a reporter that never started -- printed, and answered with null, and the application starts.installtakes back a partial installation the same way and still throws to a caller who called it directly. Declaring the Dartvel sink with no API is still refused with anArgumentError: that is the caller's wiring, the same on every run, not something to hide. -
sink: dartvelsends reports to the application's own backend.installApplicationtakes the runtime's API resolver (api) and, whendartvel.crashes.sinkisdartvel, sends what the previous run left to the crash endpoint under the API base path. Declaring the Dartvel sink with no API to reach is refused at installation rather than sending nowhere. -
A consent banner, a consent settings screen, and App Tracking Transparency.
DVConsentBannerasks over the page until somebody answers under the current policy version: drawn only once the stored consent has been read, again when the version changes. Accept all and Reject all record an answer for every category they ask about; Not now closes it for the session and records nothing; Choose opensDVConsentSettingsPage, which shows each category where it stands, a required one fixed on, and records only on Save. A choice the database refused is shown as not saved and grants nothing. Both are built fromDVBoxandDVTextand reached asDV.Analytics.ConsentBanner(child:)andDV.Analytics.ConsentSettingsPage(). On iOS atracking: truecategory is granted only when App Tracking Transparency allows it:dvRecordConsentChoiceasksDVAppTrackingTransparency.request()and records the answer under that prompt, a system no as a no, and grants nothing when no prompt could be shown.tracking.requestAuthorizationis adart:ffibinding that builds the completion block overNativeCallableand answers -1 without the framework,ATTrackingManagerorNSUserTrackingUsageDescription. -
DV.Crashes.installApplicationappliesdartvel.crashes. A build mode the configuration disables saysDV-CRASH-009and installs nothing -- no hook, no store, no install id written. Otherwise the sample rate, the breadcrumb size and the per-release limit reach the reporter, and the identity category comes from the configuration. -
DV.Crashes.identifyties reports to an account only under consent.installApplicationtakes the consent category the application declares (identityConsent), and every report carries the flags in force when it was written.identify(userId, consent:)binds the id to that category, so a report carries it only while the category is granted; with no category declared it is refused rather than quietly ignored, and before installation the identity is held and applied at install. -
DV.Crashesinstalls the crash reporter where Flutter reports errors. The runtime in dartvel_core recorded and sent, and nothing called it.DV.Crashes.installchainsFlutterError.onErrorandPlatformDispatcher.instance.onError-- the application's own handler still runs, after the report is on disk, and uninstalling restores it unless the application has replaced ours since -- adds the isolate's error listener, or on the web the window'serrorandunhandledrejectionlisteners, and sends what the previous run left. One error reaching two hooks is recorded once; the same hook seeing it again is a second occurrence. The handler never throws and never re-enters itself: a failure inside it is kept onlastHandlerFailureand the chain still runs. A second install is refused, since chaining twice records twice.installApplicationis what the generated runtime calls: records in the per-user data directory (DARTVEL_CRASH_DIRwins; Application Support on Apple platforms,LOCALAPPDATAon Windows, beside the device state in the files directory on Android) orlocalStorage, and an install id generated once and kept beside them. It does nothing underflutter test, where the test framework owns the hooks.DV.Crashes.recordrecords a caught error as non-fatal. -
DV.AnalyticsandDV.Privacy. The configured analytics runtime and privacy walk, which the generated runtime starts fromdartvel.analyticsandDARTVEL_PRIVACY_KEY. Each throws, naming what to declare, in an application that has not configured it. -
DVLocalAuthProviderno longer says whether an account exists.signInWithEmailAndPasswordthrewunknownAccount("No account exists for that e-mail address. Call signUp first.") for a missing account andinvalidPasswordfor a wrong password, soDV.Authhanded every caller a way to test which addresses were registered. Both are nowAuthException.invalidCredentials, after one verification either way: a miss is checked against a dummy hash the provider makes once at construction, where it used to hash and then verify, which was twice the work of a wrong password.signUpjudged whether an address was taken before whether the password was long enough, so a short password answered "taken" for a registered address and "too short" for a free one; it now checks the password, hashes it, and only then looks the address up. -
A GStreamer element that is not installed fails at once, by name. The Linux media backend built pipelines with
gst_parse_launchand no error out-parameter, which drops an element it cannot find and returns what is left. On a machine with libgstreamer and no plugins that was a lone filesink with no bus, polled for thirty seconds under a flood of GStreamer-CRITICAL messages. Pipelines are now built with fatal errors and a parse context:DVGStreamer.runToEosthrowsDVGStreamerPipelineErrorwith the missing elements, a player or recorder emits its failed event naming them before any polling starts, a capture source that is not installed is reported as no microphone, and a format is reported only when every element of its chain is installed.DVGStreamer.missingElementsandDVGStreamerPlayer.missingElementsread the same answer from the registry. -
A rule's reading and threshold use the runtime's wording. The alert detail formats both through
DVSignalRef.format, the same call that writes the alert's incident entry, so the rule on screen and the line on the timeline show one number one way. A crash-rate or fleet-health rule now reads as a percentage. -
A service level with no traffic shows its budget as not measured. The Operations overview showed "100% left" and a full green bar for a level whose window saw no requests. It now reads "Not measured", with an empty, neutral bar and no percentage, and its note says it was sampled with no requests to measure. The Service levels count names how many are not measured instead of saying every budget has room, and the Deploy gate tile lists them rather than showing a clear green gate.
-
A public update on an incident an alert named asks for a public title first. Studio's composer shows a Public title field for an incident whose
titleSourceis nothuman, and Post stays disabled until it is filled; the title goes with the update in the sameDVIncidents.update. The preview carries the typed title, or, while the field is empty, the neutral title the runtime would publish. The rename card says what the status page calls the incident until a person titles it. -
On Android the application key is sealed by the Android Keystore, and
dvAppKeyStoreForhands phones their keyring with no application change.DVAndroidKeystoreAppKeyStoregenerates an AES-256 key insideAndroidKeyStoreunder a per-application alias (dartvel.appkey.<app>); that key never leaves the Keystore. The 32-byte application key is sealed under it with AES/GCM/NoPadding. Only the Keystore's nonce and the sealed bytes reach storage, in the no-backup directory, so a restored copy on another device opens under nothing. It goes through jnigen-generated bindings forKeyStore,KeyGenerator,Cipher,GCMParameterSpecandKeyGenParameterSpec, never a platform channel. With no Context, no Keystore, or a Keystore that refuses, every call throwsDVAppKeyStoreUnavailablerather than writing a file. A tag that does not verify, or a sealing key the platform deleted, reads as no key.dvAppKeyStoreFor, which the generated runtime calls forDVWindowSharedStore.defaultAppKeys, supplies it toDVAppKeyStores.platform. Android and iOS now get the Keystore and the Keychain, and a key an earlier version left in the old file is moved in once. It takesplatform:andhome:for tests; the web signature matches.dvAndroidSealKeyBlobrefuses anything that is not a nonce and a sealed key, so a bare key cannot be written.dvAndroidOpenKeyBlob,dvAndroidSealedKeyPathanddvAndroidKeystoreAliasare the pure halves, tested off a device.DVAndroidBindings.applicationContextexposes the Context the bindings reach, andDVAppKeyStoreUnavailableis exported. -
World anchor tokens are encrypted at rest whatever the shared store's cipher. A token under
xr.anchors.*was encrypted only when the shared store had been given a cipher, and its default has none, so a token that re-localizes a place in somebody's home was written to the preference store as plain JSON.DVWindowSharedStore.sealedPrefixes(xr.anchors.) are now encrypted with AES-256-GCM under the application key (DVAppKeyCipheroverDVAppKey.ensure) before the store's own cipher sees them, inline and when spilled. The key store comes from the newappKeys:parameter or, for a store made without one,DVWindowSharedStore.defaultAppKeys, read when a key is first needed. When no key can be had -- no key store configured, one that cannot hold a key, one that throws -- the write throwsDVSharedStoreSealUnavailableand nothing is kept, not even in memory;DVSharedStoreAnchorStorereports it asDVSpatialAnchorNotStoredand the session leaves the anchor unpersisted. Removing a token needs no key, so a withdrawal can always delete one, and a token an earlier version left in plaintext is not read as a token. No key store is configured by default, so until an application setsdefaultAppKeysworld anchors are refused rather than stored. A write whose flush fails now completes its future with the error instead of never completing.DVConsentSpatialConsentandDVSpatialAnchorNotStoredare exported. -
Studio has an Operations section when it is given alerting or incidents.
DVStudioScreen(alerting:, incidents:)adds it; with neither there is no tab. The overview shows each service level's objective, success rate, error budget left and burn over both windows ("No traffic" where the runtime has nothing to divide, never 0×), the budget gate's decision, open incidents and firing alerts. Alerts shows each rule as OK, pending, firing or resolving, its signal against its threshold, its last episode, who each delivery reached and who it missed and why, andanalyze()findings as warnings. Incidents lists open, monitoring and resolved incidents with their timelines, and writes internal notes, public updates (previewed throughDVStatusSnapshot.build, so no internal entry can appear), public titles and resolves (offered only in monitoring, as the actor) throughDVIncidents, showing a refusal where it happened. Status page renders the snapshot as the public sees it, marked as a preview.clock:andstatusHealth:set the clock and the health report it reads. -
Volumes and immersive spaces are window kinds.
DV.Platform.Window.open(route, options: DVWindowOptions(kind: DVWindowKind.volume))andkind: DVWindowKind.immersive, immersion: DVImmersion.passthroughpresent in space where the XR runtime can, withpresentationvolumeorimmersiveand the session onwindow.spatial, and otherwise as a page withDV-WINDOW-014orDV-WINDOW-015oncodes-- neverDV-WINDOW-001, which names a different cause. An immersive space belongs to the window that opened it and closes with it, a space the system ends closes its window, and closing a window ends its space before the window is gone.capability.spatialis null unlessDVXR.refresh()found anxr.capability.querybinding reporting a headset or glasses; with it,displaysanddisplayKioskare false,tearOutis true and adisplay:hint is ignored withDV-WINDOW-013.DVXRBindingDevicereaches the device only throughDVNativeBridgeand fails typed (DV-XR-006); no target registers thexr.*bindings yet. World anchor tokens live under the reservedxr.anchors.*namespace of the shared store.DV.Test.fakeXR(capability)installs a headless device and returns it;DV.Test.fakeXR(null)is a phone. -
Scene nodes take
.anchor(...),.onGrab(...)and.onRelease(...), andDVScene(environment: DVEnvironment.passthrough). The anchor is typed data in the resolved document; grab and release handlers are delivered by a spatial session. A flatDVBox.scenedraws anchored nodes at the origin and passthrough with the studio environment, and reports each once. -
Studio has a Flags section when it is given the flag runtime.
DVStudioScreen(flags: const DVFlags())adds it; withoutflagsthere is no tab. It lists every declared flag with its type, owner, expiry, default and rule count, marks an expired flag in the list and in its detail, and shows each rule's value and targeting in words. "Who gets what" answers a typed context throughDVFlags.evaluatewith the rules and overrides a read gets, and names the rule that decided. Rules are added, edited, reordered and removed as drafts that refuse a value the flag cannot read, shown as a diff and applied throughDVFlags.setRulesonly once confirmed, labelled throughout as local to the running app because nothing publishes rules per environment. A debug build offers a marked process-wide override; a release build offers none. -
DVBox.scene. A 3D viewport as a box layout mode:DVBox.scene(DVScene(nodes: [...]))withDVModel3D,DVMesh,DVNode,DVSceneCameraandDVLightas typed scene objects rather than widgets. Node modifiers take a value or a signal; a signal moves a node on the next frame without reloading the scene. A tap reaches the nearest node under the pointer, blocked by anything in front and passed up to an ancestor's handler. An orbit camera withcontrols: trueturns on drag. Where the scene cannot render -- no renderer on the target,scene3ddisabled, the GPU failing to start, an asset that did not load -- the box shows the poster labelled with why, andDVSceneController.degradationsays the same.DVSceneCamerais not the specification'sDVCamera, which is already the device camera. -
DVBox.videoandDVBox.audio. A player is a box mode: the box's element attaches aDVMediaControllerto the player the target registered withDVMediaBackends, and disposes it with the page. A parent rebuilding the box hands the newDVBox.controllerhandle to the running player; a new source replaces it; a controller the application passes in outlives the box. A target with no player bound, and the terminal, show the poster and fail the controller with the reason. Standard controls read the signals, media keys drive the player holding audio focus, and select and the arrows map on a television.DVBox.aspectRatioholds a box's content to a ratio, andsignal.watch(context)reads a player signal in a build.DV.Platform.Media.recordAudio/recordVideorecord through the registered capture backend andDV.Platform.permissions. On Linux,DVGStreamerPlayerandDVGStreamerCaptureplay and record through GStreamer over dart:ffi, registered byDVLinuxBindings.register. Video frames are not yet drawn into Flutter there, and camera capture is not implemented. -
DVModel3DViewer. The orbit viewer a generatedproduct.viewer3D()and a generated model page render for a 3D field:DVBox.scenewith the model, a camera the user can turn, a key light and the studio environment, showing the field's poster where 3D cannot render and nothing for an empty field. -
Studio drives the content workflow.
DVStudioScreen(content:, actor:, reviewers:)attaches aDVStudioContent, and without one Studio is unchanged. The toolbar shows the open page's state as a pill (Draft, In review, Approved, Scheduled with its slot, Published) and a primary action that follows it and the actor's policy: Submit for review, Approve, Publish, Publish now, Cancel schedule, disabled with the refusing reason as its tooltip. A review panel shows the version, its author and reviewer, the approval record (who, when, which revision), a request-changes reason and a signed preview link with its expiry and a copy button; content changed since approval shows aDV-CONTENT-002warning where Publish would be. A schedule dialog picks a day and a time, moves or removes a slot. History lists every version with its state, author and activity, shows what one changes against the published version node by node through the newDVPageDocumentDiff— added, removed, moved and edited nodes with each property's old and new value — and restores a superseded version. The page list and cards carry each page's state, draft-only pages are listed, and the overview counts what needs review. Every action goes throughDVContentWorkflowas the actor, and a refusal or a stale snapshot is shown inline rather than swallowed.DVStudioContentgainscan,routes,previewLink,actorIdOf,nowand apreviewUrlshape. -
DV.Workers. The application's worker pool is reachable from a page asDV.Workers, the poolDVWorkers.configureinstalled, and the types a run hands back --DVWorkerResult,DVProgress,DVCancellation,DVWorkerFailure,DVWorkerCapability,DVWorkerBufferand the rest -- come throughpackage:dartvel_flutter/dartvel_flutter.dartlike every other namespace. A task run through it leaves the UI isolate. -
DV.Memory. The Platform Memory factory is reachable from a page like every other namespace:DV.Memory.allocate(megabytes: 512)returns an independentDVPlatformMemory, andDV.Memory.arenas/usagereport the live ones. The arena types are exported fromdartvel_flutter. -
Studio is laid out like the tools it has to stand beside. A dark navigation rail replaces the tab strip, with an icon and a label per section;
DVStudioSectiontakes an optionaliconfor it, and a section without one gets a generic glyph rather than no way to be told apart. Pages opens on a site overview instead of "Select or create a page to edit." beside an empty pane: a page list with titles and a new-page field, four numbers that are all true — pages stored, windows the application has open, sections installed, and when this session last published — and a card for every stored page with a live thumbnail drawn by the same renderer the running application uses. Opening a page gives the editor its own toolbar — back to the overview, the route with a Published or Draft badge, a device switcher for desktop, tablet and phone widths, Fit / 50% / 100% zoom, undo and redo, Code, Revert and Publish — above an Insert / Layers panel, the page on an artboard, and the inspector. The exported source is shown on a dark code surface. Windows lists each open window as a card with its kind and presentation. Every key and string the Studio tests drive is unchanged. -
context.flag(Flags.name)reads a feature flag as a signal. Read in a build method, it subscribes the element, so a widget guarded on a flag rebuilds when the synced rules change under it — a kill switch reaches the screens already open rather than the next one somebody navigates to. It is aDVReadableSignal, so it composes:context.flag(Flags.newCheckout) & user.isStaffis a signal too.read()answers without subscribing. A widget that has gone is unsubscribed on the next change rather than rebuilt. -
DVStudioStyleis Studio's style vocabulary, and it is public.DVStudioSectionis an extension seam — the Pro workflow builder attaches through it, and so does anything a team writes — and a seam with no style vocabulary produces sections that look foreign to the tool hosting them. Worse, a section written by copying an existing one inherits whatever was wrong with it, which is exactly what happened: Studio's own Pages section was never styled, the Pro workflow builder was written from it, and the copy came out identical. It carries the surface colours,control()for something that must read as pressable and say when it cannot be pressed,panes()for the list-beside-editor shape every section has, andplaceholder()for what a section shows before anything is chosen. -
Studio's panes stand beside each other at full height instead of floating in the middle of the screen. Both of its two-pane layouts — the route list beside the editor, and the palette, canvas and inspector inside it — were built with
DVBox.row, which resolvesDVCrossAlign.stretchtoCrossAxisAlignment.centeron purpose: a row of cards or a button pair should not run the full height of whatever contains it. An application layout should, and Studio is one. Each pane was therefore as tall as its own contents and centred vertically, which is why a Studio with three pages drew a short list adrift in an empty page and the builder's three panes each sat at a different height. Both rows are plainRows now, with the reason written where the next reader will look for it. -
Studio looks like a tool rather than a page of unstyled text. It had no padding, no surfaces, no rules and no affordances: tabs, routes, toolbar actions and "Create page" were all bare
DVTextwith a tap handler, and the new-route field was an undecoratedEditableTextbeside its label, which draws as two pieces of plain text. Every control is one now — bordered, padded, marked when selected, and dimmed when its action is unavailable, as Undo is with no history and Publish is mid-publish — inputs show focus, and rules divide the panes; the layout they sit in is the entry above. Studio keeps a small fixed palette rather than following the application's theme — it edits that application, so its own chrome has to stay readable over whatever the page being built looks like. The exported-source view scrolls, where a page of any size used to overflow it. -
Studio runs on
MemoryDVDatabaseAdapter, so it can be opened without a database.DVPageStorepersists throughDV.Database, and the in-memory adapter threw on the first statement the store issues, so the Pages tab rendered "Could not read pages: Invalid argument(s): ..." on every build that had no SQLite or Postgres behind it -- includingDVTest.fakeDatabase()and the Studio demo. The adapter now runs the statements the store writes (see dartvel_core's entry); nothing in Studio changed, and the store's round trip is tested on the in-memory adapter as well as on real SQLite. -
DV.Storageis deprecated; useDV.FileStorage. One storage had three names --DV.Storage,DV.FileStorageandDV.BlobStorageall returned the same object -- while the specification namedDV.FileStoragecanonical withDV.BlobStorageas its only alias.DV.FileStorageis canonical,DV.BlobStoragestays as the documented alias, andDV.Storagekeeps working, deprecated, until the next minor removes it. The framework no longer calls the deprecated name itself, and a test fails if it starts again: a framework that calls its own deprecated name teaches every reader to call it too. -
A
DVTablecolumn header announces as a column header rather than as a document heading. It was markedheader: true, the heading flag, which Flutter web draws as an<h2>: a six-column table put six headings into the page's outline between the page's real ones, which is what makes an outline useless to someone navigating by heading. The table now carries the roles Flutter defines for one -- table, row, cell and column header -- so a screen reader also gets the row and column relationship it announces cells with. -
An empty
DVTableannounces its "no rows" label once rather than twice. The label sat on aSemanticsthat did not exclude theTextunder it, so the node carried it twice over. -
Three window degradations that no window could ever carry now report where their condition occurs.
kioskLocked(DV-WINDOW-002) is set when a device-scope kiosk holds the surface,disabledByConfig(DV-WINDOW-005) when a project wrotewindowing.enabled: false, andgestureRequired(DV-WINDOW-003) when a browser refuses a window outside a user gesture. All three were declared, given codes and documented, and assigned nowhere: every one of those windows reported the generic "this target has no windows" instead, anddartvel explaindescribed situations the API could not produce. -
open()asks the browser for the window on the web, throughDVWindowManager.browserWindowOpener. The web has nowindow.openbinding and cannot have one, so every web call reported a missing binding --DV-WINDOW-006, anerrorblaming the integration for the browser doing its job. A window the browser opens is a window; one it refuses isgestureRequired; andclose()closes it through the handle the page opened it with, since a browser lets a page close only its own windows. -
DVWindowDegradation.displayHintUnmatchedreports adisplay:hint that matched no connected display (DV-WINDOW-013). That situation and a kiosk window whose display is gone (DV-WINDOW-010) both reporteddisplayUnavailable, whosecodesaid 013 -- so a kiosk window presenting in place logged 010 and carried a degradation naming 013, anddartvel explainon either described the other.displayUnavailablekeeps the kiosk meaning and now names the code that path has always logged. A window whosedisplay:hint went unhonoured reportsdisplayHintUnmatchedrather thandisplayUnavailable; the behaviour is unchanged, and a hint still never falls back to another display.
0.5.0 #
- A page's title in its app bar is its level-1 heading, on the Material and
Cupertino shells alike. Both bars mark their title a header with no level,
which the web draws as an
<h2>, so every page with a bar had headings and none at level 1 -- anddartvel build web's accessibility audit refused every such page, which is why the example application had never produced a web build. DVImageViewasks for the variant its slot needs on a web build with image variants: its laid-out width times the screen's pixel ratio, snapped to the configured widths, the way NextFaster'ssrcsetdoes -- so a phone downloads the 640 and not the 3840. An asset uses the file the build wrote at that width, a remote image on an allowed host goes through a web-server's/_dartvel/image, and a slot wider than the image uses the image itself. With no variants built it renders exactly as before, with no layout pass.- A link prefetches that variant for the visitor's own pixel ratio, not the build's, and puts it in the image cache under the provider the widget will ask for. The build records each image's slot for this, since a request for the 384-wide file says only that the slot was somewhere under 384.
- A link that preloads fetches the page's document and images as well as its
code.
loadLibrary()only ever fetched the Dart. On the web the link now adds a<link rel="prefetch">for the route's prerendered HTML -- what a new tab, a reload or a shared link opens -- and one for each imagedartvel build webrecorded the page painting, then hands each image to Flutter's image cache once the browser has it, so the page paints it on its first frame. In that order so the image is downloaded once.DVRoutePrefetchholds both steps and can be replaced; off the web it fetches nothing. - Links preload once they have been on screen for 300 ms, as well as on hover.
DVLinkPreload.visibleis the new default: hover was the only trigger, and a phone has no pointer to arrive, so no link on a touch screen preloaded anything unless it was markedimmediate. - A link is followed when the mouse button goes down rather than when it comes up, which is the hundred-odd milliseconds a click takes. Only for a mouse's primary button: touch, stylus, keyboard and screen readers keep the tap, ctrl and cmd still open beside the page, and the release that follows is not followed a second time on either the Flutter or the browser side.
DVShowingPagesruns work while a page is on screen: started with the first page and stopped with the last, counted because two are mounted whenever one is leaving as the next arrives. Registering the same id again replaces the earlier job. The generated client schedules use it, which fixes three things at once: their timer was started with the router and never stopped, a second router ran every schedule twice, and every widget test that built the router failed with "A Timer is still pending".
0.4.0 #
Android went from 10 bound native bindings to 36, and web from 16 to 41. Both
numbers are counted from source by dart tool/binding_coverage.dart rather
than written down, because every per-platform figure in this repository has
been wrong at some point.
Android now covers runtime permissions, the camera, the media picker,
contacts, location, sensors, biometric availability, local notifications, the
device runtime, files, screen geometry, NFC availability and most of
Bluetooth. The Activity that permission results and activity results are
delivered to is written into the application by dartvel build android.
Three Android bugs that had shipped since the capability list existed, all found by a new on-device gate rather than by any unit test:
- All three haptics bindings threw on every device at API 31 or above. The
vibrator_managerservice returns aVibratorManager, which holds a vibrator rather than being one, and the cast toVibratorthrew. notifications.sendLocalandfiles.writeBytesanswered with types their own callers refused.DVFiles.writeBytesasked throughrequire<bool>and the binding answered with a byte count, so the call threw on Linux, Windows and macOS while writing a perfectly correct file.
Web bindings are registered only after a probe for the API behind them, so a
browser without Web NFC or the Contact Picker leaves those names unregistered
and DVNativeBridge.isRegistered keeps telling the truth. A capability the
browser has and refuses raises DVWebPermissionDenied carrying the browser's
own words, so a refusal can no longer be read as an absence.
The Linux clipboard was worse than unimplemented: a copy made the process the owner of the X11 CLIPBOARD selection without running a GLib main loop to answer for it, which made every paste on the machine hang until the asking application timed out.
0.3.2 #
- Corrected the constraints on sibling Dartvel packages, which named the
previous release rather than the one published alongside them. A caret on a
0.x version stops at the next minor, so
dartvel_core: ^0.2.1excluded the 0.3.1 published beside it -- a user installing the 0.3.1 set resolved 0.2.x for every sibling and got none of what that release contained.dart pub publish --dry-runcould not see it, because it resolves against pubspec_overrides.yaml and every sibling points at a local path.
0.3.1 #
DVModifier.borderdraws a border, so a rule or hairline no longer needs a raw Container and BoxDecoration.files.readBytes,files.writeBytesandfiles.deletebindings, confined to a root the application names.
0.3.0 #
Four queue brokers, both network databases reachable over TLS, static generation that produces pages, and a page that can have a body.
Queues, on real brokers #
Seven adapters now: in-memory, database, Redis, SQS, RabbitMQ, Pub/Sub and Kafka. The four that talk to a network service are verified in CI against the real thing -- ElasticMQ, RabbitMQ's own image, Google's emulator and Apache Kafka -- rather than against a fake that agrees with whatever the adapter does.
That distinction found nine bugs which every unit test had passed: a backoff sent as an initial delay, an AMQP channel limit above the server's, delivery-mode written to the wrong bit, publishes returning before the broker had them, a payloadType that would have stopped every handler matching, a Fetch reply parsed with three fewer fields than it has, offset commits sent to a broker that was not the group's coordinator, and a first coordinator lookup that is always refused and always retriable.
Each adapter is written around what its service actually offers. SQS and
Pub/Sub refuse pending rather than returning an empty list, because an empty
list reads as "there is nothing" when the truth is "I cannot see". Kafka is a
log, so it has no dead letters, no priority and no out-of-order retry, and
lag gives the honest version of a backlog: a distance, not a list.
Databases #
PostgreSQL and MySQL both negotiate TLS, which is what a managed endpoint
requires -- Aurora, Neon, Supabase, PlanetScale and Cloud SQL all demand it and
most refuse plaintext, so before this the adapters reached localhost and
nothing else. sslMode takes libpq's names, so a connection string copied from
a provider's console pastes in unchanged.
A refusal is fatal at require and above. Falling back would put the password
on the wire in the clear while the caller believed the connection was
encrypted.
Pages can have bodies #
A private @DVPage input had to be a single expression, so every page needing
a local, a loop or a condition was written as a one-line wrapper around a
public helper. Block bodies are lowered into the generated widget now.
@DVFunctionalWidget and @DVBackendFunction still require expression bodies.
Static generation #
dartvel build web writes a page per route and expands parameterised routes
through the application's own resolvers. @DVModel(generatePublicPages: true)
now generates the route as well as the paths -- it previously produced a list
of addresses that all resolved to the application's own not-found page.
A page no route serves is refused rather than written.
The web output #
Crawler-visible HTML is built from the page's semantics tree rather than from
string literals in the source, so it carries real headings, anchors and
landmarks instead of one paragraph per source line. Pages gained structured
data, a stylesheet for sitemap.xml, and an .htaccess that path URLs need
and that nothing was writing.
In-app links push the route instead of tearing the document down and rebuilding the whole application, which is what a real anchor in the semantics tree does by default.
0.2.1 #
- First published release.
Dartvel's packages are published under the dartvel_dev name on pub.dev.
dartvel was taken on 2026-08-06 by an unrelated package, so the published
identifier carries a suffix while the command stays dartvel.