Tags: maxjay/patchwork
Tags
fix: restore() on Remove op uses insertAt not add() (sparse array bug) ( #37) * fix: restore() on Remove op in keyed array uses insertAt not add() When restoring a removed keyed array element, the base-time positional path may point to an index beyond the current draft array length. Calling add() fell through to upsertAt() -> setAt(), assigning directly to the index and creating a sparse array with undefined holes that crashed diff(). Fix: call insertAt() directly (splice semantics), matching the impl spec. Undo is removeAt() at the same segments. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix: restore() on Remove op uses insertAt not add() (v0.18.0) When restoring a removed keyed array element, the base-time positional path may point to an index beyond the current draft array length. Calling add() fell through to upsertAt() -> setAt(), assigning directly to the index and creating a sparse array with undefined holes that crashed diff(). Fix: call insertAt() directly (splice semantics), matching the impl spec. Undo is removeAt() at the same segments. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
fix: don't duplicate field-level ops when filtering by child array pa… …th (v0.17.2) (#36) * fix: don't duplicate field-level ops when filtering by child array path flattenOpsForFilter recursed into Replace.changes even when the Replace op itself already matched the prefix, causing field-level ops to appear alongside their parent element op in the results (e.g. changing one field on a child item produced both a Replace for the child AND a loose Replace for the field). Replace with filterOpsForPrefixes: include a matching op as-is (its .changes are already attached), and only recurse into Replace.changes when the Replace itself does NOT match the prefix. https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh * chore: bump version to 0.17.2 https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh --------- Co-authored-by: Claude <[email protected]>
fix: propagate includeUnchanged into nested keyed array diffs (v0.17.… …1) (#35) * fix: propagate includeUnchanged into nested keyed array diffs diffArrayByKey hard-coded false for includeUnchanged when recursing into matched element pairs, so a diff() call targeting a child keyed array path (e.g. orders[0].lineItems) with includeUnchanged: true always returned [] — nested Unchanged ops were never emitted. Three-part fix: - Pass includeUnchanged through to diffNode for element pairs - Detect fieldsChanged as any(op !== Unchanged) so Unchanged ops in fieldOps don't falsely trigger Replace for an unmodified element - For unchanged elements: push nested fieldOps directly to ops so path filters can reach children; add flattenOpsForFilter in _diff so Replace.changes is also searched when a parent element changed but the queried children are unchanged https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh * chore: bump version to 0.17.1 https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh --------- Co-authored-by: Claude <[email protected]>
fix: pass includeUnchanged through to diffArrayBySelf (v0.17.0) (#34) $self arrays silently dropped the includeUnchanged flag. The method signature didn't accept it so unchanged primitives were never emitted. Co-authored-by: Claude Sonnet 4.6 (1M context) <[email protected]>
fix: add restore() to NodeEngine NodeEngine was missing restore(), causing a build error when the Angular store's engine field (Engine | NodeEngine) called it. Follows the same joinPath pattern as all other NodeEngine mutation methods. Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
feat: array semantics — identity diffing, displacement, restore, casc… …ade (v0.15.0) (#32) * docs: array semantics design reference (unordered/ordered/in-place) Reset point at 0.12.0. Reference for redesigning array diff/revert after the 0.13-0.16 filter-string/segment approach was judged overweight. Captures: key = diff-time addressing; edits belong to the element not the array; identity must be stable (declared, not inferred); order = a hidden position attribute. Defines diff + revert for the three array kinds and the implementation stance (drop segment paths, express revert via the existing functional undo/redo closures). * docs: ordered-array diff — add 'effect' kind for non-revertible displacement Captures the converged model: in an ordered array an element whose index shifts because of a nearby add/remove HAS changed (order matters, so it's surfaced) but is NOT revertible on its own — a new 'effect' entry kind that carries a reason pointing at the causing entries. Genuine reorders (crossing a surviving element) are 'move' (revertible). Adds the full entry-kind table, the one-API-both-views note, prior art (RFC 6902 / Myers / CRDTs), and the worked [A,B,C]->[A,B,D,C] example. Replaces the earlier (wrong) 'removal cascades as modifications' framing. * docs: clarify move already exists; only diff-emission + effect are new OpType.Move, the {from,to} DiffOp variant, engine.move(), and import/export all exist in 0.12.0; a move op is created inside engine.move() for exportChanges. The gap is diff() never emitting one (diffNode does only add/remove/replace, never detects a reorder). So 'effect' is the only net-new concept; move just needs diff to emit the existing op. * docs: strip move from array-semantics; keep focus on effect * docs: complete array semantics spec and implementation plan with test cases Full redesign of array diff semantics: identity containment, ordered/unordered distinction, displacement as move ops, restore operation, and cascade flag. Adds implementation spec with pseudo-code and a test suite defining the contract. Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]> * feat: implement array semantics — identity diffing, displacement, restore, cascade - DiffOp extended: element-level replace with grouped changes (absolute paths), move op carries identity for displacement, new unchanged op for includeUnchanged - diffArrayByKey rewritten: matched elements emit replace+changes, move, or unchanged based on field changes and displacement - diff() gains includeUnchanged and cascade options; cascade:false stops nested identity array changes from bubbling up to the parent element - restore(op) inverts any DiffOp and pushes to the undo stack; move restore uses splice semantics (removeAt + insertAt) not overwrite - ArrayMeta replaces bare string in keyMap to carry both key and ordered flag - extractKeyMap reads x-ordered from schema - All 247 tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]> * chore: bump version to 0.15.0 Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]> * docs: update README for array semantics, restore, and new diff options Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <[email protected]>
Add selfChanged/descendantsChanged flags to tree item entries (#31) * feat(engine): items() flags selfChanged vs descendantsChanged In recursive/tree shapes (a node keyed by id whose children array has the same keyed shape), a replace entry could not say whether the node itself was edited or only a descendant node was. Both states already roll up into changes; this surfaces the distinction as two flags. A change op in an item-relative diff carries an identity iff it descends into a nested keyed element (the item-relative walk starts with no identity, so own-field ops have none). Replace entries now expose: selfChanged some change is on the item's own field descendantsChanged some change descends into a nested keyed element Both can be true. A $self set field counts as descendantsChanged (a $self set is itself a keyed array) — documented, not special-cased. Pure post-hoc classification of the existing changes array; no change to the diff walk or path machinery. Additive — add/remove/unchanged entries are unaffected. https://claude.ai/code/session_017WayuSMxaaoipNgVpHccCT * chore: bump version to 0.14.0 --------- Co-authored-by: Claude <[email protected]>
feat: propagate keyed identity to nested field ops (#25) * feat: propagate keyed identity to nested field ops When diffArrayByKey recurses into a matched item, the item's identity is now threaded through diffNode and stamped on any ops emitted from within that subtree (replace, and positional add/remove). For nested keyed arrays the innermost identity wins, since diffArrayByKey at each level stamps its own id. https://claude.ai/code/session_01BrxXxig1t9ffDT6hjzwr4s * chore: bump version to 0.12.0 https://claude.ai/code/session_01BrxXxig1t9ffDT6hjzwr4s --------- Co-authored-by: Claude <[email protected]>
feat: [-] sentinel in add() for array append (#24) * feat: support [-] sentinel in add() to append to array end add('$.items[-]', value) resolves [-] to the current array length before processing, so callers can append without tracking indices. Multiple consecutive appends each read the live draft length. Works through NodeEngine paths as well. https://claude.ai/code/session_01BrxXxig1t9ffDT6hjzwr4s * chore: bump version to 0.11.0 https://claude.ai/code/session_01BrxXxig1t9ffDT6hjzwr4s --------- Co-authored-by: Claude <[email protected]>
PreviousNext