Commit graph

12 commits

Author SHA1 Message Date
arena-agent
4df2193859 docs(doc): retire stale in-cell paste and migration-status claims
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) Has been cancelled
Tabular paste superseded the clipboard section's "in-cell pastes
always stay a single whole-cell write, newlines included", and the
doc-engine README still framed the controller migration as "next"
even though the workspace navigation already runs on CrdtDocEditor
(the classic editor stays registered as a fallback). Found during
the phase audit.
2026-08-02 09:17:57 +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
27ff51ca47 fix(doc): hit_test yields to table/node hit tests; pointer and IME runtime coverage
Some checks failed
doc-engine / consumer (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
ProjectionLayoutTree::hit_test's nearest-glyph fallback resolved any
point to a glyph, so taps landing inside tables or advanced nodes never
reached the widget's table_hit_test/node_hit_test branches — production
cell taps silently moved the text caret instead of parking in cells.
The fallback now returns None when the point is inside a table or node
rect (those layers own their taps), while empty-space snapping beside
text still places the caret.

Close the "widget input, selection, mobile gestures" test gap with a
draw-free Area::Rect stub harness (stub_hit_area): tests seed the input
area a GPU frame normally fills and mirror the two platform
pre-dispatch steps (first_mouse_button, committed key focus), so raw
MouseDown/MouseMove and IME TextInput dispatch through event.hits
against a real Cx — exactly like a running app. Covered end-to-end:
hit-dispatch sanity, caret placement from a tap, shift-press selection
extension, drag selection over glyphs, TextInput inserting at the caret
and replacing an active selection, and a cell tap + TextInput whole-cell
round trip. The harness also exposed the test fixture actor drifting
from production's single "local" actor (cross-actor mid-run anchoring
is deterministic but not chronological — now a documented doc-engine
projection invariant); the runtime fixture now uses "local" for
production fidelity.

READMEs: doc runtime-tests section documents the stub harness and the
two bugs it unearthed; roadmap item updated (only the renderer draw
pass remains GPU/Studio-bound); doc-engine projection invariants gain
the per-actor counter boundary bullet.

6 new tests, 675 -> 681.
2026-07-29 10:43:07 +00:00
arena-agent
d303484c93 feat(doc): CRDT-native cell range selection and table merge/split
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
Closes the roadmap's table merge command item on the CRDT editor
surface with a rectangular cell-range selection model driven from the
keyboard and the workspace toolbar:

- ProjectionSession::cell_selection holds stable anchor/focus row and
  column ids; Shift+Arrow at a cell boundary starts the range and steps
  the focus cell on an active one, with table-edge clamping, caret
  follow, plain-action collapse, and undo-stale self-clearing.
- table_cell_range normalizes the selection against the projected
  table; cell_selection_rects renders the highlight over visible cells
  only, so merged spans contribute one expanded anchor rect.
- Ctrl/Cmd+M (merge_selected_cells, toolbar Merge) routes through the
  engine's MergeTableCells op; undo splits through the symmetric
  compensation. cell_range_mergeable rejects ranges overlapping an
  existing merge UI-side, where ambiguous nested spans belong.
- Ctrl/Cmd+Shift+M (split_cell_at_cursor, toolbar Split) resolves the
  containing merge via merge_at_cell even from a covered cell and
  reveals the preserved hidden text; undo re-merges through
  RestoreTableMerge.
- Workspace toolbar gains Merge/Split buttons with status-line
  guidance (the only split path on touch devices).

Tests: 9 new (4 pure layout: range normalization/staleness, mergeable
rules, merge_at_cell, covered-skip highlight rects; 5 runtime on a real
Cx: shift-span + merge + undo, edge clamp, split + re-merge, plain
collapse, overlap rejection) for 669 total in nigig-build. The
doc-engine README gains the merge/split materialization invariant.
2026-07-29 05:29:05 +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