NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
The rainbow bridge connecting your app to APIs. A lightweight REST API client and repository pattern with caching, offline support, and error handling.
Last release 2 months ago
02 Aug 2026
Release timing varies
gaps range from 2 weeks to 5 months
Nearly every release is documented
notes for 15 of 16 stable releases
Nothing withdrawn
no release was ever pulled
8 months old
16 releases · first in 2026
One column per month.
Fixed the serialized cache-mutation queue retaining its completed Future across Flutter's per-test FakeAsync zones. A first widget test could cache su
Future
across Flutter's per-test FakeAsync zones. A first widget test could cache
successfully, while the next received its response and then waited forever
on a completion owned by the previous test's inactive zone. The queue now
clears its tail when the last mutation completes while preserving ordering
for concurrent mutations.Breaking: replaced the shipped `BifrostTestEnv` service graph with `BifrostTestRuntime`. Test setup now resets only Bifrost's transport, preferences,
Breaking: replaced the shipped BifrostTestEnv service graph with
BifrostTestRuntime. Test setup now resets only Bifrost's transport,
preferences, clock, decoder, performance tracker, fake mode, notifications,
and internal diagnostics. It never replaces bifrostServiceLocator; apps
rebuild their own production container through initializeApplication.
BifrostTestRuntime.install(
responseFactory: (_) => const <Object>[],
notifications: notifications,
diagnostics: diagnostics,
initializeApplication: AppBinding.reinitialize,
);
responseFactory, shouldFail, and responder now share the same
per-request responder seam, so overrides reach HTTP clients retained by vendor
SDKs such as Supabase. Notification tests record outcomes without replacing
the app's real SystemNotifier registration.
Added non-recoverable FailureReason.internal. Unexpected defects thrown by
source adapters, REST hooks, or repository callbacks no longer masquerade as
network and get rescued by stale cache. fetch and mutate return an
internal failure; the source-compatible boolean send returns false and
records an internal diagnostic. Internal failures never invoke
SystemNotifier.
RestAPI now bounds header resolution, each HTTP attempt, and unauthorized
refresh. Override requestTimeout or refreshTimeout for API-specific
budgets. Operational ClientException and timeout failures still return
null; unexpected hook/client defects preserve their original error and stack
in BifrostInternalException for repository containment. Timed-out refreshes
remain single-flight until the underlying refresh actually ends.
Serialized the complete cache save, per-key clear, and clear-all mutations
behind one failure-resilient queue shared by every repository instance. Two
concurrent reads can no longer lose a registry entry and leave cached payloads
that clearAllCache() cannot enumerate.
Added postgrestErrorResponse to package:bifrosted/testing.dart. It builds a
complete PostgREST error envelope for tests that exercise a real retained
Supabase/PostgREST client; production SQLSTATE/PGRST mapping remains app-owned.
Simplified Bifrost's private package test fixture: its locator is installed once per test file while its mutable services still reset before every test.
Breaking: `BifrostTestEnv.reset()` no longer assigns `bifrostServiceLocator`. It was a second dependency-injection system running against the app's ow
Breaking: BifrostTestEnv.reset() no longer assigns bifrostServiceLocator. It was a second dependency-injection system running against the app's own. An app's bindings already point the locator at its container, so reset() and those bindings overwrote each other and which one won depended on call order — a repository test that ran reset() last silently stopped resolving the services its production code uses, while a widget test resolved them normally. Tests should exercise the same injection production does.
Register the doubles through the app's own container instead:
bifrostTestEnv.reset();
Bind.delete<SystemNotifier>(force: true); // GetX keeps the first permanent registration
Bind.put<SystemNotifier>(bifrostTestEnv.notifier, permanent: true);
A package with no container of its own — bifrosted included — can call bifrostTestEnv.installDoubles(), which does what reset() used to. It is documented as exactly that: a shim for the no-DI case, not something an app should reach for.
Migration: if your tests broke with "…" not found after upgrading, add installDoubles() after reset() to restore the old behaviour, then move to container registration when convenient.
Fixed: `RestAPI.host` rejected any value containing a colon. _buildUri passed host straight into Uri(scheme: 'https', host: host), and Uri's host para
Fixed: RestAPI.host rejected any value containing a colon. _buildUri passed host straight into Uri(scheme: 'https', host: host), and Uri's host parameter reads a colon as an IPv6 literal — so a scheme-qualified URL, a port, and therefore any local dev server threw FormatException: Illegal IPv6 address, invalid character (at character 1), an error naming none of the actual causes.
host now accepts what you'd reasonably put in a config value:
host |
Request URL |
|---|---|
api.example.com |
https://api.example.com/... |
https://api.example.com |
as given |
http://localhost:8080 |
as given — scheme and port preserved |
10.0.2.2:3000 |
https://10.0.2.2:3000/... |
https://example.com/v1 |
base path preserved, not dropped |
Backward compatible: a bare hostname still resolves to https.
An empty host now throws ArgumentError naming the likely cause — an app launched without --dart-define-from-file — instead of silently building https:///path and failing at the transport layer.
Widened the logger re-export. Logger and Level alone were not enough to reconfigure bifrostLogger: its default DevelopmentFilter drops every log in re
logger re-export. Logger and Level alone were not enough to reconfigure bifrostLogger: its default DevelopmentFilter drops every log in release builds and ignores level, so keeping warnings in release meant naming ProductionFilter — which required adding a direct logger dependency just to reassign our own global. Now also exports ProductionFilter, DevelopmentFilter, LogFilter, LogPrinter, LogOutput, PrettyPrinter, and SimplePrinter.bifrostLogger = Logger(
level: kReleaseMode ? Level.warning : Level.debug,
filter: ProductionFilter(), // required; DevelopmentFilter ignores `level`
);
Fixed: nothing persisted across app launches. SharedPrefService.init() called SharedPreferences.setMockInitialValues({}), which replaces the platform
SharedPrefService.init() called SharedPreferences.setMockInitialValues({}), which replaces the platform store with an empty in-memory map (SharedPreferencesStorePlatform.instance = InMemorySharedPreferencesStore.withData(...)). Because init() runs on every launch, no preference, token, onboarding flag, or cached response survived a cold start. init() now only reads. If your app appeared to "forget everything," this was why.SharedPrefService.updateInitialMock. It was test scaffolding on a production class, and it set the mock store as a side effect. Replace it with useMockStorage.useMockStorage({values}) to package:bifrosted/testing.dart — the storage counterpart to useMockClient. Tests need it for two reasons: there is no SharedPreferences platform plugin in a test process, so getInstance() otherwise throws MissingPluginException; and the mock store is a static platform instance, so a value written by one test is still readable in the next unless it is reset. Call it in setUp (or once per file, before initServices):useMockStorage(); // clean slate
useMockStorage(values: {'onboarding_done': true}); // seeded state
buildResult, and inherit next/back/skip, answer collection, completion, and the analytics funnel.ChangeNotifier — no state-management dependency — matching the documented exception for one-time flows that run before the app's main state exists.started, step_viewed, answered, skipped, back, completed (with duration, reach and skip count), and abandoned on dispose-before-completion, naming the step the user quit on. The event prefix is configurable.step_viewed fires once per step per session, on first arrival only. Re-counting a revisit after back() would inflate early steps and overstate the funnel's health.completed ÷ started is a query in your analytics tool.back() never clears answers — losing input on a back tap is the most reliable way to cause abandonment.Breaking: Simplified [SystemNotifier] to three UI-only callbacks:
onNetworkError(), onUnauthorized(), onRequestFailed({statusCode, body})onForbidden, onServerError, onApiErrorbifrostLogger for diagnosticsBreaking: Removed fetchList — use fetch > with the same fromJson
fetchList — use fetch<List<T>> with the same fromJsonendpoint on fetch — use cacheKey only (it was only used for caching)fetch<R, M> auto-detects top-level JSON array vs object; fromJson is always the item parser (User.fromJson), return type is User or List<User>Nothing published for this version
- update dependencies
Breaking: Removed generics from BifrostRepository
BifrostRepositorybifrostServiceLocator - Set once, used everywhere// At app startup:
bifrostServiceLocator = <T>() => Get.find<T>();
class UserRepo extends BifrostRepository {
Future<User?> getUser(String id) => fetch<User>(...);
}
Added global mock client support for testing
useMockClient() - Enable mock responses for all RestAPI instancesuseRealClient() - Reset to real HTTP clientssetClientFactory() - Set custom client factoryFixed build.yaml to correctly combine generated code into .g.dart files
.g.dart files
build_to: cache and build_extensions: .fake.g.partAdded build.yaml for auto-discovery by build_runner
build.yaml for auto-discovery by build_runner
Added @generateFake annotation for code generation
@generateFake annotation for code generationFakeUtils utility class (uses faker package)
fakeForKey(String key) - generates fake data based on field namecreate<T>() - generates fake model from factoryfakeJson() / fakeJsonList() - generic JSON generatorsFakeGenerator for build_runner integration
.fake() extension methods for annotated classesRestAPI abstract class for REST API clients
RestAPI abstract class for REST API clients
BifrostRepository for repository pattern with caching
fetch<T>() and fetchList<T>() for automatic deserializationSystemNotifier interface for global error handling
onNetworkError(), onUnauthorized(), onForbidden()onServerError(), onApiError()StorageService interface for pluggable storage backendsConnectionChecker interface for connectivity detectionlogger package for loggingYour coding agent can read these notes before it upgrades. Set up the MCP server →