fix: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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: don't duplicate field-level ops when filtering by child array path (v0.17.2) - #36

Merged
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes
Jun 15, 2026
Merged

fix: don't duplicate field-level ops when filtering by child array path (v0.17.2)#36
maxjay merged 2 commits into
mainfrom
fix/filter-ops-for-prefixes

Conversation

@maxjay

Copy link
Copy Markdown
Owner

Summary

  • Changing a field on a child item and calling diff(childArrayPath, { includeUnchanged: true }) returned two ops for the changed item: the element-level Replace AND a loose field-level Replace that leaked out of .changes
  • Root cause: flattenOpsForFilter recursed into Replace.changes even when the parent Replace already matched the path prefix
  • Fix replaces it with filterOpsForPrefixes, which only recurses into .changes when the parent op does not match — keeping field ops nested where they belong
  • Bumps patch to 0.17.2

Root cause in detail

After #35 landed, path-filtered diffs used flattenOpsForFilter to expose ops nested inside Replace.changes (needed when a parent element changes but you're querying its children). The problem: it flattened unconditionally, so when the parent Replace itself already matched the prefix, its changes contents were also emitted at the top level — duplicating the field op.

Example: diff('$.secondaryTaskTypes', { includeUnchanged: true }) with one field changed on item 4:

  • Before fix: [Unchanged×4, Replace(item4), Replace(approvalWorkflow)] ← 6 ops, field op duplicated
  • After fix: [Unchanged×4, Replace(item4)] ← 5 ops, field op stays inside Replace.changes

Changes

src/engine.ts — replace flattenOpsForFilter with filterOpsForPrefixes: include a matching op as-is; only recurse into .changes when the op itself doesn't match.

src/engine.array-semantics.test.ts — new regression test: "field change on one child: returns one Replace op for that child — not a duplicate field-level op"

Test plan

https://claude.ai/code/session_017H6NzxYAwbmKxHH6QusCdh


Generated by Claude Code

claude added 2 commits June 15, 2026 11:47
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
@maxjay
maxjay merged commit d499706 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