Repository navigation
Sync each cloud lineup group as one row - #236
Merged
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
A lineup synced as an origin row, a landing row and a link row, and the server checked across them: a link needed live ends (LINEUP_LINK_END_MISSING), an end could not go while a link used it (LINEUP_END_IN_USE), and clients opted in to both checks with applyBatch flags. The conflict unit was one part while the user's lineup needs all three. A lineup row now holds the whole lineup, kind "lineup", keyed by its id: name, video, notes, images, and full copies of its origin and landing. Lineups sharing a spot each carry their own copy; the client draws them as one spot again. Every row stands on its own, so the cross-row checks, their flags and error codes, and the endpoint index go. Page ownership (LINEUP_PAGE_MISMATCH) and strategy-scoped keys stay. Duplicating a strategy copies each row under fresh ids from one shared map, so copied lineups that shared a spot still share it. The agent summary counts each origin once per page, read from live lineups through a new by_strategyId_and_deleted index. Image references read data.images of any lineup row. Protocol 5: a client on 4 cannot read the new rows and writes rows the server no longer takes, so it is refused until it reloads. Production holds no lineup rows (checked 2026-10-01) and dev's lineups table is empty, so the narrowed schema needs no backfill. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The client side of one row per lineup. Live sync turns the page's graph into one desired row per link, each carrying its origin and landing as they are now, so moving a shared spot patches every lineup on it, each as its own op against its own revision, and deleting a spot deletes the rows of the lineups on it. Hydration rebuilds the graph from the rows: links from rows, origins and landings deduplicated by id. When copies of one spot disagree (only after concurrent edits) every client draws the copy from the row with the highest revision, ties to the greatest publicId, and live sync compares rows in that drawn form so the choice never authors a write. The user's own pending rows outrank the server until they land. Removed with the split rows: kind-prefixed keys and the drawn-row join, held-back and unsyncable lineups, endpoint restore, undrawn remote rows, endpoint-first and link-first outbox scheduling, the missing-end Keep mine path and its messages, the sync status's unsyncable source, the graph id rewrite on upload, and the Paranoia correction for landings (lineup rows are written at the in-game size and are never corrected). A remote merge no longer holds the whole graph while the user holds any part of it: only lineups touching what is held, closed over the spots they share, keep their screen copy; the rest of the page's lineups update. An outbox record from an older build can still hold an origin, landing or link op. It is never sent: it waits in attention with its own reason until the user discards it, and Keep mine leaves it there. The local model, UI, Hive, .ica and undo are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rites Copies of a shared spot can drift apart (a move that reached only some of its lineups), and the row revision is no measure of which copy is newest. Each end of a lineup row now carries its spot's version, an integer from 1; clients draw the copy with the highest version, then the greatest lineup id. Duplicate copies versions as they are, so a copy draws every spot where the original does. A patch or reorder of a deleted element or lineup was acked onto the tombstone and the edit vanished on the next load. It is now rejected with the new reason "deleted"; an add expecting the tombstone's revision brings the row back. A write that changes nothing on a live row stays a noop whatever revision it expects, so two clients healing the same row never conflict. Clients on protocol 4 still send checkLineupLinkEnds/checkLineupEndDeletes and graph rows. The args are accepted and ignored, and the old kinds pass argument validation to be refused per op in the handler, so such a client gets CLIENT_UPGRADE_REQUIRED rather than a validation error. Storage takes only lineups. The agent summary is refreshed only after a batch that touched a page, an agent element or a lineup. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Live sync compared lineup rows in their drawn form, so a fan-out patch still queued for one row looked satisfied once the others landed, and its record was dropped; deleting the lineup holding the drawn copy then showed the stale one. Desired rows now carry each spot's drawn value and version (unchanged keeps the drawn version, a change goes one past it, a new spot starts at 1), versions taken from the rows the canvas was hydrated from, and are compared with the rows as stored. Any row holding another copy is patched until it really holds the drawn one, and a page is healed as soon as it is drawn. Versions never reach the canvas, Hive or .ica. Outbox records holding protocol 4 lineup ops are put in attention when the outbox loads, whatever state an older build left them in, so reconciling the canvas never removes them. Keep mine after a teammate's delete sends an add over the tombstone with the user's payload; a change it cannot bring back says so. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SunkenInTime
force-pushed
the
lineups-one-row
branch
from
October 2, 2026 03:25
3d41429 to
02adca3
Compare
…e agents from a side table A shared spot no longer carries a version. Each lineup row owns its copy of its spots and only ever changes when the user changes that lineup or its spot, so the server stores what it is sent and joins nothing. An accepted op whose answer was lost is now replayed as a noop at the revision it actually landed at, not the row's latest. Answering with the latest let a waiting successor rebase onto a teammate's later edit and overwrite it without a conflict. The strategy agent summary reads a small lineupAgents table, kept in step on every accepted lineup op, duplicate and purge, instead of every lineup payload, so its reads stay small however large lineups get. Production and dev held no lineup rows when this shape shipped (2026-10-01), so nothing needs backfilling. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Shared spots were resolved by version and stale copies healed on page open, which meant writing rows the user never touched: a heal could replace recovered outbox work, and a rejected heal next to an accepted delete moved a spot backwards. Now copies of a spot that agree draw as one shared spot, as before. Copies that disagree, which only happens after a partial sync or a conflict, draw as separate spots: the value the row with the smallest lineup id carries keeps the real id, every other value gets a local alias. Every client draws the same thing and nothing is written; moving one of those spots patches only the rows carrying it, under the real id. Also: a change refused because a teammate deleted the item is settled, not kept, once the user deletes the item too, so Keep mine can't bring it back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lineups that share a standing or landing spot now live in one cloud row with those spots (kind "lineups": origins, landings, links). A shared spot exists once, so copies can no longer disagree, and the row's one revision guards every lineup on it: two people changing the same group meet as an ordinary conflict. Nothing points across rows. A group only grows or merges, never splits, and a new group is named after its smallest lineup id that no group of the page has used. This replaces the copy-per-lineup model of the earlier commits, which failed review three times because copies of a spot can drift apart. - Server: group validation (every lineup aims inside its row), one lineupAgents row per origin, images across all links, duplicate copies groups. A replayed op with no recorded revision answers with none, so no successor is promoted onto a guessed revision. - Client: per-page group memory in live sync, holds take whole groups, migration uploads groups. - Conflicts on lineup groups list what each side changed, and offer Keep both: the cloud's group stays and the user's version is added beside it under fresh ids. It loads the cloud version first, so failing offline changes nothing. - settleAttention only drops work refused because a teammate deleted it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review of the group rows: - Groups no longer merge. A lineup stays in the group it was written to, new lineups join the group of a spot they share, and a row goes only when its last lineup does. A merge deleted the other group's row, and a teammate's edit to it could be lost even after Use cloud. A stored row whose lineups are still drawn is written back as stored, never deleted. - Keep both discards nothing unless the waiting work, the page and the copy's save all still hold: an edit or a page switch while the cloud version loads stops it before any copy is made, and a copy that failed to save stops it before anything is discarded. It resolves only the conflicts it copied, and pressing it again never copies twice. - The conflict popover tells the sides' changes apart only while the version both started from is the one the refused change was made against; once the page is drawn again it lists how the user's version differs from the cloud's instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the second review of the group rows: - No two live lineup group rows of a page may hold the same lineup or spot: the server refuses such a write (INVALID_LINEUP_PAYLOAD_DATA), so rows can never disagree about one spot. The check reads a small lineupItems side table, one row per lineup and spot, kept in step with every lineup write like lineupAgents. The client-side repair of overlapping rows is gone with the state it repaired. - Discarding refused work is conditional: discardRejected takes the refused and successor op ids the user saw, and leaves a key whose work has changed since. Use cloud for Keep both saves and re-checks the waiting work and the page right before redrawing it. - Keep both is not offered, and changes nothing, while the outbox cannot save, and stops before discarding if a copy failed to save. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the third review of the group rows: - Use cloud resolves only the work waiting as the user saw it once the page's edits are saved, re-checks it right before redrawing, and discards conditionally, so an edit made while the cloud version loads is neither discarded nor drawn over. - Before redrawing, the canvas must be as it was before the last save: an edit made since stops the redraw (nothing awaits between the check and it). - Keep both discards nothing unless each copy's group is queued or on the server, so a save skipped without failing (edit access lost) cannot leave the copy only on the canvas. - A lineup group refused for overlapping another says so, instead of reading as a teammate conflict Keep mine could win. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the fourth review of the group rows: - Keep both remembers each copy it made, and every press checks all of them are queued before anything is discarded, so a retry cannot drop the original while an earlier copy is still unsaved. - Use cloud stops when its last save fails: the queue may not have recorded a newer edit, so nothing is discarded or drawn over it. - The canvas check runs where the redraw happens (after the page list it awaits), and the redraw after a partial discard has one too; a skipped redraw discards nothing. - An overlap refusal says Keep mine is refused while the spot is still shared, rather than hiding it (the user may have fixed the overlap, or other waiting work may need it). Two different specific refusals no longer read as one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the fifth review of the group rows: - The redraw after a partial discard compares the canvas with what the first redraw left, so an edit made while discarding is not drawn over. - Use cloud stops only when its own last save fails (the queue now counts persistence failures), so a failure left from an earlier discard no longer blocks it forever; and it reports no success when its save turned the conflicts back into queued work. - Keep both makes a copy again when the one an earlier press made is no longer on the canvas. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the sixth review of the group rows (no blockers): - Keep both remembers all of a copy's lineups, so deleting one of them does not make the next press copy the whole group again. - Use cloud whose save settles a conflict (gone on both sides) reports success; only conflicts turned back into queued work count against it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #234. Merge this into #234's branch before #234 merges, so they deploy together.
Before this PR, a lineup synced as three kinds of cloud row: its standing spot, its landing spot, and the link holding its details, each pointing at the others. The server's conflict unit was one part, but what the user saved needed all three. That mismatch caused the long run of lineup edge cases: links arriving before landings, "end in use" and "end missing" refusals, delete ordering, rows that were held back or couldn't sync.
Now each lineup group is one cloud row. A group is the set of lineups on a page connected through a shared standing or landing spot. Its row holds those lineups and their spots:
{kind: "lineups", data: {id, origins, landings, links}}.An earlier version of this PR stored a copy of each shared spot in every lineup's row. It failed review three times because copies can drift apart, so it's replaced here.
Conflicts on lineup groups. The sync popover lists the lineups each side changed and offers three choices:
There is no edit lock. A lock can't cover offline editing, which is where group conflicts actually happen, and it would put the network in front of every edit.
Also in this PR
settleAttentionnow only drops work the server refused because a teammate deleted the item.lineupAgentsrow per origin and counts images across every lineup in a group. Duplicating a strategy copies groups.Unchanged: the local model, UI, undo, Hive and .ica.
Why now: production has zero lineup rows (read-only check, 2026-10-02), so the schema change needs no migration. Re-check right before merging.
Protocol 5: protocol-4 tabs can't read group rows, so the server asks them to reload.
Verified
npm run test:convex: 233 passednpx tsc --noEmit -p convex: cleanflutter test: 1541 passed, 5 skipped🤖 Generated with Claude Code
No outstanding blocking findings remain; this review does not identify a reason to delay merging.
Summary
The PR syncs each cloud lineup group as one row and updates conflict handling and server-side checks. The latest change registers the new lineup helper in the generated Convex API. No new findings remain.
Reviews (10) · Last reviewed commit: "Regenerate Convex API types for lib/line..."