Skip to content

fix: drawing tools desync in release builds (ghost tools, broken symbol persistence)#478

Open
ahrar-deriv wants to merge 4 commits into
masterfrom
fix-drawing-tools-sync-issue
Open

fix: drawing tools desync in release builds (ghost tools, broken symbol persistence)#478
ahrar-deriv wants to merge 4 commits into
masterfrom
fix-drawing-tools-sync-issue

Conversation

@ahrar-deriv

@ahrar-deriv ahrar-deriv commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

This PR contains the following changes:
Release-mode timing exposed several sync failures between the drawing
tools repository, the interactive layer state machine, and the painted
layer:

  • updateStateTo could await a TickerFuture that never completes when a
    keyed remount disposes the AnimationController mid-animation, leaving
    the state machine stuck; awaited animations now use .orCancel with
    mounted guards, and a sequence guard drops superseded transitions.
  • The add flow selected the preview drawing instance without the
    persisted configId, breaking later edits/deletes; the repo-backed
    config is now bound to the selected instance and reconciliation
    unifies map/selected instances sharing the same id.
  • The gesture handler painted and hit-tested a snapshot drawings list
    that could go stale when a rebuild failed to propagate (bottom-sheet
    "clear all"); it now reads the reconciled map live and repaints via a
    drawings-changed notifier independent of rebuild delivery.

Adds debug-only [chart-diag] logging and regression tests covering
remount-cancelled transitions, superseded transitions, instance
unification, and clear-all repaint.

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

Developers Note (Optional)

Pre-launch Checklist (For PR creator)

As a creator of this PR:

  • ✍️ I have included clickup id and package/app_name in the PR title.
  • 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.).
  • ⚒️ I have fixed any errors/warnings shown by the analyzer/linter.
  • 📝 I have added documentation, comments and logging wherever required.
  • 🧪 I have added necessary tests for these changes.
  • 🔎 I have ensured all existing tests are passing.

Reviewers

@behnam-deriv

Pre-launch Checklist (For Reviewers)

As a reviewer I ensure that:

  • ✴️ This PR follows the standard PR template.
  • 🪩 The information in this PR properly reflects the code changes.
  • 🧪 All the necessary tests for this PR's are passing.

Pre-launch Checklist (For QA)

  • 👌 It passes the acceptance criteria.

Pre-launch Checklist (For Maintainer)

  • [MAINTAINER_NAME] I make sure this PR fulfills its purpose.

Summary by Sourcery

Improve synchronization between drawing tools repository, interactive layer state machine, and painted layer to prevent ghost tools and stuck states in release builds.

Bug Fixes:

  • Ensure interactive layer reconciles interactable drawings with the repository on mount, widget updates, and every build so pre-loaded and cleared drawings render correctly.
  • Bind newly added drawings’ persisted configurations and IDs to the selected interactable instance to keep edits, deletes, and persistence in sync.
  • Prevent state transitions from hanging or reverting by handling remount-cancelled animations and dropping superseded transitions.
  • Reset interactive state when its selected drawing is removed from the repository so floating controls do not reference deleted tools.
  • Fix marker area and chart subtree remounting so marker- and symbol-related remounts no longer leave stale interactive/drawing state.

Enhancements:

  • Introduce a drawings-changed notifier and live getters so the gesture handler’s paint and hit-testing always reflect the latest reconciled drawings map independent of widget rebuild propagation.
  • Add debug-only chart diagnostics logging around repositories, interactive layer reconciliation, and state transitions to aid investigation of sync issues.

Build:

  • Update intl dependency to 0.20.2 and override it in the example app to match.

Tests:

  • Add unit and integration tests covering repo–layer synchronization, remount-cancelled and superseded transitions, selected-instance unification, and clear-all repaint behaviour in the interactive layer.

…ol persistence)

Release-mode timing exposed several sync failures between the drawing
tools repository, the interactive layer state machine, and the painted
layer:

- updateStateTo could await a TickerFuture that never completes when a
  keyed remount disposes the AnimationController mid-animation, leaving
  the state machine stuck; awaited animations now use .orCancel with
  mounted guards, and a sequence guard drops superseded transitions.
- The add flow selected the preview drawing instance without the
  persisted configId, breaking later edits/deletes; the repo-backed
  config is now bound to the selected instance and reconciliation
  unifies map/selected instances sharing the same id.
- The gesture handler painted and hit-tested a snapshot drawings list
  that could go stale when a rebuild failed to propagate (bottom-sheet
  "clear all"); it now reads the reconciled map live and repaints via a
  drawings-changed notifier independent of rebuild delivery.

Adds debug-only [chart-diag] logging and regression tests covering
remount-cancelled transitions, superseded transitions, instance
unification, and clear-all repaint.
@sourcery-ai

sourcery-ai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fixes multiple release-only desync issues between the drawing tools repository, interactive layer state machine, and painted layer by making state transitions robust to animation cancellation, reconciling the live drawings map with the repo/state machine on every build, wiring gesture handler painting/hit-testing to that live map, correctly binding persisted configs to selected drawings, and adding diagnostics/tests plus minor chart remounting and intl dependency fixes.

Sequence diagram for drawing tool add and selection sync

sequenceDiagram
  actor User
  participant ILBehaviour as InteractiveLayerBehaviour
  participant AddingState as InteractiveAddingToolState
  participant ILayer as InteractiveLayer
  participant Repo as AddOnsRepository
  participant LayerState as _InteractiveLayerState
  participant Handler as _InteractiveLayerGestureHandlerState

  User->>ILBehaviour: startAddingTool
  ILBehaviour->>AddingState: enter (InteractiveAddingToolState)

  User->>AddingState: onPanEnd
  AddingState->>ILayer: clearAddingDrawing
  AddingState->>ILayer: addDrawing(getUpdatedConfig)
  ILayer-->>AddingState: DrawingToolConfig addedConfig
  AddingState->>AddingState: _drawingPreview.interactableDrawing.config = addedConfig

  ILayer->>Repo: add(addedConfig)
  Repo-->>LayerState: notifyListeners
  LayerState->>LayerState: syncDrawingsWithConfigs
  LayerState->>LayerState: _reconcileDrawings
  LayerState->>LayerState: _notifyDrawingsChanged
  LayerState-->>Handler: drawingsChanged.notify

  Handler->>Handler: _checkIsAToolAdded
  Handler->>ILBehaviour: aNewToolsIsAdded(drawing)

  AddingState->>ILBehaviour: updateStateTo(InteractiveSelectedToolState,
  note right of ILBehaviour: waitForAnimation: false
  ILBehaviour->>ILayer: onUpdate()
  ILayer-->>User: selected tool is repo-backed and painted
Loading

Sequence diagram for robust state transitions with animation cancellation

sequenceDiagram
  participant Caller
  participant ILBehaviour as InteractiveLayerBehaviour
  participant Handler as _InteractiveLayerGestureHandlerState
  participant AnimCtrl as AnimationController

  Caller->>ILBehaviour: updateStateTo(newState,
  note right of ILBehaviour: waitForAnimation: true
  ILBehaviour->>ILBehaviour: _stateTransitionSeq++ (seq1)
  ILBehaviour->>Handler: animateStateChange(direction, animate)
  Handler->>Handler: _runAnimation(direction, animate)
  alt forward animation
    Handler->>AnimCtrl: reset
    Handler->>AnimCtrl: forward().orCancel
  else reverse animation
    Handler->>AnimCtrl: reverse(from: 1).orCancel
  end

  alt handler disposed during animation
    AnimCtrl--xHandler: TickerCanceled
    Handler->>Handler: catch TickerCanceled
    Handler-->>ILBehaviour: animation completes logically
  end

  ILBehaviour->>ILBehaviour: check seq1 == _stateTransitionSeq
  alt superseded by newer transition
    ILBehaviour->>ILBehaviour: drop newState assignment
  else still current
    ILBehaviour->>ILBehaviour: _controller.currentState = newState
    ILBehaviour->>Handler: onUpdate()
  end
Loading

File-Level Changes

Change Details Files
Make interactive layer <-> behaviour state transitions robust to remounts and animation cancellations to prevent stuck states and ghost tools.
  • Guard animateStateChange against unmounted gesture handlers so awaiting transitions complete instead of hanging when the layer is disposed mid-animation.
  • Use AnimationController.forward/reverse().orCancel with TickerCanceled handling so cancelled animations still complete futures cleanly.
  • Introduce a monotonic transition sequence in InteractiveLayerBehaviour.updateStateTo so transitions suspended on animations are dropped if superseded by newer transitions.
  • Add debug-only diagnostics describing state transitions and current states.
lib/src/deriv_chart/interactive_layer/interactive_layer.dart
lib/src/deriv_chart/interactive_layer/interactive_layer_behaviours/interactive_layer_behaviour.dart
lib/src/deriv_chart/interactive_layer/interactive_layer.dart
lib/src/misc/chart_diagnostics.dart
Keep the interactive layer drawings map reconciled with the repository and behaviour, and drive painting/hit-testing from this live reconciled map.
  • Add reconciliation logic that runs on repo changes and every build, creating/removing InteractableDrawings to match repo items and ensuring the selected instance from the state machine is the one stored/painted for a configId.
  • Add a drawingsChanged InteractionNotifier and wire the gesture handler to use a ValueGetter<List> plus this notifier, so paint/hit-test reacts immediately to repo changes even if rebuilds are delayed or missed.
  • Reset the behaviour state to InteractiveNormalState when a selected drawing is removed from the repo, and ensure this reset runs safely post-frame.
  • Add diagnostics around reconcile decisions and last-painted ids, and ensure listeners are moved when the repo instance changes and cleaned up on dispose.
lib/src/deriv_chart/interactive_layer/interactive_layer.dart
Fix drawing-tool add flow so the selected instance is backed by the persisted repository config instead of a preview-only ghost instance.
  • Change InteractiveAddingToolState to clear the preview, call interactiveLayer.addDrawing to persist the config and receive the stored config with an id, then assign this config back to the preview’s InteractableDrawing before selecting it.
  • Ensure the transition to InteractiveSelectedToolState after add does not wait on animation, reducing race with remounts.
  • Adjust the gesture handler add-flow wiring to react to drawingsChanged instead of relying on widget rebuild timing so selection handoff to repo-backed instances is reliable.
lib/src/deriv_chart/interactive_layer/interactive_layer_states/interactive_adding_tool_state.dart
lib/src/deriv_chart/interactive_layer/interactive_layer.dart
lib/src/deriv_chart/interactive_layer/interactive_layer.dart
Align AddOnsRepository and chart wiring so repo mutations and SharedPreferences loads synchronize reliably with the chart and interactive layer.
  • Add chartDiag logging in AddOnsRepository for loadFromPrefs/add/removeAt/clear to trace repo lifecycle and contents.
  • Ensure InteractiveLayer subscribes/unsubscribes correctly when the drawingToolsRepo instance changes, and performs an initial post-frame sync to pick up items loaded before mount.
  • Key MainChart and MarkerArea subtrees by markerSeries so marker changes force remounts instead of reusing stale subtrees.
  • Update intl dependency (and example override) to 0.20.2 to keep compatibility with other deps.
lib/src/add_ons/add_ons_repository.dart
lib/src/deriv_chart/interactive_layer/interactive_layer.dart
lib/src/deriv_chart/chart/main_chart.dart
pubspec.yaml
example/pubspec.yaml
Add regression tests and diagnostics for drawing-tools sync, remount timing, and full DerivChart integration.
  • Add interactive_layer_sync_test.dart to cover repo pre-population before mount, add/remove, repo swaps, selected-instance reuse, remount-cancelled transitions, map/selection unification, clear() repaint behaviour, and superseded transitions.
  • Add deriv_chart_drawing_tools_integration_test.dart to exercise full DerivChart wiring with SharedPreferences-backed repos, symbol switches, and deletion repaint flows.
  • Introduce chart_diagnostics.dart and wire chartDiag calls throughout interactive layer, behaviour, and repo to provide [chart-diag] logs in debug builds only.
test/deriv_chart/interactive_layer/interactive_layer_sync_test.dart
test/deriv_chart/interactive_layer/deriv_chart_drawing_tools_integration_test.dart
lib/src/misc/chart_diagnostics.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
PUB/intl 0.20.2 UnknownUnknown
PUB/intl >= 0.19.0, < 0.20.0 UnknownUnknown

Scanned Manifest Files

example/pubspec.yaml
  • intl@0.20.2
  • intl@>= 0.19.0, < 0.20.0

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown

Claude finished @ahrar-deriv's task —— View job


PR Review: Drawing Tools Desync Fix

Overview

This PR addresses critical synchronization issues between the drawing tools repository, interactive layer state machine, and painted layer that manifest primarily in release builds. The fixes are well-architected and address real timing/lifecycle issues.


Priority Issues

HIGH PRIORITY

1. Potential Memory Leak in _scheduleStateReset (lib/src/deriv_chart/interactive_layer/interactive_layer.dart:376-397)

Issue: The _stateResetScheduled flag prevents double-scheduling, but if the widget is disposed between scheduling and callback execution, there's no cleanup mechanism. The mounted guard protects setState, but the flag never gets reset if disposal happens during the scheduled frame.

Location: interactive_layer.dart:382-396

void _scheduleStateReset() {
  if (_stateResetScheduled) {
    return;  // ← Flag never cleared if widget disposed before callback
  }
  _stateResetScheduled = true;
  
  WidgetsBinding.instance.addPostFrameCallback((_) {
    _stateResetScheduled = false;  // ← Only cleared if callback executes
    if (!mounted) {
      return;
    }
    // ...
  });
}

Fix: Clear the flag in dispose():

@override
void dispose() {
  _stateResetScheduled = false;
  // ... rest of dispose
}

2. Race Condition: _reconcileDrawings During Build Phase (interactive_layer.dart:448-453)

Issue: _reconcileDrawings() is called unconditionally during build() (line 453), but inside reconciliation there's a post-frame callback that calls syncDrawingsWithConfigs() which does setState() (line 245-249). This creates a potential setState-during-build scenario.

Location: interactive_layer.dart:244-250

if (drawingContext.fullSize == Size.zero) {
  // ...
  WidgetsBinding.instance.addPostFrameCallback((_) {
    if (mounted) {
      syncDrawingsWithConfigs();  // ← calls setState() during build phase
    }
  });
  return;
}

Analysis: While the post-frame callback should defer the setState, the scheduler phase check in _notifyDrawingsChanged (line 353-354) shows awareness of this issue but doesn't cover the retry-sync path. The retry can fire during persistentCallbacks phase when builds are still running.

Recommendation: Guard the retry sync similarly:

WidgetsBinding.instance.addPostFrameCallback((_) {
  if (mounted && WidgetsBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
    syncDrawingsWithConfigs();
  }
});

MEDIUM PRIORITY

3. Incomplete Error Handling in Reconciliation (interactive_layer.dart:204-213)

Good: The try-catch around _reconcileDrawings() prevents silent failures.

Issue: The error is logged but then setState() proceeds anyway (line 212). If reconciliation threw because the map is in an inconsistent state, rebuilding from that state could cause cascading errors.

Location: interactive_layer.dart:204-213

try {
  _reconcileDrawings();
} on Object catch (error, stackTrace) {
  chartDiag('layer#$hashCode reconcile threw: $error\n$stackTrace');
}
setState(() {});  // ← Always rebuilds even if reconciliation failed

Recommendation: Consider whether setState should be conditional on success, or at minimum add a more prominent error (not just debug log) for production diagnostics.


4. Behaviour Initialization Assumption (interactive_layer.dart:228-234)

Location: interactive_layer.dart:228-234

if (!interactiveLayerBehaviour.isInitialized) {
  chartDiag('layer#$hashCode reconcile skipped: behaviour not initialized');
  return;  // ← Silently skips, relies on post-frame callback
}

Issue: If the behaviour fails to initialize (e.g., gesture handler init throws), drawings will never reconcile. The comment says "post-frame initial sync from initState will reconcile right after" but there's no guarantee.

Recommendation: Add a defensive assert or logged warning if initialization doesn't complete within a reasonable time (e.g., after 2-3 frames).


5. Animation Cancellation Handling (interactive_layer_behaviour.dart:142-158)

Excellent: The .orCancel pattern and transitionSeq guard are exactly the right solution for the stuck-state bug.

Minor Concern: The unawaited() call (line 156) when waitForAnimation: false means errors from that animation aren't caught. While TickerCanceled is handled in _runAnimation, other exceptions would be silent.

Location: interactive_layer_behaviour.dart:156-157

unawaited(
    interactiveLayer.animateStateChange(direction, animate: animate));

Recommendation: Chain a .catchError() or at minimum add a diagnostic comment explaining why errors are safe to ignore here.


6. State Machine Instance Reuse Logic (interactive_layer.dart:286-301)

Location: interactive_layer.dart:293-300

if (selectedDrawing != null &&
    _interactableDrawings.containsKey(selectedDrawing.id) &&
    !identical(
        _interactableDrawings[selectedDrawing.id], selectedDrawing)) {
  chartDiag('layer#$hashCode reconcile: replacing map instance for '
      '${selectedDrawing.id} with the selected instance');
  _interactableDrawings[selectedDrawing.id] = selectedDrawing;
  selectedInstanceReplaced = true;
}

Good: This unification logic is critical for the add-flow fix.

Edge Case: What if selectedDrawing.id is null or doesn't match any repo item? The subsequent configListIds.contains(selectedDrawing.id) check (line 331) handles this, but the map replacement happens before that check. If a null or mismatched id somehow gets here, you'd add a bad entry to the map.

Recommendation: Add an early guard:

if (selectedDrawing != null && 
    selectedDrawing.id != null &&
    configListIds.contains(selectedDrawing.id) &&
    // ... rest of checks

LOW PRIORITY (Code Quality)

7. Magic Number in Animation Duration (interactive_layer.dart:607-609)

Location: interactive_layer.dart:607-609

_stateChangeController = AnimationController(
  vsync: this,
  duration: const Duration(milliseconds: 240),
);

Issue: The 240ms duration is referenced in test comments (interactive_layer_sync_test.dart:250) but not as a named constant. Changes to this value require updating test comments.

Recommendation: Extract to a named constant: static const _kStateChangeDuration = Duration(milliseconds: 240);


8. Diagnostic Logging Verbosity (Throughout)

Good: The chartDiag() calls are extremely helpful for debugging, well-gated by kDebugMode, and provide excellent context.

Minor: Some logs include object hashcodes (e.g., layer#$hashCode) which are great for tracking instances, but in production bug reports users might share debug logs. Consider if PII or internal structure details should be redacted.


Positive Highlights

Excellent Architecture Decisions

  1. ValueGetter + Notifier Pattern (interactive_layer.dart:460-461, 488-489): Using ValueGetter<List<InteractableDrawing>> instead of snapshot lists ensures paint/hit-test always read live data. This is a brilliant solution to the stale-snapshot problem.

  2. Sequence Numbers for Transitions (interactive_layer_behaviour.dart:86, 136): The _stateTransitionSeq monotonic counter elegantly solves the superseded-transition problem without complex state tracking.

  3. Post-Frame Reconciliation Timing (interactive_layer.dart:174-178): The post-frame initial sync correctly handles the repo-loaded-before-mount race condition.

  4. .orCancel on TickerFuture (interactive_layer.dart:714-726): Perfect use of .orCancel to prevent infinite hangs when animations are cancelled mid-transition.


Test Coverage Assessment

Excellent Coverage

The test files (interactive_layer_sync_test.dart and deriv_chart_drawing_tools_integration_test.dart) demonstrate outstanding coverage of the bug scenarios:

✅ Covered:

  • Pre-mount repo population (lines 103-122)
  • Repo instance swaps (lines 143-181)
  • Remount-cancelled animation transitions (lines 236-275)
  • Selected instance unification (lines 277-323)
  • clear() repaint without rebuild (lines 325-353)
  • Superseded transitions (lines 355-392)
  • Full DerivChart integration with symbol switches (deriv_chart_drawing_tools_integration_test.dart)

⚠️ Missing Coverage:

  • Error paths: What happens if getInteractableDrawing() throws during reconciliation?
  • Rapid remounts: Multiple symbol switches in quick succession (<100ms apart)
  • Gesture interactions during reconciliation (tap/drag while reconcile is running)
  • AnimationController disposal edge cases when unawaited() animations are in flight

Performance Considerations

Good

  1. RepaintBoundary (interactive_layer.dart:812): Properly isolates drawing layer repaints.
  2. Conditional Reconciliation: Early returns in _reconcileDrawings() prevent unnecessary work.

Potential Concerns

  1. O(n²) Complexity in Hit Testing: The hitTestDrawings() calls in gesture handling iterate all drawings for every hover/pan event. With many drawings this could impact performance. Consider spatial indexing if >50 drawings become common.

  2. Map Lookup Frequency: _interactableDrawings is accessed via .values.toList() on every paint (line 825). For large drawing counts, consider caching this list between reconciliations.


Security Assessment

✅ No security concerns identified.

  • No credential handling or secret management issues
  • Diagnostic logs don't expose sensitive user data
  • SharedPreferences usage is standard and safe

Recommendations Summary

Must Fix Before Merge

Should Fix (Medium Priority)

Nice to Have (Low Priority)

  • LOW Morteza/dummy commit #7: Extract magic animation duration to constant
  • Add missing test coverage for error paths and rapid remounts
  • Consider performance optimizations for large drawing counts

Final Assessment

Overall: This is excellent work that addresses real, hard-to-reproduce timing bugs with well-thought-out architectural solutions. The diagnostic logging, comprehensive tests, and detailed PR description demonstrate exceptional engineering quality.

Code Quality: 9/10 (minor edge case handling improvements needed)
Test Coverage: 9/10 (core scenarios excellently covered, error paths missing)
Architecture: 10/10 (ValueGetter pattern, sequence numbers, .orCancel usage are exemplary)
Documentation: 10/10 (PR description, code comments, and diagnostics are outstanding)

Verdict

Approve with minor fixes recommended (HIGH priority items #1 and #2).

The HIGH priority issues are defensive fixes for edge cases that are unlikely but could cause issues in production. The core synchronization logic is sound and the tests validate the main bug fixes thoroughly.


Note: Per project guidelines, please ensure before merging:

  • flutter analyze --no-fatal-infos passes with no warnings
  • flutter test all tests pass
  • dart format . applied

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant