Skip to content

Disclose table rails per track, with grips and seam inserts - #359

Merged
MaggieAppleton merged 4 commits into
mainfrom
design/table-rails
Oct 8, 2026
Merged

MaggieAppleton merged 4 commits into
mainfrom
design/table-rails

Conversation

@MaggieAppleton

@MaggieAppleton MaggieAppleton commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Table rails showed every row and column as soon as any cell was hovered. Their controls were cramped, and row tooltips covered nearby actions.

What changed

  • Show the grip for the pointed track, with faint grips while the caret is in the table. Rails fade in and respect reduced motion.
  • Reveal insert controls at the aimed seam with a line across the table. Give remove and align controls larger, borderless targets.
  • Keep the rails clipped to the document scroller and their container pointer-transparent outside active controls.
  • Move row tooltips to the left, retain keyboard shortcuts and the existing phone toolbar.
  • Renew the reviewed design hash after checking the integrated rail source.

Verification

49 focused table tests, bun run types, bun run ci, and bun run build pass on the restacked branch. GitHub's browser suite will check the two new rail scenarios on the final published head.

Known follow-up

Tab and Shift+Tab do not yet leave the editor, so the rail grips are reached by screen reader or programmatic focus. Their existing keyboard move shortcuts work once focused.

Visuals

Table rail before

Table rail after

Insert seam

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Review: Changes needed

Checked in a real browser (1440x900, 390x844 touch and narrow, reduced motion) against a seeded document: 3-column table, 20-column table, 100-row table, tables right under a paragraph.

Blocking

  1. packages/editor/src/table/rails.tsx:61-63 (GRIP 12 + TOOL 24 = DEPTH 36, up from 28). The column rail is a 36px-tall opaque hit area directly above the table (measured: rail y 359-395, table y 395). When the table has the caret, or the pointer is over it, DIV.plan-rail is the elementFromPoint winner over the paragraph above. With the caret in a cell, I could click the first line of the paragraph above but not its last line (rail covered it at y 363-371, and click landed nowhere, selection did not move into the paragraph). The paragraph above a table is the most common thing to click next. The 8px increase makes it worse than main. Suggest keeping the tool lane out of the hit area until a track is hot: make .plan-rail itself pointer-events: none and give only the grips and the hot track's tool buttons pointer-events: auto. If that conflicts with the pointerleave flicker note in styles.css, shrink the column rail's overlap to the grip lane (12px) and put the column tool buttons inside the table's top edge or in the grip lane.

Non-blocking

  1. A table near the top of the pane: the column rail is position: fixed and not clipped to the scroller. With the table top at 8px below the pane top, the rail sits 28px above it, over the Document/Decisions tab bar. When the table scrolls partly under the top, its column grip floats in the app chrome above the pane (third frame of my top-of-pane capture) and overlaps the Decisions tab. Clip the rail to the scroller rect, or hide it when the table's top leaves the viewport.
  2. rails.tsx ~line 580-615: Remove (x) shows only after the pointer is on that row's grip. With the pointer on the cell, nothing but the grip hints it. Fine and calm, but the + insert (seam) and the x of the next row end up stacked (24px boxes, ~13px apart, 5px overlap on 38px rows) with a destructive control directly under an insert. The z-index fix makes the x win, so a click aimed at the lower part of the + hits Remove. Consider more separation or putting Remove on the far side of the grip.
  3. rails.tsx over handler calls setPointed(...) with a new object on every pointerover, so it re-renders on every element crossed within the same cell. Skip the update when row/column/table are unchanged, as setCaretCell does.
  4. Tab order: Tab and Shift+Tab never leave the editor (the editor keeps focus), so the grips are reachable only via screen reader or programmatic focus. This is not new, and the Cmd/Ctrl+Arrow moves work once a grip has focus (rows, columns, and header-neighbour no-op all verified), but aria-keyshortcuts on something a keyboard user cannot reach is thin. Out of scope here, worth a follow-up.
  5. Raw sizes in the grip dots (8px, 12px, 4px, 1px/1.25px) and padding: 2px; fine as a drawn glyph but they will want a note in the design-contract review.

What works well

  • Hover across rows, columns, and rails: 0 rail unmounts over ~80 pointer moves (no flicker), the track grip appears with a ~120ms rail fade.
  • Insert/remove row and column, keyboard move rows/columns, header row has no grip: all correct.
  • Limits: 20-column and 100-row tables show no insert buttons. The wide table still scrolls.
  • Seam insert with the brand line across the table, left-opening row tooltips, 24px targets: calm and Linear-like; the faint 0.35 grips while the caret is in the table keep things discoverable.
  • Reduced motion: animation: none, transition-duration: 0s. Touch phone: bottom toolbar, no rails, no overflow. Narrow desktop: 12px grips only.
  • Merged cells: cannot be created from MDX; not exercised.

CI: only the design-contract review hashes fail (rails.tsx lines 496, 512, 598, 617, 644, 661, "reviewed dynamic owner changed"). Acceptable per the audit rules. e2e and container pass.

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Thanks. All five points are addressed on design/table-rails:

  1. Blocker: .plan-rail is now pointer-events: none. Only grips, shown remove and align buttons, and the two seams beside the pointed track (data-plan-live) take the pointer. The caret alone never makes a seam live. New e2e: the paragraph above a table stays clickable while its rails show. It puts the caret in a cell, clicks the paragraph's last line inside the rail's box, and checks that the selection lands in that paragraph.
  2. Rails and the seam line are clipped with clip-path to the document scroller's top and bottom, so they no longer float over the tab bar. The clip is vertical only. A row rail sits in the gutter outside the scroller box, and a horizontal clip cut off its buttons (that caused the first CI e2e failure).
  3. Remove hides while an insert on the same axis is aimed. Inserts come later in the rail, so wherever the two overlap the + wins, and a destructive control never sits under an add.
  4. setPointed skips the update when the table, row and column are unchanged (same helper as setCaretCell).
  5. Grip dots and padding now use --spacing. The gradient stops are percentages.

Tab reachability is noted as a follow-up in the PR body. CI: e2e and container pass. The only failure is the rails.tsx design-contract hash; the new hash is in the PR body.

MaggieAppleton and others added 4 commits October 8, 2026 15:38
Only the pointed or caret row and column draw a grip, with a six-dot
handle. Inserts appear on their seam with a line across the table.
Targets grow to 24px, rails fade in, and row-rail tooltips sit outside
the rail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rail container now passes the pointer through, so the paragraph above
a table stays clickable; only grips, shown buttons and the seams beside the
pointed track take it. Rails clip to the document scroller, the remove
button hides while an insert is aimed, pointed-cell updates skip no-ops,
and the grip glyph uses spacing tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A row rail sits in the gutter, which can fall outside the scroller's own
box; clipping it horizontally cut off its buttons and their hit area.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaggieAppleton
MaggieAppleton enabled auto-merge (rebase) October 8, 2026 14:38
@MaggieAppleton
MaggieAppleton merged commit 519d116 into main Oct 8, 2026
5 checks passed
@MaggieAppleton
MaggieAppleton deleted the design/table-rails branch October 8, 2026 14:47
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.

1 participant