* WidgetUpdateController is a Riverpod AsyncNotifier that listens to todayHabitsProvider, topStreakProvider, and dopamineScoreProvider.
* When any of these providers emit new values, the controller syncs updated data to SharedPreferences via the home_widget package.
* Data fields written: dopamine_score (int), top_streak (int), today_habits (JSON string of up to 3 habits with id, name, and completed boolean).
* After writing data, the controller calls HomeWidget.updateWidget(androidName: 'HabitWidgetReceiver') to trigger a native widget refresh.
* The controller handles background callbacks from widget toggle actions: when a habit is toggled on the widget, Flutter's background isolate receives the URI, parses the habit ID, and invokes the habit completion logic.
* The controller debounces rapid widget updates (e.g., multiple habits completed in quick succession) to avoid excessive native widget refreshes.
* Initial widget data sync occurs during app bootstrap.
gherkin
Given the todayHabitsProvider emits an updated habit list
When the WidgetUpdateController listener fires
Then the top 3 habits are serialized to JSON and written to SharedPreferences under the key "today_habits"
Given the dopamineScoreProvider emits a new score
When the WidgetUpdateController listener fires
Then the score integer is written to SharedPreferences under the key "dopamine_score" and the widget is updated
Given 3 habits are completed in rapid succession within 500 milliseconds
When the debouncer processes the update requests
Then only one widget update call is made after the debounce period
Given a background callback is received with URI "habitforge://toggle?id=h1"
When the background isolate processes the callback
Then the habit repository's completeHabit or uncompleteHabit is invoked for habit "h1" and a subsequent sync re-updates the widget
Given the app starts for the first time
When bootstrap initialization runs
Then the WidgetUpdateController performs an initial data sync to ensure the widget has current data even before the user interacts with the app
Given the background toggle callback fails due to a database error
When the error is caught
Then the widget data is reverted to the pre-toggle state and the widget is re-updated to reflect the actual persisted state
Details and Assumptions
Acceptance Criteria