Skip to content

Tags: maxjay/patchwork

Tags

0.18.0

Toggle 0.18.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.17.2

Toggle 0.17.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.17.1

Toggle 0.17.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.17.0

Toggle 0.17.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.16.0

Toggle 0.16.0's commit message
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]>

0.15.0

Toggle 0.15.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.14.0

Toggle 0.14.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.13.0

Toggle 0.13.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Downgrade package version in package-lock.json

Downgrade package version from 0.15.0 to 0.13.0.

0.12.0

Toggle 0.12.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>

0.11.0

Toggle 0.11.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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]>