fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude
, '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

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1) - #35

Merged
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged
Jun 15, 2026
Merged

fix: propagate includeUnchanged into nested keyed array diffs (v0.17.1)#35
maxjay merged 2 commits into
mainfrom
fix/nested-keyed-array-include-unchanged

Conversation

@maxjay

Copy link
Copy Markdown
Owner

$(cat <<'EOF'

Summary

  • diffArrayByKey hard-coded includeUnchanged: false when recursing into matched element pairs, so calling diff('$.orders[0].lineItems', { includeUnchanged: true }) always returned [] — nested Unchanged ops were never emitted
  • Fix threads the flag through correctly and extends the path filter to look inside Replace.changes
  • Bumps patch version to 0.17.1

Root cause

Three separate issues compounded:

  1. Line 638 of engine.tsdiffNode for element pairs was called with false instead of includeUnchanged, stripping the flag before it could reach nested keyed arrays
  2. fieldsChanged detection — used fieldOps.length > 0; once fix 1 landed, Unchanged ops in fieldOps would falsely mark an unchanged element as changed
  3. Path filter in _diff — only searched top-level ops; when the parent element had changed, child ops lived inside Replace.changes and were invisible to the filter

Changes

src/engine.ts

  • Pass includeUnchanged (not false) into the nested diffNode call in diffArrayByKey
  • Change fieldsChanged to fieldOps.some(op => op.op !== OpType.Unchanged)
  • For unchanged elements: push fieldOps directly to ops so path filters can reach children
  • Add flattenOpsForFilter helper; use it in _diff before path-filtering so Replace.changes is also searched

src/engine.array-semantics.test.ts

  • New describe block: diff — includeUnchanged propagation into nested keyed arrays
  • 5 cases using an orders → lineItems schema (keyed by ref / sku): no changes, child removed, child added, parent-field-only change, sibling isolation

Test plan

  • All 254 existing tests continue to pass
  • 5 new regression tests cover the reported scenarios
  • diff(childPath, { includeUnchanged: true }) returns Unchanged ops when nothing has changed
  • diff(childPath, { includeUnchanged: true }) returns correct mix of Add/Remove/Unchanged when children change
  • Querying children of a parent whose other fields changed still returns child Unchanged ops

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh
EOF
)


Generated by Claude Code

claude added 2 commits June 15, 2026 11:37
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
@maxjay
maxjay merged commit 414d13e into mainJun 15, 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.

2 participants

@maxjay@claude