Skip to content

Fix ordered Multiple field producing duplicate RDF list heads#738

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/fix-rdf-list-serialization
Draft

Fix ordered Multiple field producing duplicate RDF list heads#738
Copilot wants to merge 3 commits intomainfrom
copilot/fix-rdf-list-serialization

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

ui:Multiple with ui:ordered true produces multiple disconnected list-head triples (subject property _:b1, _:b2, _:b3) instead of a single proper RDF Collection, breaking Turtle serialization and any list-aware consumer.

Root cause

Two bugs in saveListThenRefresh in src/widgets/forms.js:

1. Stale object-index after in-place mutation

rdflib's IndexedFormula indexes Collection objects by element content (ExtendedTermFactory.id), not object identity. addItem/deleteThisItem/moveThisItem all mutate list.elements after the Collection is already in the store. The object-index entry is then stale (keyed under the old element content). The subsequent removeMany() call computes the current (post-mutation) content key, finds no index entry, and throws "Statement to be removed is not on store".

2. Duplicate heads on re-fetch

After putBack invalidates the fetcher cache, updateMany re-fetches the document. The N3 parser creates a new Collection with a different blank-node ID. kb.holds() compares by blank-node ID (via equals()), so it returns false and adds a second subject property Collection triple — the duplicate head.

Fix

saveListThenRefresh — replace removeMany with a safe remove + fresh Collection pattern:

// Before (broken): removeMany re-looks up by mutated content key → throws
kb.removeMany(subject, property, null, dataDoc)

// After: find via subject/predicate index, remove by reference (skips stale object-index)
const oldStatements = kb.statementsMatching(subject, property, null, dataDoc)
oldStatements.forEach(st => kb.removeStatement(st))
// Rebuild with correct index so kb.holds() recognises re-fetched list as duplicate
list = new $rdf.Collection(currentElements)
kb.add(subject, property, list, dataDoc)

kb.statementsMatching(…, null, …) uses the subject/predicate index, bypassing the stale object-index. kb.removeStatement safely skips a missing object-index entry (rdflib's if (!this.index[p][h]) guard). The fresh Collection is keyed under the current element content, so the next re-fetch call to kb.holds() finds it and suppresses the duplicate.

createListIfNecessary — recover an existing Collection from the store before creating a new one (guards against a second head on live-update/reload).

refresh() — keep list in sync with the store's current Collection after external document reloads.

Tests

Added test/unit/widgets/forms/multiple.test.ts covering: existing Collection recovery, lazy list creation, refresh method exposure, pre-populated list rendering, reload sync, single add, double add, and reload + add deduplication.

Copilot AI and others added 2 commits April 10, 2026 06:30
… safely rebuild Collection in store

Agent-Logs-Url: https://github.com/SolidOS/solid-ui/sessions/b5b014b0-8b31-48b8-8b6e-38a780fe55b5

Co-authored-by: SharonStrats <9412507+SharonStrats@users.noreply.github.com>
…ers per code review

Agent-Logs-Url: https://github.com/SolidOS/solid-ui/sessions/b5b014b0-8b31-48b8-8b6e-38a780fe55b5

Co-authored-by: SharonStrats <9412507+SharonStrats@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix RDF serialization for ordered multiple fields Fix ordered Multiple field producing duplicate RDF list heads Apr 10, 2026
Copilot AI requested a review from SharonStrats April 10, 2026 06:34
@timea-solid timea-solid moved this to In review in SolidOS NLNet UI Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Ordered Multiple/Collection field does not serialize as proper RDF list

3 participants