diff --git a/.cspell.yaml b/.cspell.yaml
index 19f8c018..c375b3c6 100644
--- a/.cspell.yaml
+++ b/.cspell.yaml
@@ -16,6 +16,7 @@ words:
- aarch
- aars
- altool
+ - angelov
- apks
- appcenter
- astro
@@ -46,6 +47,7 @@ words:
- logcat
- longpaths
- maclinux
+ - meteo
- microsoftonline
- mipmap
- mozallowfullscreen
@@ -74,3 +76,5 @@ words:
- xcframework
- xcframeworks
- xcrun
+ignoreWords:
+ - timezone
diff --git a/public/assets/bloc_demo.gif b/public/assets/bloc_demo.gif
new file mode 100644
index 00000000..92219fec
Binary files /dev/null and b/public/assets/bloc_demo.gif differ
diff --git a/src/assets/bloc_event_diagram.jpeg b/src/assets/bloc_event_diagram.jpeg
new file mode 100644
index 00000000..4397cfd3
Binary files /dev/null and b/src/assets/bloc_event_diagram.jpeg differ
diff --git a/src/content/docs/flutter-concepts/state-management-with-BLoC.mdx b/src/content/docs/flutter-concepts/state-management-with-BLoC.mdx
new file mode 100644
index 00000000..4b33e564
--- /dev/null
+++ b/src/content/docs/flutter-concepts/state-management-with-BLoC.mdx
@@ -0,0 +1,780 @@
+---
+title: State Management in Flutter - BLoC Pattern Guide
+description: How to master state management in Flutter with BLoC
+sidebar:
+ order: 5
+---
+
+import { Image } from 'astro:assets';
+import bloc_event_diagram from '~/assets/bloc_event_diagram.jpeg';
+
+When your Flutter app has three screens, `setState` works fine. When it has
+thirty, shared user sessions, paginated lists, real-time socket data, and
+concurrent API calls turn `setState` into an obstacle. You start passing
+callbacks five widgets deep. Your `Provider` notifiers accumulate business
+logic, and suddenly you're calling repository methods from inside `build()`. The
+app works, but nobody on the team can trace what triggered a UI rebuild.
+
+This is the state management inflection point. It's the moment teams either
+adopt a disciplined architecture or spend the next 18 months debugging race
+conditions and unintended rebuilds.
+
+The [BLoC (Business Logic Component) pattern](https://bloclibrary.dev/),
+authored by former Shorebird team member Felix Angelov, is the answer most
+serious teams land on. Google's own Flutter team uses it internally.
+[Nubank](https://nubank.com.br/en/), one of the world's largest digital banks
+with 90 million customers, built its app on it.
+[BMW](https://flutter.dev/showcase/bmw) runs it in production. The
+[`flutter_bloc`](https://pub.dev/packages/flutter_bloc) package has logged over
+1.4 million downloads on pub.dev. That’s not hype, it’s validation at scale.
+
+This guide shows you how BLoC works, how to implement it with modern Dart 3.x
+features, and why its predictability makes it a strong foundation when you’re
+using tools like [Shorebird](https://shorebird.dev/) to ship over-the-air
+patches to a live app.
+
+## Core Concepts: Events, States, and Streams
+
+Before we get started, let's look at the basics first. BLoC has three moving
+parts:
+
+1. An **Event** is an intention. The user taps “Fetch Weather,” and your app
+ dispatches an event. Events don’t carry logic; they carry data. They say
+ “something happened” and provide whatever context is needed to handle it.
+2. A **State** is a snapshot of what the UI should reflect. Loading, success,
+ failure, plus any data needed to render.
+3. The **BLoC** receives events, executes business logic (validation, API calls,
+ caching), and emits new states through a Dart `Stream`. Widgets listen and
+ rebuild when the state changes.
+
+
+
+## Dart 3 Changes the Game
+
+Dart 3 gives you `sealed` classes and exhaustive `switch` expressions. With a
+`sealed` base state, the compiler knows every possible subtype, so your UI
+`switch` becomes a compile-time guarantee. Add a new state and forget to render
+it, and the build fails.
+
+That’s one of the biggest quality-of-life improvements for BLoC codebases in
+years.
+
+## Trying out BLoC in a Simple Weather App
+
+Let's see how BLoC works in action by building a weather app using it.
+
+To get started, create a new Flutter app by running the following command:
+
+```bash
+flutter create my_new_app
+```
+
+Alternatively, you could
+[install the Shorebird CLI](https://docs.shorebird.dev/getting-started/) and run
+this command instead if you want support for over-the-air updates from day 1:
+
+```bash
+shorebird create my_new_app
+```
+
+### Setting Up: Packages
+
+Add the following packages to your pubspec.yaml file:
+
+```yaml
+dependencies:
+ flutter_bloc: ^9.1.0
+ equatable: ^2.0.5
+ http: ^1.2.0
+```
+
+- `flutter_bloc` wires streams into the widget tree with `BlocProvider` and
+ `BlocBuilder`.
+- `equatable` gives your events, states, and models value equality.
+- `http` is used by the repository to call Open-Meteo.
+
+### Step-by-Step Implementation: Weather Fetch With Refresh
+
+This implementation includes two user actions.
+
+- Fetch shows a spinner and replaces the UI state.
+- Refresh updates silently so you don’t flash a loading indicator over existing
+ content.
+
+#### Step 1: Model the domain
+
+A small, UI-friendly model makes state rendering predictable. It also keeps your
+UI independent of the raw API response shape.
+
+```dart
+// lib/features/weather/models/weather.dart
+import 'package:equatable/equatable.dart';
+
+/// A minimal, UI-friendly weather model for the BLoC tutorial.
+///
+/// Notes
+/// - `tempC` is Celsius to keep formatting predictable in the UI.
+/// - `conditionCode` is the raw weather code from the API (useful for icons).
+/// - `fetchedAt` helps you show "updated X min ago" and supports refresh logic.
+final class Weather extends Equatable {
+ final String city;
+ final double tempC;
+ final int conditionCode;
+ final String condition;
+ final DateTime fetchedAt;
+
+ const Weather({
+ required this.city,
+ required this.tempC,
+ required this.conditionCode,
+ required this.condition,
+ required this.fetchedAt,
+ });
+
+ /// Open-Meteo returns current conditions with numeric weather codes.
+ /// We fetch the human-readable condition name ourselves.
+ factory Weather.fromOpenMeteo({
+ required String city,
+ required Map json,
+ }) {
+ final current = (json['current'] as Map?)?.cast();
+ if (current == null) {
+ throw const FormatException('Missing "current" in weather response.');
+ }
+
+ final temp = current['temperature_2m'];
+ final code = current['weather_code'];
+ final time = current['time'];
+
+ if (temp == null || code == null || time == null) {
+ throw const FormatException('Weather response missing required fields.');
+ }
+
+ final fetchedAt = DateTime.tryParse(time.toString());
+ if (fetchedAt == null) {
+ throw const FormatException('Invalid "time" in weather response.');
+ }
+
+ final codeInt = (code as num).toInt();
+
+ return Weather(
+ city: city,
+ tempC: (temp as num).toDouble(),
+ conditionCode: codeInt,
+ condition: WeatherCondition.fromCode(codeInt).label,
+ fetchedAt: fetchedAt.toLocal(),
+ );
+ }
+
+ @override
+ List