port: tables (approved mockup) #845

Open
kayg wants to merge 10 commits from port/port-tables into dev
Owner

First PR of the tables port (approved mockup, kayg 10-19 T1 to T6): the core interaction in the product's CodeMirror editor. Stacked follow-up: the PR "port: tables, resizing, markdown in cells, carry, motion" (base port/port-tables).

What it does

  • T1 Plain GFM pipe tables; nothing else stored. A table nobody edited is never rewritten (textFor keeps the file's bytes). An edit writes the tidy form once, and that form is stable.
  • T2 In the default mode each table is a grid you type into, no pipes (packages/editor/src/tables/). Show source draws the pipes. Tab / Shift-Tab, Enter (last row adds a row), Backspace on an empty last row, arrows walk in and out. Edits are normal transactions: undo is the note's one history.
  • T3 Create: Insert sheet "Table" (Mod+/), a typed | a | b | line + Enter, spreadsheet (TSV) or web-page (HTML) paste. Header + two rows of three, caret in the first cell.
  • T4 Bar morphs into table actions for a cell / a picked row / a picked column. Tap a row or column edge to pick it.
  • T5 Phone: scrolls inside the card, first column pinned (one soft edge, and scrolled to the end the last column starts exactly at the pinned edge, so no sliver of the column before it). No swipe row actions on tables. Phone bar: one strip inside the capsule, fixed order.
  • T6 Number-only columns right-aligned with tabular figures.
  • /table: apps/web/src/lib/insert-blocks.ts (INSERT_BLOCKS) is the list the Insert sheet reads; the / menu (port-pickers) only has to read it.

Lazy loading (this push)

The grid, the model, the bar's table actions and the table styles are separate chunks, fetched when a note with a table opens, when a | is typed or pasted in, when a spreadsheet is pasted, or when Insert makes a table. What stays in the first paint: tables/lazy.ts (a watcher, ~1 KB) and one case 'Table' in syntax/hidden.ts that gives a table's lines the height the grid will have until it arrives (cm-table-pending; they show their text after 5 s if the code never arrives). e2e/tables.spec.ts "no jump" holds the grid's chunk back and checks the text below does not move (< 4 px) when it lands.

Gates (dev-01 chromium, dev-02 webkit, CI=1 --retries=0, through ~/bin/slot)

  • editor vitest 531 passed (incl. model round trips: every mockup table, escaped pipes, all alignments, ragged rows, unedited = byte-exact); web vitest 78; ui vitest 46; tsc --noEmit clean; bun run check 0 errors (14 warnings = base); bun run lint ok.
  • e2e/tables.spec.ts chromium 1440+390: 27 passed (1 by-design skip); webkit 1440+390: same file earlier 25 passed.
  • Neighbours with the stack head (editor, big-paste, phone-bars, bar-stable, bar-stays, touch-select, enter, vim-block, block-no-text-sel, tables): 356 passed.

Bundle (project's own scripts/bundle-size.mjs, brotli bytes)

base (origin/dev) this PR budget (perf/budgets.json)
first paint JS 245,265 245,690 245,760 (240 KiB): under, 70 B to spare
first paint CSS 21,361 21,483 22,630 (22.1 KiB): under
all JS 319,370 329,147 322,560 (315 KiB): over by 6,587 B

all JS is over its budget and I have not touched the budget. The table code is 9 KB brotli of lazy chunks (grid + model 6.8, bar actions 2.0, glue 0.4) and is not an opt-in plugin, so it counts in "all". The base already leaves only 3.1 KB. This needs kayg to approve a raise (as for the Composer on 29 Oct) or to cut scope; the stacked PR adds a further 3.5 KB (resizing, inline markdown, carry, motion).

Not in this PR

Resizing, carrying a row or column, motion, markdown in cells, copy: the stacked PR.
Screenshots (product vs mockup, same states, 1440x900 and 390x844): ~/Developer/Worktrees/shots/port-tables/ on the control host.

Budget (kayg approved, 2026-11-08 08:20 IST): kayg approved raising the lazy budget total, up to 543.9 KiB. perf/budgets.json in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.

First PR of the tables port (approved mockup, kayg 10-19 T1 to T6): the core interaction in the product's CodeMirror editor. Stacked follow-up: the PR "port: tables, resizing, markdown in cells, carry, motion" (base `port/port-tables`). ## What it does - **T1** Plain GFM pipe tables; nothing else stored. A table nobody edited is never rewritten (`textFor` keeps the file's bytes). An edit writes the tidy form once, and that form is stable. - **T2** In the default mode each table is a grid you type into, no pipes (`packages/editor/src/tables/`). Show source draws the pipes. Tab / Shift-Tab, Enter (last row adds a row), Backspace on an empty last row, arrows walk in and out. Edits are normal transactions: undo is the note's one history. - **T3** Create: Insert sheet "Table" (`Mod+/`), a typed `| a | b |` line + Enter, spreadsheet (TSV) or web-page (HTML) paste. Header + two rows of three, caret in the first cell. - **T4** Bar morphs into table actions for a cell / a picked row / a picked column. Tap a row or column edge to pick it. - **T5** Phone: scrolls inside the card, first column pinned (one soft edge, and scrolled to the end the last column starts exactly at the pinned edge, so no sliver of the column before it). No swipe row actions on tables. Phone bar: one strip inside the capsule, fixed order. - **T6** Number-only columns right-aligned with tabular figures. - `/table`: `apps/web/src/lib/insert-blocks.ts` (`INSERT_BLOCKS`) is the list the Insert sheet reads; the `/` menu (port-pickers) only has to read it. ## Lazy loading (this push) The grid, the model, the bar's table actions and the table styles are separate chunks, fetched when a note with a table opens, when a `|` is typed or pasted in, when a spreadsheet is pasted, or when Insert makes a table. What stays in the first paint: `tables/lazy.ts` (a watcher, ~1 KB) and one `case 'Table'` in `syntax/hidden.ts` that gives a table's lines the height the grid will have until it arrives (`cm-table-pending`; they show their text after 5 s if the code never arrives). `e2e/tables.spec.ts` "no jump" holds the grid's chunk back and checks the text below does not move (< 4 px) when it lands. ## Gates (dev-01 chromium, dev-02 webkit, `CI=1 --retries=0`, through `~/bin/slot`) - editor vitest 531 passed (incl. model round trips: every mockup table, escaped pipes, all alignments, ragged rows, unedited = byte-exact); web vitest 78; ui vitest 46; `tsc --noEmit` clean; `bun run check` 0 errors (14 warnings = base); `bun run lint` ok. - `e2e/tables.spec.ts` chromium 1440+390: 27 passed (1 by-design skip); webkit 1440+390: same file earlier 25 passed. - Neighbours with the stack head (editor, big-paste, phone-bars, bar-stable, bar-stays, touch-select, enter, vim-block, block-no-text-sel, tables): 356 passed. ## Bundle (project's own `scripts/bundle-size.mjs`, brotli bytes) | | base (origin/dev) | this PR | budget (perf/budgets.json) | |---|---|---|---| | first paint JS | 245,265 | 245,690 | 245,760 (240 KiB): **under, 70 B to spare** | | first paint CSS | 21,361 | 21,483 | 22,630 (22.1 KiB): under | | all JS | 319,370 | 329,147 | 322,560 (315 KiB): **over by 6,587 B** | **all JS is over its budget** and I have not touched the budget. The table code is 9 KB brotli of lazy chunks (grid + model 6.8, bar actions 2.0, glue 0.4) and is not an opt-in plugin, so it counts in "all". The base already leaves only 3.1 KB. This needs kayg to approve a raise (as for the Composer on 29 Oct) or to cut scope; the stacked PR adds a further 3.5 KB (resizing, inline markdown, carry, motion). ## Not in this PR Resizing, carrying a row or column, motion, markdown in cells, copy: the stacked PR. Screenshots (product vs mockup, same states, 1440x900 and 390x844): `~/Developer/Worktrees/shots/port-tables/` on the control host. **Budget (kayg approved, 2026-11-08 08:20 IST):** kayg approved raising the lazy budget total, up to 543.9 KiB. `perf/budgets.json` in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.
kayg added 10 commits 2026-11-02 08:57:37 +00:00
Plain GFM pipe tables drawn as a grid you type into; Tab/Enter navigation,
Insert, typed pipe line + Enter, spreadsheet paste, and the table bar.
tables: the card is solid paper
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
c244134e18
Author
Owner

housekeeping #1009: rebased+failing, left open for review.

rebased table port while preserving dev note-bar styling and table pending state.

head: bc1eb03c56718a068291bd5b5f3282a430b8ff68; based on dev cd7f84432febfc63b788fbb9b9f9950452982d74, pushed with the original exact force-with-lease.

frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 231204 B (+427 B), CSS 18533 B (+147 B).

targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun.

all executed gates used the VM Nix environment, shared Mac lock and slot. exact commands, source heads, logs and dev baseline. frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head.

original-head cherry/patch inventory, pr-845.md; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.

final queue, cleanup and source-verification evidence.

housekeeping #1009: rebased+failing, left open for review. rebased table port while preserving dev note-bar styling and table pending state. head: `bc1eb03c56718a068291bd5b5f3282a430b8ff68`; based on dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, pushed with the original exact force-with-lease. frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 231204 B (+427 B), CSS 18533 B (+147 B). targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun. all executed gates used the VM Nix environment, shared Mac lock and slot. [exact commands, source heads, logs and dev baseline](https://git.kayg.org/attachments/870a0fef-2e31-425c-aea6-eb87a66d365a). frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head. [original-head cherry/patch inventory](https://git.kayg.org/attachments/e05a38dc-c028-4d77-8655-efa9d5c8ea50), `pr-845.md`; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above. [final queue, cleanup and source-verification evidence](https://git.kayg.org/attachments/7ab315a5-4b6c-4af1-971b-cdae2637adae).
Author
Owner

Final-port test pass (issue #1009), READY: no: specs pass; bundle gate fails; first paint is bigger than dev.

head bc1eb03c56718a068291bd5b5f3282a430b8ff68

Targeted specs, CI=1 --retries=0 --workers=1, dev-01 Chromium and dev-02 WebKit (through ~/bin/slot), against a dev-head backend (cd7f84432).

spec Chromium 1440 Chromium 390 WebKit 1440 WebKit 390
tables 13/13 14/14 13/13 14/14

Bundle gate: perf/web.sh --bundle-only --gate (the 17 failures from the housekeeping audit). Per the gate's own rule (total_js_brotli budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing.

  • widget chunk 6.92 KB vs 1.1 cap. First paint JS +427 B, CSS +147 B vs dev.

Not done: Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment).

Not marked READY FOR MERGE.

**Final-port test pass (issue #1009), READY: no: specs pass; bundle gate fails; first paint is bigger than dev.** head `bc1eb03c56718a068291bd5b5f3282a430b8ff68` Targeted specs, `CI=1 --retries=0 --workers=1`, dev-01 Chromium and dev-02 WebKit (through `~/bin/slot`), against a dev-head backend (`cd7f84432`). | spec | Chromium 1440 | Chromium 390 | WebKit 1440 | WebKit 390 | |---|---|---|---|---| | tables | 13/13 | 14/14 | 13/13 | 14/14 | **Bundle gate:** `perf/web.sh --bundle-only --gate` (the 17 failures from the housekeeping audit). Per the gate's own rule (`total_js_brotli` budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing. - `widget` chunk 6.92 KB vs 1.1 cap. First paint JS +427 B, CSS +147 B vs dev. **Not done:** Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment). Not marked READY FOR MERGE.
Author
Owner

READY FOR MERGE: tables core (head 8e6d8e89e)

Rebased on dev 9438918f3 (#848/#849 in), budgets regenerated from a fresh build of this head, bundle gate clean, specs on dev-02 (idle host) with calternal-h861, chromium and webkit at 1440 and 390 (preload, lazy-ui, lazy-offline, glass, shortcuts and this PR's own specs): 0 failed.

dev this PR
first paint plain / normalised 227531 / 227081 B 228097 / 227333 B (+566 / +252 B)
CSS 18234 18399 (+165)
budget total 537.55 KiB 538.74 KiB (tables 6.94)

194 passed, 42 skipped. First-paint growth: the Table block in the registry (tables/lazy.ts +840 B raw) and the table CSS; kayg decides.

## READY FOR MERGE: tables core (head `8e6d8e89e`) Rebased on dev `9438918f3` (#848/#849 in), budgets regenerated from a fresh build of this head, bundle gate clean, specs on dev-02 (idle host) with `calternal-h861`, chromium and webkit at 1440 and 390 (preload, lazy-ui, lazy-offline, glass, shortcuts and this PR's own specs): **0 failed**. | | dev | this PR | |---|---|---| | first paint plain / normalised | 227531 / 227081 B | 228097 / 227333 B (**+566 / +252 B**) | | CSS | 18234 | 18399 (+165) | | budget total | 537.55 KiB | 538.74 KiB (tables 6.94) | 194 passed, 42 skipped. First-paint growth: the Table block in the registry (`tables/lazy.ts` +840 B raw) and the table CSS; kayg decides.
Author
Owner

READY FOR MERGE: tables core (head 16c014486)

Merged dev fe2efbf11 (#1044) into the branch (it also merges cleanly with 4274dafb4, git merge-tree), budgets regenerated from a fresh build, bundle gate clean, specs on dev-02 with calternal-h861, chromium and webkit at 1440 and 390. Dev baseline on fe2efbf11: 228023 B plain / 227378 B normalised / CSS 18236.

One fix since the last head: #1044 removed the page-wide keyboard-no-motion rule, so the table's handles and edge fades (.rs/.cs::before, .tw::after) played their transitions after a key. They now name themselves (tables.css), which keys-no-motion and tables-more check.

first paint plain / normalised CSS budget total
228202 / 227989 B (+179 / +611 B; growth accepted by kayg) 18400 (+164) 539.01 KiB

Specs (preload, lazy-ui, lazy-offline, glass, shortcuts, keys-no-motion, tables): 70 passed (4 projects of the keys/tables set after the fix), earlier full set 181 passed; the one keys-no-motion webkit-1440 settings failure was a flake (passes on rerun and on the other heads). Gate clean.

## READY FOR MERGE: tables core (head `16c014486`) Merged dev `fe2efbf11` (#1044) into the branch (it also merges cleanly with `4274dafb4`, `git merge-tree`), budgets regenerated from a fresh build, bundle gate clean, specs on dev-02 with `calternal-h861`, chromium and webkit at 1440 and 390. Dev baseline on `fe2efbf11`: 228023 B plain / 227378 B normalised / CSS 18236. One fix since the last head: #1044 removed the page-wide keyboard-no-motion rule, so the table's handles and edge fades (`.rs/.cs::before`, `.tw::after`) played their transitions after a key. They now name themselves (`tables.css`), which `keys-no-motion` and `tables-more` check. | first paint plain / normalised | CSS | budget total | |---|---|---| | 228202 / 227989 B (**+179 / +611 B**; growth accepted by kayg) | 18400 (+164) | 539.01 KiB | Specs (preload, lazy-ui, lazy-offline, glass, shortcuts, keys-no-motion, tables): 70 passed (4 projects of the keys/tables set after the fix), earlier full set 181 passed; the one `keys-no-motion` webkit-1440 settings failure was a flake (passes on rerun and on the other heads). Gate clean.
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
Merge conflict checking is in progress. Try again in few moments.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin port/port-tables:port/port-tables
git switch port/port-tables

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch dev
git merge --no-ff port/port-tables
git switch port/port-tables
git rebase dev
git switch dev
git merge --ff-only port/port-tables
git switch port/port-tables
git rebase dev
git switch dev
git merge --no-ff port/port-tables
git switch dev
git merge --squash port/port-tables
git switch dev
git merge --ff-only port/port-tables
git switch dev
git merge port/port-tables
git push origin dev
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!845
No description provided.