feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0) - #32

Merged
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign
Jun 14, 2026
Merged

feat: array semantics — identity diffing, displacement, restore, cascade (v0.15.0)#32
maxjay merged 9 commits into
mainfrom
claude/array-semantics-redesign

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Identity-keyed array diffing redesigned: matched elements now emit element-level replace ops with a grouped changes array (absolute paths), rather than flat field-level ops. Displaced elements emit move ops carrying identity.
  • Ordered arrays + displacement: x-ordered: true on a schema array surfaces position changes as move ops. displacement field on replace ops when an element is both modified and displaced.
  • New diff() options: includeUnchanged returns all elements including unmodified ones (for UI rendering without a separate zip); cascade (default true) controls whether nested identity-array changes bubble up to mark the parent as modified.
  • New restore(op): inverts any DiffOp and pushes to the undo stack. Move restore uses splice semantics (removeAt + insertAt) not overwrite.
  • OpType.Unchanged: new op kind emitted only when includeUnchanged: true.
  • ArrayMeta: replaces bare string in keyMap to carry both key and ordered flag; extractKeyMap reads x-ordered from schema.
  • Full spec in docs/array-semantics.md and implementation notes in docs/implementation.md.

Test plan

  • 247 tests passing (npm test)
  • src/engine.array-semantics.test.ts covers: identity matching, ordered displacement, includeUnchanged, cascade (true and false), $self arrays, restore (all op kinds + undo), identity stability, edge cases
  • Existing engine.test.ts keyed-array tests updated to reflect new element-level grouping behavior

🤖 Generated with Claude Code

maxjayand others added 9 commits June 13, 2026 23:45
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).
…acement
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.
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.
… 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) <noreply@anthropic.com>
…tore, 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) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@maxjay
maxjayforce-pushed the claude/array-semantics-redesign branch from 1070f6e to b56420eCompareJune 14, 2026 22:34
@maxjay
maxjay merged commit be848a2 into mainJun 14, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxjay