Start from installed source
BlocSignal is young and its API may change between releases. Inspect the consumer project's
pubspec.yaml, lockfile, imports, and installed package source before editing code. Use this
repository only when the project follows its current branch or the task concerns the repository
itself.
Do not infer API parity from package:bloc, package:flutter_bloc, or Riverpod. BlocSignal uses
signals, has no state stream or provider dependency graph, and does not implement every source
package lifecycle, predicate, or widget API.
Related Signals skills
This plugin is self-contained for BlocSignal work. When the agent already has skills from
rodydavis/signals.dart, use these optional companions for lower-level Signals tasks:
signals-dartforsignals_coreprimitives and utilities.signals-flutterfor Flutter bindings and signal-aware widgets.signals-hooksforflutter_hooksintegration.signals-lintfor analyzer rules and quick fixes.signals-migration-6-to-7for Signals 6 to 7 migrations.
Do not assume these skills are installed. If they are unavailable, inspect the consumer project's installed Signals source before changing code.
Route the task
- Read decision_matrix.md for the architectural decision rubric comparing raw Signals, CubitSignal, BlocSignal, Hydrate, and Replay.
- Read core.md for event dispatch, equality, errors, closure, observers, and reactive ownership.
- Read flutter.md for providers, builders, listeners, multi-listeners, consumers,
selectors, context extensions, infinite scroll pagination recipes,
flutter_hooksintegration (signals_hooks), widget ownership, and derived UI state. - Read testing.md for synchronous assertions, deterministic async tests, zones, and widget tests.
- Read migration.md before replacing
bloc,flutter_bloc,bloc_concurrency,hydrated_bloc,replay_bloc, orbloc_test. - Read migration_bridge.md for Stream and Listenable interop bridges.
- Read riverpod_migration.md before replacing Riverpod providers, consumers, families, scopes, or generated declarations.
- Read interoperability.md for the universal state bridge across BLoC, Riverpod, and Provider ecosystems.
- Read hydration.md for persistent state storage (
bloc_signals_hydrate). - Read replay.md for undo and redo state tracking (
bloc_signals_replay). - Read jaspr.md for Jaspr web component integration (
bloc_signals_jaspr). - Read devtools.md for VM Service RPC extensions & DevTools UI (
bloc_signals_devtools). - Read lint.md for analyzer rules and IDE diagnostics (
bloc_signals_lint). - Read otel.md for
OtelBlocSignalObserver, span completion gaps, and telemetry data choices.
Load only the references needed for the task.
Workflow
- Identify the exact package versions and target platform.
- Trace the current event, state, ownership, and disposal path before changing it.
- Check the installed public API for every type or member you plan to use.
- Make the smallest coherent change that preserves existing user behavior.
- Format changed Dart files and run scoped analysis plus the nearest tests.
- Report the versions inspected, checks run, and any unsupported source behavior that remains.
Contracts to preserve
BlocSignalBase<State>owns state and lifecycle. UseCubitSignal<State>for public methods andBlocSignal<Event, State>for event dispatch. UseCubitSignalMixin<State>orBlocSignalMixin<Event, State>when a class already extends an existing superclass (such asChangeNotifier,TextEditingController, orBaseRepository) and invokeinitCubitSignal(initialState: ...)in the constructor.- Constructors require named parameter
initialState:(for example: super(initialState: initial)), NOT positionalsuper(initial). - Use
value(the preferred modern getter) orstateValue(permanent alias for backward compatibility) to read rawStateTypevalues inside methods/handlers (such asemit(value + 1)).statereturnsReadonlySignal<StateType>for reactive signal subscriptions. emitchanges state synchronously and skips a value equal to the current state.BlocSignal.addreturnsvoid. Synchronous handlers finish before it returns. Async handler futures are observed for errors but are not returned or cancelled byclose.on<E>registration is runtime routing. Duplicate exact types throwStateError; registration does not give sealed-class exhaustiveness. AnonEventoverride must callsuper.onEvent.closereturnsFuture<void>, disposes effects registered throughcreateEffect, and disposes the internal model. New events are dropped after closure. A post-closeemitasserts in debug mode and returns without updating state in release mode.- A non-equal event transition runs before state mutation.
onChangeruns after mutation. Both local hooks requiresuper; equal emits run neither hook. BlocSignalProvider(create:)is lazy by default, owns its bloc, and closes it. Uselazy: falsefor eager creation.BlocSignalProvider.valuedoes not own its bloc. BothBlocSignalProviderandBlocSignalListenersupport an optionalchildparameter (defaulting toconst SizedBox.shrink()in Flutter andconst _NullComponent()in Jaspr), eliminating placeholderchildarguments insideMultiBlocSignalProviderorMultiBlocSignalListener.context.watch<T>()tracks provider replacement, not state changes. UseBlocSignalBuilderor a signals widget to rebuild for state.BlocSignalListenersuppresses its initial effect run and supportslistenWhen(previous, current). It passes only the current state to the listener callback.- A global
BlocSignalObserveris a single slot. Installing a telemetry observer can replace an existing logger unless the application composes them. BlocSignalBaseoverridestoString()to output$runtimeType($stateValue), providing immediate diagnostic visibility across allCubitSignalandBlocSignalsubclasses.- Prefer inline
late finalfield declarations with type inference for computed properties (for examplelate final itemCount = computed(() => stateValue.items.length);) rather than uninitialized field declarations with constructor-body assignments. - Defend Flutter's 16.6ms/8.3ms frame budget during heavy workloads using
Isolate.runfor CPU compute,batch()for multi-signal mutations, and cooperative time-slicing viaStopwatch+Future.pause()(Dart 3.13+) orFuture<void>.delayed(Duration.zero)(Dart 3.5). Never use event loop delays as a crutch to wait for state transitions to settle.
Never create an effect or computed during a Flutter build method. Keep its owner and disposal
path explicit.