linked copies
Ideallo supports linked copies – objects that share the same underlying content while maintaining independent position, style, and other metadata.
Overview
When an object is duplicated as a linked copy, the new object shares the same text content, polygon geometry, or freehand path data as the original. Editing the shared content in one object immediately updates all linked copies. However, each copy has its own position (xy), rotation (r), style fields (sc, fc, etc.), and dimensions (wh).
This is useful for repeating elements like labels, shapes, or icons that should stay synchronized across the board.
Content ID Fields
Three optional fields control which content map entry an object references:
| Field | Object Types | Content Map | Description |
|---|---|---|---|
tid |
Text (T), Sticky (S) |
txt |
Text content ID – keys into the Y.Text map |
gid |
Polygon (P) |
geo |
Geometry ID – keys into the Y.Array<number> map |
pid |
Freehand (F) |
paths |
Path ID – keys into the SVG path string map |
Rule: When the content ID field is omitted, the object’s own ObjectId is used as the key in the corresponding content map. When present, it points to a different object’s content entry.
How It Works
graph LR
subgraph "Objects Map (o)"
A["Object A<br/>(original)<br/>tid: --"]
B["Object B<br/>(linked copy)<br/>tid: 'A'"]
end
subgraph "Text Map (txt)"
T["'A' → Y.Text<br/>'Hello, world!'"]
end
A -- "uses own ID as key" --> T
B -- "tid points to A" --> T
Object A uses its own ID (A) as the key in the txt map. Object B has tid: 'A', so it references the same Y.Text entry. Editing the text through either object updates the shared content.
True Copy vs Linked Copy
Ideallo provides two duplication operations with different content semantics:
True Copy (duplicateObject)
Creates a fully independent copy with its own content entry:
// Original object "aB3x_Qm7kL9p"
{ "t": "T", "xy": [100, 100], "wh": [200, 50] }
// txt map: "aB3x_Qm7kL9p" → "Hello"
// True copy "Xk2nR8vH_wYq" — independent content
{ "t": "T", "xy": [120, 120], "wh": [200, 50] }
// txt map: "Xk2nR8vH_wYq" → "Hello" (separate Y.Text copy)The true copy gets a new Y.Text entry with the same initial content but is fully independent – editing one does not affect the other. No tid field is stored.
Linked Copy (duplicateAsLinkedCopy)
Creates a copy that shares the original’s content entry:
// Original object "aB3x_Qm7kL9p"
{ "t": "T", "xy": [100, 100], "wh": [200, 50] }
// txt map: "aB3x_Qm7kL9p" → "Hello"
// Linked copy "Hw5_qT2mLkJx" — shared content
{ "t": "T", "xy": [120, 120], "wh": [200, 50], "tid": "aB3x_Qm7kL9p" }
// No new txt map entry — uses "aB3x_Qm7kL9p" from originalThe linked copy has an explicit tid field pointing to the original’s content. Both objects render the same text, and editing through either one updates both.
Chained Linking
When duplicating a linked copy as another linked copy, the new copy points to the original source, not to the intermediate copy. This prevents chains of indirection:
// Object B is a linked copy of A: B.tid = 'A'
// Creating a linked copy of B:
duplicated.tid = existing.tid ?? objectId
// Result: C.tid = 'A' (not 'B')
All linked copies in a group always point directly to the same source content entry, keeping lookups to a single indirection.
Deletion and Orphan Handling
When an object is deleted, its associated content map entries are not deleted. This is for two reasons:
- CRDT tombstones: Deleting a
Y.Mapentry creates a tombstone that takes space anyway – no storage is reclaimed by also deleting content entries. - Linked copies may still reference the content: Other objects might have
tid/gid/pidpointing to the deleted object’s content.
Orphaned content entries (those with no remaining objects referencing them) are cleaned up during export or compaction operations.
Content Access Helpers
The CRDT module provides helper functions that automatically resolve content ID indirection:
| Function | Returns | Description |
|---|---|---|
getObjectYText(doc, objectId) |
Y.Text | undefined |
Resolves tid or falls back to objectId for Text/Sticky objects |
getObjectYArray(doc, objectId) |
Y.Array<number> | undefined |
Resolves gid or falls back to objectId for Polygon objects |
getObjectPathData(doc, objectId) |
string | undefined |
Resolves pid or falls back to objectId for Freehand objects |
These helpers look up the stored object, check for a content ID field, and return the corresponding content map entry. Application code should always use these helpers rather than accessing content maps directly.