Skip to content

Resolve cloud conflicts item by item so Edit placement stays open - #266

Merged
SunkenInTime merged 8 commits into
mainfrom
t3/lineup-edit-survives-reload
Oct 10, 2026
Merged

SunkenInTime merged 8 commits into
mainfrom
t3/lineup-edit-survives-reload

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Use cloud on a conflict on another lineup keeps Edit placement open, and Save merges the drag

This is the Windows release build of this branch with main merged in, so it includes #269 and #270. It runs as two instances on two test accounts against the dev deployment. The user quit with an unsent note on the B-site lineup. A teammate changed the same note, so when the user reopened, it waited as a conflict. The user opens Edit placement on the A-site lineup and drags its landing, while the teammate moves that same landing. Use cloud gives B site the cloud's version, and the edit stays open with its drag. Save lands the drag, which is the later write, on both screens.

#265 is merged, and this PR now targets main.

In #265, resolving a cloud conflict closed Edit placement and threw away the user's drags, with a red toast: "The page changed, so the placement edit closed without saving." That happened even when the conflict was on a different lineup. The cause was that Use cloud and Keep both reloaded the whole page to show the cloud's version, and a reload ends the edit.

The page doesn't need to reload. Since #208, a teammate's change to the page on screen applies one item at a time, leaving alone whatever the user is in the middle of changing. Use cloud and Keep both never moved to that path. Now they do, so they replace only the lineups and items in conflict.

Since #270, edits made online merge field by field, so conflicts are rarer. The prompt now appears only when an edit made offline, or left unsent before the app closed, collides with a teammate's change to the same field. That's the case this PR handles.

What the user sees

  • A conflict on another lineup. Use cloud or Keep both shows the cloud's version of that lineup. Edit placement stays open with its drags, and Save writes them as one undo step.
  • A teammate moved a spot the user is dragging. The drag stays on screen. On Save, the user's drag is the later write, so it wins, and both screens show it. A teammate's move of another spot in the same edit isn't touched: Save names only the spots the user dragged. If Save is made offline and collides with the teammate's move, it waits as a conflict like any other offline edit.
  • A conflict on the lineup being edited. Use cloud takes the cloud's version of that lineup and closes Edit placement, with no toast, since the user chose the cloud's version of it (Dara's call). With Keep both, the user's version also appears beside it as a copy.
  • Opening a different page still ends the edit without saving, with Lock placed lineups and move them in an Edit placement mode #265's toast if anything had been dragged.

How

_useCloudVersionsFor now draws through _showCloudVersions. For the page on screen, that runs the merge teammates' changes use (_mergeRemotePage). The merge leaves alone what the user has open on the canvas: the placement edit's lineups, a lineup being placed, open text drafts and items under the pointer. The exception is anything in the conflicts being resolved, which takes the page data's version. Any other page still loads whole, as before.

Leaving the edit's lineups alone also keeps the version the user started from (markPageHydrated(keepBaseFor:)). Save's changed fields are measured from that version, which is why a teammate's move of a spot the user didn't drag survives the Save. Without it, Save would also name that spot and move it back. The new test fails that way when keepBaseFor is switched off.

editorHeldEntitiesProvider returns null while any press off the canvas is down, so teammates' changes wait. Conflict resolution doesn't wait, because it's what the user just asked for. The set of held items therefore moved into editorHeldCanvasItemsProvider, which the conflict path reads directly.

Two edge paths changed with it, both found in review:

  • A discard that partly fails. The redraw that follows gives every refused entity the reloaded page data's version, even if the user holds it. That's the cloud's version where the discard landed, and the user's own where it didn't, since that work still waits.
  • A hold that ends during the discard. Resolution used to clear any pending re-apply of the page, which was harmless after a full reload. Now a teammate's change that was held back applies once resolution ends.

Ground truth

On main's sync (this branch with main merged in), on dev, with two test accounts. The account in the GIF quit with an unsent note on the B-site lineup, and the teammate changed the same note.

  • On reopening, the note waited as a conflict (field_conflict).
  • The user opened Edit placement on the A-site lineup and dragged its landing. Meanwhile the teammate moved that landing, and the edit held the move back.
  • Use cloud:
    • B site took the teammate's note.
    • The conflict cleared.
    • Edit placement stayed open with the drag.
  • Save:
    • It sent only landings/lA, which landed at revision 3.
    • Both screens show the landing where the user dragged it.

After Use cloud, Edit placement is still open with its drag

Before #269, on the server that refused any change to a group someone else had changed. These runs used the same two accounts:

  • Use cloud on another lineup while editing. The edit stayed open with its drag.
  • Save after a teammate moved the dragged landing. The server refused it, and the teammate's position stayed.
  • Use cloud on the lineup being edited. Edit placement closed, with no toast, and the lineup showed the teammate's version.

A scratch driver that isn't committed sent pointer events through Flutter's hit testing and recorded state after each step.

Tests

These tests are in strategy_page_session_provider_test, "lineup history through cloud sync", and run on the real op queue against the fake server. Each new one fails on #265 or without its fix:

  • Use cloud and Keep both on another group keep the edit open with its drag. Save reaches the server as one undo step, and so does its undo.
  • Use cloud keeps a drag of a spot a teammate moved meanwhile. Save is checked against the version the edit started from: on a server that checks each group whole, it waits as a conflict.
  • On a server that merges by field, Save names only the dragged spot. It wins over the teammate's move of that spot, and their move of another spot stays.
  • Use cloud on the group being edited takes the cloud's version and closes the edit.
  • Use cloud takes the cloud's version of a group the user is placing a new lineup from. The placement and its mode stay.
  • When the discard fails, the user's version comes back on a group they are placing a lineup from.
  • A teammate's move that the edit held back applies when the user ends the edit during the discard.
  • A drag made while Use cloud loads the cloud's version stays, and the conflict still resolves.

openOnRealQueue can now give the fake server's header a theme. Without one, any redraw queued a map and theme patch. Its acknowledgement re-read the page, which hid the held-back-change bug.

The full suite passes locally with main merged in (1851 tests, 9 skipped), and analyze is clean.

Review

Astra did two rounds. Round 1 found the two edge paths above, and both are fixed here, each with a test. Round 2 found an existing race from #265's base: a re-apply waiting when the user's own edit was acknowledged could run before the refreshed page arrived, briefly showing the old version. #270 fixed it at its root, and that fix is on main now.

ci: analyze test web

🤖 Generated with Claude Code

SunkenInTime and others added 4 commits October 9, 2026 14:54
Resolving an unrelated cloud conflict (Use cloud, Keep both) reloads the
page on screen, which closed Edit placement and threw away the user's
drags. A reload of the same page, the one that keeps undo history, now
keeps the edit open on whichever of its lineups survive. A drag stays
while the reload has its end where the drag started; an end the cloud
moved or removed shows the cloud's version, with a toast. Opening another
page still ends the edit without writing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload in the middle of a drag (pointer still down) left the editor's
drag based on the old position: the next pointer move put back the drag
the reload had just undone, and when the dragged end was gone, the next
drag of another end started from its numbers. The editor now lets a drag
carry on only if the reload kept its end where the drag began.

fromHive sets the reopened edit in one assignment, so nothing watching
sees the edit end on the way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor compared a reload against where its own gesture began, which
for a second drag of an end is the earlier draft, not the saved position
fromHive compares against. The two could disagree: a reload that moved
the spot to the first draft dropped the drag, and the editor then put it
back. The editor now asks the reopened edit whether it kept the draft.

A drag taken back on an end the reload removed no longer outlives its
gesture, and dragging another end clears it.

The screenshot view's lineup fake takes fromHive's new samePage flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload that took a drag back and a second reload that removed its end,
both before the pointer lifted, left the marker set: when a later reload
brought the end back, its first drag was ignored. Every reopen now clears
a marker whose end is no longer in the edit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b715d3e5-75b8-429e-b71a-1e66c6e7bfc6

📥 Commits

Reviewing files that changed from the base of the PR and between eb4c0a1 and b8c3f30.


📒 Files selected for processing (1)
  • test/strategy_page_session_provider_test.dart

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2ca953f4-f73c-4b13-a52c-a50b31e505ec


📥 Commits

Reviewing files that changed from the base of the PR and between 844ecde and eb4c0a1.



📒 Files selected for processing (3)
  • lib/providers/editor_operation_provider.dart
  • lib/providers/strategy_page_session_provider.dart
  • test/strategy_page_session_provider_test.dart


Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.




📝 Walkthrough
📝 Walkthrough

Walkthrough

Conflict resolution now redraws selected cloud entities on the active hydrated page while preserving other editor-held items. Off-screen pages still use full-page application. Pending remote reapplication resumes after resolution, and tests cover placement edits and concurrent changes.

Changes

Selective cloud conflict resolution

Layer / File(s) Summary
Compute editor-held canvas items
lib/providers/editor_operation_provider.dart
A new provider computes held canvas item IDs from pointer holds and editor state. The held-entities provider delegates to it when pointer holds can be attributed to the canvas.
Redraw selected cloud entities
lib/providers/strategy_page_session_provider.dart
Conflict redraws pass rejected entity keys and the loaded snapshot to a selective merge for active hydrated pages. Off-screen pages retain full-page application, and pending remote reapplication resumes after resolution.
Validate placement-edit conflict handling
test/strategy_page_session_provider_test.dart
Tests cover preserved placement edits, conflicts on the edited group, failed discards, and edits made during cloud reads or outbox writes.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant StrategyPageSessionProvider
  participant editorHeldCanvasItemsProvider
  participant _mergeRemotePage
  StrategyPageSessionProvider->>StrategyPageSessionProvider: Pass rejected keys and loaded snapshot to _showCloudVersions
  StrategyPageSessionProvider->>editorHeldCanvasItemsProvider: Get held canvas item IDs
  editorHeldCanvasItemsProvider-->>StrategyPageSessionProvider: Return held canvas item IDs
  StrategyPageSessionProvider->>_mergeRemotePage: Merge selected cloud entities while preserving held items
  _mergeRemotePage-->>StrategyPageSessionProvider: Return merged page state
Loading


Merge Risk: ⚪ Minimal · up to eb4c0

Resolving a sync conflict now updates only the affected items, so an open placement edit on another lineup stays open. The changes come with targeted tests for failure and concurrency cases. No outstanding merge-blocking issues were identified.

Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main change: resolving cloud conflicts item by item while keeping Edit placement open.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.



✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR



  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

SunkenInTime and others added 2 commits October 9, 2026 23:34
Use cloud and Keep both reloaded the whole page to show the cloud's
version, which closed Edit placement and threw away its drags even when
the conflict was on another lineup. Teammates' changes already apply item
by item (#208); conflict resolution now goes the same way, holding what
the user has open except what they chose the cloud's version of. A
placement edit of a lineup in the conflict ends.

This replaces the earlier commits' reopen-after-reload logic in the lineup
provider and editor, which go back to #265's versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When part of Use cloud's discard fails, the redraw that follows puts the
user's version back on every refused entity, held or not; holding one
kept the cloud's version on screen while the work still waited.

A teammate's change held back by a hold the user let go of during the
discard waited for a reapply that conflict resolution then cleared. It
now resumes once resolution ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SunkenInTime SunkenInTime changed the title Keep Edit placement open when its page reloads Resolve cloud conflicts item by item so Edit placement stays open Oct 10, 2026
@SunkenInTime
SunkenInTime changed the base branch from t3/lineup-edit-placement to main October 10, 2026 15:13
SunkenInTime and others added 2 commits October 10, 2026 11:13
On a server that merges by field, Save names only the spots the user
dragged, measured from the version the edit started from: the user's
drag wins over a teammate's move of that spot, and the teammate's move
of another spot the edit held back stays. The test that Save waits as a
conflict now says it runs on a server that checks whole groups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SunkenInTime
SunkenInTime merged commit 830f3b4 into main Oct 10, 2026
14 checks passed
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