Commit graph

11 commits

Author SHA1 Message Date
arena-agent
54e1148b39 feat(doc): round-trip tabular clipboard payloads with RFC-4180 quoting
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
doc-engine / engine (push) Successful in 16s
doc-engine / consumer (push) Successful in 3m58s
Cells holding tabs, newlines, CRs, or quotes re-distributed across the
grid on a copy/paste cycle — the documented caveat of every tabular
clipboard milestone. This closes it on both sides:

- table_grid_tsv quotes such fields on the way out (wrapped in double
  quotes, inner quotes doubled) via a new quote_tabular_field helper;
  plain and empty fields stay raw, so payloads stay byte-compatible
  with spreadsheets and plain text editors. The cell-range payload and
  the block-span document payload share the one builder, so both
  inherit the quoting at once.
- paste_table_payload replaces its split('\n')/split('\t') walk with
  split_tabular_payload, an RFC-4180-style tokenizer: quotes open only
  at field start (mid-field quotes are literal), doubled quotes read
  as one, tabs/newlines/CRs inside quotes are literal field text,
  CRLF rows outside quotes keep their tolerance, an unterminated
  quote reads to the end as best effort, and a single trailing
  newline adds no phantom row (a deliberate empty row survives).
- Caret parking, no-op skipping, empty-field clears, and the one-undo
  grouped write semantics of the raw paste milestone are unchanged;
  the caret offset in a multi-line value counts the newline too.

Tests: unit coverage for the writer and the tokenizer (every quoting
rule plus the quote/split round-trip property), runtime coverage of
copy quoting, paste restoring embedded tab/newline values verbatim
with one-undo and redo, and an end-to-end copy-cut-paste cycle; a
doc-engine materialize test pins special-character cell text
surviving peer sync and undo/redo verbatim. README caveats updated
and the milestone documented.
2026-08-02 09:54:40 +00:00
arena-agent
c5c1e0a3e9 feat(doc): paste tabular payloads across table cells
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Successful in 15s
doc-engine / consumer (push) Successful in 3m55s
A TextInput carrying tabs or newlines while the caret lives in a
table now pastes the spreadsheet way — one cell per tab stop, one
row per line — starting at the caret cell, or at the armed range's
normalized top-left (consuming the range like any paste-over-
selection). The distribution rides the grouped set_table_cells from
the previous milestone so the rectangle un-pastes in one undo step,
and the caret parks at the last cell the payload touched. Rows or
columns past the table edge clip, CRLF strips per line like
text-block pastes, empty fields clear their targets, and unchanged
cells are skipped so a paste lands neither redundant LWW ops nor
dead group members. Plain payloads keep the whole-cell char splice,
and cells still never spawn blocks.

The pre-existing in-cell paste test asserting the old "newline stays
embedded in the cell" behavior is rewritten to the distribution
semantics; new runtime tests cover the 2x2 distribution with caret
parking and the one-undo/redo round trip, edge clipping without
wrap-around, backward-spanned ranges pasting from their top-left,
CRLF with empty-field clears, and the mobile menu's Paste action
distributing from the pressed cell. Engine integration coverage
replays a grouped multi-cell write into a peer and asserts the
projections converge.
2026-08-02 09:02:05 +00:00
arena-agent
08c3eed18d feat(doc): copy and clear cell ranges through the clipboard
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
An armed table cell range now copies as tab/newline text (rows top
to bottom, cells left to right, the spreadsheet convention) through
the same copyable_selection_text the TextCopy/TextCut hits answer
with, so the long-press cell menu floats the full action set instead
of a paste-only one. Cut and Backspace/Delete clear every non-empty
spanned cell via the engine's new set_table_cells, which pops each
sub-write's compensation into one Compensation::Group — the span
un-clears in a single undo step where Backspace previously dropped
the range and edited only the caret cell. Single writes keep the
leaf compensation and empty write lists record nothing.

Grouping cell writes surfaced a real undo asymmetry: an undo after a
redo re-applied the redone cell text, because redo pushed the write's
own text as its undo compensation (inverse() only swaps the verb,
keeping the stale text) while undo's leaf special-case captured live
cell state only in one direction. undo()/redo() now resolve
cell-text inverses per compensation member against the projected
text at both transition boundaries (redo_compensation/
undo_compensation), so leaves and groups round-trip through
arbitrary cycles and the "cells stay out of groups" restriction is
gone.
2026-08-02 08:21:31 +00:00
arena-agent
920827a440 feat(doc): split multi-line paste into blocks with one-step undo
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
A pasted TextInput payload containing newlines used to land as a
single run of text with literal line feed characters. It now splits
block-per-line like a desktop editor: line 0 splices into the caret
block, middle lines become sibling blocks inheriting the caret block's
kind, and the trailing SplitBlock carries the caret block's suffix
onto the last pasted line (ab|XY pasted with "l0\nl1\nl2" yields
abl0 / l1 / l2XY). The caret parks after the last pasted atom.

The whole paste folds into one Compensation::Group (undo applies group
members in reverse, redo applies the inverse group in authored order),
so N lines retract in a single Ctrl+Z. The caret anchor may be
tombstoned (the caret a selection delete leaves behind): the new
CrdtDocument::live_offset_of resolves it to the same live-text offset
RGA splicing uses, so pasting over a deleted selection lands exactly
where a single-line paste would. CRLF payloads strip their \r.

Surfaced and fixed at the source while building this: the synthetic
block a SplitBlock opens was a text dead end -- atoms and style ops
addressed to a split op id landed in the op log but evaporated from
the projection, and the child always spliced directly behind its
parent. Split children are now first-class materialization targets:
suffix atoms re-link head-to-tail (keeping ids and inherited style
runs), child-addressed atoms splice in RGA-wise, and the child's
splice skips the parent's real-chain descendants, keeping the middle
paste lines ahead of it.

Runtime tests cover the split/suffix/caret/undo/redo cycle,
paste-over-selection as two undo steps, and the in-cell newline guard.
Engine tests cover grouped-undo chronology, tombstone anchors, empty
middle lines, CRLF, table refusal, peer convergence, and the
synthetic-child text/style/order regressions underneath.
2026-08-01 05:44:28 +00:00
arena-agent
7e0012b866 feat(doc): undo cross-block cuts losslessly via range tombstones
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
ReplaceBlockRange pushed no undo compensation, so a multi-block cut or
selection delete could never be undone: the range op rewrites only the
projection (drained blocks and every atom beneath the span stay in the
op log untouched), and no op existed to make it stop applying.

Add the CancelBlockRange/RestoreBlockRange op+compensation pair,
resolved chronologically per target like every other tombstone pair
(merge splits, cell styles, blocks, text, rows, columns). Undo of a
cross-block delete tombstones the range op itself, which is lossless:
splices, middle blocks and their texts all re-materialize exactly from
the untouched log, in a single undo step. Redo clears the tombstone.

The controller also refuses spans whose endpoints do not resolve
against the projection in order, so a no-op range never strands an
entry on the undo stack.

Coverage: engine tests for the one-step undo/redo cycle (with a double
undo proving the chronological tombstone), text typed beneath a
cancelled range surviving, and unresolved-span refusal growing no undo
stack. Widget runtime test cuts across three blocks, asserts the joined
payload, then Ctrl+Z restores all three blocks and Ctrl+Shift+Z re-cuts.
2026-08-01 05:02:19 +00:00
arena-agent
a3ff59e512 feat(doc): clipboard copy/cut/paste across blocks and table cells
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
The CRDT editor had no clipboard surface at all. It now answers the
platform clipboard queries exactly like makepad's TextInput:

- Hit::TextCopy / Hit::TextCut fill the response cell with the selection
  payload: the text-block selection joined with newlines, or the in-cell
  character span when the caret lives in a table. Nothing selected
  leaves the response empty, so the platform keeps the clipboard alone.
- Cut removes the payload through the shared selection-deletion path and
  paste flows through the existing TextInput insert path (replace-
  selection semantics for both blocks and cells).

Three latent production bugs uncovered and fixed at the source:

1. delete_selection's applied flag was wired to the empty replacement's
   missing trailing atom, so same-block Backspace-over-selection
   over-deleted by one char and left live anchors behind.
2. engine delete_text pushed no undo compensation, making every
   selection deletion (cut, type-over, Backspace-over) unrecoverable.
   It now pushes the symmetric RestoreText pair.
3. All six engine tombstone pairs (text, blocks, rows, columns, merge
   splits, cell style clears) resolved as union-minus-union: once a
   restore op existed, delete→undo→redo could never re-delete. They now
   resolve chronologically per target in operation order.

13 new tests: 5 clipboard runtime (payload, cut+undo, cell span,
no-payload, newline join), 2 widget regressions (over-delete,
delete-key), 6 engine (delete undo/redo, empty-replace single-step,
block/row/merge/cell-style chronology cycles). nigig-build 704 -> 711,
doc-engine 62 -> 68.
2026-08-01 04:31:06 +00:00
arena-agent
df2471105b feat(doc): CRDT-native table cell style runs
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
Cell text was plain LWW strings, so style toggles were silently
disabled whenever the caret sat in a table. Cells now carry styled runs
end to end:

- Engine: new `SetTableCellStyle` / `ClearTableCellStyle` /
  `RestoreTableCellStyle` ops. Ranges are char-offset snapshots replayed
  over the settled LWW cell text in operation order (newer patches win
  on overlap, spans clamp when text shrinks but do not stretch when it
  grows). Undo tombstones the style op exactly like a merge split,
  keeping overlapping later styles intact and undo/redo convergent
  across peers. `ProjectedTable::cell_runs` mirrors `cells` — every
  projected cell has at least one default-styled run — built by the new
  `apply_style_offset_range` helper, with `TableCellRef` addressing
  cells in the controller API.
- Layout: `ProjectedTableCellLayout.runs` mirrors the projection runs
  for the renderer.
- Renderer: `draw_table_projection` draws cell text run by run through
  the same regular/bold/italic/bold-italic pens as block text, advancing
  on the shared fixed char grid.
- Widget: `toggle_selection_style` routes by session — a parked cell
  cursor styles the whole cell (cell text has no selection ranges yet),
  a text selection styles its range — so Ctrl/Cmd+B/I/U and the toolbar
  buttons now work in tables. Undo/redo round-trips like any other op.

9 engine tests (split/merge/clamp/overlap/toggle/reject/undo/convergence),
5 widget tests (layout runs, Ctrl+B whole-cell, undo/redo, toolbar path,
empty-cell no-op). doc-engine 53 -> 62, nigig-build 685 -> 690.

The doc README's "cell text carries no style runs yet" gap is closed and
the doc-engine projection invariants document the new semantics.
2026-07-31 22:09:18 +00:00
arena-agent
9a167f4df3 fix(doc-engine): honor after anchors when ordering table rows and columns
InsertTableRow/InsertTableColumn carried an after anchor on the wire but
materialization ignored it, ordering the row/column vectors by op id only;
an 'insert below X' could land anywhere once ids diverged. Rows and
columns now materialize over the anchor chain with RGA-style sibling
order (counter descending, actor ascending), matching text atoms, so a
newer insert below an existing row renders right after it and peers
converge on the same grid order.
2026-07-29 05:06:16 +00:00
arena-agent
24d9f9269e feat(doc): CRDT-native keyboard editing with block split/merge ops
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
CrdtDocEditor now handles the desktop keyboard surface natively through
doc-engine operations: Ctrl/Cmd+Z/Shift+Z undo-redo via CrdtHistory,
Ctrl/Cmd+B/I/U selection style toggles, arrow-key caret moves with
shift-extend across block boundaries via the step_glyph stream,
selection-aware Backspace/Delete with block-boundary merges, Return
splitting paragraphs or appending table rows, and TextInput that
replaces the active selection at the session caret block.

Engine additions: Operation::SplitBlock/MergeBlocks with symmetric
Compensation pairs; materialization keeps a split parent's trailing
runs as a synthetic child spliced after the parent so peers converge
without a new InsertBlock op; merge_runs coalesces equal style runs.
2026-07-29 03:00:55 +00:00
arena-agent
7bedc677a6 feat(doc): render CRDT-native advanced nodes in CrdtDocEditor
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
- layout_projection walks the unified DocumentProjection::order so
  advanced nodes interleave with paragraphs and tables at their anchor
  positions; block_origins are assigned by index for both paths, with a
  blocks-only fallback for projections assembled without the op log
- projected_node_metrics mirrors legacy AdvancedLayout heights, labels
  and interactivity per kind (unknown kinds follow the bridge's
  EmbeddedWidget mapping: 'Widget: <kind>')
- ProjectionRenderer::draw_node_projection reuses the legacy
  draw_advanced_blocks colors; dividers collapse to a centered 1px line;
  new draw_node_fill/draw_node_border live fields on CrdtDocEditor
- fix(doc-engine): the after-chain was block-only, so any block anchored
  after an advanced node was unreachable during materialization and
  vanished from the projection; nodes now participate as chain connectors
- tests: unified-order interleaving, node metrics, node hit test, mixed
  table/node stacking, order-less fallback (nigig-build); node-anchored
  block materialization and mixed sibling ordering (doc-engine)
2026-07-29 02:03:37 +00:00
cc05abdc71 Initial commit 2026-07-26 19:38:26 +03:00