githubnext/chopinPublic

Let's compose rich plans together

Give a table rails to reshape it by #5

MergedKrzysztof-Cieslakmerged 3 commits intomainfromrefs/pull/5/head
  1. KC

    Tables were schema and text-in-cells only: no way to add a row, remove a column, reorder anything, or make one in the first place. `packages/editor` had no table code at all — not even the plugins `@lexical/table` assumes are registered.

    ## What this adds

    Hovering a table, or putting the caret in one, raises a rail on each axis: a grip per column above it and per row beside it. Drag a grip to reorder, press the cross to remove, press the plus between two grips to insert there. A column's grip also carries an alignment button, cycling default → left → centre → right. Everything has a keyboard equivalent — `Ctrl/Cmd+Arrow` on a focused grip moves it — because a reorder that is only a drag is a reorder some people cannot perform at all.

    `/table` joins the slash menu, and column alignment is finally drawn: it has round-tripped through MDX all along while the stylesheet hard-coded `text-align: start` over the top of it.

    Also registered, because the editor was running without them: `registerTablePlugin` (cell navigation, the integrity transforms), `registerTableSelectionObserver` (cell-range selection), and `registerTableCellUnmergeTransform`, which keeps a table pasted from HTML to a shape GFM can actually write — the export visitor ignores spans, so a merged cell silently redistributed itself at the next save.

    ## Three things worth reviewing

    **The header row does not take part.** It cannot be dragged, dropped onto, removed, or inserted above. Nothing in the document marks a row as the header: being first is the whole of what makes it one, and the column alignment hangs off its cells. A drag that moved it would not reorder the table so much as change what it claims — two rows would swap meaning at the next save. The last *body* row can go, though; `| a |` over `| - |` is well formed and round-trips.

    **Limits are checked where the button is, not where the document is.** A table past `MAX_TABLE_ROWS` or `MAX_TABLE_COLUMNS` applies locally, syncs cleanly and is then refused by the server — which cannot undo a Yjs transaction, so it rebuilds the room under a fresh epoch and everyone in it loses their undo and their cursors. `table/shape.ts` is asked before anything is created. Same argument as `toolbar/url.ts`, one order of magnitude worse.

    **The rails are a measured overlay, not chrome in the node.** A `<table>` cannot contain a `<div>` — the browser hoists a stray one straight back out — so the `data-plan-chrome` slot that callouts and tabs portal into is not available at any price. The choice was between subclassing `TableNode` and reading cell rectangles; reading rectangles is what the selection bubble already does.

    ## Two silent failures found on the way

    `@lexical/table` paints a selected cell by adding `theme.tableCellSelected` and does nothing else — no inline style, no fallback — and MDXEditor's `lexicalTheme` names no table class of any kind. Cell selection was therefore live and completely invisible, and the next keystroke replaced everything it covered. The theme entries are in `plan-editor.tsx` beside the `collaboration` block, which exists for the identical reason.

    `$moveTableRow` and `$moveTableColumn` return silently on a table with merged cells, which is why `shape.ts` carries `simple` and the grips go inert rather than becoming drags that do nothing.

    Both are written down in `agents.md`, along with two mistakes made and fixed here: a remove button placed in the middle of a grip swallowed the drag it was drawn beside — intermittently, depending on whether a re-render landed between the pointer arriving and the button going down — and a rail that passed pointer events through its gaps blinked out from under a hand moving across it.

    ## Layout

    ``` table/shape.ts what a table permits; the limits live here table/geometry.ts seam ↔ index arithmetic and the rail's boxes table/ops.ts the Lexical mutations, each guarded by shape.ts table/rails.tsx the overlay: measuring, pointers, registration ```

    The first three are pure and tested; `rails.tsx` is rectangles and pointers and the test runtime has no DOM, so it is untested on purpose — the `trail.ts` and `changes.ts` split.

    ## Verification

    `bun test` (500), `bun run types` and `bun run ci` are green on each of the three commits independently, and `bun run build` is clean with one copy of `@lexical/table` in the bundle — two would make every `$isTableNode` return false in silence, which `table/ops.test.ts` would catch the way `interop.test.ts` does for the dialect.

    Driven in Chrome against a real room as well: insert, remove, drag, keyboard move and alignment all confirmed against the canonical MDX written to disk. A column dragged to the end produced `| B | C | A |` with every row following; a right-align wrote `---:`; a row dragged above the header clamped to just below it rather than going nowhere.

    `agents.md` also had its line counts and test count corrected — they were already stale before this branch.

  2. Krzysztof-Cieslakmerged 3a9582a9f2893 months ago on GitHub

  3. Krzysztof-Cieslakclosed3 months ago on GitHub

  4. Krzysztof-Cieslakhead-ref-deleted3 months ago on GitHub