Composition / foldkit-builder

Copy, cut and paste

A copy is a node and all it holds, as Composition.takeTree takes it. It goes in two places: the Model's clipboard, and the system clipboard as JSON, { "format": "foldkit-composition", "tree": … }, so a copy made in one tab pastes in another.

  • CopyAsked({ id }) keeps the copy and writes it out; CutAsked({ id }) also removes the node, as one undoable edit. A node its Region cannot do without is not cut, and nothing is copied.
  • PasteAsked() reads the system clipboard in a Command, which answers ClipboardRead({ text }). Text read wins; the Model's copy is used only when the browser would not let the clipboard be read.
  • The text is untrusted. It is decoded strictly (a key it should not have is refused, not dropped), checked by its own ids (Composition.treeRefusal), so a refusal names what was copied, then each node gets a newly minted id by Composition.rekey, and the tree goes in by one insertTree, which checks every node against the Catalog. Anything that does not decode or fit (text that is not part of a page, an unknown Block, a prop of the wrong type, a child it names but does not hold) is refused whole: nothing goes in, and refused says why. Text that is not part of a page, or no copy at all, is builder:nothing-to-paste; the rest are apply's own codes.
  • It goes where a new node of its root's Block would (placeFor): inside the selection when it fits there, else after it, else last among the roots, or, for a Block that is no root, last in the last Region with room that takes it, so a node cut and pasted with nothing selected comes back. Where it can go nowhere, it is refused before ids are minted, as builder:no-place, with the Block's label.

The announcer debounces and clears on Effect's clock through Commands named LiveAnnounce.read and LiveAnnounce.clear. A runtime runs them beside everything else; a test that follows each Command in turn should leave them out, as it has no clock to wait on.