Skip to content

BL-16818 Tables 1/6: a table you can put in an origami section - #8325

Open
hatton wants to merge 2 commits into
masterfrom
BL-16818-table-core
Open

BL-16818 Tables 1/6: a table you can put in an origami section#8325
hatton wants to merge 2 commits into
masterfrom
BL-16818-table-core

Conversation

@hatton

@hatton hatton commented Sep 7, 2026

Copy link
Copy Markdown
Member

First of six stacked PRs splitting #8315 (107 files) into pieces a human can review. Merge in order. #8315 stays open, untouched, as the reference for what the finished tree should look like.

The stack:

  1. this PR — the dependency, the feature gate, and a table in an origami section
  2. BL-16818-table-on-canvas — a table as a canvas element
  3. BL-16818-table-video-cells — video as a cell content type
  4. BL-16818-e2e-hooks — the production hooks the e2e suite needs
  5. BL-16818-e2e-tests — the table e2e suite
  6. BL-16818-spreadsheet-tables — the spreadsheet round trip

The Alphabet Book template page left this work entirely; it is BL-16823, branched from this PR.

What to try

Turn on Tables in the collection's Advanced settings, then choose Table in an origami section's type chooser and edit it. Type in cells, add rows and columns, put a picture in a cell, undo across both the table's history and CKEditor's.

What is here

The dependency. bloom-table is not on npm and is not going to be; it ships as a committed-dist tag on GitHub, the same mechanism bloom-ai-image-tools uses. package.json pins github:BloomBooks/bloom-table#dist-v1.0.0, and basePage.less inlines the library's stylesheet from node_modules. Vitest needs test.server.deps.inline for it: the library's ESM build imports MUI by subpath, MUI 5 ships no exports map, and Node's loader rejects those as directory imports. CI green here is load-bearing — it is the first proof this dependency resolves anywhere but a developer's machine.

The gate. Pro tier, behind a tables experiment. FeatureRegistry gains FeatureName.Table with PreventPublishingInOriginalBooks; ExperimentalFeatures gains kTables, surfaced by the Advanced tab checkbox.

The table. tableEditing.ts attaches a table, wires each cell's translation group for editing — including cells the library builds after page load, and nested tables — and tears the listeners down again. Undo has two stacks now, the table's and CKEditor's, so undoOrdering.ts records which one changed last and editablePage/workspaceRoot route Ctrl+Z accordingly. A picture cell is usually smaller than the 63×52 image buttons, so those are hidden inside a cell and the user clicks the picture itself.

Video cells, the Canvas palette entry, the spreadsheet round trip and the end-to-end suite follow in later PRs.

Checks

pnpm lint, pnpm typecheck, pnpm test (800 passed), build/agent-vite.sh, and FeatureStatusTests all green on this branch alone.

[Claude Opus 5 following a prompt from Hatton]

Devin review


This change is Reviewable

First of six PRs splitting BL-16818 (PR 8315) into reviewable pieces. This
one lands enough to try the feature: turn on Tables in the collection's
Advanced settings, then choose Table in an origami section's type chooser
and edit it.

Depend on bloom-table. It is not on npm and is not going to be; it ships as
a committed-dist tag on GitHub, the same mechanism bloom-ai-image-tools
uses, so package.json pins github:BloomBooks/bloom-table#dist-v1.0.0.
basePage.less inlines the library's stylesheet from node_modules. Vitest
needs test.server.deps.inline for it, because the library's ESM build
imports MUI by subpath and MUI 5 ships no "exports" map, so Node's loader
rejects those as directory imports.

Gate the feature at the Pro tier behind a "tables" experiment:
FeatureRegistry gains FeatureName.Table with
PreventPublishingInOriginalBooks, and ExperimentalFeatures gains kTables,
surfaced by the Advanced tab checkbox.

tableEditing.ts attaches a table, wires each cell's translation group for
editing (including cells the library builds after page load, and nested
tables), and tears the listeners down again. Undo has two stacks now, the
table's and CKEditor's, so undoOrdering.ts records which one changed last
and editablePage/workspaceRoot route Ctrl+Z accordingly. A picture cell is
usually smaller than the 63x52 image buttons, so those are hidden inside a
cell and the user clicks the picture itself.

Video cells, the Canvas palette entry, the spreadsheet round trip and the
end-to-end suite follow in later PRs.
CKEditor copies a box's content when CKEDITOR.inline() creates the instance and
writes that copy back into the box when its asynchronous startup finishes. The
box is contenteditable the whole time, so anything typed in between is silently
overwritten by the copy.

A table cell hits this every time. The library builds the cell empty, Bloom
attaches an editor to it, and the cell can be typed in at once; instrumenting a
run measured the startup taking between half a second and 1.3 seconds, and the
write at the end of it destroying a word that had been typed and verified in
between. Two of the table end-to-end tests failed on this, in different places
from run to run, according to where the timing landed.

So remember what the box holds just before the startup write - our "loaded"
handler runs before it - and, if the write changed the text, put the content
back and hand the editor the same content, so what was typed survives and the
editor agrees with the DOM. The comparison is on the text rather than the
markup, because the startup write also tidies an empty paragraph (<p></p>
becomes <p><br /></p>) when nothing was typed, and that is not a loss worth
undoing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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