Skip to content

Conversation

@tupizz
Copy link
Contributor

@tupizz tupizz commented Feb 8, 2026

Summary

This PR contains three fixes for table interaction in the layout engine:

1. Fix cursor placement in table cells (SD-1788)

Problem: Clicking on table cell padding, borders, or areas where elementsFromPoint doesn't return a line element caused the cursor to land in the wrong column.

Root cause: clickToPositionDom in dom-mapping.ts used processFragment for table fragments even when no line was in the hit chain. processFragment searches all lines across all cells using only Y-coordinate matching, so it picks the first cell with a matching Y — which is often the wrong column.

Fix: Added a guard that returns null for table fragments without a line in the hit chain, deferring to the geometry fallback (hitTestTableFragment) which correctly uses both X and Y coordinates to resolve the target cell.

File: packages/layout-engine/layout-bridge/src/dom-mapping.ts


2. Fix table resize handles overflowing on split tables

Problem: When a table splits across page boundaries, the column resize handles (blue vertical lines) extended far beyond the table fragment — overflowing into the page gap and neighboring content below.

Root cause: In renderTableFragment.ts, the data-table-boundaries segment computation iterated over all rows in the table block (0 to rowCount), but each fragment only renders a subset of rows (fromRow to toRow + optional repeated headers). This caused every fragment to get segments with the full table's height.

Fix: Changed the segment computation to build a renderedRows list that mirrors the exact row rendering logic.

File: packages/layout-engine/painters/dom/src/table/renderTableFragment.ts


3. Fix table cell click on page 2+ (geometry fallback coordinate fix)

Problem: Clicking in empty space below text in a table cell worked on page 1 but failed on page 2+. The cursor either didn't appear or landed in the wrong position.

Root cause: Two issues compounding:

  1. Snap-to-nearest picks wrong target: When clicking empty table cell space (below text lines), the DOM-based mapping returns null. The geometry fallback's snap-to-nearest logic then picks a nearby paragraph instead of letting hitTestTableFragment handle the click.

  2. Coordinate double-subtraction on page 2+: normalizeClientPoint adjusts Y by the page's DOM offset (pageOffsetY), making containerPoint page-relative. On page 1 this offset is ~0, but on page 2+ it's significant. Then clickToPosition subtracts pageTopY again, producing incorrect negative coordinates that cause hitTestPage to find the wrong page and hitTestTableFragment to fail entirely.

Fix:

  • Skip snap-to-nearest when the click is within a table fragment's bounds (let hitTestTableFragment handle it)
  • When DOM info is available, determine the page directly from elementsFromPoint (same technique normalizeClientPoint uses) and treat containerPoint as already page-relative, avoiding the double subtraction

File: packages/layout-engine/layout-bridge/src/index.ts

Test plan

  • Added unit tests for table fragment click fallback behavior
  • Added unit test for split-table segment scoping
  • Added unit tests for table cell empty space clicks (page 1)
  • Added unit tests for table cell clicks on page 2 (multi-page geometry path)
  • All 1205 layout-bridge tests pass
  • All 9105 tests pass across the monorepo
  • Manual: verify cursor placement in table cells on page 1 and page 2+
  • Manual: verify clicking below text in a table cell places cursor correctly
  • Manual: open advanced-tables.docx, verify resize handles stay within each fragment

…llback

When clicking on table cell padding or borders, elementsFromPoint returns
the table fragment but not a specific line element. Previously,
processFragment would search all lines across all cells using only Y
coordinate matching, causing clicks to land in the wrong column.

Now when a table fragment is found without a line in the hit chain, we
return null to let the geometry fallback (hitTestTableFragment) handle
cell resolution correctly using accumulated row/column dimensions.
Copilot AI review requested due to automatic review settings February 8, 2026 01:59
@linear
Copy link

linear bot commented Feb 8, 2026

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect cursor placement in table cells by ensuring DOM-based click mapping defers to geometry-based table hit-testing when a click lands on a table fragment but doesn’t directly hit a .superdoc-line element (e.g., padding/borders where elementsFromPoint omits the line).

Changes:

  • Update clickToPositionDom to return null for table fragments when no line is present in the hit chain, allowing clickToPosition to fall back to hitTestTableFragment.
  • Add unit tests covering the “table fragment without line hit” deferral behavior.
  • Add a table-related test intended to cover the “line is hit” path (but it currently doesn’t exercise that branch in JSDOM—see comments).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/layout-engine/layout-bridge/src/dom-mapping.ts Defers table fragment clicks without direct line hits to geometry fallback by returning null.
packages/layout-engine/layout-bridge/test/dom-mapping.test.ts Adds tests for table fragment fallback behavior when elementsFromPoint omits line elements.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b48cd5609b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

When a table splits across pages, each fragment's data-table-boundaries
attribute contained segment heights for the entire table rather than
just the rows rendered in that fragment. This caused resize handles to
overflow beyond the fragment into the page gap and neighboring content.

Scope the segment computation to iterate only over the rows actually
rendered in each fragment (repeated headers + body rows from fromRow to
toRow), matching the existing row rendering logic. Handle partial row
heights for mid-row splits.
@tupizz tupizz self-assigned this Feb 8, 2026
…d page detection

normalizeClientPoint adjusts Y by the page's DOM offset, making
containerPoint page-relative. On page 1 this is ~0, but on page 2+
the geometry fallback double-subtracted pageTopY, causing hitTestPage
to find the wrong page and table cell hit testing to fail.

Fix: when DOM info is available, determine the page from
elementsFromPoint and treat containerPoint as already page-relative.
Also clean up debug logging from prior iteration.
Copy link
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @tupizz! This is great.

I added a few comments, mainly regarding maintainability and cleanness of the codebase.

And also in this case, I would advise creating interaction stories here as well - since this happens specific on custom table through cursor positioning

- Add TABLE_FRAGMENT to DOM_CLASS_NAMES constant, use across packages
- Export and reuse findPageElement in clickToPosition instead of duplicating
- Extract snapToNearestFragment helper from clickToPosition
- Add buildTableFixtures shared test builder in mock-data
- Polyfill elementsFromPoint in table hit chain test for proper coverage
- Add cross-reference comments between metadata and rendering iterations
@tupizz
Copy link
Contributor Author

tupizz commented Feb 11, 2026

@caio-pizzol I'll be adding the visual testing

@caio-pizzol caio-pizzol self-requested a review February 11, 2026 19:11
Copy link
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel free to merge after interaction stories are added

great job!

Verifies that clicking in different table cells correctly places the
cursor in the targeted cell, not in nearby paragraphs or wrong columns.
Copy link
Collaborator

@harbournick harbournick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@harbournick harbournick merged commit 0eac43c into main Feb 12, 2026
2 checks passed
@harbournick harbournick deleted the tadeu/sd-1788-bug-cursor-cannot-be-reliably-placed-in-table-cell branch February 12, 2026 00:20
@superdoc-bot
Copy link

superdoc-bot bot commented Feb 12, 2026

🎉 This PR is included in superdoc v1.12.0-next.14

The release is available on GitHub release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants