Skip to content

bug: list items are cut off on left edge inside table (SD-1836)#1996

Open
luccas-harbour wants to merge 2 commits intomainfrom
luccas/sd-1836-bug-list-items-are-cut-off-on-left-edge-inside-table
Open

bug: list items are cut off on left edge inside table (SD-1836)#1996
luccas-harbour wants to merge 2 commits intomainfrom
luccas/sd-1836-bug-list-items-are-cut-off-on-left-edge-inside-table

Conversation

@luccas-harbour
Copy link
Contributor

@luccas-harbour luccas-harbour commented Feb 11, 2026

  • Lists markers inside tables were being rendered incorrectly, causing them to be cut off in some situations
  • A fix was made to implement the same rendering logic as when lists show up as top-level elements
  • A visual test document was uploaded to the corpus

Before:
Screenshot 2026-02-11 at 3 09 38 PM

After:
Screenshot 2026-02-11 at 3 09 57 PM

@linear
Copy link

linear bot commented Feb 11, 2026

@luccas-harbour luccas-harbour force-pushed the luccas/sd-1836-bug-list-items-are-cut-off-on-left-edge-inside-table branch from 7f70b78 to 8b0e219 Compare February 11, 2026 18:02
@luccas-harbour luccas-harbour marked this pull request as ready for review February 11, 2026 18:11
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: 8b0e21929e

ℹ️ 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".

// Align text start to the marker start position (gutter spacing comes from marker padding)
lineEl.style.paddingLeft = `${markerStartPos}px`;
// Position marker within the container
if (['center', 'right'].includes(markerJustification)) {

Choose a reason for hiding this comment

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

P1 Badge Apply anchor offset for left-justified table list markers

This block only applies marker/text offset logic for center and right, so the default left case never uses anchorPoint to position the first line. In table list paragraphs with non-zero list indents/hanging indents, the marker and following text are laid out from the cell edge instead of the computed list anchor, which shifts content left and can still clip near the table border.

Useful? React with 👍 / 👎.

Copy link
Contributor

Choose a reason for hiding this comment

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

left-justified markers start at x=0 in the container -- anchorPoint offset is not applied. continuation lines get paddingLeft = indentLeftPx, so first-line text won't align with body text.

maybe lineEl.paddingLeft = anchorPoint would fix this (like in renderer.ts)

Comment on lines 1023 to 1027
markerMeasure,
indentLeftPx,
hangingIndentPx,
firstLineIndentPx,
});

Choose a reason for hiding this comment

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

P2 Badge Pass paragraph tab stops when rendering table list markers

renderListMarker now computes tab spacing from tabsPx, but this call site never forwards wordLayout?.tabsPx, so explicit tab stops from the paragraph are dropped for list items inside table cells. Documents that use custom list tab stops will therefore fall back to default/implicit tab widths and render marker-to-text spacing incorrectly.

Useful? React with 👍 / 👎.

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.

added a few comments!

also worth mentioning - we might need to upload the sample/test file to R2 so we can run visual testing against it

* This is applied when a gutter width is not explicitly provided in the marker layout.
* The 8px default matches Microsoft Word's standard list marker spacing.
*/
const LIST_MARKER_GAP = 8;
Copy link
Contributor

Choose a reason for hiding this comment

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

LIST_MARKER_GAP is dead code after the rewrite -- no longer referenced.

* Word layout information for paragraph list markers.
* Contains positioning, styling, and rendering details for list markers (bullets/numbers).
*/
type WordLayoutMarker = {
Copy link
Contributor

Choose a reason for hiding this comment

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

local WordLayoutMarker/WordLayoutInfo duplicate types in @superdoc/contracts.

maybe import from contracts to stay in sync?

markerStartPos = anchorPoint - markerTextWidth;
currentPos = anchorPoint;
} else {
markerStartPos = anchorPoint - markerTextWidth / 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

center justification computes left = anchorPoint - markerTextWidth (half-width subtracted twice), identical to right-justified positioning. renderer.ts line 2305 has the same math -- intentional, or pre-existing bug?

// Align text start to the marker start position (gutter spacing comes from marker padding)
lineEl.style.paddingLeft = `${markerStartPos}px`;
// Position marker within the container
if (['center', 'right'].includes(markerJustification)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

left-justified markers start at x=0 in the container -- anchorPoint offset is not applied. continuation lines get paddingLeft = indentLeftPx, so first-line text won't align with body text.

maybe lineEl.paddingLeft = anchorPoint would fix this (like in renderer.ts)

lineEl.style.paddingLeft = `${markerStartPos}px`;
// Position marker within the container
if (['center', 'right'].includes(markerJustification)) {
lineEl.style.paddingLeft = parseFloat(lineEl.style.paddingLeft || '0') + currentPos + listTabWidth + 'px';
Copy link
Contributor

Choose a reason for hiding this comment

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

paddingLeft includes listTabWidth, but the separator element (width listTabWidth) is a sibling outside lineEl -- tab width counted twice. text shifts one tab-width too far right.

should we drop listTabWidth from the padding or move the separator inside lineEl?

// Create wrapper for this paragraph's SDT metadata
// Use absolute positioning within the content container to stack blocks vertically
const paraWrapper = doc.createElement('div');
paraWrapper.classList.add('superdoc-table-paragraph');
Copy link
Contributor

Choose a reason for hiding this comment

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

superdoc-table-paragraph class added but no CSS or JS references it.

* This creates a container with the marker positioned absolutely
* and the line content positioned with appropriate padding.
*/
const lineContainer = renderListMarker({
Copy link
Contributor

Choose a reason for hiding this comment

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

tabsPx is accepted but never passed -- explicit tab stops from the document will be ignored in table cells, falling back to default 48px intervals.

should we add tabsPx to WordLayoutInfo and pass it here?

@@ -0,0 +1,60 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

extraction is justified (2 call sites) but the export has no JSDoc.

let's add a one-liner describing what it does.

): number => {
const nextDefaultTabStop = currentPos + DEFAULT_TAB_INTERVAL_PX - (currentPos % DEFAULT_TAB_INTERVAL_PX);
let tabWidth: number;
if ((justification ?? 'left') === 'left') {
Copy link
Contributor

Choose a reason for hiding this comment

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

(justification ?? 'left') -- param is string, never null or undefined. redudant?

explicitTabs.push(implicitTabPos);
// Sort tab stops to maintain order
explicitTabs.sort((a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
Copy link
Contributor

Choose a reason for hiding this comment

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

typeof guards in sort comparator looks like dead code -- the array is number[].

Sign up for free to 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.

3 participants