#169 · Merge saved edits onto what others saved meanwhile
- timeline-app
- pull request
Closes #165. ## The problem **Save to server** sent the editor's staged edits with the revision they were made on. Any save in between made the server refuse them with 409: "Someone changed this timeline. Export your edits before reloading." A timeline being written to (by syslog, a log import or any API client) saves every few seconds, so every save failed. The view also stayed pinned to the old revision, so panning failed with the same error. ## What changes ### The server merges, given what the edits were made on `PUT /api/timelines/ /changes` takes an optional `base`: each changed moment's and duration's version as the editor loaded it (null for one it added), and the settings as loaded. `server/save-merge.mjs` then applies to the latest revision whatever no one else changed: | What | Rule | | --- | --- | | Moment or duration | The editor's version if no one else changed it. If both changed it, fields merge (`mergeValue`, as for pull requests): their title plus my notes keeps both. | | Setting (title, description, tags, time display, plugins) | The editor's value where it changed it, theirs where it didn't. Images merge one by one. | | Relationships | Added or removed as asked. | **Conflicts.** A field both sides changed differently, or an edit to something deleted meanwhile, is a conflict. The save then saves nothing, and the 409 lists them as `conflicts: [{kind, id, title}]`. - **Overriding:** a change or setting left out of `base` applies as sent; that's how "keep my versions" works. - **Checked on the latest revision too:** a save with a `base` is checked even when its revision is the latest, because an editor following others' saves still made its edits on what it saw earlier. I found this case in the browser test: without the check, a conflicting edit silently overwrote theirs. - **Without a `base`:** a save on an older revision is still refused, as before. The syslog receiver and log importer are unchanged. ### The editor - **Sends what its edits were made on:** each staged change already kept its `before`, and the settings base is the loaded timeline. - **Conflict dialog:** "Edits saved meanwhile" lists the conflicts. - **Keep my versions** resends them without a base. - **Use the saved versions** drops those edits (`RemoteWorkspace.discardMoment` / `discardDuration`, plus the undo history), takes the saved settings, and saves the rest. - **After a merged save:** the workspace takes the merged settings, except fields edited again since. - **Follows others' saves while edits are staged:** on a live update, or a query refused as stale, it moves to the latest revision (`moveToLatest`) and redraws it under the edits. It takes the latest settings for fields not edited locally, so a later save doesn't revert them. "Reload latest" now only appears while a save is under way or in a pull request's working copy. ## Tests - **New:** `test/save-merge-postgres.mjs` covers: - a save on an older revision without `base` is refused, as before; - with `base`, it merges: my rename and their rename of different moments, my notes plus their title on one moment, my delete, my addition, their addition, my duration edit, my link, my title plus their description; - conflicts (same field, edit vs delete, a setting) save nothing and are named; - keeping my versions saves them and merges the rest; - a stale edit is caught on the latest revision; - a malformed `base` gets 400. - **New:** `test/save-merge-live-postgres.mjs`, in Chromium against the built platform. The owner renames moments while a second session saves the same timeline; the editor shows the other session's moments under its unsaved edits and saves both. A conflicting rename, settled with "Keep my versions" and then "Use the saved versions", ends as chosen. - **Results:** - all 38 database tests pass locally, as do 262 unit tests and the typecheck; - in the Playwright container (Chromium), `next-platform`, `browser`, `sharing`, `moderation-live`, `browser-fork`, `comparison-liv…
Sources
Open #169 · Merge saved edits onto what others saved meanwhile in the timeline