Fix bugs found in full code audit

- Edit screens (note, task): cache _initFuture in initState instead of
  calling _loadExisting() from FutureBuilder on every rebuild. Prevents
  race condition where multiple concurrent loads could overwrite edits.
  Also removes unnecessary _loaded flag and simplifies the pattern.

- Note editor: remove onChanged: (_) => setState((){}) on the body
  TextField — triggered a full rebuild on every keystroke for no reason.

- SSE streaming: use utf8.decode(chunk, allowMalformed: true) so a
  malformed byte sequence from the server skips rather than throwing an
  unhandled FormatException that escaped all catch blocks.

- Chat provider: guard msgs.isEmpty before accessing msgs.last during
  SSE chunk processing to prevent a potential index error on empty state.

- Offline queue drain: check mounted before and after each async gap in
  _drainQueue so ref is never accessed on a disposed widget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 11:56:26 -05:00
parent 647b36837e
commit eef101bef3
5 changed files with 30 additions and 18 deletions
+13 -8
View File
@@ -23,7 +23,16 @@ class _TaskEditScreenState extends ConsumerState<TaskEditScreen> {
TaskPriority _priority = TaskPriority.medium;
DateTime? _dueDate;
bool _saving = false;
bool _loaded = false;
// Future is created once in initState so FutureBuilder never restarts it.
late final Future<void> _initFuture;
@override
void initState() {
super.initState();
_initFuture =
widget.taskId != null ? _loadExisting() : Future.value();
}
@override
void dispose() {
@@ -33,17 +42,13 @@ class _TaskEditScreenState extends ConsumerState<TaskEditScreen> {
}
Future<void> _loadExisting() async {
if (_loaded || widget.taskId == null) {
_loaded = true;
return;
}
final task = await ref.read(tasksRepositoryProvider).getOne(widget.taskId!);
final task =
await ref.read(tasksRepositoryProvider).getOne(widget.taskId!);
_titleController.text = task.title;
_descController.text = task.description ?? '';
_status = task.status;
_priority = task.priority;
_dueDate = task.dueDate;
_loaded = true;
}
Future<void> _save() async {
@@ -117,7 +122,7 @@ class _TaskEditScreenState extends ConsumerState<TaskEditScreen> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _loadExisting(),
future: _initFuture,
builder: (context, snapshot) {
return Scaffold(
appBar: AppBar(