docs/solutions/logic-errors/2026-03-30-table-adjacent-cell-queries-must-resolve-visual-neighbors.md
Table border toggles broke once merged cells changed the visual grid.
Selecting a cell below a colSpan merge and toggling its top border wrote to the wrong cell in the row above, because the helper treated "cell above" as "same sibling index in the previous row."
PathApi.previous(...) and [row - 1, cellIndex]Move adjacent-cell ownership into a shared query helper that resolves neighbors by visual table coordinates, not raw sibling position.
getAdjacentTableCell(...) now:
getTableEntries(...)getCellIndices(...)findCellByIndexes(...)getCellPath(...)getTopTableCell(...) and getLeftTableCell(...) both delegate to that helper.
Merged cells split visual position from tree sibling position.
Once a cell spans extra columns or rows, the neighbor you want is "the cell covering visual row X / col Y," not "the previous sibling" or "the child at the same index in another row."
findCellByIndexes(...) already understands spans, so reusing it at the query seam fixes border toggles without teaching every caller how merged layouts work.
These checks passed:
pnpm --filter @platejs/table test packages/table/src/lib/queries/getTopTableCell.spec.tsx packages/table/src/lib/queries/getSelectedCellsBorders.spec.tsx packages/table/src/react/components/TableCellElement/setSelectedCellsBorder.integration.spec.tsx packages/table/src/react/components/TableCellElement/setSelectedCellsBorder.spec.tsx
pnpm install
pnpm turbo build --filter=./packages/table
pnpm turbo typecheck --filter=./packages/table
pnpm lint:fix
The new coverage proves both layers:
getTopTableCell.spec.tsx proves merged columns resolve the spanning cell abovesetSelectedCellsBorder.integration.spec.tsx proves the top-border toggle updates the spanning neighbor instead of the wrong sibling#4111.claude/docs/solutions/logic-errors/2026-03-29-table-border-toggle-must-clear-active-cell-selection-before-adjacent-cell-writes.md