coed icon indicating copy to clipboard operation
coed copied to clipboard

Add minimal reproducible example for issue #3 with identical recursive DOM structures

Open Copilot opened this issue 3 months ago • 8 comments

Issue #3 describes DOM nodes without unique id attributes getting incorrectly combined during patching, particularly when removing middle items from lists. This manifests as wrong nodes being removed or content getting mixed up between similar elements.

What's Added

Interactive demo application (examples/patching-bug-repro/)

  • Demonstrates the bug with recursive nested query structures modeled after gobaith
  • Recursive query types: Query = Filter | And(Query, Query) | Or(Query, Query) - queries can contain other queries infinitely
  • 6-7+ levels of nesting at each recursive level with branch wrappers, indent containers, combinator selectors, and triple-nested filter inputs
  • IDENTICAL query structures for reliable reproduction: All 3 initial queries use the same And(Filter, Filter) pattern:
    • Query 1: And(Filter(status, active), Filter(priority, high))
    • Query 2: And(Filter(category, bug), Filter(severity, critical))
    • Query 3: And(Filter(type, feature), Filter(phase, development))
  • Distinguishable queries only by their field values (status, priority, category, severity, type, phase) to clearly show when content gets mixed up
  • Visual AND combinator badges between branches
  • Toggle between buggy (no IDs) and fixed (with IDs) versions via useBuggyVersion flag
  • Visual reproduction matching issue description

Playwright test suite

  • Automated reproduction of the bug with recursive nested structures
  • Screenshot capture at each step for debugging
  • Programmatic verification of input field values getting mixed between queries

Documentation

  • Root cause explanation (patching algorithm in src/coed.ts lines 1042-1055 only compares tag + id)
  • Detailed explanation of why identical structures are needed to reproduce the bug
  • Workaround guidance
  • Setup instructions for manual and automated testing

The Bug

The bug is particularly evident with recursive tree structures that have IDENTICAL patterns. All queries must use the same recursive structure (e.g., all use And(Filter, Filter)) for the bug to manifest reliably:

// Recursive query types (like gobaith)
type FilterQuery = { kind: "Filter"; field: string; value: string; operator: string };
type AndQuery = { kind: "And"; left: Query; right: Query };
type OrQuery = { kind: "Or"; left: Query; right: Query };
type Query = FilterQuery | AndQuery | OrQuery;  // Recursive!

// All queries use And(Filter, Filter) - IDENTICAL structure
// Buggy - recursive nodes without IDs get confused during patching
coed.div([], [coed.class_("filter-query")], [  // NO ID - causes bug!
  coed.div([], [coed.class_("query-header")], [...]),
  coed.div([], [coed.class_("combinator-container")], [
    coed.div([], [coed.class_("left-branch")], [
      coed.div([], [coed.class_("branch-wrapper")], [
        coed.div([], [coed.class_("indent")], [
          renderQueryBuilder(query.left, ...)  // RECURSIVE CALL
        ])
      ])
    ]),
    coed.div([], [coed.class_("combinator-selector")], [
      coed.span([], [], [coed.text("AND")])  // All queries have "AND"
    ]),
    coed.div([], [coed.class_("right-branch")], [
      coed.div([], [coed.class_("branch-wrapper")], [
        coed.div([], [coed.class_("indent")], [
          renderQueryBuilder(query.right, ...)  // RECURSIVE CALL
        ])
      ])
    ])
  ])
]);

// Fixed - unique ID allows proper patching
coed.div([], [
  coed.class_("filter-query"), 
  coed.attribute("id", `filter-query-${index}`)  // WITH ID - works!
], [
  // ... same recursive structure
]);

Why Identical Structures Are Needed

When removing Query 2 from the middle:

  1. Old state: [Q1, Q2, Q3, Q4] at indices [0, 1, 2, 3]
  2. New state: [Q1, Q3, Q4] at indices [0, 1, 2]
  3. The patching algorithm patches old-DOM-at-index-1 with new-virtual-tree-at-index-1
  4. Since all queries have IDENTICAL structure (same tags, classes, nesting) and NO IDs, the algorithm patches children in place instead of replacing
  5. Result: Query 3's content appears in Query 2's DOM location!

Usage

cd examples/patching-bug-repro
npm install && npm run serve      # Manual testing at http://localhost:8000
npx playwright install chromium   # One-time setup
npm test                          # Automated reproduction

Screenshots

Initial state with 3 queries using identical And(Filter, Filter) structure:

All queries have the same recursive pattern - only the field names and values differ.

After adding Query 4 and removing Query 2 - demonstrates the bug:

When you remove Query 2 (which contains category=bug AND severity=critical), the patching algorithm gets confused by the identical recursive structures:

  • Query 2 now shows type=feature AND phase=development (which was Query 3's content)
  • Query 3 now shows field=value AND status=active (which was Query 4's content)

The input field values get mixed up between queries because the identical recursive tree nodes without unique IDs cause the patching algorithm to confuse which node is which.

This provides a debugging harness for investigating and fixing the patching logic issue with identical recursive nested DOM trees, matching the complexity found in real-world applications like gobaith.

Original prompt

Please create a minimial reproduceable code snippet for the issue decribed here: https://github.com/eeue56/coed/issues/3

Use Playwright with @eeue56/coed in order to create an example that I can use for debugging the issue


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot avatar Nov 20 '25 00:11 Copilot