fix(mvc.Collection): id lookups can return the wrong model when a user id collides with a cid - #3487
Merged
kumilingus merged 1 commit intoAug 27, 2026
Conversation
A single _byId map stored models under both their id and their cid.
cids are generated as c<counter> with a realm-global counter, so a
user-supplied id matching /^c\d+$/ (e.g. 'c856') was clobbered as soon
as some other model's auto-generated cid reached the same value:
graph.getCell('c856') returned the wrong cell, and set() could silently
merge a genuinely new model away as a duplicate of the id-holder.
Ids and cids now live in separate maps and a real id always wins the
string lookup; cid string lookup still works when no id claims the
string.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Geliogabalus
approved these changes
Aug 27, 2026
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.
Description
mvc.Collectionstored models in a single_byIdmap under both their model id and their internal client id (cid). cids are generated asc<counter>with a counter global to the JS realm that never resets — so a user-supplied id matching/^c\d+$/(e.g."c856") is clobbered as soon as the session has created that many models and some other model's auto-generated cid collides with it.Two silent failure modes:
graph.getCell('c856')returns the wrong cell — and everything downstream of it: link end resolution,findViewByModel, etc.set()/add()dedup runs throughget(), so a genuinely new model whose cid collides with an existing model's id is silently merged away as a duplicate and never added.Both are timing/volume dependent — a graph loads fine on a fresh page and breaks after enough models have been created in the session — which makes them look like heisenbugs.
Observed in the wild:
demos/libavoid-standalone-link-routingexample-2.jsonhad an element with id"c856"; after loading a second large diagram, a link's cid collided,@joint/router-avoid'svalidateEnds()resolved the link's end to another link and permanently fallback-routed it.Fix
Ids and cids now live in separate maps (
_byId/_byCid):get()checks real ids first — a real id always wins the string lookup;collection.get('c12'), Backbone compatibility) still works whenever no real id claims the string;Inherited from Backbone's design; any graph with generated short ids is exposed.
Tests
get: a model id colliding with another model's cid— pins winning lookup, both models reachable, removal of the id-holder restores the cid string lookup. Fails onmaster(the colliding model is merged away as a duplicate)._addReference binds all collection events & adds to the lookup hashestest asserts against_byCidfor the cid entry (it inspects the internal maps directly).🤖 Generated with Claude Code