about summary refs log tree commit diff stats
path: root/html/edit
diff options
context:
space:
mode:
Diffstat (limited to 'html/edit')
-rw-r--r--html/edit/001-editor.mu.html539
-rw-r--r--html/edit/002-typing.mu.html1092
-rw-r--r--html/edit/003-shortcuts.mu.html3107
-rw-r--r--html/edit/004-programming-environment.mu.html826
-rw-r--r--html/edit/005-sandbox.mu.html555
-rw-r--r--html/edit/006-sandbox-edit.mu.html212
-rw-r--r--html/edit/007-sandbox-delete.mu.html149
-rw-r--r--html/edit/008-sandbox-test.mu.html211
-rw-r--r--html/edit/009-sandbox-trace.mu.html248
-rw-r--r--html/edit/010-warnings.mu.html428
-rw-r--r--html/edit/011-editor-undo.mu.html2095
11 files changed, 9462 insertions, 0 deletions
diff --git a/html/edit/001-editor.mu.html b/html/edit/001-editor.mu.html
new file mode 100644
index 00000000..be568c9d
--- /dev/null
+++ b/html/edit/001-editor.mu.html
@@ -0,0 +1,539 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/001-editor.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muRecipe { color: #ff8700; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muScenario { color: #00af00; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## the basic editor data structure, and how it displays text to the screen</span>
+
+<span class="Comment"># temporary main for this layer: just render the given string at the given</span>
+<span class="Comment"># screen dimensions, then stop</span>
+<span class="muRecipe">recipe!</span> main [
+  <span class="Constant">local-scope</span>
+  text:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  open-console
+  hide-screen <span class="Constant">0/screen</span>
+  new-editor text, <span class="Constant">0/screen</span>, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  show-screen <span class="Constant">0/screen</span>
+  wait-for-event <span class="Constant">0/console</span>
+  close-console
+]
+
+<span class="muScenario">scenario</span> editor-initially-prints-string-to-screen [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  run [
+    <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+    new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  ]
+  screen-should-contain [
+    <span class="Comment"># top line of screen reserved for menu</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muData">container</span> editor-data [
+  <span class="Comment"># editable text: doubly linked list of characters (head contains a special sentinel)</span>
+  data:address:duplex-list:character
+  top-of-screen:address:duplex-list:character
+  bottom-of-screen:address:duplex-list:character
+  <span class="Comment"># location before cursor inside data</span>
+  before-cursor:address:duplex-list:character
+
+  <span class="Comment"># raw bounds of display area on screen</span>
+  <span class="Comment"># always displays from row 1 (leaving row 0 for a menu) and at most until bottom of screen</span>
+  left:number
+  right:number
+  <span class="Comment"># raw screen coordinates of cursor</span>
+  cursor-row:number
+  cursor-column:number
+]
+
+<span class="Comment"># editor:address, screen &lt;- new-editor s:address:array:character, screen:address, left:number, right:number</span>
+<span class="Comment"># creates a new editor widget and renders its initial appearance to screen.</span>
+<span class="Comment">#   top/left/right constrain the screen area available to the new editor.</span>
+<span class="Comment">#   right is exclusive.</span>
+<span class="muRecipe">recipe</span> new-editor [
+  <span class="Constant">local-scope</span>
+  s:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># no clipping of bounds</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right<span class="Special"> &lt;- </span>subtract right, <span class="Constant">1</span>
+  result:address:editor-data<span class="Special"> &lt;- </span>new <span class="Constant">editor-data:type</span>
+  <span class="Comment"># initialize screen-related fields</span>
+  x:address:number<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">left:offset</span>
+  *x<span class="Special"> &lt;- </span>copy left
+  x<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">right:offset</span>
+  *x<span class="Special"> &lt;- </span>copy right
+  <span class="Comment"># initialize cursor</span>
+  x<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">cursor-row:offset</span>
+  *x<span class="Special"> &lt;- </span>copy <span class="Constant">1/top</span>
+  x<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">cursor-column:offset</span>
+  *x<span class="Special"> &lt;- </span>copy left
+  init:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">data:offset</span>
+  *init<span class="Special"> &lt;- </span>push-duplex <span class="Constant">167/§</span>, <span class="Constant">0/tail</span>
+  top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">top-of-screen:offset</span>
+  *top-of-screen<span class="Special"> &lt;- </span>copy *init
+  y:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">before-cursor:offset</span>
+  *y<span class="Special"> &lt;- </span>copy *init
+  result<span class="Special"> &lt;- </span>insert-text result, s
+  <span class="Comment"># initialize cursor to top of screen</span>
+  y<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">before-cursor:offset</span>
+  *y<span class="Special"> &lt;- </span>copy *init
+  <span class="Comment"># initial render to screen, just for some old tests</span>
+  _, _, screen, result<span class="Special"> &lt;- </span>render screen, result
+<span class="Constant">  &lt;editor-initialization&gt;</span>
+  <span class="muControl">reply</span> result
+]
+
+<span class="muRecipe">recipe</span> insert-text [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  text:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># early exit if text is empty</span>
+  <span class="muControl">reply-unless</span> text, editor/same-as-ingredient:<span class="Constant">0</span>
+  len:number<span class="Special"> &lt;- </span>length *text
+  <span class="muControl">reply-unless</span> len, editor/same-as-ingredient:<span class="Constant">0</span>
+  idx:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="Comment"># now we can start appending the rest, character by character</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  <span class="Delimiter">{</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal idx, len
+    <span class="muControl">break-if</span> done?
+    c:character<span class="Special"> &lt;- </span>index *text, idx
+    insert-duplex c, curr
+    <span class="Comment"># next iter</span>
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    idx<span class="Special"> &lt;- </span>add idx, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muScenario">scenario</span> editor-initializes-without-data [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">3/height</span>
+  run [
+    <span class="Constant">1</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">0/data</span>, screen:address, <span class="Constant">2/left</span>, <span class="Constant">5/right</span>
+    <span class="Constant">2</span>:editor-data<span class="Special"> &lt;- </span>copy *<span class="Constant">1</span>:address:editor-data
+  ]
+  memory-should-contain [
+    <span class="Comment"># 2 (data) &lt;- just the § sentinel</span>
+    <span class="Comment"># 3 (top of screen) &lt;- the § sentinel</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># bottom-of-screen; null since text fits on screen</span>
+    <span class="Comment"># 5 (before cursor) &lt;- the § sentinel</span>
+    <span class="Constant">6</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># left</span>
+    <span class="Constant">7</span><span class="Special"> &lt;- </span><span class="Constant">4</span>  <span class="Comment"># right  (inclusive)</span>
+    <span class="Constant">8</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">9</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor column</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="Comment"># last-row:number, last-column:number, screen, editor &lt;- render screen:address, editor:address:editor-data</span>
+<span class="Comment">#</span>
+<span class="Comment"># Assumes cursor should be at coordinates (cursor-row, cursor-column) and</span>
+<span class="Comment"># updates before-cursor to match. Might also move coordinates if they're</span>
+<span class="Comment"># outside text.</span>
+<span class="muRecipe">recipe</span> render [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> editor, <span class="Constant">1/top</span>, <span class="Constant">0/left</span>, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  <span class="Comment"># traversing editor</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  prev:address:duplex-list<span class="Special"> &lt;- </span>copy curr  <span class="Comment"># just in case curr becomes null and we can't compute prev-duplex</span>
+  curr<span class="Special"> &lt;- </span>next-duplex curr
+  <span class="Comment"># traversing screen</span>
+<span class="Constant">  +render-loop-initialization</span>
+  color:number<span class="Special"> &lt;- </span>copy <span class="Constant">7/white</span>
+  row:number<span class="Special"> &lt;- </span>copy <span class="Constant">1/top</span>
+  column:number<span class="Special"> &lt;- </span>copy left
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+  <span class="Delimiter">{</span>
+<span class="Constant">    +next-character</span>
+    <span class="muControl">break-unless</span> curr
+    off-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+    <span class="muControl">break-if</span> off-screen?
+    <span class="Comment"># update editor-data.before-cursor</span>
+    <span class="Comment"># Doing so at the start of each iteration ensures it stays one step behind</span>
+    <span class="Comment"># the current character.</span>
+    <span class="Delimiter">{</span>
+      at-cursor-row?:boolean<span class="Special"> &lt;- </span>equal row, *cursor-row
+      <span class="muControl">break-unless</span> at-cursor-row?
+      at-cursor?:boolean<span class="Special"> &lt;- </span>equal column, *cursor-column
+      <span class="muControl">break-unless</span> at-cursor?
+      *before-cursor<span class="Special"> &lt;- </span>copy prev
+    <span class="Delimiter">}</span>
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+<span class="Constant">    &lt;character-c-received&gt;</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># newline? move to left rather than 0</span>
+      newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+      <span class="muControl">break-unless</span> newline?
+      <span class="Comment"># adjust cursor if necessary</span>
+      <span class="Delimiter">{</span>
+        at-cursor-row?:boolean<span class="Special"> &lt;- </span>equal row, *cursor-row
+        <span class="muControl">break-unless</span> at-cursor-row?
+        left-of-cursor?:boolean<span class="Special"> &lt;- </span>lesser-than column, *cursor-column
+        <span class="muControl">break-unless</span> left-of-cursor?
+        *cursor-column<span class="Special"> &lt;- </span>copy column
+        *before-cursor<span class="Special"> &lt;- </span>prev-duplex curr
+      <span class="Delimiter">}</span>
+      <span class="Comment"># clear rest of line in this window</span>
+      clear-line-delimited screen, column, right
+      <span class="Comment"># skip to next line</span>
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      column<span class="Special"> &lt;- </span>copy left
+      screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+      curr<span class="Special"> &lt;- </span>next-duplex curr
+      prev<span class="Special"> &lt;- </span>next-duplex prev
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># at right? wrap. even if there's only one more letter left; we need</span>
+      <span class="Comment"># room for clicking on the cursor after it.</span>
+      at-right?:boolean<span class="Special"> &lt;- </span>equal column, right
+      <span class="muControl">break-unless</span> at-right?
+      <span class="Comment"># print wrap icon</span>
+      print-character screen, <span class="Constant">8617/loop-back-to-left</span>, <span class="Constant">245/grey</span>
+      column<span class="Special"> &lt;- </span>copy left
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+      <span class="Comment"># don't increment curr</span>
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
+    <span class="Delimiter">}</span>
+    print-character screen, c, color
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    prev<span class="Special"> &lt;- </span>next-duplex prev
+    column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># save first character off-screen</span>
+  bottom-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">bottom-of-screen:offset</span>
+  *bottom-of-screen<span class="Special"> &lt;- </span>copy curr
+  <span class="Comment"># is cursor to the right of the last line? move to end</span>
+  <span class="Delimiter">{</span>
+    at-cursor-row?:boolean<span class="Special"> &lt;- </span>equal row, *cursor-row
+    cursor-outside-line?:boolean<span class="Special"> &lt;- </span>lesser-or-equal column, *cursor-column
+    before-cursor-on-same-line?:boolean<span class="Special"> &lt;- </span>and at-cursor-row?, cursor-outside-line?
+    above-cursor-row?:boolean<span class="Special"> &lt;- </span>lesser-than row, *cursor-row
+    before-cursor?:boolean<span class="Special"> &lt;- </span>or before-cursor-on-same-line?, above-cursor-row?
+    <span class="muControl">break-unless</span> before-cursor?
+    *cursor-row<span class="Special"> &lt;- </span>copy row
+    *cursor-column<span class="Special"> &lt;- </span>copy column
+    *before-cursor<span class="Special"> &lt;- </span>copy prev
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> row, column, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="muRecipe">recipe</span> clear-line-delimited [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  column:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-than column, right
+    <span class="muControl">break-if</span> done?
+    print-character screen, <span class="Constant">32/space</span>
+    column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> clear-screen-from [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  column:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># if it's the real screen, use the optimized primitive</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> screen
+    clear-display-from row, column, left, right
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if not, go the slower route</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+  clear-line-delimited screen, column, right
+  clear-rest-of-screen screen, row, left, right
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muRecipe">recipe</span> clear-rest-of-screen [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, left
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  <span class="Delimiter">{</span>
+    at-bottom-of-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+    <span class="muControl">break-if</span> at-bottom-of-screen?
+    screen<span class="Special"> &lt;- </span>move-cursor screen, row, left
+    clear-line-delimited screen, left, right
+    row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-initially-prints-multiple-lines [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .abc  .</span>
+   <span class="Constant"> .def  .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-initially-handles-offsets [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">1/left</span>, <span class="Constant">5/right</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> . abc .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-initially-prints-multiple-lines-at-offset [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">1/left</span>, <span class="Constant">5/right</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> . abc .</span>
+   <span class="Constant"> . def .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-initially-wraps-long-lines [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc def]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .abc ↩.</span>
+   <span class="Constant"> .def  .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">245/grey</span> [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .    ↩.</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-initially-wraps-barely-long-lines [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcde]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  ]
+  <span class="Comment"># still wrap, even though the line would fit. We need room to click on the</span>
+  <span class="Comment"># end of the line</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+<span class="Constant">    .abcd↩.</span>
+   <span class="Constant"> .e    .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">245/grey</span> [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .    ↩.</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-initializes-empty-text [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+    <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># cursor column</span>
+  ]
+]
+
+<span class="Comment"># just a little color for mu code</span>
+
+<span class="muScenario">scenario</span> render-colors-comments [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant"># de</span>
+<span class="Constant">f]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .abc  .</span>
+   <span class="Constant"> .# de .</span>
+   <span class="Constant"> .f    .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">12/lightblue</span>, [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .# de .</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">7/white</span>, [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .abc  .</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .f    .</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;character-c-received&gt;</span> [
+  color<span class="Special"> &lt;- </span>get-color color, c
+]
+
+<span class="Comment"># color &lt;- get-color color:number, c:character</span>
+<span class="Comment"># so far the previous color is all the information we need; that may change</span>
+<span class="muRecipe">recipe</span> get-color [
+  <span class="Constant">local-scope</span>
+  color:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  c:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  color-is-white?:boolean<span class="Special"> &lt;- </span>equal color, <span class="Constant">7/white</span>
+  <span class="Comment"># if color is white and next character is '#', switch color to blue</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> color-is-white?
+    starting-comment?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">35/#</span>
+    <span class="muControl">break-unless</span> starting-comment?
+    trace <span class="Constant">90</span>, <span class="Constant">[app]</span>, <span class="Constant">[switch color back to blue]</span>
+    color<span class="Special"> &lt;- </span>copy <span class="Constant">12/lightblue</span>
+    <span class="muControl">jump</span> <span class="Constant">+exit:label</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if color is blue and next character is newline, switch color to white</span>
+  <span class="Delimiter">{</span>
+    color-is-blue?:boolean<span class="Special"> &lt;- </span>equal color, <span class="Constant">12/lightblue</span>
+    <span class="muControl">break-unless</span> color-is-blue?
+    ending-comment?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> ending-comment?
+    trace <span class="Constant">90</span>, <span class="Constant">[app]</span>, <span class="Constant">[switch color back to white]</span>
+    color<span class="Special"> &lt;- </span>copy <span class="Constant">7/white</span>
+    <span class="muControl">jump</span> <span class="Constant">+exit:label</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if color is white (no comments) and next character is '&lt;', switch color to red</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> color-is-white?
+    starting-assignment?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">60/&lt;</span>
+    <span class="muControl">break-unless</span> starting-assignment?
+    color<span class="Special"> &lt;- </span>copy <span class="Constant">1/red</span>
+    <span class="muControl">jump</span> <span class="Constant">+exit:label</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if color is red and next character is space, switch color to white</span>
+  <span class="Delimiter">{</span>
+    color-is-red?:boolean<span class="Special"> &lt;- </span>equal color, <span class="Constant">1/red</span>
+    <span class="muControl">break-unless</span> color-is-red?
+    ending-assignment?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">32/space</span>
+    <span class="muControl">break-unless</span> ending-assignment?
+    color<span class="Special"> &lt;- </span>copy <span class="Constant">7/white</span>
+    <span class="muControl">jump</span> <span class="Constant">+exit:label</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># otherwise no change</span>
+<span class="Constant">  +exit</span>
+  <span class="muControl">reply</span> color
+]
+
+<span class="muScenario">scenario</span> render-colors-assignment [
+  assume-screen <span class="Constant">8/width</span>, <span class="Constant">5/height</span>
+  run [
+    s:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d &lt;- e</span>
+<span class="Constant">f]</span>
+    new-editor s:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">8/right</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .        .</span>
+   <span class="Constant"> .abc     .</span>
+   <span class="Constant"> .d &lt;- e  .</span>
+   <span class="Constant"> .f       .</span>
+   <span class="Constant"> .        .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">1/red</span>, [
+   <span class="Constant"> .        .</span>
+   <span class="Constant"> .        .</span>
+   <span class="Constant"> .  &lt;-    .</span>
+   <span class="Constant"> .        .</span>
+   <span class="Constant"> .        .</span>
+  ]
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/002-typing.mu.html b/html/edit/002-typing.mu.html
new file mode 100644
index 00000000..787e3a4e
--- /dev/null
+++ b/html/edit/002-typing.mu.html
@@ -0,0 +1,1092 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/002-typing.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muRecipe { color: #ff8700; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muScenario { color: #00af00; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## handling events from the keyboard, mouse, touch screen, ...</span>
+
+<span class="Comment"># temporary main: interactive editor</span>
+<span class="Comment"># hit ctrl-c to exit</span>
+<span class="muRecipe">recipe!</span> main [
+  <span class="Constant">local-scope</span>
+  text:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  open-console
+  editor:address:editor-data<span class="Special"> &lt;- </span>new-editor text, <span class="Constant">0/screen</span>, <span class="Constant">5/left</span>, <span class="Constant">45/right</span>
+  editor-event-loop <span class="Constant">0/screen</span>, <span class="Constant">0/console</span>, editor
+  close-console
+]
+
+<span class="muRecipe">recipe</span> editor-event-loop [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  console:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># looping over each (keyboard or touch) event as it occurs</span>
+<span class="Constant">    +next-event</span>
+    cursor-row:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-row:offset</span>
+    cursor-column:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-column:offset</span>
+    screen<span class="Special"> &lt;- </span>move-cursor screen, cursor-row, cursor-column
+    e:event, console:address, found?:boolean, quit?:boolean<span class="Special"> &lt;- </span>read-event console
+    <span class="muControl">loop-unless</span> found?
+    <span class="muControl">break-if</span> quit?  <span class="Comment"># only in tests</span>
+    trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[next-event]</span>
+    <span class="Comment"># 'touch' event</span>
+    t:address:touch-event<span class="Special"> &lt;- </span>maybe-convert e, <span class="Constant">touch:variant</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-unless</span> t
+      move-cursor-in-editor screen, editor, *t
+      <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># keyboard events</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-if</span> t
+      screen, editor, go-render?:boolean<span class="Special"> &lt;- </span>handle-keyboard-event screen, editor, e
+      <span class="Delimiter">{</span>
+        <span class="muControl">break-unless</span> go-render?
+        screen<span class="Special"> &lt;- </span>editor-render screen, editor
+      <span class="Delimiter">}</span>
+    <span class="Delimiter">}</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># process click, return if it was on current editor</span>
+<span class="muRecipe">recipe</span> move-cursor-in-editor [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  t:touch-event<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> editor, <span class="Constant">0/false</span>
+  click-row:number<span class="Special"> &lt;- </span>get t, <span class="Constant">row:offset</span>
+  <span class="muControl">reply-unless</span> click-row, <span class="Constant">0/false</span>  <span class="Comment"># ignore clicks on 'menu'</span>
+  click-column:number<span class="Special"> &lt;- </span>get t, <span class="Constant">column:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  too-far-left?:boolean<span class="Special"> &lt;- </span>lesser-than click-column, left
+  <span class="muControl">reply-if</span> too-far-left?, <span class="Constant">0/false</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  too-far-right?:boolean<span class="Special"> &lt;- </span>greater-than click-column, right
+  <span class="muControl">reply-if</span> too-far-right?, <span class="Constant">0/false</span>
+  <span class="Comment"># position cursor</span>
+<span class="Constant">  &lt;move-cursor-begin&gt;</span>
+  editor<span class="Special"> &lt;- </span>snap-cursor screen, editor, click-row, click-column
+  undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">  &lt;move-cursor-end&gt;</span>
+  <span class="Comment"># gain focus</span>
+  <span class="muControl">reply</span> <span class="Constant">1/true</span>
+]
+
+<span class="Comment"># editor &lt;- snap-cursor screen:address, editor:address:editor-data, target-row:number, target-column:number</span>
+<span class="Comment">#</span>
+<span class="Comment"># Variant of 'render' that only moves the cursor (coordinates and</span>
+<span class="Comment"># before-cursor). If it's past the end of a line, it 'slides' it left. If it's</span>
+<span class="Comment"># past the last line it positions at end of last line.</span>
+<span class="muRecipe">recipe</span> snap-cursor [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  target-row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  target-column:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> editor, <span class="Constant">1/top</span>, editor/same-as-ingredient:<span class="Constant">1</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  <span class="Comment"># count newlines until screen row</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  prev:address:duplex-list<span class="Special"> &lt;- </span>copy curr  <span class="Comment"># just in case curr becomes null and we can't compute prev-duplex</span>
+  curr<span class="Special"> &lt;- </span>next-duplex curr
+  row:number<span class="Special"> &lt;- </span>copy <span class="Constant">1/top</span>
+  column:number<span class="Special"> &lt;- </span>copy left
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  *cursor-row<span class="Special"> &lt;- </span>copy target-row
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  *cursor-column<span class="Special"> &lt;- </span>copy target-column
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  <span class="Delimiter">{</span>
+<span class="Constant">    +next-character</span>
+    <span class="muControl">break-unless</span> curr
+    off-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+    <span class="muControl">break-if</span> off-screen?
+    <span class="Comment"># update editor-data.before-cursor</span>
+    <span class="Comment"># Doing so at the start of each iteration ensures it stays one step behind</span>
+    <span class="Comment"># the current character.</span>
+    <span class="Delimiter">{</span>
+      at-cursor-row?:boolean<span class="Special"> &lt;- </span>equal row, *cursor-row
+      <span class="muControl">break-unless</span> at-cursor-row?
+      at-cursor?:boolean<span class="Special"> &lt;- </span>equal column, *cursor-column
+      <span class="muControl">break-unless</span> at-cursor?
+      *before-cursor<span class="Special"> &lt;- </span>copy prev
+    <span class="Delimiter">}</span>
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># newline? move to left rather than 0</span>
+      newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+      <span class="muControl">break-unless</span> newline?
+      <span class="Comment"># adjust cursor if necessary</span>
+      <span class="Delimiter">{</span>
+        at-cursor-row?:boolean<span class="Special"> &lt;- </span>equal row, *cursor-row
+        <span class="muControl">break-unless</span> at-cursor-row?
+        left-of-cursor?:boolean<span class="Special"> &lt;- </span>lesser-than column, *cursor-column
+        <span class="muControl">break-unless</span> left-of-cursor?
+        *cursor-column<span class="Special"> &lt;- </span>copy column
+        *before-cursor<span class="Special"> &lt;- </span>copy prev
+      <span class="Delimiter">}</span>
+      <span class="Comment"># skip to next line</span>
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      column<span class="Special"> &lt;- </span>copy left
+      curr<span class="Special"> &lt;- </span>next-duplex curr
+      prev<span class="Special"> &lt;- </span>next-duplex prev
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># at right? wrap. even if there's only one more letter left; we need</span>
+      <span class="Comment"># room for clicking on the cursor after it.</span>
+      at-right?:boolean<span class="Special"> &lt;- </span>equal column, right
+      <span class="muControl">break-unless</span> at-right?
+      column<span class="Special"> &lt;- </span>copy left
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      <span class="Comment"># don't increment curr/prev</span>
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
+    <span class="Delimiter">}</span>
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    prev<span class="Special"> &lt;- </span>next-duplex prev
+    column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># is cursor to the right of the last line? move to end</span>
+  <span class="Delimiter">{</span>
+    at-cursor-row?:boolean<span class="Special"> &lt;- </span>equal row, *cursor-row
+    cursor-outside-line?:boolean<span class="Special"> &lt;- </span>lesser-or-equal column, *cursor-column
+    before-cursor-on-same-line?:boolean<span class="Special"> &lt;- </span>and at-cursor-row?, cursor-outside-line?
+    above-cursor-row?:boolean<span class="Special"> &lt;- </span>lesser-than row, *cursor-row
+    before-cursor?:boolean<span class="Special"> &lt;- </span>or before-cursor-on-same-line?, above-cursor-row?
+    <span class="muControl">break-unless</span> before-cursor?
+    *cursor-row<span class="Special"> &lt;- </span>copy row
+    *cursor-column<span class="Special"> &lt;- </span>copy column
+    *before-cursor<span class="Special"> &lt;- </span>copy prev
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="Comment"># screen, editor, go-render?:boolean &lt;- handle-keyboard-event screen:address, editor:address:editor-data, e:event</span>
+<span class="Comment"># Process an event 'e' and try to minimally update the screen.</span>
+<span class="Comment"># Set 'go-render?' to true to indicate the caller must perform a non-minimal update.</span>
+<span class="muRecipe">recipe</span> handle-keyboard-event [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  e:event<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> editor, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+  screen-width:number<span class="Special"> &lt;- </span>screen-width screen
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  save-row:number<span class="Special"> &lt;- </span>copy *cursor-row
+  save-column:number<span class="Special"> &lt;- </span>copy *cursor-column
+  <span class="Comment"># character</span>
+  <span class="Delimiter">{</span>
+    c:address:character<span class="Special"> &lt;- </span>maybe-convert e, <span class="Constant">text:variant</span>
+    <span class="muControl">break-unless</span> c
+    trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[handle-keyboard-event: special character]</span>
+    <span class="Comment"># exceptions for special characters go here</span>
+<span class="Constant">    &lt;handle-special-character&gt;</span>
+    <span class="Comment"># ignore any other special characters</span>
+    regular-character?:boolean<span class="Special"> &lt;- </span>greater-or-equal *c, <span class="Constant">32/space</span>
+    <span class="muControl">reply-unless</span> regular-character?, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+    <span class="Comment"># otherwise type it in</span>
+<span class="Constant">    &lt;insert-character-begin&gt;</span>
+    editor, screen, go-render?:boolean<span class="Special"> &lt;- </span>insert-at-cursor editor, *c, screen
+<span class="Constant">    &lt;insert-character-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+  <span class="Comment"># special key to modify the text or move the cursor</span>
+  k:address:number<span class="Special"> &lt;- </span>maybe-convert e:event, <span class="Constant">keycode:variant</span>
+  assert k, <span class="Constant">[event was of unknown type; neither keyboard nor mouse]</span>
+  <span class="Comment"># handlers for each special key will go here</span>
+<span class="Constant">  &lt;handle-special-key&gt;</span>
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+]
+
+<span class="muRecipe">recipe</span> insert-at-cursor [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  c:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  insert-duplex c, *before-cursor
+  *before-cursor<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  save-row:number<span class="Special"> &lt;- </span>copy *cursor-row
+  save-column:number<span class="Special"> &lt;- </span>copy *cursor-column
+  screen-width:number<span class="Special"> &lt;- </span>screen-width screen
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  <span class="Comment"># occasionally we'll need to mess with the cursor</span>
+<span class="Constant">  &lt;insert-character-special-case&gt;</span>
+  <span class="Comment"># but mostly we'll just move the cursor right</span>
+  *cursor-column<span class="Special"> &lt;- </span>add *cursor-column, <span class="Constant">1</span>
+  next:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  <span class="Delimiter">{</span>
+    <span class="Comment"># at end of all text? no need to scroll? just print the character and leave</span>
+    at-end?:boolean<span class="Special"> &lt;- </span>equal next, <span class="Constant">0/null</span>
+    <span class="muControl">break-unless</span> at-end?
+    bottom:number<span class="Special"> &lt;- </span>subtract screen-height, <span class="Constant">1</span>
+    at-bottom?:boolean<span class="Special"> &lt;- </span>equal save-row, bottom
+    at-right?:boolean<span class="Special"> &lt;- </span>equal save-column, right
+    overflow?:boolean<span class="Special"> &lt;- </span>and at-bottom?, at-right?
+    <span class="muControl">break-if</span> overflow?
+    move-cursor screen, save-row, save-column
+    print-character screen, c
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">2</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># not at right margin? print the character and rest of line</span>
+    <span class="muControl">break-unless</span> next
+    at-right?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-column, screen-width
+    <span class="muControl">break-if</span> at-right?
+    curr:address:duplex-list<span class="Special"> &lt;- </span>copy *before-cursor
+    move-cursor screen, save-row, save-column
+    curr-column:number<span class="Special"> &lt;- </span>copy save-column
+    <span class="Delimiter">{</span>
+      <span class="Comment"># hit right margin? give up and let caller render</span>
+      at-right?:boolean<span class="Special"> &lt;- </span>greater-than curr-column, right
+      <span class="muControl">reply-if</span> at-right?, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">2</span>, <span class="Constant">1/go-render</span>
+      <span class="muControl">break-unless</span> curr
+      <span class="Comment"># newline? done.</span>
+      currc:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+      at-newline?:boolean<span class="Special"> &lt;- </span>equal currc, <span class="Constant">10/newline</span>
+      <span class="muControl">break-if</span> at-newline?
+      print-character screen, currc
+      curr-column<span class="Special"> &lt;- </span>add curr-column, <span class="Constant">1</span>
+      curr<span class="Special"> &lt;- </span>next-duplex curr
+      <span class="muControl">loop</span>
+    <span class="Delimiter">}</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">2</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">2</span>, <span class="Constant">1/go-render</span>
+]
+
+<span class="Comment"># helper for tests</span>
+<span class="muRecipe">recipe</span> editor-render [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  row:number, column:number<span class="Special"> &lt;- </span>render screen, editor
+  clear-line-delimited screen, column, right
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  draw-horizontal screen, row, left, right, <span class="Constant">9480/horizontal-dotted</span>
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  clear-screen-from screen, row, left, left, right
+]
+
+<span class="muScenario">scenario</span> editor-handles-empty-event-queue [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console <span class="Constant">[]</span>
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-handles-mouse-clicks [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>  <span class="Comment"># on the 'b'</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># cursor is at row 0..</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># ..and column 1</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-text [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">7</span>  <span class="Comment"># last line, to the right of text</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># cursor column</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-text-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">7</span>  <span class="Comment"># interior line, to the right of text</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># cursor column</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-text-3 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">7</span>  <span class="Comment"># below text</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># cursor column</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-column [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Comment"># editor occupies only left half of screen</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    <span class="Comment"># click on right half of screen</span>
+    left-click <span class="Constant">3</span>, <span class="Constant">8</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># no change to cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># ..or column</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-handles-mouse-clicks-in-menu-area [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    <span class="Comment"># click on first, 'menu' row</span>
+    left-click <span class="Constant">0</span>, <span class="Constant">3</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># no change to cursor</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-into-empty-editor [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    type <span class="Constant">[abc]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">3</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-at-cursor [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># type two letters at different places</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    type <span class="Constant">[d]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0adbc     .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">7</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># 4 for first letter, 3 for second</span>
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">5</span>  <span class="Comment"># right of last line</span>
+    type <span class="Constant">[d]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-5 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">5</span>  <span class="Comment"># right of non-last line</span>
+    type <span class="Constant">[e]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abce      .</span>
+   <span class="Constant"> .d         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-3 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">5</span>  <span class="Comment"># below all text</span>
+    type <span class="Constant">[d]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-4 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">5</span>  <span class="Comment"># below all text</span>
+    type <span class="Constant">[e]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .de        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-6 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">5</span>  <span class="Comment"># below all text</span>
+    type <span class="Constant">[ef]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">2</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-after-inserting-characters [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ab]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[01]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .01ab      .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># if the cursor reaches the right margin, wrap the line</span>
+
+<span class="muScenario">scenario</span> editor-wraps-line-on-insert [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># type a letter</span>
+  assume-console [
+    type <span class="Constant">[e]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># no wrap yet</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .eabc .</span>
+<span class="Constant">    .┈┈┈┈┈.</span>
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  <span class="Comment"># type a second letter</span>
+  assume-console [
+    type <span class="Constant">[f]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># now wrap</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+<span class="Constant">    .efab↩.</span>
+   <span class="Constant"> .c    .</span>
+<span class="Constant">    .┈┈┈┈┈.</span>
+   <span class="Constant"> .     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-wraps-line-on-insert-2 [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdefg</span>
+<span class="Constant">defg]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># type more text at the start</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    type <span class="Constant">[abc]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor is not wrapped</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  <span class="Comment"># but line is wrapped</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .efg       .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .efg       .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;insert-character-special-case&gt;</span> [
+  <span class="Comment"># if the line wraps at the cursor, move cursor to start of next row</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if we're at the column just before the wrap indicator</span>
+    wrap-column:number<span class="Special"> &lt;- </span>subtract right, <span class="Constant">1</span>
+    at-wrap?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-column, wrap-column
+    <span class="muControl">break-unless</span> at-wrap?
+    *cursor-column<span class="Special"> &lt;- </span>subtract *cursor-column, wrap-column
+    *cursor-column<span class="Special"> &lt;- </span>add *cursor-column, left
+    *cursor-row<span class="Special"> &lt;- </span>add *cursor-row, <span class="Constant">1</span>
+    <span class="Comment"># if we're out of the screen, scroll down</span>
+    <span class="Delimiter">{</span>
+      below-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-row, screen-height
+      <span class="muControl">break-unless</span> below-screen?
+<span class="Constant">      &lt;scroll-down&gt;</span>
+    <span class="Delimiter">}</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">2</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-wraps-cursor-after-inserting-characters [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcde]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">4</span>  <span class="Comment"># line is full; no wrap icon yet</span>
+    type <span class="Constant">[f]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .fe        .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># cursor column</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-wraps-cursor-after-inserting-characters-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcde]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>  <span class="Comment"># right before the wrap icon</span>
+    type <span class="Constant">[f]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcf↩     .</span>
+   <span class="Constant"> .de        .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># cursor column</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-wraps-cursor-to-left-margin [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcde]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">2/left</span>, <span class="Constant">7/right</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">5</span>  <span class="Comment"># line is full; no wrap icon yet</span>
+    type <span class="Constant">[01]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  abc0↩   .</span>
+   <span class="Constant"> .  1de     .</span>
+   <span class="Constant"> .  ┈┈┈┈┈   .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># cursor column</span>
+  ]
+]
+
+<span class="Comment"># if newline, move cursor to start of next line, and maybe align indent with previous line</span>
+
+<span class="muData">container</span> editor-data [
+  indent?:boolean
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;editor-initialization&gt;</span> [
+  indent?:address:boolean<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">indent?:offset</span>
+  *indent?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-down-after-inserting-newline [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  assume-console [
+    type <span class="Constant">[0</span>
+<span class="Constant">1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0         .</span>
+   <span class="Constant"> .1abc      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    newline?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> newline?
+<span class="Constant">    &lt;insert-enter-begin&gt;</span>
+    editor<span class="Special"> &lt;- </span>insert-new-line-and-indent editor, screen
+<span class="Constant">    &lt;insert-enter-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> insert-new-line-and-indent [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  <span class="Comment"># insert newline</span>
+  insert-duplex <span class="Constant">10/newline</span>, *before-cursor
+  *before-cursor<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  *cursor-row<span class="Special"> &lt;- </span>add *cursor-row, <span class="Constant">1</span>
+  *cursor-column<span class="Special"> &lt;- </span>copy left
+  <span class="Comment"># maybe scroll</span>
+  <span class="Delimiter">{</span>
+    below-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-row, screen-height  <span class="Comment"># must be equal, never greater</span>
+    <span class="muControl">break-unless</span> below-screen?
+<span class="Constant">    &lt;scroll-down&gt;</span>
+    *cursor-row<span class="Special"> &lt;- </span>subtract *cursor-row, <span class="Constant">1</span>  <span class="Comment"># bring back into screen range</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># indent if necessary</span>
+  indent?:boolean<span class="Special"> &lt;- </span>get *editor, <span class="Constant">indent?:offset</span>
+  <span class="muControl">reply-unless</span> indent?, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+  d:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  end-of-previous-line:address:duplex-list<span class="Special"> &lt;- </span>prev-duplex *before-cursor
+  indent:number<span class="Special"> &lt;- </span>line-indent end-of-previous-line, d
+  i:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="Delimiter">{</span>
+    indent-done?:boolean<span class="Special"> &lt;- </span>greater-or-equal i, indent
+    <span class="muControl">break-if</span> indent-done?
+    editor, screen, go-render?:boolean<span class="Special"> &lt;- </span>insert-at-cursor editor, <span class="Constant">32/space</span>, screen
+    i<span class="Special"> &lt;- </span>add i, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="Comment"># takes a pointer 'curr' into the doubly-linked list and its sentinel, counts</span>
+<span class="Comment"># the number of spaces at the start of the line containing 'curr'.</span>
+<span class="muRecipe">recipe</span> line-indent [
+  <span class="Constant">local-scope</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  start:address:duplex-list<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  result:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="muControl">reply-unless</span> curr, result
+  at-start?:boolean<span class="Special"> &lt;- </span>equal curr, start
+  <span class="muControl">reply-if</span> at-start?, result
+  <span class="Delimiter">{</span>
+    curr<span class="Special"> &lt;- </span>prev-duplex curr
+    <span class="muControl">break-unless</span> curr
+    at-start?:boolean<span class="Special"> &lt;- </span>equal curr, start
+    <span class="muControl">break-if</span> at-start?
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    at-newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-newline?
+    <span class="Comment"># if c is a space, increment result</span>
+    is-space?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">32/space</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-unless</span> is-space?
+      result<span class="Special"> &lt;- </span>add result, <span class="Constant">1</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># if c is not a space, reset result</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-if</span> is-space?
+      result<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+    <span class="Delimiter">}</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> result
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-down-after-inserting-newline-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">1/left</span>, <span class="Constant">10/right</span>
+  assume-console [
+    type <span class="Constant">[0</span>
+<span class="Constant">1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> . 0        .</span>
+   <span class="Constant"> . 1abc     .</span>
+   <span class="Constant"> . ┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-clears-previous-line-completely-after-inserting-newline [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcde]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  assume-console [
+    press enter
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># line should be fully cleared</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-inserts-indent-after-newline [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">10/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ab</span>
+<span class="Constant">  cd</span>
+<span class="Constant">ef]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># position cursor after 'cd' and hit 'newline'</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">8</span>
+    type [
+]
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor should be below start of previous line</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor column (indented)</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-skips-indent-around-paste [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">10/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ab</span>
+<span class="Constant">  cd</span>
+<span class="Constant">ef]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># position cursor after 'cd' and hit 'newline' surrounded by paste markers</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">8</span>
+    press <span class="Constant">65507</span>  <span class="Comment"># start paste</span>
+    press enter
+    press <span class="Constant">65506</span>  <span class="Comment"># end paste</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor should be below start of previous line</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># cursor row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># cursor column (not indented)</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    paste-start?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65507/paste-start</span>
+    <span class="muControl">break-unless</span> paste-start?
+    indent?:address:boolean<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">indent?:offset</span>
+    *indent?<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    paste-end?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65506/paste-end</span>
+    <span class="muControl">break-unless</span> paste-end?
+    indent?:address:boolean<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">indent?:offset</span>
+    *indent?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="SalientComment">## helpers</span>
+
+<span class="muRecipe">recipe</span> draw-horizontal [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  x:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  style:character, style-found?:boolean<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> style-found?
+    style<span class="Special"> &lt;- </span>copy <span class="Constant">9472/horizontal</span>
+  <span class="Delimiter">}</span>
+  color:number, color-found?:boolean<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># default color to white</span>
+    <span class="muControl">break-if</span> color-found?
+    color<span class="Special"> &lt;- </span>copy <span class="Constant">245/grey</span>
+  <span class="Delimiter">}</span>
+  bg-color:number, bg-color-found?:boolean<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> bg-color-found?
+    bg-color<span class="Special"> &lt;- </span>copy <span class="Constant">0/black</span>
+  <span class="Delimiter">}</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, x
+  <span class="Delimiter">{</span>
+    continue?:boolean<span class="Special"> &lt;- </span>lesser-or-equal x, right  <span class="Comment"># right is inclusive, to match editor-data semantics</span>
+    <span class="muControl">break-unless</span> continue?
+    print-character screen, style, color, bg-color
+    x<span class="Special"> &lt;- </span>add x, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/003-shortcuts.mu.html b/html/edit/003-shortcuts.mu.html
new file mode 100644
index 00000000..82273ba7
--- /dev/null
+++ b/html/edit/003-shortcuts.mu.html
@@ -0,0 +1,3107 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/003-shortcuts.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muScenario { color: #00af00; }
+.Special { color: #ff6060; }
+.muRecipe { color: #ff8700; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## special shortcuts for manipulating the editor</span>
+<span class="Comment"># Some keys on the keyboard generate unicode characters, others generate</span>
+<span class="Comment"># terminfo key codes. We need to modify different places in the two cases.</span>
+
+<span class="Comment"># tab - insert two spaces</span>
+
+<span class="muScenario">scenario</span> editor-inserts-two-spaces-on-tab [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># just one character in final line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ab</span>
+<span class="Constant">cd]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  assume-console [
+    press tab
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab      .</span>
+   <span class="Constant"> .cd        .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    tab?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">9/tab</span>
+    <span class="muControl">break-unless</span> tab?
+<span class="Constant">    &lt;insert-character-begin&gt;</span>
+    editor, screen, go-render?:boolean<span class="Special"> &lt;- </span>insert-at-cursor editor, <span class="Constant">32/space</span>, screen
+    editor, screen, go-render?:boolean<span class="Special"> &lt;- </span>insert-at-cursor editor, <span class="Constant">32/space</span>, screen
+<span class="Constant">    &lt;insert-character-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># backspace - delete character before cursor</span>
+
+<span class="muScenario">scenario</span> editor-handles-backspace-key [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    press backspace
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .bc        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">3</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># length of original line to overwrite</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    delete-previous-character?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">8/backspace</span>
+    <span class="muControl">break-unless</span> delete-previous-character?
+<span class="Constant">    &lt;backspace-character-begin&gt;</span>
+    editor, screen, go-render?:boolean, backspaced-cell:address:duplex-list<span class="Special"> &lt;- </span>delete-before-cursor editor, screen
+<span class="Constant">    &lt;backspace-character-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># editor, screen, go-render?:boolean, backspaced-cell:address:duplex-list &lt;- delete-before-cursor editor:address:editor-data, screen</span>
+<span class="Comment"># return values:</span>
+<span class="Comment">#   go-render? - whether caller needs to update the screen</span>
+<span class="Comment">#   backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc.</span>
+<span class="muRecipe">recipe</span> delete-before-cursor [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  <span class="Comment"># if at start of text (before-cursor at § sentinel), return</span>
+  prev:address:duplex-list<span class="Special"> &lt;- </span>prev-duplex *before-cursor
+  <span class="muControl">reply-unless</span> prev, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>, <span class="Constant">0/nothing-deleted</span>
+  trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[delete-before-cursor]</span>
+  original-row:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-row:offset</span>
+  editor, scroll?:boolean<span class="Special"> &lt;- </span>move-cursor-coordinates-left editor
+  backspaced-cell:address:duplex-list<span class="Special"> &lt;- </span>copy *before-cursor
+  remove-duplex *before-cursor  <span class="Comment"># will also neatly trim next/prev pointers in backspaced-cell/*before-cursor</span>
+  *before-cursor<span class="Special"> &lt;- </span>copy prev
+  <span class="muControl">reply-if</span> scroll?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">1/go-render</span>, backspaced-cell
+  screen-width:number<span class="Special"> &lt;- </span>screen-width screen
+  cursor-row:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-column:offset</span>
+  <span class="Comment"># did we just backspace over a newline?</span>
+  same-row?:boolean<span class="Special"> &lt;- </span>equal cursor-row, original-row
+  <span class="muControl">reply-unless</span> same-row?, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>, backspaced-cell
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  screen<span class="Special"> &lt;- </span>move-cursor screen, cursor-row, cursor-column
+  curr-column:number<span class="Special"> &lt;- </span>copy cursor-column
+  <span class="Delimiter">{</span>
+    <span class="Comment"># hit right margin? give up and let caller render</span>
+    at-right?:boolean<span class="Special"> &lt;- </span>greater-or-equal curr-column, screen-width
+    <span class="muControl">reply-if</span> at-right?, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>, backspaced-cell
+    <span class="muControl">break-unless</span> curr
+    <span class="Comment"># newline? done.</span>
+    currc:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    at-newline?:boolean<span class="Special"> &lt;- </span>equal currc, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-newline?
+    screen<span class="Special"> &lt;- </span>print-character screen, currc
+    curr-column<span class="Special"> &lt;- </span>add curr-column, <span class="Constant">1</span>
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># we're guaranteed not to be at the right margin</span>
+  screen<span class="Special"> &lt;- </span>print-character screen, <span class="Constant">32/space</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>, backspaced-cell
+]
+
+<span class="muRecipe">recipe</span> move-cursor-coordinates-left [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  before-cursor:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">before-cursor:offset</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  <span class="Comment"># if not at left margin, move one character left</span>
+  <span class="Delimiter">{</span>
+    at-left-margin?:boolean<span class="Special"> &lt;- </span>equal *cursor-column, left
+    <span class="muControl">break-if</span> at-left-margin?
+    trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[decrementing cursor column]</span>
+    *cursor-column<span class="Special"> &lt;- </span>subtract *cursor-column, <span class="Constant">1</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if at left margin, we must move to previous row:</span>
+  top-of-screen?:boolean<span class="Special"> &lt;- </span>equal *cursor-row, <span class="Constant">1</span>  <span class="Comment"># exclude menu bar</span>
+  go-render?:boolean<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> top-of-screen?
+    *cursor-row<span class="Special"> &lt;- </span>subtract *cursor-row, <span class="Constant">1</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> top-of-screen?
+<span class="Constant">    &lt;scroll-up&gt;</span>
+    go-render?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># case 1: if previous character was newline, figure out how long the previous line is</span>
+    previous-character:character<span class="Special"> &lt;- </span>get *before-cursor, <span class="Constant">value:offset</span>
+    previous-character-is-newline?:boolean<span class="Special"> &lt;- </span>equal previous-character, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> previous-character-is-newline?
+    <span class="Comment"># compute length of previous line</span>
+    trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[switching to previous line]</span>
+    d:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+    end-of-line:number<span class="Special"> &lt;- </span>previous-line-length before-cursor, d
+    *cursor-column<span class="Special"> &lt;- </span>add left, end-of-line
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, go-render?
+  <span class="Delimiter">}</span>
+  <span class="Comment"># case 2: if previous-character was not newline, we're just at a wrapped line</span>
+  trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[wrapping to previous line]</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  *cursor-column<span class="Special"> &lt;- </span>subtract right, <span class="Constant">1</span>  <span class="Comment"># leave room for wrap icon</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, go-render?
+]
+
+<span class="Comment"># takes a pointer 'curr' into the doubly-linked list and its sentinel, counts</span>
+<span class="Comment"># the length of the previous line before the 'curr' pointer.</span>
+<span class="muRecipe">recipe</span> previous-line-length [
+  <span class="Constant">local-scope</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  start:address:duplex-list<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  result:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="muControl">reply-unless</span> curr, result
+  at-start?:boolean<span class="Special"> &lt;- </span>equal curr, start
+  <span class="muControl">reply-if</span> at-start?, result
+  <span class="Delimiter">{</span>
+    curr<span class="Special"> &lt;- </span>prev-duplex curr
+    <span class="muControl">break-unless</span> curr
+    at-start?:boolean<span class="Special"> &lt;- </span>equal curr, start
+    <span class="muControl">break-if</span> at-start?
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    at-newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-newline?
+    result<span class="Special"> &lt;- </span>add result, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> result
+]
+
+<span class="muScenario">scenario</span> editor-clears-last-line-on-backspace [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># just one character in final line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ab</span>
+<span class="Constant">cd]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">0</span>  <span class="Comment"># cursor at only character in final line</span>
+    press backspace
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+]
+
+<span class="Comment"># delete - delete character at cursor</span>
+
+<span class="muScenario">scenario</span> editor-handles-delete-key [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    press delete
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .bc        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">3</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># length of original line to overwrite</span>
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    press delete
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">2</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># new length to overwrite</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    delete-next-character?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65522/delete</span>
+    <span class="muControl">break-unless</span> delete-next-character?
+<span class="Constant">    &lt;delete-character-begin&gt;</span>
+    editor, screen, go-render?:boolean, deleted-cell:address:duplex-list<span class="Special"> &lt;- </span>delete-at-cursor editor, screen
+<span class="Constant">    &lt;delete-character-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> delete-at-cursor [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  candidate:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  <span class="muControl">reply-unless</span> candidate, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>, <span class="Constant">0/nothing-deleted</span>
+  currc:character<span class="Special"> &lt;- </span>get *candidate, <span class="Constant">value:offset</span>
+  remove-duplex candidate
+  deleted-newline?:boolean<span class="Special"> &lt;- </span>equal currc, <span class="Constant">10/newline</span>
+  <span class="muControl">reply-if</span> deleted-newline?, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>, candidate/deleted-cell
+  <span class="Comment"># wasn't a newline? render rest of line</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor  <span class="Comment"># refresh after remove-duplex above</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, *cursor-row, *cursor-column
+  curr-column:number<span class="Special"> &lt;- </span>copy *cursor-column
+  screen-width:number<span class="Special"> &lt;- </span>screen-width screen
+  <span class="Delimiter">{</span>
+    <span class="Comment"># hit right margin? give up and let caller render</span>
+    at-right?:boolean<span class="Special"> &lt;- </span>greater-or-equal curr-column, screen-width
+    <span class="muControl">reply-if</span> at-right?, editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>, candidate/deleted-cell
+    <span class="muControl">break-unless</span> curr
+    <span class="Comment"># newline? done.</span>
+    currc:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    at-newline?:boolean<span class="Special"> &lt;- </span>equal currc, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-newline?
+    screen<span class="Special"> &lt;- </span>print-character screen, currc
+    curr-column<span class="Special"> &lt;- </span>add curr-column, <span class="Constant">1</span>
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># we're guaranteed not to be at the right margin</span>
+  screen<span class="Special"> &lt;- </span>print-character screen, <span class="Constant">32/space</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>, candidate/deleted-cell
+]
+
+<span class="Comment"># right arrow</span>
+
+<span class="muScenario">scenario</span> editor-moves-cursor-right-with-key [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    press right-arrow
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a0bc      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">3</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># 0 and following characters</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-next-character?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65514/right-arrow</span>
+    <span class="muControl">break-unless</span> move-to-next-character?
+    <span class="Comment"># if not at end of text</span>
+    next-cursor:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    <span class="muControl">break-unless</span> next-cursor
+    <span class="Comment"># scan to next character</span>
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    *before-cursor<span class="Special"> &lt;- </span>copy next-cursor
+    editor, go-render?:boolean<span class="Special"> &lt;- </span>move-cursor-coordinates-right editor, screen-height
+    screen<span class="Special"> &lt;- </span>move-cursor screen, *cursor-row, *cursor-column
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">2/right-arrow</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> move-cursor-coordinates-right [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen-height:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  before-cursor:address:duplex-list<span class="Special"> &lt;- </span>get *editor <span class="Constant">before-cursor:offset</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  <span class="Comment"># if crossed a newline, move cursor to start of next row</span>
+  <span class="Delimiter">{</span>
+    old-cursor-character:character<span class="Special"> &lt;- </span>get *before-cursor, <span class="Constant">value:offset</span>
+    was-at-newline?:boolean<span class="Special"> &lt;- </span>equal old-cursor-character, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> was-at-newline?
+    *cursor-row<span class="Special"> &lt;- </span>add *cursor-row, <span class="Constant">1</span>
+    *cursor-column<span class="Special"> &lt;- </span>copy left
+    below-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-row, screen-height  <span class="Comment"># must be equal</span>
+    <span class="muControl">reply-unless</span> below-screen?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+<span class="Constant">    &lt;scroll-down&gt;</span>
+    *cursor-row<span class="Special"> &lt;- </span>subtract *cursor-row, <span class="Constant">1</span>  <span class="Comment"># bring back into screen range</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if the line wraps, move cursor to start of next row</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if we're at the column just before the wrap indicator</span>
+    wrap-column:number<span class="Special"> &lt;- </span>subtract right, <span class="Constant">1</span>
+    at-wrap?:boolean<span class="Special"> &lt;- </span>equal *cursor-column, wrap-column
+    <span class="muControl">break-unless</span> at-wrap?
+    <span class="Comment"># and if next character isn't newline</span>
+    next:address:duplex-list<span class="Special"> &lt;- </span>next-duplex before-cursor
+    <span class="muControl">break-unless</span> next
+    next-character:character<span class="Special"> &lt;- </span>get *next, <span class="Constant">value:offset</span>
+    newline?:boolean<span class="Special"> &lt;- </span>equal next-character, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> newline?
+    *cursor-row<span class="Special"> &lt;- </span>add *cursor-row, <span class="Constant">1</span>
+    *cursor-column<span class="Special"> &lt;- </span>copy left
+    below-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-row, screen-height  <span class="Comment"># must be equal</span>
+    <span class="muControl">reply-unless</span> below-screen?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+<span class="Constant">    &lt;scroll-down&gt;</span>
+    *cursor-row<span class="Special"> &lt;- </span>subtract *cursor-row, <span class="Constant">1</span>  <span class="Comment"># bring back into screen range</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># otherwise move cursor one character right</span>
+  *cursor-column<span class="Special"> &lt;- </span>add *cursor-column, <span class="Constant">1</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-next-line-with-right-arrow [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># type right-arrow a few times to get to start of second line</span>
+  assume-console [
+    press right-arrow
+    press right-arrow
+    press right-arrow
+    press right-arrow  <span class="Comment"># next line</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  <span class="Comment"># type something and ensure it goes where it should</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .0d        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">2</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># new length of second line</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-next-line-with-right-arrow-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">1/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    press right-arrow
+    press right-arrow
+    press right-arrow
+    press right-arrow  <span class="Comment"># next line</span>
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> . abc      .</span>
+   <span class="Constant"> . 0d       .</span>
+   <span class="Constant"> . ┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdef]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press right-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .ef        .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># line just barely wrapping</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcde]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># position cursor at last character before wrap and hit right-arrow</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press right-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  <span class="Comment"># now hit right arrow again</span>
+  assume-console [
+    press right-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdef]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">1/left</span>, <span class="Constant">6/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">4</span>
+    press right-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> . abcd↩    .</span>
+   <span class="Constant"> . ef       .</span>
+   <span class="Constant"> . ┈┈┈┈┈    .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># move to end of line, press right-arrow, type a character</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press right-arrow
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># new character should be in next line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .0d        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">2</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="Comment"># todo: ctrl-right: next word-end</span>
+
+<span class="Comment"># left arrow</span>
+
+<span class="muScenario">scenario</span> editor-moves-cursor-left-with-key [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    press left-arrow
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a0bc      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">3</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-previous-character?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65515/left-arrow</span>
+    <span class="muControl">break-unless</span> move-to-previous-character?
+    trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[left arrow]</span>
+    <span class="Comment"># if not at start of text (before-cursor at § sentinel)</span>
+    prev:address:duplex-list<span class="Special"> &lt;- </span>prev-duplex *before-cursor
+    <span class="muControl">reply-unless</span> prev, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    editor, go-render?<span class="Special"> &lt;- </span>move-cursor-coordinates-left editor
+    *before-cursor<span class="Special"> &lt;- </span>copy prev
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">1/left-arrow</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize editor with two lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># position cursor at start of second line (so there's no previous newline)</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">0</span>
+    press left-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize editor with three lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">g]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># position cursor further down (so there's a newline before the character at</span>
+  <span class="Comment"># the cursor)</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press left-arrow
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def0      .</span>
+   <span class="Constant"> .g         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># just the '0'</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">g]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># position cursor at start of text, press left-arrow, then type a character</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">0</span>
+    press left-arrow
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># left-arrow should have had no effect</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0abc      .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .g         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+  check-trace-count-for-label <span class="Constant">4</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># length of first line</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize editor with text containing an empty line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+
+d]
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># position cursor right after empty line</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press left-arrow
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .0         .</span>
+   <span class="Constant"> .d         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># just the '0'</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-across-screen-lines-across-wrap-with-left-arrow [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize editor with text containing an empty line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdef]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .ef        .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># position cursor right after empty line</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">0</span>
+    press left-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># previous row</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>  <span class="Comment"># end of wrapped line</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="Comment"># todo: ctrl-left: previous word-start</span>
+
+<span class="Comment"># up arrow</span>
+
+<span class="muScenario">scenario</span> editor-moves-to-previous-line-with-up-arrow [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a0bc      .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-previous-line?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65517/up-arrow</span>
+    <span class="muControl">break-unless</span> move-to-previous-line?
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    editor, go-render?<span class="Special"> &lt;- </span>move-to-previous-line editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">3/up-arrow</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> move-to-previous-line [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  already-at-top?:boolean<span class="Special"> &lt;- </span>lesser-or-equal *cursor-row, <span class="Constant">1/top</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if cursor not at top, move it</span>
+    <span class="muControl">break-if</span> already-at-top?
+    <span class="Comment"># if not at newline, move to start of line (previous newline)</span>
+    <span class="Comment"># then scan back another line</span>
+    <span class="Comment"># if either step fails, give up without modifying cursor or coordinates</span>
+    curr:address:duplex-list<span class="Special"> &lt;- </span>copy *before-cursor
+    <span class="Delimiter">{</span>
+      old:address:duplex-list<span class="Special"> &lt;- </span>copy curr
+      c2:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+      at-newline?:boolean<span class="Special"> &lt;- </span>equal c2, <span class="Constant">10/newline</span>
+      <span class="muControl">break-if</span> at-newline?
+      curr:address:duplex-list<span class="Special"> &lt;- </span>before-previous-line curr, editor
+      no-motion?:boolean<span class="Special"> &lt;- </span>equal curr, old
+      <span class="muControl">reply-if</span> no-motion?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+    <span class="Delimiter">}</span>
+    <span class="Delimiter">{</span>
+      old<span class="Special"> &lt;- </span>copy curr
+      curr<span class="Special"> &lt;- </span>before-previous-line curr, editor
+      no-motion?:boolean<span class="Special"> &lt;- </span>equal curr, old
+      <span class="muControl">reply-if</span> no-motion?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+    <span class="Delimiter">}</span>
+    *before-cursor<span class="Special"> &lt;- </span>copy curr
+    *cursor-row<span class="Special"> &lt;- </span>subtract *cursor-row, <span class="Constant">1</span>
+    <span class="Comment"># scan ahead to right column or until end of line</span>
+    target-column:number<span class="Special"> &lt;- </span>copy *cursor-column
+    *cursor-column<span class="Special"> &lt;- </span>copy left
+    <span class="Delimiter">{</span>
+      done?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-column, target-column
+      <span class="muControl">break-if</span> done?
+      curr:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+      <span class="muControl">break-unless</span> curr
+      currc:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+      at-newline?:boolean<span class="Special"> &lt;- </span>equal currc, <span class="Constant">10/newline</span>
+      <span class="muControl">break-if</span> at-newline?
+      <span class="Comment">#</span>
+      *before-cursor<span class="Special"> &lt;- </span>copy curr
+      *cursor-column<span class="Special"> &lt;- </span>add *cursor-column, <span class="Constant">1</span>
+      <span class="muControl">loop</span>
+    <span class="Delimiter">}</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if cursor already at top, scroll up</span>
+    <span class="muControl">break-unless</span> already-at-top?
+<span class="Constant">    &lt;scroll-up&gt;</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-adjusts-column-at-previous-line [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ab</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">3</span>
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .ab0       .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-adjusts-column-at-empty-line [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new [
+def]
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">3</span>
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0         .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-previous-line-from-left-margin [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># start out with three lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># click on the third line and hit up-arrow, so you end up just after a newline</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .0def      .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="Comment"># down arrow</span>
+
+<span class="muScenario">scenario</span> editor-moves-to-next-line-with-down-arrow [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># cursor starts out at (1, 0)</span>
+  assume-console [
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># ..and ends at (2, 0)</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .0def      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-next-line?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65516/down-arrow</span>
+    <span class="muControl">break-unless</span> move-to-next-line?
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    editor, go-render?<span class="Special"> &lt;- </span>move-to-next-line editor, screen-height
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">4/down-arrow</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, go-render?
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> move-to-next-line [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen-height:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-row:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  last-line:number<span class="Special"> &lt;- </span>subtract screen-height, <span class="Constant">1</span>
+  already-at-bottom?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-row, last-line
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if cursor not at bottom, move it</span>
+    <span class="muControl">break-if</span> already-at-bottom?
+    <span class="Comment"># scan to start of next line, then to right column or until end of line</span>
+    max:number<span class="Special"> &lt;- </span>subtract right, left
+    next-line:address:duplex-list<span class="Special"> &lt;- </span>before-start-of-next-line *before-cursor, max
+    <span class="Delimiter">{</span>
+      <span class="Comment"># already at end of buffer? try to scroll up (so we can see more</span>
+      <span class="Comment"># warnings or sandboxes below)</span>
+      no-motion?:boolean<span class="Special"> &lt;- </span>equal next-line, *before-cursor
+      <span class="muControl">break-unless</span> no-motion?
+      scroll?:boolean<span class="Special"> &lt;- </span>greater-than *cursor-row, <span class="Constant">1</span>
+      <span class="muControl">break-if</span> scroll?, <span class="Constant">+try-to-scroll:label</span>
+      <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+    <span class="Delimiter">}</span>
+    *cursor-row<span class="Special"> &lt;- </span>add *cursor-row, <span class="Constant">1</span>
+    *before-cursor<span class="Special"> &lt;- </span>copy next-line
+    target-column:number<span class="Special"> &lt;- </span>copy *cursor-column
+    *cursor-column<span class="Special"> &lt;- </span>copy left
+    <span class="Delimiter">{</span>
+      done?:boolean<span class="Special"> &lt;- </span>greater-or-equal *cursor-column, target-column
+      <span class="muControl">break-if</span> done?
+      curr:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+      <span class="muControl">break-unless</span> curr
+      currc:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+      at-newline?:boolean<span class="Special"> &lt;- </span>equal currc, <span class="Constant">10/newline</span>
+      <span class="muControl">break-if</span> at-newline?
+      <span class="Comment">#</span>
+      *before-cursor<span class="Special"> &lt;- </span>copy curr
+      *cursor-column<span class="Special"> &lt;- </span>add *cursor-column, <span class="Constant">1</span>
+      <span class="muControl">loop</span>
+    <span class="Delimiter">}</span>
+    <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+<span class="Constant">  +try-to-scroll</span>
+<span class="Constant">  &lt;scroll-down&gt;</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">1/go-render</span>
+]
+
+<span class="muScenario">scenario</span> editor-adjusts-column-at-next-line [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">de]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .de0       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-at-end-on-down-arrow [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">de]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># try to move down past end of text</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen should scroll, moving cursor to end of text</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .de0       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># try to move down again</span>
+<span class="Constant">  $clear-trace</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen stops scrolling because cursor is already at top</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .de01      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># ctrl-a/home - move cursor to start of line</span>
+
+<span class="muScenario">scenario</span> editor-moves-to-start-of-line-with-ctrl-a [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on second line, press ctrl-a</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">3</span>
+    press ctrl-a
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to start of line</span>
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-start-of-line?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">1/ctrl-a</span>
+    <span class="muControl">break-unless</span> move-to-start-of-line?
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    move-to-start-of-line editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-start-of-line?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65521/home</span>
+    <span class="muControl">break-unless</span> move-to-start-of-line?
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    move-to-start-of-line editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> move-to-start-of-line [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># update cursor column</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  *cursor-column<span class="Special"> &lt;- </span>copy left
+  <span class="Comment"># update before-cursor</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  init:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  <span class="Comment"># while not at start of line, move </span>
+  <span class="Delimiter">{</span>
+    at-start-of-text?:boolean<span class="Special"> &lt;- </span>equal *before-cursor, init
+    <span class="muControl">break-if</span> at-start-of-text?
+    prev:character<span class="Special"> &lt;- </span>get **before-cursor, <span class="Constant">value:offset</span>
+    at-start-of-line?:boolean<span class="Special"> &lt;- </span>equal prev, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-start-of-line?
+    *before-cursor<span class="Special"> &lt;- </span>prev-duplex *before-cursor
+    assert *before-cursor, <span class="Constant">[move-to-start-of-line tried to move before start of text]</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-start-of-line-with-ctrl-a-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on first line (no newline before), press ctrl-a</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press ctrl-a
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to start of line</span>
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-start-of-line-with-home [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on second line, press 'home'</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">3</span>
+    press home
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to start of line</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-start-of-line-with-home-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on first line (no newline before), press 'home'</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press home
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to start of line</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="Comment"># ctrl-e/end - move cursor to end of line</span>
+
+<span class="muScenario">scenario</span> editor-moves-to-end-of-line-with-ctrl-e [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on first line, press ctrl-e</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    press ctrl-e
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to end of line</span>
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+  <span class="Comment"># editor inserts future characters at cursor</span>
+  assume-console [
+    type <span class="Constant">[z]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">4</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123z      .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  check-trace-count-for-label <span class="Constant">1</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-end-of-line?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">5/ctrl-e</span>
+    <span class="muControl">break-unless</span> move-to-end-of-line?
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    move-to-end-of-line editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    move-to-end-of-line?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65520/end</span>
+    <span class="muControl">break-unless</span> move-to-end-of-line?
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    move-to-end-of-line editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> move-to-end-of-line [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  <span class="Comment"># while not at start of line, move </span>
+  <span class="Delimiter">{</span>
+    next:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    <span class="muControl">break-unless</span> next  <span class="Comment"># end of text</span>
+    nextc:character<span class="Special"> &lt;- </span>get *next, <span class="Constant">value:offset</span>
+    at-end-of-line?:boolean<span class="Special"> &lt;- </span>equal nextc, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-end-of-line?
+    *before-cursor<span class="Special"> &lt;- </span>copy next
+    *cursor-column<span class="Special"> &lt;- </span>add *cursor-column, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-end-of-line-with-ctrl-e-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on second line (no newline after), press ctrl-e</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press ctrl-e
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to end of line</span>
+  memory-should-contain [
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-end-of-line-with-end [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on first line, press 'end'</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    press end
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to end of line</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="muScenario">scenario</span> editor-moves-to-end-of-line-with-end-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+<span class="Constant">  $clear-trace</span>
+  <span class="Comment"># start on second line (no newline after), press 'end'</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press end
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to end of line</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  check-trace-count-for-label <span class="Constant">0</span>, <span class="Constant">[print-character]</span>
+]
+
+<span class="Comment"># ctrl-u - delete text from start of line until (but not at) cursor</span>
+
+<span class="muScenario">scenario</span> editor-deletes-to-start-of-line-with-ctrl-u [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start on second line, press ctrl-u</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">2</span>
+    press ctrl-u
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes to start of line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123       .</span>
+   <span class="Constant"> .6         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    delete-to-start-of-line?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">21/ctrl-u</span>
+    <span class="muControl">break-unless</span> delete-to-start-of-line?
+<span class="Constant">    &lt;delete-to-start-of-line-begin&gt;</span>
+    deleted-cells:address:duplex-list<span class="Special"> &lt;- </span>delete-to-start-of-line editor
+<span class="Constant">    &lt;delete-to-start-of-line-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> delete-to-start-of-line [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># compute range to delete</span>
+  init:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  start:address:duplex-list<span class="Special"> &lt;- </span>copy *before-cursor
+  end:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  <span class="Delimiter">{</span>
+    at-start-of-text?:boolean<span class="Special"> &lt;- </span>equal start, init
+    <span class="muControl">break-if</span> at-start-of-text?
+    curr:character<span class="Special"> &lt;- </span>get *start, <span class="Constant">value:offset</span>
+    at-start-of-line?:boolean<span class="Special"> &lt;- </span>equal curr, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-start-of-line?
+    start<span class="Special"> &lt;- </span>prev-duplex start
+    assert start, <span class="Constant">[delete-to-start-of-line tried to move before start of text]</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># snip it out</span>
+  result:address:duplex-list<span class="Special"> &lt;- </span>next-duplex start
+  remove-duplex-between start, end
+  <span class="Comment"># adjust cursor</span>
+  *before-cursor<span class="Special"> &lt;- </span>prev-duplex end
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">cursor-column:offset</span>
+  *cursor-column<span class="Special"> &lt;- </span>copy left
+  <span class="muControl">reply</span> result
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-start-of-line-with-ctrl-u-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start on first line (no newline before), press ctrl-u</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    press ctrl-u
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes to start of line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .3         .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-start-of-line-with-ctrl-u-3 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start past end of line, press ctrl-u</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press ctrl-u
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes to start of line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-start-of-final-line-with-ctrl-u [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start past end of final line, press ctrl-u</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">3</span>
+    press ctrl-u
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes to start of line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123       .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># ctrl-k - delete text from cursor to end of line (but not the newline)</span>
+
+<span class="muScenario">scenario</span> editor-deletes-to-end-of-line-with-ctrl-k [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start on first line, press ctrl-k</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    press ctrl-k
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes to end of line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .1         .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    delete-to-end-of-line?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">11/ctrl-k</span>
+    <span class="muControl">break-unless</span> delete-to-end-of-line?
+<span class="Constant">    &lt;delete-to-end-of-line-begin&gt;</span>
+    deleted-cells:address:duplex-list<span class="Special"> &lt;- </span>delete-to-end-of-line editor
+<span class="Constant">    &lt;delete-to-end-of-line-end&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> delete-to-end-of-line [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># compute range to delete</span>
+  start:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">before-cursor:offset</span>
+  end:address:duplex-list<span class="Special"> &lt;- </span>next-duplex start
+  <span class="Delimiter">{</span>
+    at-end-of-text?:boolean<span class="Special"> &lt;- </span>equal end, <span class="Constant">0/null</span>
+    <span class="muControl">break-if</span> at-end-of-text?
+    curr:character<span class="Special"> &lt;- </span>get *end, <span class="Constant">value:offset</span>
+    at-end-of-line?:boolean<span class="Special"> &lt;- </span>equal curr, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-end-of-line?
+    end<span class="Special"> &lt;- </span>next-duplex end
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># snip it out</span>
+  result:address:duplex-list<span class="Special"> &lt;- </span>next-duplex start
+  remove-duplex-between start, end
+  <span class="muControl">reply</span> result
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-end-of-line-with-ctrl-k-2 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start on second line (no newline after), press ctrl-k</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press ctrl-k
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes to end of line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123       .</span>
+   <span class="Constant"> .4         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-end-of-line-with-ctrl-k-3 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start at end of line</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    press ctrl-k
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes just last character</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .12        .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-end-of-line-with-ctrl-k-4 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start past end of line</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">3</span>
+    press ctrl-k
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes nothing</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123       .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-end-of-line-with-ctrl-k-5 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start at end of text</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">2</span>
+    press ctrl-k
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes just the final character</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123       .</span>
+   <span class="Constant"> .45        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-deletes-to-end-of-line-with-ctrl-k-6 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[123</span>
+<span class="Constant">456]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  <span class="Comment"># start past end of text</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">3</span>
+    press ctrl-k
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># cursor deletes nothing</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .123       .</span>
+   <span class="Constant"> .456       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># cursor-down can scroll if necessary</span>
+
+<span class="muScenario">scenario</span> editor-can-scroll-down-using-arrow-keys [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with &gt;3 lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># position cursor at last line, then try to move further down</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen slides by one line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;scroll-down&gt;</span> [
+  trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[scroll down]</span>
+  top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  max:number<span class="Special"> &lt;- </span>subtract right, left
+  old-top:address:duplex-list<span class="Special"> &lt;- </span>copy *top-of-screen
+  *top-of-screen<span class="Special"> &lt;- </span>before-start-of-next-line *top-of-screen, max
+  no-movement?:boolean<span class="Special"> &lt;- </span>equal old-top, *top-of-screen
+  <span class="Comment"># Hack: this reply doesn't match one of the locations of &lt;scroll-down&gt;,</span>
+  <span class="Comment"># directly within insert-at-cursor. However, I'm unable to trigger the</span>
+  <span class="Comment"># error.. If necessary create a duplicate copy of &lt;scroll-down&gt; with the</span>
+  <span class="Comment"># right 'reply-if'.</span>
+  <span class="muControl">reply-if</span> no-movement?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+]
+
+<span class="Comment"># takes a pointer into the doubly-linked list, scans ahead at most 'max'</span>
+<span class="Comment"># positions until the next newline</span>
+<span class="Comment"># beware: never return null pointer.</span>
+<span class="muRecipe">recipe</span> before-start-of-next-line [
+  <span class="Constant">local-scope</span>
+  original:address:duplex-list<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  max:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  count:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>copy original
+  <span class="Comment"># skip the initial newline if it exists</span>
+  <span class="Delimiter">{</span>
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    at-newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> at-newline?
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    count<span class="Special"> &lt;- </span>add count, <span class="Constant">1</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">reply-unless</span> curr, original
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal count, max
+    <span class="muControl">break-if</span> done?
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    at-newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-if</span> at-newline?
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    count<span class="Special"> &lt;- </span>add count, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply-unless</span> curr, original
+  <span class="muControl">reply</span> curr
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-down-past-wrapped-line-using-arrow-keys [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with a long, wrapped line and more than a screen of</span>
+  <span class="Comment"># other lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdef</span>
+<span class="Constant">g</span>
+<span class="Constant">h</span>
+<span class="Constant">i]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .ef        .</span>
+   <span class="Constant"> .g         .</span>
+  ]
+  <span class="Comment"># position cursor at last line, then try to move further down</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .ef        .</span>
+   <span class="Constant"> .g         .</span>
+   <span class="Constant"> .h         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor starts with a long line wrapping twice</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdefghij</span>
+<span class="Constant">k</span>
+<span class="Constant">l</span>
+<span class="Constant">m]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at last line, then try to move further down</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line containing a wrap icon</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .efgh↩     .</span>
+   <span class="Constant"> .ij        .</span>
+   <span class="Constant"> .k         .</span>
+  ]
+  <span class="Comment"># scroll down again</span>
+  assume-console [
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .ij        .</span>
+   <span class="Constant"> .k         .</span>
+   <span class="Constant"> .l         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-down-when-line-wraps [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains a long line in the third line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">cdef]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at end, type a character</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">4</span>
+    type <span class="Constant">[g]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen scrolls</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+<span class="Constant">    .cdef↩.</span>
+   <span class="Constant"> .g    .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-down-on-newline [
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># position cursor after last line and type newline</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">4</span>
+    type [
+]
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen scrolls</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+   <span class="Constant"> .c    .</span>
+   <span class="Constant"> .     .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-down-on-right-arrow [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains a wrapped line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">cdefgh]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at end of screen and try to move right</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">3</span>
+    press right-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen scrolls</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+<span class="Constant">    .cdef↩.</span>
+   <span class="Constant"> .gh   .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-down-on-right-arrow-2 [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains more lines than can fit on screen</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at end of screen and try to move right</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">3</span>
+    press right-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen scrolls</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+   <span class="Constant"> .c    .</span>
+   <span class="Constant"> .d    .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-combines-page-and-line-scroll [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with a few pages of lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e</span>
+<span class="Constant">f</span>
+<span class="Constant">g]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># scroll down one page and one line</span>
+  assume-console [
+    press page-down
+    left-click <span class="Constant">3</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen scrolls down 3 lines</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .d         .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .f         .</span>
+  ]
+]
+
+<span class="Comment"># cursor-up can scroll if necessary</span>
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-using-arrow-keys [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with &gt;3 lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># position cursor at top of second page, then try to move up</span>
+  assume-console [
+    press page-down
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen slides by one line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;scroll-up&gt;</span> [
+  trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[scroll up]</span>
+  top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+  old-top:address:duplex-list<span class="Special"> &lt;- </span>copy *top-of-screen
+  *top-of-screen<span class="Special"> &lt;- </span>before-previous-line *top-of-screen, editor
+  no-movement?:boolean<span class="Special"> &lt;- </span>equal old-top, *top-of-screen
+  <span class="muControl">reply-if</span> no-movement?, editor/same-as-ingredient:<span class="Constant">0</span>, <span class="Constant">0/no-more-render</span>
+]
+
+<span class="Comment"># takes a pointer into the doubly-linked list, scans back to before start of</span>
+<span class="Comment"># previous *wrapped* line</span>
+<span class="Comment"># beware: never return null pointer</span>
+<span class="muRecipe">recipe</span> before-previous-line [
+  <span class="Constant">local-scope</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+  <span class="Comment"># compute max, number of characters to skip</span>
+  <span class="Comment">#   1 + len%(width-1)</span>
+  <span class="Comment">#   except rotate second term to vary from 1 to width-1 rather than 0 to width-2</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">right:offset</span>
+  max-line-length:number<span class="Special"> &lt;- </span>subtract right, left, <span class="Constant">-1/exclusive-right</span>, <span class="Constant">1/wrap-icon</span>
+  sentinel:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  len:number<span class="Special"> &lt;- </span>previous-line-length curr, sentinel
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> len
+    <span class="Comment"># empty line; just skip this newline</span>
+    prev:address:duplex-list<span class="Special"> &lt;- </span>prev-duplex curr
+    <span class="muControl">reply-unless</span> prev, curr
+    <span class="muControl">reply</span> prev
+  <span class="Delimiter">}</span>
+  _, max:number<span class="Special"> &lt;- </span>divide-with-remainder len, max-line-length
+  <span class="Comment"># remainder 0 =&gt; scan one width-worth</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> max
+    max<span class="Special"> &lt;- </span>copy max-line-length
+  <span class="Delimiter">}</span>
+  max<span class="Special"> &lt;- </span>add max, <span class="Constant">1</span>
+  count:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="Comment"># skip 'max' characters</span>
+  <span class="Delimiter">{</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal count, max
+    <span class="muControl">break-if</span> done?
+    prev:address:duplex-list<span class="Special"> &lt;- </span>prev-duplex curr
+    <span class="muControl">break-unless</span> prev
+    curr<span class="Special"> &lt;- </span>copy prev
+    count<span class="Special"> &lt;- </span>add count, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> curr
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-up-past-wrapped-line-using-arrow-keys [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with a long, wrapped line and more than a screen of</span>
+  <span class="Comment"># other lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdef</span>
+<span class="Constant">g</span>
+<span class="Constant">h</span>
+<span class="Constant">i]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .ef        .</span>
+   <span class="Constant"> .g         .</span>
+  ]
+  <span class="Comment"># position cursor at top of second page, just below wrapped line</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .g         .</span>
+   <span class="Constant"> .h         .</span>
+   <span class="Constant"> .i         .</span>
+  ]
+  <span class="Comment"># now move up one line</span>
+  assume-console [
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .ef        .</span>
+   <span class="Constant"> .g         .</span>
+   <span class="Constant"> .h         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [
+  <span class="Comment"># screen has 1 line for menu + 4 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># editor starts with a long line wrapping twice, occupying 3 of the 4 lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdefghij</span>
+<span class="Constant">k</span>
+<span class="Constant">l</span>
+<span class="Constant">m]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at top of second page</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .k         .</span>
+   <span class="Constant"> .l         .</span>
+   <span class="Constant"> .m         .</span>
+   <span class="Constant"> .┈┈┈┈┈     .</span>
+  ]
+  <span class="Comment"># move up one line</span>
+  assume-console [
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .ij        .</span>
+   <span class="Constant"> .k         .</span>
+   <span class="Constant"> .l         .</span>
+   <span class="Constant"> .m         .</span>
+  ]
+  <span class="Comment"># move up a second line</span>
+  assume-console [
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .efgh↩     .</span>
+   <span class="Constant"> .ij        .</span>
+   <span class="Constant"> .k         .</span>
+   <span class="Constant"> .l         .</span>
+  ]
+  <span class="Comment"># move up a third line</span>
+  assume-console [
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcd↩     .</span>
+   <span class="Constant"> .efgh↩     .</span>
+   <span class="Constant"> .ij        .</span>
+   <span class="Constant"> .k         .</span>
+  ]
+]
+
+<span class="Comment"># same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length</span>
+<span class="Comment"># slightly off, just to prevent over-training</span>
+<span class="muScenario">scenario</span> editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with a long, wrapped line and more than a screen of</span>
+  <span class="Comment"># other lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abcdef</span>
+<span class="Constant">g</span>
+<span class="Constant">h</span>
+<span class="Constant">i]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">6/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcde↩    .</span>
+   <span class="Constant"> .f         .</span>
+   <span class="Constant"> .g         .</span>
+  ]
+  <span class="Comment"># position cursor at top of second page, just below wrapped line</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .g         .</span>
+   <span class="Constant"> .h         .</span>
+   <span class="Constant"> .i         .</span>
+  ]
+  <span class="Comment"># now move up one line</span>
+  assume-console [
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows partial wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .f         .</span>
+   <span class="Constant"> .g         .</span>
+   <span class="Constant"> .h         .</span>
+  ]
+]
+
+<span class="Comment"># check empty lines</span>
+<span class="muScenario">scenario</span> editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with some lines around an empty line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+
+c
+d
+e]
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">6/right</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+  ]
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .d         .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .┈┈┈┈┈┈    .</span>
+  ]
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-scrolls-up-on-left-arrow [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains &gt;3 lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at top of second page</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .c    .</span>
+   <span class="Constant"> .d    .</span>
+   <span class="Constant"> .e    .</span>
+  ]
+  <span class="Comment"># now try to move left</span>
+  assume-console [
+    press left-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen scrolls</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+   <span class="Constant"> .c    .</span>
+   <span class="Constant"> .d    .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-to-start-of-file [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with &gt;3 lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># position cursor at top of second page, then try to move up to start of</span>
+  <span class="Comment"># text</span>
+  assume-console [
+    press page-down
+    press up-arrow
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen slides by one line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># try to move up again</span>
+  assume-console [
+    press up-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen remains unchanged</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+]
+
+<span class="Comment"># ctrl-f/page-down - render next page if it exists</span>
+
+<span class="muScenario">scenario</span> editor-can-scroll [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># scroll down</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows next page</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    page-down?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">6/ctrl-f</span>
+    <span class="muControl">break-unless</span> page-down?
+    top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+    old-top:address:duplex-list<span class="Special"> &lt;- </span>copy *top-of-screen
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    page-down editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    no-movement?:boolean<span class="Special"> &lt;- </span>equal *top-of-screen, old-top
+    <span class="muControl">reply-if</span> no-movement?, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    page-down?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65518/page-down</span>
+    <span class="muControl">break-unless</span> page-down?
+    top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+    old-top:address:duplex-list<span class="Special"> &lt;- </span>copy *top-of-screen
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    page-down editor
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    no-movement?:boolean<span class="Special"> &lt;- </span>equal *top-of-screen, old-top
+    <span class="muControl">reply-if</span> no-movement?, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># page-down skips entire wrapped lines, so it can't scroll past lines</span>
+<span class="Comment"># taking up the entire screen</span>
+<span class="muRecipe">recipe</span> page-down [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># if editor contents don't overflow screen, do nothing</span>
+  bottom-of-screen:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">bottom-of-screen:offset</span>
+  <span class="muControl">reply-unless</span> bottom-of-screen, editor/same-as-ingredient:<span class="Constant">0</span>
+  <span class="Comment"># if not, position cursor at final character</span>
+  before-cursor:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">before-cursor:offset</span>
+  *before-cursor<span class="Special"> &lt;- </span>prev-duplex bottom-of-screen
+  <span class="Comment"># keep one line in common with previous page</span>
+  <span class="Delimiter">{</span>
+    last:character<span class="Special"> &lt;- </span>get **before-cursor, <span class="Constant">value:offset</span>
+    newline?:boolean<span class="Special"> &lt;- </span>equal last, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> newline?:boolean
+    *before-cursor<span class="Special"> &lt;- </span>prev-duplex *before-cursor
+  <span class="Delimiter">}</span>
+  <span class="Comment"># move cursor and top-of-screen to start of that line</span>
+  move-to-start-of-line editor
+  top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+  *top-of-screen<span class="Special"> &lt;- </span>copy *before-cursor
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muScenario">scenario</span> editor-does-not-scroll-past-end [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+  <span class="Comment"># scroll down</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen remains unmodified</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-starts-next-page-at-start-of-wrapped-line [
+  <span class="Comment"># screen has 1 line for menu + 3 lines for text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains a long last line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">cdefgh]</span>
+  <span class="Comment"># editor screen triggers wrap of last line</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">4/right</span>
+  <span class="Comment"># some part of last line is not displayed</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .cde↩      .</span>
+  ]
+  <span class="Comment"># scroll down</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows entire wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .cde↩      .</span>
+   <span class="Constant"> .fgh       .</span>
+   <span class="Constant"> .┈┈┈┈      .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-starts-next-page-at-start-of-wrapped-line-2 [
+  <span class="Comment"># screen has 1 line for menu + 3 lines for text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains a very long line that occupies last two lines of screen</span>
+  <span class="Comment"># and still has something left over</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">bcdefgh]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">4/right</span>
+  <span class="Comment"># some part of last line is not displayed</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .bcd↩      .</span>
+   <span class="Constant"> .efg↩      .</span>
+  ]
+  <span class="Comment"># scroll down</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows entire wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .bcd↩      .</span>
+   <span class="Constant"> .efg↩      .</span>
+   <span class="Constant"> .h         .</span>
+  ]
+]
+
+<span class="Comment"># ctrl-b/page-up - render previous page if it exists</span>
+
+<span class="muScenario">scenario</span> editor-can-scroll-up [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># scroll down</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows next page</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+  <span class="Comment"># scroll back up</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows original page again</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    page-up?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">2/ctrl-b</span>
+    <span class="muControl">break-unless</span> page-up?
+    top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+    old-top:address:duplex-list<span class="Special"> &lt;- </span>copy *top-of-screen
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    editor<span class="Special"> &lt;- </span>page-up editor, screen-height
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    no-movement?:boolean<span class="Special"> &lt;- </span>equal *top-of-screen, old-top
+    <span class="muControl">reply-if</span> no-movement?, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-key&gt;</span> [
+  <span class="Delimiter">{</span>
+    page-up?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65519/page-up</span>
+    <span class="muControl">break-unless</span> page-up?
+    top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+    old-top:address:duplex-list<span class="Special"> &lt;- </span>copy *top-of-screen
+<span class="Constant">    &lt;move-cursor-begin&gt;</span>
+    editor<span class="Special"> &lt;- </span>page-up editor, screen-height
+    undo-coalesce-tag:number<span class="Special"> &lt;- </span>copy <span class="Constant">0/never</span>
+<span class="Constant">    &lt;move-cursor-end&gt;</span>
+    no-movement?:boolean<span class="Special"> &lt;- </span>equal *top-of-screen, old-top
+    <span class="Comment"># don't bother re-rendering if nothing changed. todo: test this</span>
+    <span class="muControl">reply-if</span> no-movement?, screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">0/no-more-render</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> page-up [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen-height:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  max:number<span class="Special"> &lt;- </span>subtract screen-height, <span class="Constant">1/menu-bar</span>, <span class="Constant">1/overlapping-line</span>
+  count:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+  <span class="Delimiter">{</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal count, max
+    <span class="muControl">break-if</span> done?
+    prev:address:duplex-list<span class="Special"> &lt;- </span>before-previous-line *top-of-screen, editor
+    <span class="muControl">break-unless</span> prev
+    *top-of-screen<span class="Special"> &lt;- </span>copy prev
+    count<span class="Special"> &lt;- </span>add count, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-multiple-pages [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># initialize editor with 8 lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e</span>
+<span class="Constant">f</span>
+<span class="Constant">g</span>
+<span class="Constant">h]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+  <span class="Comment"># scroll down two pages</span>
+  assume-console [
+    press page-down
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows third page</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .f         .</span>
+   <span class="Constant"> .g         .</span>
+  ]
+  <span class="Comment"># scroll up</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows second page</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+   <span class="Constant"> .e         .</span>
+  ]
+  <span class="Comment"># scroll up again</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows original page again</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-wrapped-lines [
+  <span class="Comment"># screen has 1 line for menu + 5 lines for text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">6/height</span>
+  <span class="Comment"># editor contains a long line in the first page</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">cdefgh</span>
+<span class="Constant">i</span>
+<span class="Constant">j</span>
+<span class="Constant">k</span>
+<span class="Constant">l</span>
+<span class="Constant">m</span>
+<span class="Constant">n</span>
+<span class="Constant">o]</span>
+  <span class="Comment"># editor screen triggers wrap of last line</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">4/right</span>
+  <span class="Comment"># some part of last line is not displayed</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .cde↩      .</span>
+   <span class="Constant"> .fgh       .</span>
+   <span class="Constant"> .i         .</span>
+  ]
+  <span class="Comment"># scroll down a page and a line</span>
+  assume-console [
+    press page-down
+    left-click <span class="Constant">5</span>, <span class="Constant">0</span>
+    press down-arrow
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows entire wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .j         .</span>
+   <span class="Constant"> .k         .</span>
+   <span class="Constant"> .l         .</span>
+   <span class="Constant"> .m         .</span>
+   <span class="Constant"> .n         .</span>
+  ]
+  <span class="Comment"># now scroll up one page</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen resets</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .cde↩      .</span>
+   <span class="Constant"> .fgh       .</span>
+   <span class="Constant"> .i         .</span>
+   <span class="Constant"> .j         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-wrapped-lines-2 [
+  <span class="Comment"># screen has 1 line for menu + 3 lines for text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains a very long line that occupies last two lines of screen</span>
+  <span class="Comment"># and still has something left over</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">bcdefgh]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">4/right</span>
+  <span class="Comment"># some part of last line is not displayed</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .bcd↩      .</span>
+   <span class="Constant"> .efg↩      .</span>
+  ]
+  <span class="Comment"># scroll down</span>
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen shows entire wrapped line</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .bcd↩      .</span>
+   <span class="Constant"> .efg↩      .</span>
+   <span class="Constant"> .h         .</span>
+  ]
+  <span class="Comment"># scroll back up</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># screen resets</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .bcd↩      .</span>
+   <span class="Constant"> .efg↩      .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-past-nonempty-lines [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># text with empty line in second screen</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[axx</span>
+<span class="Constant">bxx</span>
+<span class="Constant">cxx</span>
+<span class="Constant">dxx</span>
+<span class="Constant">exx</span>
+<span class="Constant">fxx</span>
+<span class="Constant">gxx</span>
+<span class="Constant">hxx</span>
+<span class="Constant">]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">4/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .axx       .</span>
+   <span class="Constant"> .bxx       .</span>
+   <span class="Constant"> .cxx       .</span>
+  ]
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .cxx       .</span>
+   <span class="Constant"> .dxx       .</span>
+   <span class="Constant"> .exx       .</span>
+  ]
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .exx       .</span>
+   <span class="Constant"> .fxx       .</span>
+   <span class="Constant"> .gxx       .</span>
+  ]
+  <span class="Comment"># scroll back up past empty line</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .cxx       .</span>
+   <span class="Constant"> .dxx       .</span>
+   <span class="Constant"> .exx       .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-scroll-up-past-empty-lines [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># text with empty line in second screen</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[axy</span>
+<span class="Constant">bxy</span>
+<span class="Constant">cxy</span>
+
+dxy
+exy
+fxy
+gxy
+]
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">4/right</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .axy       .</span>
+   <span class="Constant"> .bxy       .</span>
+   <span class="Constant"> .cxy       .</span>
+  ]
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .cxy       .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .dxy       .</span>
+  ]
+  assume-console [
+    press page-down
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .dxy       .</span>
+   <span class="Constant"> .exy       .</span>
+   <span class="Constant"> .fxy       .</span>
+  ]
+  <span class="Comment"># scroll back up past empty line</span>
+  assume-console [
+    press page-up
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .cxy       .</span>
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .dxy       .</span>
+  ]
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/004-programming-environment.mu.html b/html/edit/004-programming-environment.mu.html
new file mode 100644
index 00000000..406c19a9
--- /dev/null
+++ b/html/edit/004-programming-environment.mu.html
@@ -0,0 +1,826 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/004-programming-environment.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muRecipe { color: #ff8700; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muScenario { color: #00af00; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.CommentedCode { color: #6c6c6c; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## putting the environment together out of editors</span>
+<span class="Comment">#</span>
+<span class="Comment"># Consists of one editor on the left for recipes and one on the right for the</span>
+<span class="Comment"># sandbox.</span>
+
+<span class="muRecipe">recipe!</span> main [
+  <span class="Constant">local-scope</span>
+  open-console
+  initial-recipe:address:array:character<span class="Special"> &lt;- </span>restore <span class="Constant">[recipes.mu]</span>
+  initial-sandbox:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  hide-screen <span class="Constant">0/screen</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment <span class="Constant">0/screen</span>, initial-recipe, initial-sandbox
+  env<span class="Special"> &lt;- </span>restore-sandboxes env
+  render-all <span class="Constant">0/screen</span>, env
+  event-loop <span class="Constant">0/screen</span>, <span class="Constant">0/console</span>, env
+  <span class="Comment"># never gets here</span>
+]
+
+<span class="muData">container</span> programming-environment-data [
+  recipes:address:editor-data
+  current-sandbox:address:editor-data
+  sandbox-in-focus?:boolean  <span class="Comment"># false =&gt; cursor in recipes; true =&gt; cursor in current-sandbox</span>
+]
+
+<span class="muRecipe">recipe</span> new-programming-environment [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  initial-recipe-contents:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  initial-sandbox-contents:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  width:number<span class="Special"> &lt;- </span>screen-width screen
+  height:number<span class="Special"> &lt;- </span>screen-height screen
+  <span class="Comment"># top menu</span>
+  result:address:programming-environment-data<span class="Special"> &lt;- </span>new <span class="Constant">programming-environment-data:type</span>
+  draw-horizontal screen, <span class="Constant">0</span>, <span class="Constant">0/left</span>, width, <span class="Constant">32/space</span>, <span class="Constant">0/black</span>, <span class="Constant">238/grey</span>
+  button-start:number<span class="Special"> &lt;- </span>subtract width, <span class="Constant">20</span>
+  button-on-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal button-start, <span class="Constant">0</span>
+  assert button-on-screen?, <span class="Constant">[screen too narrow for menu]</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, <span class="Constant">0/row</span>, button-start
+  run-button:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ run (F4) ]</span>
+  print-string screen, run-button, <span class="Constant">255/white</span>, <span class="Constant">161/reddish</span>
+  <span class="Comment"># dotted line down the middle</span>
+  divider:number, _<span class="Special"> &lt;- </span>divide-with-remainder width, <span class="Constant">2</span>
+  draw-vertical screen, divider, <span class="Constant">1/top</span>, height, <span class="Constant">9482/vertical-dotted</span>
+  <span class="Comment"># recipe editor on the left</span>
+  recipes:address:address:editor-data<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">recipes:offset</span>
+  *recipes<span class="Special"> &lt;- </span>new-editor initial-recipe-contents, screen, <span class="Constant">0/left</span>, divider/right
+  <span class="Comment"># sandbox editor on the right</span>
+  new-left:number<span class="Special"> &lt;- </span>add divider, <span class="Constant">1</span>
+  current-sandbox:address:address:editor-data<span class="Special"> &lt;- </span>get-address *result, <span class="Constant">current-sandbox:offset</span>
+  *current-sandbox<span class="Special"> &lt;- </span>new-editor initial-sandbox-contents, screen, new-left, width/right
+<span class="Constant">  +programming-environment-initialization</span>
+  <span class="muControl">reply</span> result
+]
+
+<span class="muRecipe">recipe</span> event-loop [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  console:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  sandbox-in-focus?:address:boolean<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">sandbox-in-focus?:offset</span>
+  <span class="Comment"># if we fall behind we'll stop updating the screen, but then we have to</span>
+  <span class="Comment"># render the entire screen when we catch up.</span>
+  <span class="Comment"># todo: test this</span>
+  render-all-on-no-more-events?:boolean<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># looping over each (keyboard or touch) event as it occurs</span>
+<span class="Constant">    +next-event</span>
+    e:event, console, found?:boolean, quit?:boolean<span class="Special"> &lt;- </span>read-event console
+    <span class="muControl">loop-unless</span> found?
+    <span class="muControl">break-if</span> quit?  <span class="Comment"># only in tests</span>
+    trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[next-event]</span>
+<span class="Constant">    &lt;handle-event&gt;</span>
+    <span class="Comment"># check for global events that will trigger regardless of which editor has focus</span>
+    <span class="Delimiter">{</span>
+      k:address:number<span class="Special"> &lt;- </span>maybe-convert e:event, <span class="Constant">keycode:variant</span>
+      <span class="muControl">break-unless</span> k
+<span class="Constant">      &lt;global-keypress&gt;</span>
+    <span class="Delimiter">}</span>
+    <span class="Delimiter">{</span>
+      c:address:character<span class="Special"> &lt;- </span>maybe-convert e:event, <span class="Constant">text:variant</span>
+      <span class="muControl">break-unless</span> c
+<span class="Constant">      &lt;global-type&gt;</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># 'touch' event - send to both sides, see what picks it up</span>
+    <span class="Delimiter">{</span>
+      t:address:touch-event<span class="Special"> &lt;- </span>maybe-convert e:event, <span class="Constant">touch:variant</span>
+      <span class="muControl">break-unless</span> t
+      <span class="Comment"># ignore all but 'left-click' events for now</span>
+      <span class="Comment"># todo: test this</span>
+      touch-type:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">type:offset</span>
+      is-left-click?:boolean<span class="Special"> &lt;- </span>equal touch-type, <span class="Constant">65513/mouse-left</span>
+      <span class="muControl">loop-unless</span> is-left-click?, <span class="Constant">+next-event:label</span>
+      <span class="Comment"># later exceptions for non-editor touches will go here</span>
+<span class="Constant">      &lt;global-touch&gt;</span>
+      <span class="Comment"># send to both editors</span>
+      _<span class="Special"> &lt;- </span>move-cursor-in-editor screen, recipes, *t
+      *sandbox-in-focus?<span class="Special"> &lt;- </span>move-cursor-in-editor screen, current-sandbox, *t
+      screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+      <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># 'resize' event - redraw editor</span>
+    <span class="Comment"># todo: test this after supporting resize in assume-console</span>
+    <span class="Delimiter">{</span>
+      r:address:resize-event<span class="Special"> &lt;- </span>maybe-convert e:event, <span class="Constant">resize:variant</span>
+      <span class="muControl">break-unless</span> r
+      <span class="Comment"># if more events, we're still resizing; wait until we stop</span>
+      more-events?:boolean<span class="Special"> &lt;- </span>has-more-events? console
+      <span class="Delimiter">{</span>
+        <span class="muControl">break-unless</span> more-events?
+        render-all-on-no-more-events?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>  <span class="Comment"># no rendering now, full rendering on some future event</span>
+      <span class="Delimiter">}</span>
+      <span class="Delimiter">{</span>
+        <span class="muControl">break-if</span> more-events?
+        env<span class="Special"> &lt;- </span>resize screen, env
+        screen<span class="Special"> &lt;- </span>render-all screen, env
+        render-all-on-no-more-events?<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>  <span class="Comment"># full render done</span>
+      <span class="Delimiter">}</span>
+      <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># if it's not global and not a touch event, send to appropriate editor</span>
+    <span class="Delimiter">{</span>
+      hide-screen screen
+      <span class="Delimiter">{</span>
+        <span class="muControl">break-if</span> *sandbox-in-focus?
+        screen, recipes, render?:boolean<span class="Special"> &lt;- </span>handle-keyboard-event screen, recipes, e:event
+        <span class="Comment"># refresh screen only if no more events</span>
+        <span class="Comment"># if there are more events to process, wait for them to clear up, then make sure you render-all afterward.</span>
+        more-events?:boolean<span class="Special"> &lt;- </span>has-more-events? console
+        <span class="Delimiter">{</span>
+          <span class="muControl">break-unless</span> more-events?
+          render-all-on-no-more-events?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>  <span class="Comment"># no rendering now, full rendering on some future event</span>
+          <span class="muControl">jump</span> <span class="Constant">+finish-event:label</span>
+        <span class="Delimiter">}</span>
+        <span class="Delimiter">{</span>
+          <span class="muControl">break-if</span> more-events?
+          <span class="Delimiter">{</span>
+            <span class="muControl">break-unless</span> render-all-on-no-more-events?
+            <span class="Comment"># no more events, and we have to force render</span>
+            screen<span class="Special"> &lt;- </span>render-all screen, env
+            render-all-on-no-more-events?<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>
+            <span class="muControl">jump</span> <span class="Constant">+finish-event:label</span>
+          <span class="Delimiter">}</span>
+          <span class="Comment"># no more events, no force render</span>
+          <span class="Delimiter">{</span>
+            <span class="muControl">break-unless</span> render?
+            screen<span class="Special"> &lt;- </span>render-recipes screen, env
+            <span class="muControl">jump</span> <span class="Constant">+finish-event:label</span>
+          <span class="Delimiter">}</span>
+        <span class="Delimiter">}</span>
+      <span class="Delimiter">}</span>
+      <span class="Delimiter">{</span>
+        <span class="muControl">break-unless</span> *sandbox-in-focus?
+        screen, current-sandbox, render?:boolean<span class="Special"> &lt;- </span>handle-keyboard-event screen, current-sandbox, e:event
+        <span class="Comment"># refresh screen only if no more events</span>
+        <span class="Comment"># if there are more events to process, wait for them to clear up, then make sure you render-all afterward.</span>
+        more-events?:boolean<span class="Special"> &lt;- </span>has-more-events? console
+        <span class="Delimiter">{</span>
+          <span class="muControl">break-unless</span> more-events?
+          render-all-on-no-more-events?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>  <span class="Comment"># no rendering now, full rendering on some future event</span>
+          <span class="muControl">jump</span> <span class="Constant">+finish-event:label</span>
+        <span class="Delimiter">}</span>
+        <span class="Delimiter">{</span>
+          <span class="muControl">break-if</span> more-events?
+          <span class="Delimiter">{</span>
+            <span class="muControl">break-unless</span> render-all-on-no-more-events?
+            <span class="Comment"># no more events, and we have to force render</span>
+            screen<span class="Special"> &lt;- </span>render-all screen, env
+            render-all-on-no-more-events?<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>
+            <span class="muControl">jump</span> <span class="Constant">+finish-event:label</span>
+          <span class="Delimiter">}</span>
+          <span class="Comment"># no more events, no force render</span>
+          <span class="Delimiter">{</span>
+            <span class="muControl">break-unless</span> render?
+            screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env
+            <span class="muControl">jump</span> <span class="Constant">+finish-event:label</span>
+          <span class="Delimiter">}</span>
+        <span class="Delimiter">}</span>
+      <span class="Delimiter">}</span>
+<span class="Constant">      +finish-event</span>
+      screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+      show-screen screen
+    <span class="Delimiter">}</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> resize [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  clear-screen screen  <span class="Comment"># update screen dimensions</span>
+  width:number<span class="Special"> &lt;- </span>screen-width screen
+  divider:number, _<span class="Special"> &lt;- </span>divide-with-remainder width, <span class="Constant">2</span>
+  <span class="Comment"># update recipe editor</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+  right:address:number<span class="Special"> &lt;- </span>get-address *recipes, <span class="Constant">right:offset</span>
+  *right<span class="Special"> &lt;- </span>subtract divider, <span class="Constant">1</span>
+  <span class="Comment"># reset cursor (later we'll try to preserve its position)</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *recipes, <span class="Constant">cursor-row:offset</span>
+  *cursor-row<span class="Special"> &lt;- </span>copy <span class="Constant">1</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *recipes, <span class="Constant">cursor-column:offset</span>
+  *cursor-column<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="Comment"># update sandbox editor</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  left:address:number<span class="Special"> &lt;- </span>get-address *current-sandbox, <span class="Constant">left:offset</span>
+  right:address:number<span class="Special"> &lt;- </span>get-address *current-sandbox, <span class="Constant">right:offset</span>
+  *left<span class="Special"> &lt;- </span>add divider, <span class="Constant">1</span>
+  *right<span class="Special"> &lt;- </span>subtract width, <span class="Constant">1</span>
+  <span class="Comment"># reset cursor (later we'll try to preserve its position)</span>
+  cursor-row:address:number<span class="Special"> &lt;- </span>get-address *current-sandbox, <span class="Constant">cursor-row:offset</span>
+  *cursor-row<span class="Special"> &lt;- </span>copy <span class="Constant">1</span>
+  cursor-column:address:number<span class="Special"> &lt;- </span>get-address *current-sandbox, <span class="Constant">cursor-column:offset</span>
+  *cursor-column<span class="Special"> &lt;- </span>copy *left
+  <span class="muControl">reply</span> env/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="muScenario">scenario</span> point-at-multiple-editors [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">30/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize both halves of screen</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[def]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># focus on both sides</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    left-click <span class="Constant">1</span>, <span class="Constant">17</span>
+  ]
+  <span class="Comment"># check cursor column in each</span>
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    <span class="Constant">4</span>:address:editor-data<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:programming-environment-data, <span class="Constant">recipes:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">4</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+    <span class="Constant">6</span>:address:editor-data<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:programming-environment-data, <span class="Constant">current-sandbox:offset</span>
+    <span class="Constant">7</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">6</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">7</span><span class="Special"> &lt;- </span><span class="Constant">17</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> edit-multiple-editors [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">30/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize both halves of screen</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[def]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  render-all screen, <span class="Constant">3</span>:address:programming-environment-data
+  <span class="Comment"># type one letter in each of them</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    type <span class="Constant">[0]</span>
+    left-click <span class="Constant">1</span>, <span class="Constant">17</span>
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    <span class="Constant">4</span>:address:editor-data<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:programming-environment-data, <span class="Constant">recipes:offset</span>
+    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">4</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+    <span class="Constant">6</span>:address:editor-data<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:programming-environment-data, <span class="Constant">current-sandbox:offset</span>
+    <span class="Constant">7</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">6</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .  # this line has a different background, but we don't test that yet</span>
+   <span class="Constant"> .a0bc           ┊d1ef          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">2</span>  <span class="Comment"># cursor column of recipe editor</span>
+    <span class="Constant">7</span><span class="Special"> &lt;- </span><span class="Constant">18</span>  <span class="Comment"># cursor column of sandbox editor</span>
+  ]
+  <span class="Comment"># show the cursor at the right window</span>
+  run [
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .a0bc           ┊d1␣f          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> multiple-editors-cover-only-their-own-areas [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">60/width</span>, <span class="Constant">10/height</span>
+  run [
+    <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+    <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[def]</span>
+    <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+    render-all screen, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># divider isn't messed up</span>
+  screen-should-contain [
+   <span class="Constant"> .                                         run (F4)           .</span>
+   <span class="Constant"> .abc                           ┊def                          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                              ┊                             .</span>
+   <span class="Constant"> .                              ┊                             .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-in-focus-keeps-cursor [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">30/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[def]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  render-all screen, <span class="Constant">3</span>:address:programming-environment-data
+  <span class="Comment"># initialize programming environment and highlight cursor</span>
+  assume-console <span class="Constant">[]</span>
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  <span class="Comment"># is cursor at the right place?</span>
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .␣bc            ┊def           .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+  <span class="Comment"># now try typing a letter</span>
+  assume-console [
+    type <span class="Constant">[z]</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  <span class="Comment"># cursor should still be right</span>
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .z␣bc           ┊def           .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> backspace-in-sandbox-editor-joins-lines [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">30/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize sandbox side with two lines</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  render-all screen, <span class="Constant">3</span>:address:programming-environment-data
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .               ┊abc           .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊def           .</span>
+   <span class="Constant"> .               ┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+  <span class="Comment"># position cursor at start of second line and hit backspace</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">16</span>
+    press backspace
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  <span class="Comment"># cursor moves to end of old line</span>
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .               ┊abc␣ef        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+]
+
+<span class="muRecipe">recipe</span> render-all [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  trace <span class="Constant">10</span>, <span class="Constant">[app]</span>, <span class="Constant">[render all]</span>
+  hide-screen screen
+  <span class="Comment"># top menu</span>
+  trace <span class="Constant">11</span>, <span class="Constant">[app]</span>, <span class="Constant">[render top menu]</span>
+  width:number<span class="Special"> &lt;- </span>screen-width screen
+  draw-horizontal screen, <span class="Constant">0</span>, <span class="Constant">0/left</span>, width, <span class="Constant">32/space</span>, <span class="Constant">0/black</span>, <span class="Constant">238/grey</span>
+  button-start:number<span class="Special"> &lt;- </span>subtract width, <span class="Constant">20</span>
+  button-on-screen?:boolean<span class="Special"> &lt;- </span>greater-or-equal button-start, <span class="Constant">0</span>
+  assert button-on-screen?, <span class="Constant">[screen too narrow for menu]</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, <span class="Constant">0/row</span>, button-start
+  run-button:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ run (F4) ]</span>
+  print-string screen, run-button, <span class="Constant">255/white</span>, <span class="Constant">161/reddish</span>
+  <span class="Comment"># dotted line down the middle</span>
+  trace <span class="Constant">11</span>, <span class="Constant">[app]</span>, <span class="Constant">[render divider]</span>
+  divider:number, _<span class="Special"> &lt;- </span>divide-with-remainder width, <span class="Constant">2</span>
+  height:number<span class="Special"> &lt;- </span>screen-height screen
+  draw-vertical screen, divider, <span class="Constant">1/top</span>, height, <span class="Constant">9482/vertical-dotted</span>
+  <span class="Comment">#</span>
+  screen<span class="Special"> &lt;- </span>render-recipes screen, env
+  screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env
+<span class="Constant">  &lt;render-components-end&gt;</span>
+  <span class="Comment">#</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  sandbox-in-focus?:boolean<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox-in-focus?:offset</span>
+  screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, sandbox-in-focus?
+  <span class="Comment">#</span>
+  show-screen screen
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muRecipe">recipe</span> render-recipes [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  trace <span class="Constant">11</span>, <span class="Constant">[app]</span>, <span class="Constant">[render recipes]</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+  <span class="Comment"># render recipes</span>
+  left:number<span class="Special"> &lt;- </span>get *recipes, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *recipes, <span class="Constant">right:offset</span>
+  row:number, column:number, screen<span class="Special"> &lt;- </span>render screen, recipes
+  clear-line-delimited screen, column, right
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+<span class="Constant">  &lt;render-recipe-components-end&gt;</span>
+  <span class="Comment"># draw dotted line after recipes</span>
+  draw-horizontal screen, row, left, right, <span class="Constant">9480/horizontal-dotted</span>
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  clear-screen-from screen, row, left, left, right
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># replaced in a later layer</span>
+<span class="muRecipe">recipe</span> render-sandbox-side [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">right:offset</span>
+  row:number, column:number, screen, current-sandbox<span class="Special"> &lt;- </span>render screen, current-sandbox
+  clear-line-delimited screen, column, right
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  <span class="Comment"># draw solid line after recipes (you'll see why in later layers)</span>
+  draw-horizontal screen, row, left, right, <span class="Constant">9473/horizontal</span>
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  clear-screen-from screen, row, left, left, right
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muRecipe">recipe</span> update-cursor [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  sandbox-in-focus?:boolean<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> sandbox-in-focus?
+    cursor-row:number<span class="Special"> &lt;- </span>get *recipes, <span class="Constant">cursor-row:offset</span>
+    cursor-column:number<span class="Special"> &lt;- </span>get *recipes, <span class="Constant">cursor-column:offset</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> sandbox-in-focus?
+    cursor-row:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">cursor-row:offset</span>
+    cursor-column:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">cursor-column:offset</span>
+  <span class="Delimiter">}</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, cursor-row, cursor-column
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># row, screen &lt;- render-string screen:address, s:address:array:character, left:number, right:number, color:number, row:number</span>
+<span class="Comment"># print a string 's' to 'editor' in 'color' starting at 'row'</span>
+<span class="Comment"># clear rest of last line, move cursor to next line</span>
+<span class="muRecipe">recipe</span> render-string [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  s:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  color:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> s, row/same-as-ingredient:<span class="Constant">5</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+  column:number<span class="Special"> &lt;- </span>copy left
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  i:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  len:number<span class="Special"> &lt;- </span>length *s
+  <span class="Delimiter">{</span>
+<span class="Constant">    +next-character</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal i, len
+    <span class="muControl">break-if</span> done?
+    done?<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+    <span class="muControl">break-if</span> done?
+    c:character<span class="Special"> &lt;- </span>index *s, i
+    <span class="Delimiter">{</span>
+      <span class="Comment"># at right? wrap.</span>
+      at-right?:boolean<span class="Special"> &lt;- </span>equal column, right
+      <span class="muControl">break-unless</span> at-right?
+      <span class="Comment"># print wrap icon</span>
+      print-character screen, <span class="Constant">8617/loop-back-to-left</span>, <span class="Constant">245/grey</span>
+      column<span class="Special"> &lt;- </span>copy left
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>  <span class="Comment"># retry i</span>
+    <span class="Delimiter">}</span>
+    i<span class="Special"> &lt;- </span>add i, <span class="Constant">1</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># newline? move to left rather than 0</span>
+      newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+      <span class="muControl">break-unless</span> newline?
+      <span class="Comment"># clear rest of line in this window</span>
+      <span class="Delimiter">{</span>
+        done?:boolean<span class="Special"> &lt;- </span>greater-than column, right
+        <span class="muControl">break-if</span> done?
+        print-character screen, <span class="Constant">32/space</span>
+        column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+        <span class="muControl">loop</span>
+      <span class="Delimiter">}</span>
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      column<span class="Special"> &lt;- </span>copy left
+      screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
+    <span class="Delimiter">}</span>
+    print-character screen, c, color
+    column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  was-at-left?:boolean<span class="Special"> &lt;- </span>equal column, left
+  clear-line-delimited screen, column, right
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> was-at-left?
+    row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  <span class="Delimiter">}</span>
+  move-cursor row, left
+  <span class="muControl">reply</span> row/same-as-ingredient:<span class="Constant">5</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># row, screen &lt;- render-code-string screen:address, s:address:array:character, left:number, right:number, row:number</span>
+<span class="Comment"># like 'render-string' but with colorization for comments like in the editor</span>
+<span class="muRecipe">recipe</span> render-code-string [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  s:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> s, row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+  color:number<span class="Special"> &lt;- </span>copy <span class="Constant">7/white</span>
+  column:number<span class="Special"> &lt;- </span>copy left
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  i:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  len:number<span class="Special"> &lt;- </span>length *s
+  <span class="Delimiter">{</span>
+<span class="Constant">    +next-character</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal i, len
+    <span class="muControl">break-if</span> done?
+    done?<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+    <span class="muControl">break-if</span> done?
+    c:character<span class="Special"> &lt;- </span>index *s, i
+    <span class="Constant">&lt;character-c-received&gt;</span>  <span class="Comment"># only line different from render-string</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># at right? wrap.</span>
+      at-right?:boolean<span class="Special"> &lt;- </span>equal column, right
+      <span class="muControl">break-unless</span> at-right?
+      <span class="Comment"># print wrap icon</span>
+      print-character screen, <span class="Constant">8617/loop-back-to-left</span>, <span class="Constant">245/grey</span>
+      column<span class="Special"> &lt;- </span>copy left
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>  <span class="Comment"># retry i</span>
+    <span class="Delimiter">}</span>
+    i<span class="Special"> &lt;- </span>add i, <span class="Constant">1</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># newline? move to left rather than 0</span>
+      newline?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
+      <span class="muControl">break-unless</span> newline?
+      <span class="Comment"># clear rest of line in this window</span>
+      <span class="Delimiter">{</span>
+        done?:boolean<span class="Special"> &lt;- </span>greater-than column, right
+        <span class="muControl">break-if</span> done?
+        print-character screen, <span class="Constant">32/space</span>
+        column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+        <span class="muControl">loop</span>
+      <span class="Delimiter">}</span>
+      row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+      column<span class="Special"> &lt;- </span>copy left
+      screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+      <span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
+    <span class="Delimiter">}</span>
+    print-character screen, c, color
+    column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  was-at-left?:boolean<span class="Special"> &lt;- </span>equal column, left
+  clear-line-delimited screen, column, right
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> was-at-left?
+    row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  <span class="Delimiter">}</span>
+  move-cursor row, left
+  <span class="muControl">reply</span> row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># ctrl-l - redraw screen (just in case it printed junk somehow)</span>
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-type&gt;</span> [
+  <span class="Delimiter">{</span>
+    redraw-screen?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">12/ctrl-l</span>
+    <span class="muControl">break-unless</span> redraw-screen?
+    screen<span class="Special"> &lt;- </span>render-all screen, env:address:programming-environment-data
+    sync-screen screen
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># ctrl-n - switch focus</span>
+<span class="Comment"># todo: test this</span>
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-type&gt;</span> [
+  <span class="Delimiter">{</span>
+    switch-side?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">14/ctrl-n</span>
+    <span class="muControl">break-unless</span> switch-side?
+    *sandbox-in-focus?<span class="Special"> &lt;- </span>not *sandbox-in-focus?
+    screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># ctrl-x - maximize/unmaximize the side with focus</span>
+
+<span class="muScenario">scenario</span> maximize-side [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">30/width</span>, <span class="Constant">5/height</span>
+  <span class="Comment"># initialize both halves of screen</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[def]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  screen<span class="Special"> &lt;- </span>render-all screen, <span class="Constant">3</span>:address:programming-environment-data
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .abc            ┊def           .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+  <span class="Comment"># hit ctrl-x</span>
+  assume-console [
+    press ctrl-x
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># only left side visible</span>
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .abc                           .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .                              .</span>
+  ]
+  <span class="Comment"># hit any key to toggle back</span>
+  assume-console [
+    press ctrl-x
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .           run (F4)           .</span>
+   <span class="Constant"> .abc            ┊def           .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .               ┊              .</span>
+  ]
+]
+
+<span class="CommentedCode">#? # ctrl-t - browse trace</span>
+<span class="CommentedCode">#? after &lt;global-type&gt; [</span>
+<span class="CommentedCode">#?   {</span>
+<span class="CommentedCode">#?     browse-trace?:boolean &lt;- equal *c, 20/ctrl-t</span>
+<span class="CommentedCode">#?     break-unless browse-trace?</span>
+<span class="CommentedCode">#?     $browse-trace</span>
+<span class="CommentedCode">#?     screen &lt;- render-all screen, env:address:programming-environment-data</span>
+<span class="CommentedCode">#?     loop +next-event:label</span>
+<span class="CommentedCode">#?   }</span>
+<span class="CommentedCode">#? ]</span>
+
+<span class="muData">container</span> programming-environment-data [
+  maximized?:boolean
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-type&gt;</span> [
+  <span class="Delimiter">{</span>
+    maximize?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">24/ctrl-x</span>
+    <span class="muControl">break-unless</span> maximize?
+    screen, console<span class="Special"> &lt;- </span>maximize screen, console, env:address:programming-environment-data
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> maximize [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  console:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  hide-screen screen
+  <span class="Comment"># maximize one of the sides</span>
+  maximized?:address:boolean<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">maximized?:offset</span>
+  *maximized?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>
+  <span class="Comment">#</span>
+  sandbox-in-focus?:boolean<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox-in-focus?:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> sandbox-in-focus?
+    editor:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+    right:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">right:offset</span>
+    *right<span class="Special"> &lt;- </span>screen-width screen
+    *right<span class="Special"> &lt;- </span>subtract *right, <span class="Constant">1</span>
+    screen<span class="Special"> &lt;- </span>render-recipes screen, env
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> sandbox-in-focus?
+    editor:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+    left:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">left:offset</span>
+    *left<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+    screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env
+  <span class="Delimiter">}</span>
+  show-screen screen
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, console/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="Comment"># when maximized, wait for any event and simply unmaximize</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-event&gt;</span> [
+  <span class="Delimiter">{</span>
+    maximized?:address:boolean<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">maximized?:offset</span>
+    <span class="muControl">break-unless</span> *maximized?
+    *maximized?<span class="Special"> &lt;- </span>copy <span class="Constant">0/false</span>
+    <span class="Comment"># undo maximize</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-if</span> *sandbox-in-focus?
+      editor:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+      right:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">right:offset</span>
+      *right<span class="Special"> &lt;- </span>screen-width screen
+      *right<span class="Special"> &lt;- </span>divide *right, <span class="Constant">2</span>
+      *right<span class="Special"> &lt;- </span>subtract *right, <span class="Constant">1</span>
+    <span class="Delimiter">}</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-unless</span> *sandbox-in-focus?
+      editor:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+      left:address:number<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">left:offset</span>
+      *left<span class="Special"> &lt;- </span>screen-width screen
+      *left<span class="Special"> &lt;- </span>divide *left, <span class="Constant">2</span>
+      *left<span class="Special"> &lt;- </span>add *left, <span class="Constant">1</span>
+    <span class="Delimiter">}</span>
+    render-all screen, env
+    show-screen screen
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="SalientComment">## helpers</span>
+
+<span class="muRecipe">recipe</span> draw-vertical [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  col:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  y:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  bottom:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  style:character, style-found?:boolean<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> style-found?
+    style<span class="Special"> &lt;- </span>copy <span class="Constant">9474/vertical</span>
+  <span class="Delimiter">}</span>
+  color:number, color-found?:boolean<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># default color to white</span>
+    <span class="muControl">break-if</span> color-found?
+    color<span class="Special"> &lt;- </span>copy <span class="Constant">245/grey</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    continue?:boolean<span class="Special"> &lt;- </span>lesser-than y, bottom
+    <span class="muControl">break-unless</span> continue?
+    screen<span class="Special"> &lt;- </span>move-cursor screen, y, col
+    print-character screen, style, color
+    y<span class="Special"> &lt;- </span>add y, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/005-sandbox.mu.html b/html/edit/005-sandbox.mu.html
new file mode 100644
index 00000000..7115ca00
--- /dev/null
+++ b/html/edit/005-sandbox.mu.html
@@ -0,0 +1,555 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/005-sandbox.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muScenario { color: #00af00; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muRecipe { color: #ff8700; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## running code from the editor and creating sandboxes</span>
+<span class="Comment">#</span>
+<span class="Comment"># Running code in the sandbox editor prepends its contents to a list of</span>
+<span class="Comment"># (non-editable) sandboxes below the editor, showing the result and a maybe</span>
+<span class="Comment"># few other things.</span>
+
+<span class="muData">container</span> programming-environment-data [
+  sandbox:address:sandbox-data  <span class="Comment"># list of sandboxes, from top to bottom</span>
+]
+
+<span class="muData">container</span> sandbox-data [
+  data:address:array:character
+  response:address:array:character
+  expected-response:address:array:character
+  <span class="Comment"># coordinates to track clicks</span>
+  starting-row-on-screen:number
+  code-ending-row-on-screen:number  <span class="Comment"># past end of code</span>
+  response-starting-row-on-screen:number
+  screen:address:screen  <span class="Comment"># prints in the sandbox go here</span>
+  next-sandbox:address:sandbox-data
+]
+
+<span class="muScenario">scenario</span> run-and-show-results [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Comment"># recipe editor is empty</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Comment"># sandbox editor contains an instruction without storing outputs</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[divide-with-remainder 11, 3]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the code in the editors</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that screen prints the results</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊divide-with-remainder 11, 3                      .</span>
+   <span class="Constant"> .                                                  ┊3                                                .</span>
+   <span class="Constant"> .                                                  ┊2                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">7/white</span>, [
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                   divide-with-remainder 11, 3                      .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">245/grey</span>, [
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊3                                                .</span>
+   <span class="Constant"> .                                                  ┊2                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  <span class="Comment"># run another command</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">80</span>
+    type <span class="Constant">[add 2, 2]</span>
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that screen prints the results</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊add 2, 2                                         .</span>
+   <span class="Constant"> .                                                  ┊4                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊divide-with-remainder 11, 3                      .</span>
+   <span class="Constant"> .                                                  ┊3                                                .</span>
+   <span class="Constant"> .                                                  ┊2                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="Comment"># hook into event-loop recipe: read non-unicode keypress from k, process it if</span>
+<span class="Comment"># necessary, then go to next level</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-keypress&gt;</span> [
+  <span class="Comment"># F4? load all code and run all sandboxes.</span>
+  <span class="Delimiter">{</span>
+    do-run?:boolean<span class="Special"> &lt;- </span>equal *k, <span class="Constant">65532/F4</span>
+    <span class="muControl">break-unless</span> do-run?
+    status:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[running...  ]</span>
+    screen<span class="Special"> &lt;- </span>update-status screen, status, <span class="Constant">245/grey</span>
+    error?:boolean, env, screen<span class="Special"> &lt;- </span>run-sandboxes env, screen
+    <span class="Comment"># F4 might update warnings and results on both sides</span>
+    screen<span class="Special"> &lt;- </span>render-all screen, env
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-if</span> error?
+      status:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[            ]</span>
+      screen<span class="Special"> &lt;- </span>update-status screen, status, <span class="Constant">245/grey</span>
+    <span class="Delimiter">}</span>
+    screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> run-sandboxes [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  stop?:boolean, env, screen<span class="Special"> &lt;- </span>update-recipes env, screen
+  <span class="muControl">reply-if</span> stop?, <span class="Constant">1/errors-found</span>, env/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+  <span class="Comment"># check contents of right editor (sandbox)</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  <span class="Delimiter">{</span>
+    sandbox-contents:address:array:character<span class="Special"> &lt;- </span>editor-contents current-sandbox
+    <span class="muControl">break-unless</span> sandbox-contents
+    <span class="Comment"># if contents exist, first save them</span>
+    <span class="Comment"># run them and turn them into a new sandbox-data</span>
+    new-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>new <span class="Constant">sandbox-data:type</span>
+    data:address:address:array:character<span class="Special"> &lt;- </span>get-address *new-sandbox, <span class="Constant">data:offset</span>
+    *data<span class="Special"> &lt;- </span>copy sandbox-contents
+    <span class="Comment"># push to head of sandbox list</span>
+    dest:address:address:sandbox-data<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">sandbox:offset</span>
+    next:address:address:sandbox-data<span class="Special"> &lt;- </span>get-address *new-sandbox, <span class="Constant">next-sandbox:offset</span>
+    *next<span class="Special"> &lt;- </span>copy *dest
+    *dest<span class="Special"> &lt;- </span>copy new-sandbox
+    <span class="Comment"># clear sandbox editor</span>
+    init:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *current-sandbox, <span class="Constant">data:offset</span>
+    *init<span class="Special"> &lt;- </span>push-duplex <span class="Constant">167/§</span>, <span class="Constant">0/tail</span>
+    top-of-screen:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *current-sandbox, <span class="Constant">top-of-screen:offset</span>
+    *top-of-screen<span class="Special"> &lt;- </span>copy *init
+  <span class="Delimiter">}</span>
+  <span class="Comment"># save all sandboxes before running, just in case we die when running</span>
+  save-sandboxes env
+  <span class="Comment"># run all sandboxes</span>
+  curr:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> curr
+    update-sandbox curr
+    curr<span class="Special"> &lt;- </span>get *curr, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> <span class="Constant">0/no-errors-found</span>, env/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="Comment"># copy code from recipe editor, persist, load into mu</span>
+<span class="Comment"># replaced in a later layer</span>
+<span class="muRecipe">recipe</span> update-recipes [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+  in:address:array:character<span class="Special"> &lt;- </span>editor-contents recipes
+  save <span class="Constant">[recipes.mu]</span>, in
+  reload in
+  <span class="muControl">reply</span> <span class="Constant">0/no-errors-found</span>, env/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="Comment"># replaced in a later layer</span>
+<span class="muRecipe">recipe</span> update-sandbox [
+  <span class="Constant">local-scope</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  data:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">data:offset</span>
+  response:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">response:offset</span>
+  fake-screen:address:address:screen<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">screen:offset</span>
+  *response, _, *fake-screen<span class="Special"> &lt;- </span>run-interactive data
+]
+
+<span class="muRecipe">recipe</span> update-status [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  msg:address:array:character<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  color:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, <span class="Constant">0</span>, <span class="Constant">2</span>
+  screen<span class="Special"> &lt;- </span>print-string screen, msg, color, <span class="Constant">238/grey/background</span>
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muRecipe">recipe</span> save-sandboxes [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  <span class="Comment"># first clear previous versions, in case we deleted some sandbox</span>
+  $system <span class="Constant">[rm lesson/[0-9]</span>* &gt;/dev/null <span class="Constant">2</span>&gt;/dev/null]  <span class="Comment"># some shells can't handle '&gt;&amp;'</span>
+  curr:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+  suffix:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[.out]</span>
+  idx:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> curr
+    data:address:array:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">data:offset</span>
+    filename:address:array:character<span class="Special"> &lt;- </span>integer-to-decimal-string idx
+    save filename, data
+    <span class="Delimiter">{</span>
+      expected-response:address:array:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">expected-response:offset</span>
+      <span class="muControl">break-unless</span> expected-response
+      filename<span class="Special"> &lt;- </span>string-append filename, suffix
+      save filename, expected-response
+    <span class="Delimiter">}</span>
+    idx<span class="Special"> &lt;- </span>add idx, <span class="Constant">1</span>
+    curr<span class="Special"> &lt;- </span>get *curr, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe!</span> render-sandbox-side [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  trace <span class="Constant">11</span>, <span class="Constant">[app]</span>, <span class="Constant">[render sandbox side]</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  left:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">left:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">right:offset</span>
+  row:number, column:number, screen, current-sandbox<span class="Special"> &lt;- </span>render screen, current-sandbox
+  clear-screen-from screen, row, column, left, right
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  draw-horizontal screen, row, left, right, <span class="Constant">9473/horizontal-double</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+  row, screen<span class="Special"> &lt;- </span>render-sandboxes screen, sandbox, left, right, row
+  clear-rest-of-screen screen, row, left, left, right
+  <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muRecipe">recipe</span> render-sandboxes [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> sandbox, row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  at-bottom?:boolean<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+  <span class="muControl">reply-if</span> at-bottom?:boolean, row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+  <span class="Comment"># render sandbox menu</span>
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, left
+  clear-line-delimited screen, left, right
+  print-character screen, <span class="Constant">120/x</span>, <span class="Constant">245/grey</span>
+  <span class="Comment"># save menu row so we can detect clicks to it later</span>
+  starting-row:address:number<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+  *starting-row<span class="Special"> &lt;- </span>copy row
+  <span class="Comment"># render sandbox contents</span>
+  row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, left
+  sandbox-data:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">data:offset</span>
+  row, screen<span class="Special"> &lt;- </span>render-code-string screen, sandbox-data, left, right, row
+  code-ending-row:address:number<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">code-ending-row-on-screen:offset</span>
+  *code-ending-row<span class="Special"> &lt;- </span>copy row
+  <span class="Comment"># render sandbox warnings, screen or response, in that order</span>
+  response-starting-row:address:number<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">response-starting-row-on-screen:offset</span>
+  sandbox-response:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">response:offset</span>
+<span class="Constant">  &lt;render-sandbox-results&gt;</span>
+  <span class="Delimiter">{</span>
+    sandbox-screen:address<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">screen:offset</span>
+    empty-screen?:boolean<span class="Special"> &lt;- </span>fake-screen-is-empty? sandbox-screen
+    <span class="muControl">break-if</span> empty-screen?
+    row, screen<span class="Special"> &lt;- </span>render-screen screen, sandbox-screen, left, right, row
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> empty-screen?
+    *response-starting-row<span class="Special"> &lt;- </span>copy row
+<span class="Constant">    &lt;render-sandbox-response&gt;</span>
+    row, screen<span class="Special"> &lt;- </span>render-string screen, sandbox-response, left, right, <span class="Constant">245/grey</span>, row
+  <span class="Delimiter">}</span>
+<span class="Constant">  +render-sandbox-end</span>
+  at-bottom?:boolean<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+  <span class="muControl">reply-if</span> at-bottom?, row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+  <span class="Comment"># draw solid line after sandbox</span>
+  draw-horizontal screen, row, left, right, <span class="Constant">9473/horizontal-double</span>
+  <span class="Comment"># draw next sandbox</span>
+  next-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">next-sandbox:offset</span>
+  row, screen<span class="Special"> &lt;- </span>render-sandboxes screen, next-sandbox, left, right, row
+  <span class="muControl">reply</span> row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># assumes programming environment has no sandboxes; restores them from previous session</span>
+<span class="muRecipe">recipe</span> restore-sandboxes [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># read all scenarios, pushing them to end of a list of scenarios</span>
+  suffix:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[.out]</span>
+  idx:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  curr:address:address:sandbox-data<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">sandbox:offset</span>
+  <span class="Delimiter">{</span>
+    filename:address:array:character<span class="Special"> &lt;- </span>integer-to-decimal-string idx
+    contents:address:array:character<span class="Special"> &lt;- </span>restore filename
+    <span class="muControl">break-unless</span> contents  <span class="Comment"># stop at first error; assuming file didn't exist</span>
+    <span class="Comment"># create new sandbox for file</span>
+    *curr<span class="Special"> &lt;- </span>new <span class="Constant">sandbox-data:type</span>
+    data:address:address:array:character<span class="Special"> &lt;- </span>get-address **curr, <span class="Constant">data:offset</span>
+    *data<span class="Special"> &lt;- </span>copy contents
+    <span class="Comment"># restore expected output for sandbox if it exists</span>
+    <span class="Delimiter">{</span>
+      filename<span class="Special"> &lt;- </span>string-append filename, suffix
+      contents<span class="Special"> &lt;- </span>restore filename
+      <span class="muControl">break-unless</span> contents
+      expected-response:address:address:array:character<span class="Special"> &lt;- </span>get-address **curr, <span class="Constant">expected-response:offset</span>
+      *expected-response<span class="Special"> &lt;- </span>copy contents
+    <span class="Delimiter">}</span>
+<span class="Constant">    +continue</span>
+    idx<span class="Special"> &lt;- </span>add idx, <span class="Constant">1</span>
+    curr<span class="Special"> &lt;- </span>get-address **curr, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> env/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># row, screen &lt;- render-screen screen:address, sandbox-screen:address, left:number, right:number, row:number</span>
+<span class="Comment"># print the fake sandbox screen to 'screen' with appropriate delimiters</span>
+<span class="Comment"># leave cursor at start of next line</span>
+<span class="muRecipe">recipe</span> render-screen [
+  <span class="Constant">local-scope</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  s:address:screen<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  left:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  right:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="muControl">reply-unless</span> s, row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+  <span class="Comment"># print 'screen:'</span>
+  header:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[screen:]</span>
+  row<span class="Special"> &lt;- </span>render-string screen, header, left, right, <span class="Constant">245/grey</span>, row
+  screen<span class="Special"> &lt;- </span>move-cursor screen, row, left
+  <span class="Comment"># start printing s</span>
+  column:number<span class="Special"> &lt;- </span>copy left
+  s-width:number<span class="Special"> &lt;- </span>screen-width s
+  s-height:number<span class="Special"> &lt;- </span>screen-height s
+  buf:address:array:screen-cell<span class="Special"> &lt;- </span>get *s, <span class="Constant">data:offset</span>
+  stop-printing:number<span class="Special"> &lt;- </span>add left, s-width, <span class="Constant">3</span>
+  max-column:number<span class="Special"> &lt;- </span>min stop-printing, right
+  i:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  len:number<span class="Special"> &lt;- </span>length *buf
+  screen-height:number<span class="Special"> &lt;- </span>screen-height screen
+  <span class="Delimiter">{</span>
+    done?:boolean<span class="Special"> &lt;- </span>greater-or-equal i, len
+    <span class="muControl">break-if</span> done?
+    done?<span class="Special"> &lt;- </span>greater-or-equal row, screen-height
+    <span class="muControl">break-if</span> done?
+    column<span class="Special"> &lt;- </span>copy left
+    screen<span class="Special"> &lt;- </span>move-cursor screen, row, column
+    <span class="Comment"># initial leader for each row: two spaces and a '.'</span>
+    print-character screen, <span class="Constant">32/space</span>, <span class="Constant">245/grey</span>
+    print-character screen, <span class="Constant">32/space</span>, <span class="Constant">245/grey</span>
+    print-character screen, <span class="Constant">46/full-stop</span>, <span class="Constant">245/grey</span>
+    column<span class="Special"> &lt;- </span>add left, <span class="Constant">3</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># print row</span>
+      row-done?:boolean<span class="Special"> &lt;- </span>greater-or-equal column, max-column
+      <span class="muControl">break-if</span> row-done?
+      curr:screen-cell<span class="Special"> &lt;- </span>index *buf, i
+      c:character<span class="Special"> &lt;- </span>get curr, <span class="Constant">contents:offset</span>
+      color:number<span class="Special"> &lt;- </span>get curr, <span class="Constant">color:offset</span>
+      <span class="Delimiter">{</span>
+        <span class="Comment"># damp whites down to grey</span>
+        white?:boolean<span class="Special"> &lt;- </span>equal color, <span class="Constant">7/white</span>
+        <span class="muControl">break-unless</span> white?
+        color<span class="Special"> &lt;- </span>copy <span class="Constant">245/grey</span>
+      <span class="Delimiter">}</span>
+      print-character screen, c, color
+      column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+      i<span class="Special"> &lt;- </span>add i, <span class="Constant">1</span>
+      <span class="muControl">loop</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># print final '.'</span>
+    print-character screen, <span class="Constant">46/full-stop</span>, <span class="Constant">245/grey</span>
+    column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># clear rest of current line</span>
+      line-done?:boolean<span class="Special"> &lt;- </span>greater-than column, right
+      <span class="muControl">break-if</span> line-done?
+      print-character screen, <span class="Constant">32/space</span>
+      column<span class="Special"> &lt;- </span>add column, <span class="Constant">1</span>
+      <span class="muControl">loop</span>
+    <span class="Delimiter">}</span>
+    row<span class="Special"> &lt;- </span>add row, <span class="Constant">1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> row/same-as-ingredient:<span class="Constant">4</span>, screen/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muScenario">scenario</span> run-updates-results [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">12/height</span>
+  <span class="Comment"># define a recipe (no indent for the 'add' line below so column numbers are more obvious)</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">z:number &lt;- add 2, 2</span>
+<span class="Constant">]</span>]
+  <span class="Comment"># sandbox editor contains an instruction without storing outputs</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the code in the editors</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that screen prints the results</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .z:number &lt;- add 2, 2                              ┊                                                x.</span>
+   <span class="Constant"> .]                                                 ┊foo                                              .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  <span class="Comment"># make a change (incrementing one of the args to 'add'), then rerun</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">28</span>  <span class="Comment"># one past the value of the second arg</span>
+    press backspace
+    type <span class="Constant">[3]</span>
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that screen updates the result on the right</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .z:number &lt;- add 2, 3                              ┊                                                x.</span>
+   <span class="Constant"> .]                                                 ┊foo                                              .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊5                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-instruction-manages-screen-per-sandbox [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">20/height</span>
+  <span class="Comment"># left editor is empty</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Comment"># right editor contains an instruction</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[print-integer screen:address, 4]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the code in the editor</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that it prints a little toy screen</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊print-integer screen:address, 4                  .</span>
+   <span class="Constant"> .                                                  ┊screen:                                          .</span>
+   <span class="Constant"> .                                                  ┊  .4                             .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muRecipe">recipe</span> editor-contents [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  buf:address:buffer<span class="Special"> &lt;- </span>new-buffer <span class="Constant">80</span>
+  curr:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  <span class="Comment"># skip § sentinel</span>
+  assert curr, <span class="Constant">[editor without data is illegal; must have at least a sentinel]</span>
+  curr<span class="Special"> &lt;- </span>next-duplex curr
+  <span class="muControl">reply-unless</span> curr, <span class="Constant">0</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> curr
+    c:character<span class="Special"> &lt;- </span>get *curr, <span class="Constant">value:offset</span>
+    buffer-append buf, c
+    curr<span class="Special"> &lt;- </span>next-duplex curr
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  result:address:array:character<span class="Special"> &lt;- </span>buffer-to-array buf
+  <span class="muControl">reply</span> result
+]
+
+<span class="muScenario">scenario</span> editor-provides-edited-contents [
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    type <span class="Constant">[def]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:address:array:character<span class="Special"> &lt;- </span>editor-contents <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">4</span>:array:character<span class="Special"> &lt;- </span>copy *<span class="Constant">3</span>:address:array:character
+  ]
+  memory-should-contain [
+    <span class="Constant">4</span>:string<span class="Special"> &lt;- </span><span class="Constant">[abdefc]</span>
+  ]
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/006-sandbox-edit.mu.html b/html/edit/006-sandbox-edit.mu.html
new file mode 100644
index 00000000..e5c2d430
--- /dev/null
+++ b/html/edit/006-sandbox-edit.mu.html
@@ -0,0 +1,212 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/006-sandbox-edit.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muScenario { color: #00af00; }
+.Special { color: #ff6060; }
+.muRecipe { color: #ff8700; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## editing sandboxes after they've been created</span>
+
+<span class="muScenario">scenario</span> clicking-on-a-sandbox-moves-it-to-editor [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">40/width</span>, <span class="Constant">10/height</span>
+  <span class="Comment"># basic recipe</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  add 2, 2</span>
+<span class="Constant">]</span>]
+  <span class="Comment"># run it</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  assume-console [
+    press F4
+  ]
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  add 2, 2          ┊                  x.</span>
+   <span class="Constant"> .]                   ┊foo                .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4                  .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># click somewhere on the sandbox</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">30</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># it pops back into editor</span>
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊foo                .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  add 2, 2          ┊                   .</span>
+   <span class="Constant"> .]                   ┊                   .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                   .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊0foo               .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  add 2, 2          ┊                   .</span>
+   <span class="Constant"> .]                   ┊                   .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                   .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-touch&gt;</span> [
+  <span class="Comment"># right side of screen and below sandbox editor? pop appropriate sandbox</span>
+  <span class="Comment"># contents back into sandbox editor provided it's empty</span>
+  <span class="Delimiter">{</span>
+    sandbox-left-margin:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">left:offset</span>
+    click-column:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">column:offset</span>
+    on-sandbox-side?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-column, sandbox-left-margin
+    <span class="muControl">break-unless</span> on-sandbox-side?
+    first-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+    <span class="muControl">break-unless</span> first-sandbox
+    first-sandbox-begins:number<span class="Special"> &lt;- </span>get *first-sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+    click-row:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">row:offset</span>
+    below-sandbox-editor?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, first-sandbox-begins
+    <span class="muControl">break-unless</span> below-sandbox-editor?
+    empty-sandbox-editor?:boolean<span class="Special"> &lt;- </span>empty-editor? current-sandbox
+    <span class="muControl">break-unless</span> empty-sandbox-editor?  <span class="Comment"># make the user hit F4 before editing a new sandbox</span>
+    <span class="Comment"># identify the sandbox to edit and remove it from the sandbox list</span>
+    sandbox:address:sandbox-data<span class="Special"> &lt;- </span>extract-sandbox env, click-row
+    text:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">data:offset</span>
+    current-sandbox<span class="Special"> &lt;- </span>insert-text current-sandbox, text
+    hide-screen screen
+    screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env
+    screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+    show-screen screen
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> empty-editor? [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  head:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">data:offset</span>
+  first:address:duplex-list<span class="Special"> &lt;- </span>next-duplex head
+  result:boolean<span class="Special"> &lt;- </span>not first
+  <span class="muControl">reply</span> result
+]
+
+<span class="muRecipe">recipe</span> extract-sandbox [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  click-row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># assert click-row &gt;= sandbox.starting-row-on-screen</span>
+  sandbox:address:address:sandbox-data<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">sandbox:offset</span>
+  start:number<span class="Special"> &lt;- </span>get **sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+  clicked-on-sandboxes?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, start
+  assert clicked-on-sandboxes?, <span class="Constant">[extract-sandbox called on click to sandbox editor]</span>
+  <span class="Delimiter">{</span>
+    next-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get **sandbox, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">break-unless</span> next-sandbox
+    <span class="Comment"># if click-row &lt; sandbox.next-sandbox.starting-row-on-screen, break</span>
+    next-start:number<span class="Special"> &lt;- </span>get *next-sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+    found?:boolean<span class="Special"> &lt;- </span>lesser-than click-row, next-start
+    <span class="muControl">break-if</span> found?
+    sandbox<span class="Special"> &lt;- </span>get-address **sandbox, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># snip sandbox out of its list</span>
+  result:address:sandbox-data<span class="Special"> &lt;- </span>copy *sandbox
+  *sandbox<span class="Special"> &lt;- </span>copy next-sandbox
+  <span class="Comment"># position cursor in sandbox editor</span>
+  sandbox-in-focus?:address:boolean<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">sandbox-in-focus?:offset</span>
+  *sandbox-in-focus?<span class="Special"> &lt;- </span>copy <span class="Constant">1/true</span>
+  <span class="muControl">reply</span> result
+]
+
+<span class="muScenario">scenario</span> sandbox-with-print-can-be-edited [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">20/height</span>
+  <span class="Comment"># left editor is empty</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Comment"># right editor contains an instruction</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[print-integer screen:address, 4]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the sandbox</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊print-integer screen:address, 4                  .</span>
+   <span class="Constant"> .                                                  ┊screen:                                          .</span>
+   <span class="Constant"> .                                                  ┊  .4                             .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊  .                              .               .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  <span class="Comment"># edit the sandbox</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">70</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊print-integer screen:address, 4                  .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/007-sandbox-delete.mu.html b/html/edit/007-sandbox-delete.mu.html
new file mode 100644
index 00000000..5f3cdfcf
--- /dev/null
+++ b/html/edit/007-sandbox-delete.mu.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/007-sandbox-delete.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muScenario { color: #00af00; }
+.Special { color: #ff6060; }
+.muRecipe { color: #ff8700; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## deleting sandboxes</span>
+
+<span class="muScenario">scenario</span> deleting-sandboxes [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run a few commands</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">80</span>
+    type <span class="Constant">[divide-with-remainder 11, 3]</span>
+    press F4
+    type <span class="Constant">[add 2, 2]</span>
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊add 2, 2                                         .</span>
+   <span class="Constant"> .                                                  ┊4                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊divide-with-remainder 11, 3                      .</span>
+   <span class="Constant"> .                                                  ┊3                                                .</span>
+   <span class="Constant"> .                                                  ┊2                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  <span class="Comment"># delete second sandbox</span>
+  assume-console [
+    left-click <span class="Constant">7</span>, <span class="Constant">99</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊add 2, 2                                         .</span>
+   <span class="Constant"> .                                                  ┊4                                                .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  <span class="Comment"># delete first sandbox</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">99</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-touch&gt;</span> [
+  <span class="Comment"># on a sandbox delete icon? process delete</span>
+  <span class="Delimiter">{</span>
+    was-delete?:boolean<span class="Special"> &lt;- </span>delete-sandbox *t, env
+    <span class="muControl">break-unless</span> was-delete?
+    hide-screen screen
+    screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env
+    screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+    show-screen screen
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># was-deleted?:boolean &lt;- delete-sandbox t:touch-event, env:address:programming-environment-data</span>
+<span class="muRecipe">recipe</span> delete-sandbox [
+  <span class="Constant">local-scope</span>
+  t:touch-event<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  click-column:number<span class="Special"> &lt;- </span>get t, <span class="Constant">column:offset</span>
+  current-sandbox:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">current-sandbox:offset</span>
+  right:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">right:offset</span>
+  at-right?:boolean<span class="Special"> &lt;- </span>equal click-column, right
+  <span class="muControl">reply-unless</span> at-right?, <span class="Constant">0/false</span>
+  click-row:number<span class="Special"> &lt;- </span>get t, <span class="Constant">row:offset</span>
+  prev:address:address:sandbox-data<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">sandbox:offset</span>
+  curr:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> curr
+    <span class="Comment"># more sandboxes to check</span>
+    <span class="Delimiter">{</span>
+      target-row:number<span class="Special"> &lt;- </span>get *curr, <span class="Constant">starting-row-on-screen:offset</span>
+      delete-curr?:boolean<span class="Special"> &lt;- </span>equal target-row, click-row
+      <span class="muControl">break-unless</span> delete-curr?
+      <span class="Comment"># delete this sandbox, rerender and stop</span>
+      *prev<span class="Special"> &lt;- </span>get *curr, <span class="Constant">next-sandbox:offset</span>
+      <span class="muControl">reply</span> <span class="Constant">1/true</span>
+    <span class="Delimiter">}</span>
+    prev<span class="Special"> &lt;- </span>get-address *curr, <span class="Constant">next-sandbox:offset</span>
+    curr<span class="Special"> &lt;- </span>get *curr, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> <span class="Constant">0/false</span>
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/008-sandbox-test.mu.html b/html/edit/008-sandbox-test.mu.html
new file mode 100644
index 00000000..0cdd6a08
--- /dev/null
+++ b/html/edit/008-sandbox-test.mu.html
@@ -0,0 +1,211 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/008-sandbox-test.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muScenario { color: #00af00; }
+.Special { color: #ff6060; }
+.muRecipe { color: #ff8700; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## clicking on sandbox results to 'fix' them and turn sandboxes into tests</span>
+
+<span class="muScenario">scenario</span> sandbox-click-on-result-toggles-color-to-green [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">40/width</span>, <span class="Constant">10/height</span>
+  <span class="Comment"># basic recipe</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  add 2, 2</span>
+<span class="Constant">]</span>]
+  <span class="Comment"># run it</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  assume-console [
+    press F4
+  ]
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  add 2, 2          ┊                  x.</span>
+   <span class="Constant"> .]                   ┊foo                .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4                  .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># click on the '4' in the result</span>
+  assume-console [
+    left-click <span class="Constant">5</span>, <span class="Constant">21</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># color toggles to green</span>
+  screen-should-contain-in-color <span class="Constant">2/green</span>, [
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                     4                  .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+  ]
+  <span class="Comment"># cursor should remain unmoved</span>
+  run [
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .␣                   ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  add 2, 2          ┊                  x.</span>
+   <span class="Constant"> .]                   ┊foo                .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4                  .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># now change the second arg of the 'add'</span>
+  <span class="Comment"># then rerun</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">11</span>  <span class="Comment"># cursor to end of line</span>
+    press backspace
+    type <span class="Constant">[3]</span>
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># result turns red</span>
+  screen-should-contain-in-color <span class="Constant">1/red</span>, [
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                     5                  .</span>
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                                        .</span>
+  ]
+]
+
+<span class="Comment"># clicks on sandbox responses save it as 'expected'</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-touch&gt;</span> [
+  <span class="Comment"># right side of screen? check if it's inside the output of any sandbox</span>
+  <span class="Delimiter">{</span>
+    sandbox-left-margin:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">left:offset</span>
+    click-column:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">column:offset</span>
+    on-sandbox-side?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-column, sandbox-left-margin
+    <span class="muControl">break-unless</span> on-sandbox-side?
+    first-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+    <span class="muControl">break-unless</span> first-sandbox
+    first-sandbox-begins:number<span class="Special"> &lt;- </span>get *first-sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+    click-row:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">row:offset</span>
+    below-sandbox-editor?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, first-sandbox-begins
+    <span class="muControl">break-unless</span> below-sandbox-editor?
+    <span class="Comment"># identify the sandbox whose output is being clicked on</span>
+    sandbox:address:sandbox-data<span class="Special"> &lt;- </span>find-click-in-sandbox-output env, click-row
+    <span class="muControl">break-unless</span> sandbox
+    <span class="Comment"># toggle its expected-response, and save session</span>
+    sandbox<span class="Special"> &lt;- </span>toggle-expected-response sandbox
+    save-sandboxes env
+    hide-screen screen
+    screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env, <span class="Constant">1/clear</span>
+    screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+    <span class="Comment"># no change in cursor</span>
+    show-screen screen
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> find-click-in-sandbox-output [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  click-row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># assert click-row &gt;= sandbox.starting-row-on-screen</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+  start:number<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+  clicked-on-sandboxes?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, start
+  assert clicked-on-sandboxes?, <span class="Constant">[extract-sandbox called on click to sandbox editor]</span>
+  <span class="Comment"># while click-row &lt; sandbox.next-sandbox.starting-row-on-screen</span>
+  <span class="Delimiter">{</span>
+    next-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">break-unless</span> next-sandbox
+    next-start:number<span class="Special"> &lt;- </span>get *next-sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+    found?:boolean<span class="Special"> &lt;- </span>lesser-than click-row, next-start
+    <span class="muControl">break-if</span> found?
+    sandbox<span class="Special"> &lt;- </span>copy next-sandbox
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># return sandbox if click is in its output region</span>
+  response-starting-row:number<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">response-starting-row-on-screen:offset</span>
+  <span class="muControl">reply-unless</span> response-starting-row, <span class="Constant">0/no-click-in-sandbox-output</span>
+  click-in-response?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, response-starting-row
+  <span class="muControl">reply-unless</span> click-in-response?, <span class="Constant">0/no-click-in-sandbox-output</span>
+  <span class="muControl">reply</span> sandbox
+]
+
+<span class="muRecipe">recipe</span> toggle-expected-response [
+  <span class="Constant">local-scope</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  expected-response:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">expected-response:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if expected-response is set, reset</span>
+    <span class="muControl">break-unless</span> *expected-response
+    *expected-response<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+    <span class="muControl">reply</span> sandbox/same-as-ingredient:<span class="Constant">0</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if not, current response is the expected response</span>
+  response:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">response:offset</span>
+  *expected-response<span class="Special"> &lt;- </span>copy response
+  <span class="muControl">reply</span> sandbox/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="Comment"># when rendering a sandbox, color it in red/green if expected response exists</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;render-sandbox-response&gt;</span> [
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> sandbox-response
+    expected-response:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">expected-response:offset</span>
+    <span class="muControl">break-unless</span> expected-response  <span class="Comment"># fall-through to print in grey</span>
+    response-is-expected?:boolean<span class="Special"> &lt;- </span>string-equal expected-response, sandbox-response
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-if</span> response-is-expected?:boolean
+      row, screen<span class="Special"> &lt;- </span>render-string screen, sandbox-response, left, right, <span class="Constant">1/red</span>, row
+    <span class="Delimiter">}</span>
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-unless</span> response-is-expected?:boolean
+      row, screen<span class="Special"> &lt;- </span>render-string screen, sandbox-response, left, right, <span class="Constant">2/green</span>, row
+    <span class="Delimiter">}</span>
+    <span class="muControl">jump</span> <span class="Constant">+render-sandbox-end:label</span>
+  <span class="Delimiter">}</span>
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/009-sandbox-trace.mu.html b/html/edit/009-sandbox-trace.mu.html
new file mode 100644
index 00000000..b73fbee6
--- /dev/null
+++ b/html/edit/009-sandbox-trace.mu.html
@@ -0,0 +1,248 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/009-sandbox-trace.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muScenario { color: #00af00; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muRecipe { color: #ff8700; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## clicking on the code typed into a sandbox toggles its trace</span>
+
+<span class="muScenario">scenario</span> sandbox-click-on-code-toggles-app-trace [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">40/width</span>, <span class="Constant">10/height</span>
+  <span class="Comment"># basic recipe</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  stash [abc]</span>
+]]
+  <span class="Comment"># run it</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  assume-console [
+    press F4
+  ]
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  stash [abc]       ┊                  x.</span>
+   <span class="Constant"> .]                   ┊foo                .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># click on the 'foo' line in the sandbox</span>
+  assume-console [
+    left-click <span class="Constant">4</span>, <span class="Constant">21</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  <span class="Comment"># trace now printed and cursor shouldn't have budged</span>
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .␣                   ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  stash [abc]       ┊                  x.</span>
+   <span class="Constant"> .]                   ┊foo                .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊abc                .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">245/grey</span>, [
+   <span class="Constant"> .                                        .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                  x.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊abc                .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># click again on the same region</span>
+  assume-console [
+    left-click <span class="Constant">4</span>, <span class="Constant">25</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+    print-character screen:address, <span class="Constant">9251/␣/cursor</span>
+  ]
+  <span class="Comment"># trace hidden again</span>
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .␣                   ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  stash [abc]       ┊                  x.</span>
+   <span class="Constant"> .]                   ┊foo                .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> sandbox-shows-app-trace-and-result [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">40/width</span>, <span class="Constant">10/height</span>
+  <span class="Comment"># basic recipe</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  stash [abc]</span>
+  add <span class="Constant">2</span>, <span class="Constant">2</span>
+]]
+  <span class="Comment"># run it</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  assume-console [
+    press F4
+  ]
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  stash [abc]       ┊                  x.</span>
+   <span class="Constant"> .  add 2, 2          ┊foo                .</span>
+   <span class="Constant"> .]                   ┊4                  .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+  <span class="Comment"># click on the 'foo' line in the sandbox</span>
+  assume-console [
+    left-click <span class="Constant">4</span>, <span class="Constant">21</span>
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># trace now printed</span>
+  screen-should-contain [
+   <span class="Constant"> .                     run (F4)           .</span>
+   <span class="Constant"> .                    ┊                   .</span>
+   <span class="Constant"> .recipe foo [        ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  stash [abc]       ┊                  x.</span>
+   <span class="Constant"> .  add 2, 2          ┊foo                .</span>
+   <span class="Constant"> .]                   ┊abc                .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4                  .</span>
+   <span class="Constant"> .                    ┊━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                    ┊                   .</span>
+  ]
+]
+
+<span class="muData">container</span> sandbox-data [
+  trace:address:array:character
+  display-trace?:boolean
+]
+
+<span class="Comment"># replaced in a later layer</span>
+<span class="muRecipe">recipe!</span> update-sandbox [
+  <span class="Constant">local-scope</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  data:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">data:offset</span>
+  response:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">response:offset</span>
+  trace:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">trace:offset</span>
+  fake-screen:address:address:screen<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">screen:offset</span>
+  *response, _, *fake-screen, *trace<span class="Special"> &lt;- </span>run-interactive data
+]
+
+<span class="Comment"># clicks on sandbox code toggle its display-trace? flag</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;global-touch&gt;</span> [
+  <span class="Comment"># right side of screen? check if it's inside the code of any sandbox</span>
+  <span class="Delimiter">{</span>
+    sandbox-left-margin:number<span class="Special"> &lt;- </span>get *current-sandbox, <span class="Constant">left:offset</span>
+    click-column:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">column:offset</span>
+    on-sandbox-side?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-column, sandbox-left-margin
+    <span class="muControl">break-unless</span> on-sandbox-side?
+    first-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+    <span class="muControl">break-unless</span> first-sandbox
+    first-sandbox-begins:number<span class="Special"> &lt;- </span>get *first-sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+    click-row:number<span class="Special"> &lt;- </span>get *t, <span class="Constant">row:offset</span>
+    below-sandbox-editor?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, first-sandbox-begins
+    <span class="muControl">break-unless</span> below-sandbox-editor?
+    <span class="Comment"># identify the sandbox whose code is being clicked on</span>
+    sandbox:address:sandbox-data<span class="Special"> &lt;- </span>find-click-in-sandbox-code env, click-row
+    <span class="muControl">break-unless</span> sandbox
+    <span class="Comment"># toggle its display-trace? property</span>
+    x:address:boolean<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">display-trace?:offset</span>
+    *x<span class="Special"> &lt;- </span>not *x
+    hide-screen screen
+    screen<span class="Special"> &lt;- </span>render-sandbox-side screen, env, <span class="Constant">1/clear</span>
+    screen<span class="Special"> &lt;- </span>update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
+    <span class="Comment"># no change in cursor</span>
+    show-screen screen
+    <span class="muControl">loop</span> <span class="Constant">+next-event:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">recipe</span> find-click-in-sandbox-code [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  click-row:number<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  <span class="Comment"># assert click-row &gt;= sandbox.starting-row-on-screen</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">sandbox:offset</span>
+  start:number<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+  clicked-on-sandboxes?:boolean<span class="Special"> &lt;- </span>greater-or-equal click-row, start
+  assert clicked-on-sandboxes?, <span class="Constant">[extract-sandbox called on click to sandbox editor]</span>
+  <span class="Comment"># while click-row &lt; sandbox.next-sandbox.starting-row-on-screen</span>
+  <span class="Delimiter">{</span>
+    next-sandbox:address:sandbox-data<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">next-sandbox:offset</span>
+    <span class="muControl">break-unless</span> next-sandbox
+    next-start:number<span class="Special"> &lt;- </span>get *next-sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+    found?:boolean<span class="Special"> &lt;- </span>lesser-than click-row, next-start
+    <span class="muControl">break-if</span> found?
+    sandbox<span class="Special"> &lt;- </span>copy next-sandbox
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># return sandbox if click is in its code region</span>
+  code-ending-row:number<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">code-ending-row-on-screen:offset</span>
+  click-above-response?:boolean<span class="Special"> &lt;- </span>lesser-than click-row, code-ending-row
+  start:number<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">starting-row-on-screen:offset</span>
+  click-below-menu?:boolean<span class="Special"> &lt;- </span>greater-than click-row, start
+  click-on-sandbox-code?:boolean<span class="Special"> &lt;- </span>and click-above-response?, click-below-menu?
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> click-on-sandbox-code?
+    <span class="muControl">reply</span> <span class="Constant">0/no-click-in-sandbox-output</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> sandbox
+]
+
+<span class="Comment"># when rendering a sandbox, dump its trace before response/warning if display-trace? property is set</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;render-sandbox-results&gt;</span> [
+  <span class="Delimiter">{</span>
+    display-trace?:boolean<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">display-trace?:offset</span>
+    <span class="muControl">break-unless</span> display-trace?
+    sandbox-trace:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">trace:offset</span>
+    <span class="muControl">break-unless</span> sandbox-trace  <span class="Comment"># nothing to print; move on</span>
+    row, screen<span class="Special"> &lt;- </span>render-string, screen, sandbox-trace, left, right, <span class="Constant">245/grey</span>, row
+  <span class="Delimiter">}</span>
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/010-warnings.mu.html b/html/edit/010-warnings.mu.html
new file mode 100644
index 00000000..ca9008b7
--- /dev/null
+++ b/html/edit/010-warnings.mu.html
@@ -0,0 +1,428 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/010-warnings.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muRecipe { color: #ff8700; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muScenario { color: #00af00; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## handling malformed programs</span>
+
+<span class="muData">container</span> programming-environment-data [
+  recipe-warnings:address:array:character
+]
+
+<span class="Comment"># copy code from recipe editor, persist, load into mu, save any warnings</span>
+<span class="muRecipe">recipe!</span> update-recipes [
+  <span class="Constant">local-scope</span>
+  env:address:programming-environment-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  screen:address<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  recipes:address:editor-data<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipes:offset</span>
+  in:address:array:character<span class="Special"> &lt;- </span>editor-contents recipes
+  save <span class="Constant">[recipes.mu]</span>, in
+  recipe-warnings:address:address:array:character<span class="Special"> &lt;- </span>get-address *env, <span class="Constant">recipe-warnings:offset</span>
+  *recipe-warnings<span class="Special"> &lt;- </span>reload in
+  <span class="Comment"># if recipe editor has errors, stop</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> *recipe-warnings
+    status:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[errors found]</span>
+    update-status screen, status, <span class="Constant">1/red</span>
+    <span class="muControl">reply</span> <span class="Constant">1/errors-found</span>, env/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+  <span class="Delimiter">}</span>
+  <span class="muControl">reply</span> <span class="Constant">0/no-errors-found</span>, env/same-as-ingredient:<span class="Constant">0</span>, screen/same-as-ingredient:<span class="Constant">1</span>
+]
+
+<span class="muRecipe">before</span> <span class="Constant">&lt;render-components-end&gt;</span> [
+  trace <span class="Constant">11</span>, <span class="Constant">[app]</span>, <span class="Constant">[render status]</span>
+  recipe-warnings:address:array:character<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipe-warnings:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> recipe-warnings
+    status:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[errors found]</span>
+    update-status screen, status, <span class="Constant">1/red</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">before</span> <span class="Constant">&lt;render-recipe-components-end&gt;</span> [
+  <span class="Delimiter">{</span>
+    recipe-warnings:address:array:character<span class="Special"> &lt;- </span>get *env, <span class="Constant">recipe-warnings:offset</span>
+    <span class="muControl">break-unless</span> recipe-warnings
+    row, screen<span class="Special"> &lt;- </span>render-string screen, recipe-warnings, left, right, <span class="Constant">1/red</span>, row
+  <span class="Delimiter">}</span>
+]
+
+<span class="muData">container</span> sandbox-data [
+  warnings:address:array:character
+]
+
+<span class="muRecipe">recipe!</span> update-sandbox [
+  <span class="Constant">local-scope</span>
+  sandbox:address:sandbox-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  data:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">data:offset</span>
+  response:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">response:offset</span>
+  warnings:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">warnings:offset</span>
+  trace:address:address:array:character<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">trace:offset</span>
+  fake-screen:address:address:screen<span class="Special"> &lt;- </span>get-address *sandbox, <span class="Constant">screen:offset</span>
+  *response, *warnings, *fake-screen, *trace, completed?:boolean<span class="Special"> &lt;- </span>run-interactive data
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-if</span> *warnings
+    <span class="muControl">break-if</span> completed?:boolean
+    *warnings<span class="Special"> &lt;- </span>new <span class="Constant">[took too long!</span>
+<span class="Constant">]</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;render-sandbox-results&gt;</span> [
+  <span class="Delimiter">{</span>
+    sandbox-warnings:address:array:character<span class="Special"> &lt;- </span>get *sandbox, <span class="Constant">warnings:offset</span>
+    <span class="muControl">break-unless</span> sandbox-warnings
+    *response-starting-row<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>  <span class="Comment"># no response</span>
+    row, screen<span class="Special"> &lt;- </span>render-string screen, sandbox-warnings, left, right, <span class="Constant">1/red</span>, row
+    <span class="muControl">jump</span> <span class="Constant">+render-sandbox-end:label</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> run-shows-warnings-in-get [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  get 123:number, foo:offset</span>
+<span class="Constant">]</span>]
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .  errors found                                                                   run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊foo                                              .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  get 123:number, foo:offset                      ┊                                                 .</span>
+   <span class="Constant"> .]                                                 ┊                                                 .</span>
+   <span class="Constant"> .unknown element foo in container number           ┊                                                 .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">1/red</span>, [
+   <span class="Constant"> .  errors found                                                                                      .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .unknown element foo in container number                                                             .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-shows-missing-type-warnings [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  x &lt;- copy 0</span>
+<span class="Constant">]</span>]
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .  errors found                                                                   run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊foo                                              .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  x &lt;- copy 0                                     ┊                                                 .</span>
+   <span class="Constant"> .]                                                 ┊                                                 .</span>
+   <span class="Constant"> .missing type in 'x &lt;- copy 0'                     ┊                                                 .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-shows-unbalanced-bracket-warnings [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Comment"># recipe is incomplete (unbalanced '[')</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo «</span>
+<span class="Constant">  x &lt;- copy 0</span>
+<span class="Constant">]</span>
+  string-replace <span class="Constant">1</span>:address:array:character, <span class="Constant">171/«</span>, <span class="Constant">91</span>  <span class="Comment"># '['</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .  errors found                                                                   run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊foo                                              .</span>
+   <span class="Constant"> .recipe foo \\\[                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  x &lt;- copy 0                                     ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .9: unbalanced '\\\[' for recipe                      ┊                                                 .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-shows-get-on-non-container-warnings [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  x:address:point &lt;- new point:type</span>
+<span class="Constant">  get x:address:point, 1:offset</span>
+<span class="Constant">]</span>]
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  x:address:point &lt;- new point:type               ┊                                                x.</span>
+   <span class="Constant"> .  get x:address:point, 1:offset                   ┊foo                                              .</span>
+   <span class="Constant"> .]                                                 ┊foo: first ingredient of 'get' should be a conta↩.</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊iner, but got x:address:point                    .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-shows-non-literal-get-argument-warnings [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  x:number &lt;- copy 0</span>
+<span class="Constant">  y:address:point &lt;- new point:type</span>
+<span class="Constant">  get *y:address:point, x:number</span>
+<span class="Constant">]</span>]
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .  errors found                                                                   run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊foo                                              .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  x:number &lt;- copy 0                              ┊                                                 .</span>
+   <span class="Constant"> .  y:address:point &lt;- new point:type               ┊                                                 .</span>
+   <span class="Constant"> .  get *y:address:point, x:number                  ┊                                                 .</span>
+   <span class="Constant"> .]                                                 ┊                                                 .</span>
+   <span class="Constant"> .foo: expected ingredient 1 of 'get' to have type ↩┊                                                 .</span>
+   <span class="Constant"> .'offset'; got x:number                            ┊                                                 .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-shows-warnings-everytime [
+  $close-trace  <span class="Comment"># trace too long</span>
+  <span class="Comment"># try to run a file with an error</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">15/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[ </span>
+<span class="Constant">recipe foo [</span>
+<span class="Constant">  x:number &lt;- copy y:number</span>
+<span class="Constant">]</span>]
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .  errors found                                                                   run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊foo                                              .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  x:number &lt;- copy y:number                       ┊                                                 .</span>
+   <span class="Constant"> .]                                                 ┊                                                 .</span>
+   <span class="Constant"> .use before set: y in foo                          ┊                                                 .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  <span class="Comment"># rerun the file, check for the same error</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .  errors found                                                                   run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊foo                                              .</span>
+   <span class="Constant"> .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .  x:number &lt;- copy y:number                       ┊                                                 .</span>
+   <span class="Constant"> .]                                                 ┊                                                 .</span>
+   <span class="Constant"> .use before set: y in foo                          ┊                                                 .</span>
+   <span class="Constant"> .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-instruction-and-print-warnings [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">10/height</span>
+  <span class="Comment"># left editor is empty</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Comment"># right editor contains an illegal instruction</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[get 1234:number, foo:offset]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the code in the editors</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that screen prints error message in red</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊get 1234:number, foo:offset                      .</span>
+   <span class="Constant"> .                                                  ┊unknown element foo in container number          .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">7/white</span>, [
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                   get 1234:number, foo:offset                      .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">1/red</span>, [
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                   unknown element foo in container number          .</span>
+   <span class="Constant"> .                                                                                                    .</span>
+  ]
+  screen-should-contain-in-color <span class="Constant">245/grey</span>, [
+   <span class="Constant"> .                                                                                                    .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> run-instruction-and-print-warnings-only-once [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">10/height</span>
+  <span class="Comment"># left editor is empty</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Comment"># right editor contains an illegal instruction</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[get 1234:number, foo:offset]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the code in the editors multiple times</span>
+  assume-console [
+    press F4
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  <span class="Comment"># check that screen prints error message just once</span>
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                x.</span>
+   <span class="Constant"> .                                                  ┊get 1234:number, foo:offset                      .</span>
+   <span class="Constant"> .                                                  ┊unknown element foo in container number          .</span>
+   <span class="Constant"> .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> sandbox-can-handle-infinite-loop [
+  $close-trace  <span class="Comment"># trace too long</span>
+  assume-screen <span class="Constant">100/width</span>, <span class="Constant">20/height</span>
+  <span class="Comment"># left editor is empty</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[recipe foo [</span>
+<span class="Constant">  {</span>
+<span class="Constant">    loop</span>
+<span class="Constant">  }</span>
+<span class="Constant">]</span>]
+  <span class="Comment"># right editor contains an instruction</span>
+  <span class="Constant">2</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[foo]</span>
+  <span class="Constant">3</span>:address:programming-environment-data<span class="Special"> &lt;- </span>new-programming-environment screen:address, <span class="Constant">1</span>:address:array:character, <span class="Constant">2</span>:address:array:character
+  <span class="Comment"># run the sandbox</span>
+  assume-console [
+    press F4
+  ]
+  run [
+    event-loop screen:address, console:address, <span class="Constant">3</span>:address:programming-environment-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .                                                                                 run (F4)           .</span>
+   <span class="Constant"> .recipe foo [                                      ┊                                                 .</span>
+   <span class="Constant"> .  {                                               ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .    loop                                          ┊                                                x.</span>
+   <span class="Constant"> .  }                                               ┊foo                                              .</span>
+   <span class="Constant"> .]                                                 ┊took too long!                                   .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.</span>
+   <span class="Constant"> .                                                  ┊                                                 .</span>
+  ]
+]
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->
diff --git a/html/edit/011-editor-undo.mu.html b/html/edit/011-editor-undo.mu.html
new file mode 100644
index 00000000..37417b7f
--- /dev/null
+++ b/html/edit/011-editor-undo.mu.html
@@ -0,0 +1,2095 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Mu - edit/011-editor-undo.mu</title>
+<meta name="Generator" content="Vim/7.4">
+<meta name="plugin-version" content="vim7.4_v1">
+<meta name="syntax" content="none">
+<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
+<meta name="colorscheme" content="minimal">
+<style type="text/css">
+<!--
+pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
+body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+* { font-size: 1.05em; }
+.muRecipe { color: #ff8700; }
+.muData { color: #ffff00; }
+.Special { color: #ff6060; }
+.muScenario { color: #00af00; }
+.Comment { color: #9090ff; }
+.Constant { color: #00a0a0; }
+.SalientComment { color: #00ffff; }
+.Delimiter { color: #a04060; }
+.muControl { color: #c0a020; }
+-->
+</style>
+
+<script type='text/javascript'>
+<!--
+
+-->
+</script>
+</head>
+<body>
+<pre id='vimCodeElement'>
+<span class="SalientComment">## undo/redo</span>
+
+<span class="Comment"># for every undoable event, create a type of *operation* that contains all the</span>
+<span class="Comment"># information needed to reverse it</span>
+<span class="muData">exclusive-container</span> operation [
+  typing:insert-operation
+  move:move-operation
+  delete:delete-operation
+]
+
+<span class="muData">container</span> insert-operation [
+  before-row:number
+  before-column:number
+  before-top-of-screen:address:duplex-list:character
+  after-row:number
+  after-column:number
+  after-top-of-screen:address:duplex-list:character
+  <span class="Comment"># inserted text is from 'insert-from' until 'insert-until'; list doesn't have to terminate</span>
+  insert-from:address:duplex-list:character
+  insert-until:address:duplex-list:character
+  tag:number  <span class="Comment"># event causing this operation; might be used to coalesce runs of similar events</span>
+    <span class="Comment"># 0: no coalesce (enter+indent)</span>
+    <span class="Comment"># 1: regular alphanumeric characters</span>
+]
+
+<span class="muData">container</span> move-operation [
+  before-row:number
+  before-column:number
+  before-top-of-screen:address:duplex-list:character
+  after-row:number
+  after-column:number
+  after-top-of-screen:address:duplex-list:character
+  tag:number  <span class="Comment"># event causing this operation; might be used to coalesce runs of similar events</span>
+    <span class="Comment"># 0: no coalesce (touch events, etc)</span>
+    <span class="Comment"># 1: left arrow</span>
+    <span class="Comment"># 2: right arrow</span>
+    <span class="Comment"># 3: up arrow</span>
+    <span class="Comment"># 4: down arrow</span>
+]
+
+<span class="muData">container</span> delete-operation [
+  before-row:number
+  before-column:number
+  before-top-of-screen:address:duplex-list:character
+  after-row:number
+  after-column:number
+  after-top-of-screen:address:duplex-list:character
+  deleted-text:address:duplex-list:character
+  delete-from:address:duplex-list:character
+  delete-until:address:duplex-list:character
+  tag:number  <span class="Comment"># event causing this operation; might be used to coalesce runs of similar events</span>
+    <span class="Comment"># 0: no coalesce (ctrl-k, ctrl-u)</span>
+    <span class="Comment"># 1: backspace</span>
+    <span class="Comment"># 2: delete</span>
+]
+
+<span class="Comment"># every editor accumulates a list of operations to undo/redo</span>
+<span class="muData">container</span> editor-data [
+  undo:address:list:address:operation
+  redo:address:list:address:operation
+]
+
+<span class="Comment"># ctrl-z - undo operation</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    undo?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">26/ctrl-z</span>
+    <span class="muControl">break-unless</span> undo?
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    <span class="muControl">break-unless</span> *undo
+    op:address:operation<span class="Special"> &lt;- </span>first *undo
+    *undo<span class="Special"> &lt;- </span>rest *undo
+    redo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">redo:offset</span>
+    *redo<span class="Special"> &lt;- </span>push op, *redo
+<span class="Constant">    &lt;handle-undo&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># ctrl-y - redo operation</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-special-character&gt;</span> [
+  <span class="Delimiter">{</span>
+    redo?:boolean<span class="Special"> &lt;- </span>equal *c, <span class="Constant">25/ctrl-y</span>
+    <span class="muControl">break-unless</span> redo?
+    redo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">redo:offset</span>
+    <span class="muControl">break-unless</span> *redo
+    op:address:operation<span class="Special"> &lt;- </span>first *redo
+    *redo<span class="Special"> &lt;- </span>rest *redo
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    *undo<span class="Special"> &lt;- </span>push op, *undo
+<span class="Constant">    &lt;handle-redo&gt;</span>
+    <span class="muControl">reply</span> screen/same-as-ingredient:<span class="Constant">0</span>, editor/same-as-ingredient:<span class="Constant">1</span>, <span class="Constant">1/go-render</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># undo typing</span>
+
+<span class="muScenario">scenario</span> editor-can-undo-typing [
+  <span class="Comment"># create an editor and type a character</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># character should be gone</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .1         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># save operation to undo</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;insert-character-begin&gt;</span> [
+  top-before:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  cursor-before:address:duplex-list<span class="Special"> &lt;- </span>copy *before-cursor
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;insert-character-end&gt;</span> [
+  top-after:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="Comment"># if previous operation was an insert, coalesce this operation with it</span>
+    <span class="muControl">break-unless</span> *undo
+    op:address:operation<span class="Special"> &lt;- </span>first *undo
+    typing:address:insert-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">typing:variant</span>
+    <span class="muControl">break-unless</span> typing
+    previous-coalesce-tag:number<span class="Special"> &lt;- </span>get *typing, <span class="Constant">tag:offset</span>
+    <span class="muControl">break-unless</span> previous-coalesce-tag
+    insert-until:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *typing, <span class="Constant">insert-until:offset</span>
+    *insert-until<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    after-row:address:number<span class="Special"> &lt;- </span>get-address *typing, <span class="Constant">after-row:offset</span>
+    *after-row<span class="Special"> &lt;- </span>copy *cursor-row
+    after-column:address:number<span class="Special"> &lt;- </span>get-address *typing, <span class="Constant">after-column:offset</span>
+    *after-column<span class="Special"> &lt;- </span>copy *cursor-column
+    after-top:address:number<span class="Special"> &lt;- </span>get-address *typing, <span class="Constant">after-top-of-screen:offset</span>
+    *after-top<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    <span class="muControl">break</span> <span class="Constant">+done-adding-insert-operation:label</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># if not, create a new operation</span>
+  insert-from:address:duplex-list<span class="Special"> &lt;- </span>next-duplex cursor-before
+  insert-to:address:duplex-list<span class="Special"> &lt;- </span>next-duplex insert-from
+  op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+  *op<span class="Special"> &lt;- </span>merge <span class="Constant">0/insert-operation</span>, save-row/<span class="muRecipe">before</span>, save-column/<span class="muRecipe">before</span>, top-before, *cursor-row/<span class="muRecipe">after</span>, *cursor-column/<span class="muRecipe">after</span>, top-after, insert-from, insert-to, <span class="Constant">1/coalesce</span>
+  editor<span class="Special"> &lt;- </span>add-operation editor, op
+<span class="Constant">  +done-adding-insert-operation</span>
+]
+
+<span class="Comment"># enter operations never coalesce with typing before or after</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;insert-enter-begin&gt;</span> [
+  cursor-row-before:number<span class="Special"> &lt;- </span>copy *cursor-row
+  cursor-column-before:number<span class="Special"> &lt;- </span>copy *cursor-column
+  top-before:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  cursor-before:address:duplex-list<span class="Special"> &lt;- </span>copy *before-cursor
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;insert-enter-end&gt;</span> [
+  top-after:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  <span class="Comment"># never coalesce</span>
+  insert-from:address:duplex-list<span class="Special"> &lt;- </span>next-duplex cursor-before
+  insert-to:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+  op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+  *op<span class="Special"> &lt;- </span>merge <span class="Constant">0/insert-operation</span>, cursor-row-before, cursor-column-before, top-before, *cursor-row/<span class="muRecipe">after</span>, *cursor-column/<span class="muRecipe">after</span>, top-after, insert-from, insert-to, <span class="Constant">0/never-coalesce</span>
+  editor<span class="Special"> &lt;- </span>add-operation editor, op
+]
+
+<span class="Comment"># Everytime you add a new operation to the undo stack, be sure to clear the</span>
+<span class="Comment"># redo stack, because it's now obsolete.</span>
+<span class="Comment"># Beware: since we're counting cursor moves as operations, this means just</span>
+<span class="Comment"># moving the cursor can lose work on the undo stack.</span>
+<span class="muRecipe">recipe</span> add-operation [
+  <span class="Constant">local-scope</span>
+  editor:address:editor-data<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  op:address:operation<span class="Special"> &lt;- </span><span class="Constant">next-ingredient</span>
+  undo:address:address:list:address:operation<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+  *undo<span class="Special"> &lt;- </span>push op *undo
+  redo:address:address:list:address:operation<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">redo:offset</span>
+  *redo<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
+  <span class="muControl">reply</span> editor/same-as-ingredient:<span class="Constant">0</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-undo&gt;</span> [
+  <span class="Delimiter">{</span>
+    typing:address:insert-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">typing:variant</span>
+    <span class="muControl">break-unless</span> typing
+    start:address:duplex-list<span class="Special"> &lt;- </span>get *typing, <span class="Constant">insert-from:offset</span>
+    end:address:duplex-list<span class="Special"> &lt;- </span>get *typing, <span class="Constant">insert-until:offset</span>
+    <span class="Comment"># assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen</span>
+    *before-cursor<span class="Special"> &lt;- </span>prev-duplex start
+    remove-duplex-between *before-cursor, end
+    *cursor-row<span class="Special"> &lt;- </span>get *typing, <span class="Constant">before-row:offset</span>
+    *cursor-column<span class="Special"> &lt;- </span>get *typing, <span class="Constant">before-column:offset</span>
+    top:address:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    *top<span class="Special"> &lt;- </span>get *typing, <span class="Constant">before-top-of-screen:offset</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-typing-multiple [
+  <span class="Comment"># create an editor and type multiple characters</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[012]</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># all characters must be gone</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-typing-multiple-2 [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># type some characters</span>
+  assume-console [
+    type <span class="Constant">[012]</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .012a      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># back to original text</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[3]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .3a        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-typing-enter [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[  abc]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># new line</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">8</span>
+    press enter
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  abc     .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># line is indented</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">5</span>
+  ]
+  <span class="Comment"># back to original text</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  abc     .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># cursor should be at end of line</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  abc1    .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># redo typing</span>
+
+<span class="muScenario">scenario</span> editor-redo-typing [
+  <span class="Comment"># create an editor, type something, undo</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[012]</span>
+    press ctrl-z
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># all characters must be back</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .012a      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[3]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0123a     .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-redo&gt;</span> [
+  <span class="Delimiter">{</span>
+    typing:address:insert-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">typing:variant</span>
+    <span class="muControl">break-unless</span> typing
+    insert-from:address:duplex-list<span class="Special"> &lt;- </span>get *typing, <span class="Constant">insert-from:offset</span>  <span class="Comment"># ignore insert-to because it's already been spliced away</span>
+    <span class="Comment"># assert insert-to matches next-duplex(*before-cursor)</span>
+    insert-duplex-range *before-cursor, insert-from
+    <span class="Comment"># assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen</span>
+    *cursor-row<span class="Special"> &lt;- </span>get *typing, <span class="Constant">after-row:offset</span>
+    *cursor-column<span class="Special"> &lt;- </span>get *typing, <span class="Constant">after-column:offset</span>
+    top:address:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    *top<span class="Special"> &lt;- </span>get *typing, <span class="Constant">after-top-of-screen:offset</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-redo-typing-empty [
+  <span class="Comment"># create an editor, type something, undo</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[012]</span>
+    press ctrl-z
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># all characters must be back</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .012       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[3]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0123      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-work-clears-redo-stack [
+  <span class="Comment"># create an editor with some text, do some work, undo</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[1]</span>
+    press ctrl-z
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># do some more work</span>
+  assume-console [
+    type <span class="Constant">[0]</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0abc      .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># nothing should happen</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .0abc      .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-redo-typing-and-enter-and-tab [
+  <span class="Comment"># create an editor</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># insert some text and tabs, hit enter, some more text and tabs</span>
+  assume-console [
+    press tab
+    type <span class="Constant">[ab]</span>
+    press tab
+    type <span class="Constant">[cd]</span>
+    press enter
+    press tab
+    type <span class="Constant">[efg]</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab  cd  .</span>
+   <span class="Constant"> .    efg   .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">7</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># typing in second line deleted, but not indent</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab  cd  .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># undo again</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># indent and newline deleted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">8</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab  cd  .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># undo again</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># empty screen</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># first line inserted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">8</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab  cd  .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo again</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># newline and indent inserted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab  cd  .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo again</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># indent and newline deleted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">7</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .  ab  cd  .</span>
+   <span class="Constant"> .    efg   .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># undo cursor movement and scroll</span>
+
+<span class="muScenario">scenario</span> editor-can-undo-touch [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">1</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># click undone</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .1abc      .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;move-cursor-begin&gt;</span> [
+  before-cursor-row:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-row:offset</span>
+  before-cursor-column:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-column:offset</span>
+  before-top-of-screen:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;move-cursor-end&gt;</span> [
+  after-cursor-row:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-row:offset</span>
+  after-cursor-column:number<span class="Special"> &lt;- </span>get *editor, <span class="Constant">cursor-column:offset</span>
+  after-top-of-screen:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> undo-coalesce-tag
+    <span class="Comment"># if previous operation was also a move, and also had the same coalesce</span>
+    <span class="Comment"># tag, coalesce with it</span>
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    <span class="muControl">break-unless</span> *undo
+    op:address:operation<span class="Special"> &lt;- </span>first *undo
+    move:address:move-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">move:variant</span>
+    <span class="muControl">break-unless</span> move
+    previous-coalesce-tag:number<span class="Special"> &lt;- </span>get *move, <span class="Constant">tag:offset</span>
+    coalesce?:boolean<span class="Special"> &lt;- </span>equal undo-coalesce-tag, previous-coalesce-tag
+    <span class="muControl">break-unless</span> coalesce?
+    after-row:address:number<span class="Special"> &lt;- </span>get-address *move, <span class="Constant">after-row:offset</span>
+    *after-row<span class="Special"> &lt;- </span>copy after-cursor-row
+    after-column:address:number<span class="Special"> &lt;- </span>get-address *move, <span class="Constant">after-column:offset</span>
+    *after-column<span class="Special"> &lt;- </span>copy after-cursor-column
+    after-top:address:number<span class="Special"> &lt;- </span>get-address *move, <span class="Constant">after-top-of-screen:offset</span>
+    *after-top<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    <span class="muControl">break</span> <span class="Constant">+done-adding-move-operation:label</span>
+  <span class="Delimiter">}</span>
+  op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+  *op<span class="Special"> &lt;- </span>merge <span class="Constant">1/move-operation</span>, before-cursor-row, before-cursor-column, before-top-of-screen, after-cursor-row, after-cursor-column, after-top-of-screen, undo-coalesce-tag
+  editor<span class="Special"> &lt;- </span>add-operation editor, op
+<span class="Constant">  +done-adding-move-operation</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-undo&gt;</span> [
+  <span class="Delimiter">{</span>
+    move:address:move-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">move:variant</span>
+    <span class="muControl">break-unless</span> move
+    <span class="Comment"># assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen</span>
+    top:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">top-of-screen:offset</span>
+    *cursor-row<span class="Special"> &lt;- </span>get *move, <span class="Constant">before-row:offset</span>
+    *cursor-column<span class="Special"> &lt;- </span>get *move, <span class="Constant">before-column:offset</span>
+    *top<span class="Special"> &lt;- </span>get *move, <span class="Constant">before-top-of-screen:offset</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-scroll [
+  <span class="Comment"># screen has 1 line for menu + 3 lines</span>
+  assume-screen <span class="Constant">5/width</span>, <span class="Constant">4/height</span>
+  <span class="Comment"># editor contains a wrapped line</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">cdefgh]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
+  <span class="Comment"># position cursor at end of screen and try to move right</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">3</span>
+    press right-arrow
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  <span class="Comment"># screen scrolls</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+<span class="Constant">    .cdef↩.</span>
+   <span class="Constant"> .gh   .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moved back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  <span class="Comment"># scroll undone</span>
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .a    .</span>
+   <span class="Constant"> .b    .</span>
+<span class="Constant">    .cdef↩.</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .     .</span>
+   <span class="Constant"> .b    .</span>
+<span class="Constant">    .cde1↩.</span>
+   <span class="Constant"> .fgh  .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-left-arrow [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">1</span>
+    press left-arrow
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .g1hi      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-up-arrow [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor</span>
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">1</span>
+    press up-arrow
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .g1hi      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-down-arrow [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press down-arrow
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .d1ef      .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-ctrl-f [
+  <span class="Comment"># create an editor with multiple pages of text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e</span>
+<span class="Constant">f]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># scroll the page</span>
+  assume-console [
+    press ctrl-f
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen should again show page 1</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-page-down [
+  <span class="Comment"># create an editor with multiple pages of text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e</span>
+<span class="Constant">f]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># scroll the page</span>
+  assume-console [
+    press page-down
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen should again show page 1</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .b         .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .d         .</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-ctrl-b [
+  <span class="Comment"># create an editor with multiple pages of text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e</span>
+<span class="Constant">f]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># scroll the page down and up</span>
+  assume-console [
+    press page-down
+    press ctrl-b
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen should again show page 2</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .d         .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .f         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-page-up [
+  <span class="Comment"># create an editor with multiple pages of text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[a</span>
+<span class="Constant">b</span>
+<span class="Constant">c</span>
+<span class="Constant">d</span>
+<span class="Constant">e</span>
+<span class="Constant">f]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># scroll the page down and up</span>
+  assume-console [
+    press page-down
+    press page-up
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen should again show page 2</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .d         .</span>
+   <span class="Constant"> .e         .</span>
+   <span class="Constant"> .f         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-ctrl-a [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor, then to start of line</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press ctrl-a
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .d1ef      .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-home [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor, then to start of line</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press home
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .d1ef      .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-ctrl-e [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor, then to start of line</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press ctrl-e
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .d1ef      .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-end [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor, then to start of line</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press end
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves back</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .d1ef      .</span>
+   <span class="Constant"> .ghi       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-separates-undo-insert-from-undo-cursor-move [
+  <span class="Comment"># create an editor, type some text, move the cursor, type some more text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    type <span class="Constant">[abc]</span>
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    type <span class="Constant">[d]</span>
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .adbc      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># last letter typed is deleted</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># undo again</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># no change to screen; cursor moves</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  <span class="Comment"># undo again</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># screen empty</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .          .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># first insert</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  <span class="Comment"># redo again</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># redo again</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># second insert</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .adbc      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+]
+
+<span class="muScenario">scenario</span> editor-can-undo-multiple-arrows-in-the-same-direction [
+  <span class="Comment"># create an editor with some text</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># move the cursor</span>
+  assume-console [
+    left-click <span class="Constant">2</span>, <span class="Constant">1</span>
+    press right-arrow
+    press right-arrow
+    press up-arrow
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># up-arrow is undone</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  <span class="Comment"># undo again</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># both right-arrows are undone</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+]
+
+<span class="Comment"># redo cursor movement and scroll</span>
+
+<span class="muScenario">scenario</span> editor-redo-touch [
+  <span class="Comment"># create an editor with some text, click on a character, undo</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def</span>
+<span class="Constant">ghi]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  assume-console [
+    left-click <span class="Constant">3</span>, <span class="Constant">1</span>
+    press ctrl-z
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  ]
+  <span class="Comment"># cursor moves to left-click</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def       .</span>
+   <span class="Constant"> .g1hi      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-redo&gt;</span> [
+  <span class="Delimiter">{</span>
+    move:address:move-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">move:variant</span>
+    <span class="muControl">break-unless</span> move
+    <span class="Comment"># assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen</span>
+    *cursor-row<span class="Special"> &lt;- </span>get *move, <span class="Constant">after-row:offset</span>
+    *cursor-column<span class="Special"> &lt;- </span>get *move, <span class="Constant">after-column:offset</span>
+    top:address:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    *top<span class="Special"> &lt;- </span>get *move, <span class="Constant">after-top-of-screen:offset</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># undo backspace</span>
+
+<span class="muScenario">scenario</span> editor-can-undo-and-redo-backspace [
+  <span class="Comment"># create an editor</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># insert some text and hit backspace</span>
+  assume-console [
+    type <span class="Constant">[abc]</span>
+    press backspace
+    press backspace
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">3</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="Comment"># save operation to undo</span>
+<span class="muRecipe">after</span> <span class="Constant">&lt;backspace-character-begin&gt;</span> [
+  top-before:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;backspace-character-end&gt;</span> [
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> backspaced-cell  <span class="Comment"># backspace failed; don't add an undo operation</span>
+    top-after:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># if previous operation was an insert, coalesce this operation with it</span>
+      <span class="muControl">break-unless</span> *undo
+      op:address:operation<span class="Special"> &lt;- </span>first *undo
+      deletion:address:delete-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">delete:variant</span>
+      <span class="muControl">break-unless</span> deletion
+      previous-coalesce-tag:number<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">tag:offset</span>
+      coalesce?:boolean<span class="Special"> &lt;- </span>equal previous-coalesce-tag, <span class="Constant">1/coalesce-backspace</span>
+      <span class="muControl">break-unless</span> coalesce?
+      delete-from:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">delete-from:offset</span>
+      *delete-from<span class="Special"> &lt;- </span>copy *before-cursor
+      backspaced-so-far:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">deleted-text:offset</span>
+      insert-duplex-range backspaced-cell, *backspaced-so-far
+      *backspaced-so-far<span class="Special"> &lt;- </span>copy backspaced-cell
+      after-row:address:number<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">after-row:offset</span>
+      *after-row<span class="Special"> &lt;- </span>copy *cursor-row
+      after-column:address:number<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">after-column:offset</span>
+      *after-column<span class="Special"> &lt;- </span>copy *cursor-column
+      after-top:address:number<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">after-top-of-screen:offset</span>
+      *after-top<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+      <span class="muControl">break</span> <span class="Constant">+done-adding-backspace-operation:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># if not, create a new operation</span>
+    op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+    deleted-until:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    *op<span class="Special"> &lt;- </span>merge <span class="Constant">2/delete-operation</span>, save-row/<span class="muRecipe">before</span>, save-column/<span class="muRecipe">before</span>, top-before, *cursor-row/<span class="muRecipe">after</span>, *cursor-column/<span class="muRecipe">after</span>, top-after, backspaced-cell/deleted, *before-cursor/delete-from, deleted-until, <span class="Constant">1/coalesce-backspace</span>
+    editor<span class="Special"> &lt;- </span>add-operation editor, op
+<span class="Constant">    +done-adding-backspace-operation</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-undo&gt;</span> [
+  <span class="Delimiter">{</span>
+    deletion:address:delete-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">delete:variant</span>
+    <span class="muControl">break-unless</span> deletion
+    start2:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">data:offset</span>
+    anchor:address:duplex-list<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">delete-from:offset</span>
+    <span class="muControl">break-unless</span> anchor
+    deleted:address:duplex-list<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">deleted-text:offset</span>
+    old-cursor:address:duplex-list<span class="Special"> &lt;- </span>last-duplex deleted
+    insert-duplex-range anchor, deleted
+    <span class="Comment"># assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen</span>
+    *before-cursor<span class="Special"> &lt;- </span>copy old-cursor
+    *cursor-row<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">before-row:offset</span>
+    *cursor-column<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">before-column:offset</span>
+    top:address:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    *top<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">before-top-of-screen:offset</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;handle-redo&gt;</span> [
+  <span class="Delimiter">{</span>
+    deletion:address:delete-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">delete:variant</span>
+    <span class="muControl">break-unless</span> deletion
+    start:address:duplex-list<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">delete-from:offset</span>
+    end:address:duplex-list<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">delete-until:offset</span>
+    remove-duplex-between start, end
+    <span class="Comment"># assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen</span>
+    *cursor-row<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">after-row:offset</span>
+    *cursor-column<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">after-column:offset</span>
+    top:address:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    *top<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">after-top-of-screen:offset</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># undo delete</span>
+
+<span class="muScenario">scenario</span> editor-can-undo-and-redo-delete [
+  <span class="Comment"># create an editor</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># insert some text and hit delete and backspace a few times</span>
+  assume-console [
+    type <span class="Constant">[abcdef]</span>
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    press delete
+    press backspace
+    press delete
+    press delete
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .af        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># undo deletes</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .adef      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># undo backspace</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abdef     .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># undo first delete</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abcdef    .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo first delete</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># first line inserted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abdef     .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo backspace</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># first line inserted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .adef      .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Comment"># redo deletes</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># first line inserted</span>
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .af        .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;delete-character-begin&gt;</span> [
+  top-before:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;delete-character-end&gt;</span> [
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> deleted-cell  <span class="Comment"># delete failed; don't add an undo operation</span>
+    top-after:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    <span class="Delimiter">{</span>
+      <span class="Comment"># if previous operation was an insert, coalesce this operation with it</span>
+      <span class="muControl">break-unless</span> *undo
+      op:address:operation<span class="Special"> &lt;- </span>first *undo
+      deletion:address:delete-operation<span class="Special"> &lt;- </span>maybe-convert *op, <span class="Constant">delete:variant</span>
+      <span class="muControl">break-unless</span> deletion
+      previous-coalesce-tag:number<span class="Special"> &lt;- </span>get *deletion, <span class="Constant">tag:offset</span>
+      coalesce?:boolean<span class="Special"> &lt;- </span>equal previous-coalesce-tag, <span class="Constant">2/coalesce-delete</span>
+      <span class="muControl">break-unless</span> coalesce?
+      delete-until:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">delete-until:offset</span>
+      *delete-until<span class="Special"> &lt;- </span>next-duplex *before-cursor
+      deleted-so-far:address:address:duplex-list<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">deleted-text:offset</span>
+      *deleted-so-far<span class="Special"> &lt;- </span>append-duplex *deleted-so-far, deleted-cell
+      after-row:address:number<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">after-row:offset</span>
+      *after-row<span class="Special"> &lt;- </span>copy *cursor-row
+      after-column:address:number<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">after-column:offset</span>
+      *after-column<span class="Special"> &lt;- </span>copy *cursor-column
+      after-top:address:number<span class="Special"> &lt;- </span>get-address *deletion, <span class="Constant">after-top-of-screen:offset</span>
+      *after-top<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+      <span class="muControl">break</span> <span class="Constant">+done-adding-delete-operation:label</span>
+    <span class="Delimiter">}</span>
+    <span class="Comment"># if not, create a new operation</span>
+    op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+    deleted-until:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    *op<span class="Special"> &lt;- </span>merge <span class="Constant">2/delete-operation</span>, save-row/<span class="muRecipe">before</span>, save-column/<span class="muRecipe">before</span>, top-before, *cursor-row/<span class="muRecipe">after</span>, *cursor-column/<span class="muRecipe">after</span>, top-after, deleted-cell/deleted, *before-cursor/delete-from, deleted-until, <span class="Constant">2/coalesce-delete</span>
+    editor<span class="Special"> &lt;- </span>add-operation editor, op
+<span class="Constant">    +done-adding-delete-operation</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># undo ctrl-k</span>
+
+<span class="muScenario">scenario</span> editor-can-undo-and-redo-ctrl-k [
+  <span class="Comment"># create an editor</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># insert some text and hit delete and backspace a few times</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">1</span>
+    press ctrl-k
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># first line inserted</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a         .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .a1        .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;delete-to-end-of-line-begin&gt;</span> [
+  top-before:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;delete-to-end-of-line-end&gt;</span> [
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> deleted-cells  <span class="Comment"># delete failed; don't add an undo operation</span>
+    top-after:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+    deleted-until:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    *op<span class="Special"> &lt;- </span>merge <span class="Constant">2/delete-operation</span>, save-row/<span class="muRecipe">before</span>, save-column/<span class="muRecipe">before</span>, top-before, *cursor-row/<span class="muRecipe">after</span>, *cursor-column/<span class="muRecipe">after</span>, top-after, deleted-cells/deleted, *before-cursor/delete-from, deleted-until, <span class="Constant">0/never-coalesce</span>
+    editor<span class="Special"> &lt;- </span>add-operation editor, op
+<span class="Constant">    +done-adding-delete-operation</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># undo ctrl-u</span>
+
+<span class="muScenario">scenario</span> editor-can-undo-and-redo-ctrl-u [
+  <span class="Comment"># create an editor</span>
+  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
+  <span class="Constant">1</span>:address:array:character<span class="Special"> &lt;- </span>new <span class="Constant">[abc</span>
+<span class="Constant">def]</span>
+  <span class="Constant">2</span>:address:editor-data<span class="Special"> &lt;- </span>new-editor <span class="Constant">1</span>:address:array:character, screen:address, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
+  editor-render screen, <span class="Constant">2</span>:address:editor-data
+  <span class="Comment"># insert some text and hit delete and backspace a few times</span>
+  assume-console [
+    left-click <span class="Constant">1</span>, <span class="Constant">2</span>
+    press ctrl-u
+  ]
+  editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  <span class="Comment"># undo</span>
+  assume-console [
+    press ctrl-z
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .abc       .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">2</span>
+  ]
+  <span class="Comment"># redo</span>
+  assume-console [
+    press ctrl-y
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  <span class="Comment"># first line inserted</span>
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .c         .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+  <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-row:offset</span>
+  <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:editor-data, <span class="Constant">cursor-column:offset</span>
+  memory-should-contain [
+    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">1</span>
+    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>
+  ]
+  <span class="Comment"># cursor should be in the right place</span>
+  assume-console [
+    type <span class="Constant">[1]</span>
+  ]
+  run [
+    editor-event-loop screen:address, console:address, <span class="Constant">2</span>:address:editor-data
+  ]
+  screen-should-contain [
+   <span class="Constant"> .          .</span>
+   <span class="Constant"> .1c        .</span>
+   <span class="Constant"> .def       .</span>
+<span class="Constant">    .┈┈┈┈┈┈┈┈┈┈.</span>
+   <span class="Constant"> .          .</span>
+  ]
+]
+
+<span class="muRecipe">after</span> <span class="Constant">&lt;delete-to-start-of-line-begin&gt;</span> [
+  top-before:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+]
+<span class="muRecipe">before</span> <span class="Constant">&lt;delete-to-start-of-line-end&gt;</span> [
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> deleted-cells  <span class="Comment"># delete failed; don't add an undo operation</span>
+    top-after:address:duplex-list<span class="Special"> &lt;- </span>get *editor, <span class="Constant">top-of-screen:offset</span>
+    undo:address:address:list<span class="Special"> &lt;- </span>get-address *editor, <span class="Constant">undo:offset</span>
+    op:address:operation<span class="Special"> &lt;- </span>new <span class="Constant">operation:type</span>
+    deleted-until:address:duplex-list<span class="Special"> &lt;- </span>next-duplex *before-cursor
+    *op<span class="Special"> &lt;- </span>merge <span class="Constant">2/delete-operation</span>, save-row/<span class="muRecipe">before</span>, save-column/<span class="muRecipe">before</span>, top-before, *cursor-row/<span class="muRecipe">after</span>, *cursor-column/<span class="muRecipe">after</span>, top-after, deleted-cells/deleted, *before-cursor/delete-from, deleted-until, <span class="Constant">0/never-coalesce</span>
+    editor<span class="Special"> &lt;- </span>add-operation editor, op
+<span class="Constant">    +done-adding-delete-operation</span>
+  <span class="Delimiter">}</span>
+]
+
+<span class="Comment"># todo:</span>
+<span class="Comment"># operations for recipe side and each sandbox-data</span>
+<span class="Comment"># undo delete sandbox as a separate primitive on the status bar</span>
+</pre>
+</body>
+</html>
+<!-- vim: set foldmethod=manual : -->