about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--081print.mu27
-rw-r--r--082scenario_screen.cc1
-rw-r--r--edit/001-editor.mu20
-rw-r--r--edit/005-sandbox.mu7
-rw-r--r--edit/007-sandbox-delete.mu6
-rw-r--r--sandbox/001-editor.mu20
-rw-r--r--sandbox/004-programming-environment.mu2
-rw-r--r--sandbox/005-sandbox.mu107
-rw-r--r--sandbox/007-sandbox-delete.mu260
-rw-r--r--sandbox/008-sandbox-edit.mu (renamed from sandbox/006-sandbox-edit.mu)164
-rw-r--r--sandbox/009-sandbox-test.mu (renamed from sandbox/008-sandbox-test.mu)4
-rw-r--r--sandbox/010-sandbox-trace.mu (renamed from sandbox/009-sandbox-trace.mu)102
-rw-r--r--sandbox/011-errors.mu (renamed from sandbox/010-errors.mu)30
-rw-r--r--sandbox/012-editor-undo.mu (renamed from sandbox/011-editor-undo.mu)0
14 files changed, 366 insertions, 384 deletions
diff --git a/081print.mu b/081print.mu
index fd5ed196..1d6b1f65 100644
--- a/081print.mu
+++ b/081print.mu
@@ -368,6 +368,26 @@ def clear-line screen:address:screen -> screen:address:screen [
   clear-line-on-display
 ]
 
+def clear-line-until screen:address:screen, right:number/inclusive -> screen:address:screen [
+  local-scope
+  load-ingredients
+  _, column:number <- cursor-position screen
+  space:character <- copy 32/space
+  bg-color:number, bg-color-found?:boolean <- next-ingredient
+  {
+    # default bg-color to black
+    break-if bg-color-found?
+    bg-color <- copy 0/black
+  }
+  {
+    done?:boolean <- greater-than column, right
+    break-if done?
+    screen <- print screen, space, 7/white, bg-color  # foreground color is mostly unused except if the cursor shows up at this cell
+    column <- add column, 1
+    loop
+  }
+]
+
 def cursor-position screen:address:screen -> row:number, column:number [
   local-scope
   load-ingredients
@@ -525,6 +545,13 @@ def cursor-to-next-line screen:address:screen -> screen:address:screen [
   screen <- cursor-to-start-of-line screen
 ]
 
+def move-cursor-to-column screen:address:screen, column:number -> screen:address:screen [
+  local-scope
+  load-ingredients
+  row:number, _ <- cursor-position screen
+  move-cursor screen, row, column
+]
+
 def screen-width screen:address:screen -> width:number [
   local-scope
   load-ingredients
diff --git a/082scenario_screen.cc b/082scenario_screen.cc
index e1a8d1dc..f90b926b 100644
--- a/082scenario_screen.cc
+++ b/082scenario_screen.cc
@@ -234,6 +234,7 @@ void check_screen(const string& expected_contents, const int color) {
         if (Current_scenario && !Scenario_testing_scenario) {
           // genuine test in a mu file
           raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(get_or_insert(Memory, addr)) << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << "\n" << end();
+          dump_screen();
         }
         else {
           // just testing check_screen
diff --git a/edit/001-editor.mu b/edit/001-editor.mu
index 68bb1775..be210b95 100644
--- a/edit/001-editor.mu
+++ b/edit/001-editor.mu
@@ -222,26 +222,6 @@ def render screen:address:screen, editor:address:editor-data -> last-row:number,
   return row, column, screen/same-as-ingredient:0, editor/same-as-ingredient:1
 ]
 
-def clear-line-until screen:address:screen, right:number/inclusive -> screen:address:screen [
-  local-scope
-  load-ingredients
-  _, column:number <- cursor-position screen
-  space:character <- copy 32/space
-  bg-color:number, bg-color-found?:boolean <- next-ingredient
-  {
-    # default bg-color to black
-    break-if bg-color-found?
-    bg-color <- copy 0/black
-  }
-  {
-    done?:boolean <- greater-than column, right
-    break-if done?
-    screen <- print screen, space, 7/white, bg-color  # foreground color is mostly unused except if the cursor shows up at this cell
-    column <- add column, 1
-    loop
-  }
-]
-
 def clear-screen-from screen:address:screen, row:number, column:number, left:number, right:number -> screen:address:screen [
   local-scope
   load-ingredients
diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu
index e63b7141..655d3eca 100644
--- a/edit/005-sandbox.mu
+++ b/edit/005-sandbox.mu
@@ -353,13 +353,6 @@ def sandbox-menu-columns left:number, right:number -> edit-button-left:number, e
   copy-button-right:number <- subtract delete-button-left, 1
 ]
 
-def move-cursor-to-column screen:address:screen, column:number -> screen:address:screen [
-  local-scope
-  load-ingredients
-  row:number, _ <- cursor-position screen
-  move-cursor screen, row, column
-]
-
 # assumes programming environment has no sandboxes; restores them from previous session
 def restore-sandboxes env:address:programming-environment-data -> env:address:programming-environment-data [
   local-scope
diff --git a/edit/007-sandbox-delete.mu b/edit/007-sandbox-delete.mu
index 6e377e38..83e6282e 100644
--- a/edit/007-sandbox-delete.mu
+++ b/edit/007-sandbox-delete.mu
@@ -30,9 +30,9 @@ scenario deleting-sandboxes [
     .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  ┊                                                 .
   ]
-  # delete second sandbox
+  # delete second sandbox by clicking on left edge of 'delete' button
   assume-console [
-    left-click 7, 99
+    left-click 7, 85
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -48,7 +48,7 @@ scenario deleting-sandboxes [
     .                                                  ┊                                                 .
     .                                                  ┊                                                 .
   ]
-  # delete first sandbox
+  # delete first sandbox by clicking at right edge of 'delete' button
   assume-console [
     left-click 3, 99
   ]
diff --git a/sandbox/001-editor.mu b/sandbox/001-editor.mu
index 1d4b4ac2..be210b95 100644
--- a/sandbox/001-editor.mu
+++ b/sandbox/001-editor.mu
@@ -222,26 +222,6 @@ def render screen:address:screen, editor:address:editor-data -> last-row:number,
   return row, column, screen/same-as-ingredient:0, editor/same-as-ingredient:1
 ]
 
-def clear-line-until screen:address:screen, right:number -> screen:address:screen [
-  local-scope
-  load-ingredients
-  _, column:number <- cursor-position screen
-  space:character <- copy 32/space
-  bg-color:number, bg-color-found?:boolean <- next-ingredient
-  {
-    # default bg-color to black
-    break-if bg-color-found?
-    bg-color <- copy 0/black
-  }
-  {
-    done?:boolean <- greater-than column, right
-    break-if done?
-    screen <- print screen, space, 7/white, bg-color  # foreground color is mostly unused except if the cursor shows up at this cell
-    column <- add column, 1
-    loop
-  }
-]
-
 def clear-screen-from screen:address:screen, row:number, column:number, left:number, right:number -> screen:address:screen [
   local-scope
   load-ingredients
diff --git a/sandbox/004-programming-environment.mu b/sandbox/004-programming-environment.mu
index 5b743a5c..9ed48fb9 100644
--- a/sandbox/004-programming-environment.mu
+++ b/sandbox/004-programming-environment.mu
@@ -74,6 +74,8 @@ def event-loop screen:address:screen, console:address:console, env:address:progr
       touch-type:number <- get t, type:offset
       is-left-click?:boolean <- equal touch-type, 65513/mouse-left
       loop-unless is-left-click?, +next-event:label
+      click-row:number <- get t, row:offset
+      click-column:number <- get t, column:offset
       # later exceptions for non-editor touches will go here
       <global-touch>
       move-cursor-in-editor screen, current-sandbox, t
diff --git a/sandbox/005-sandbox.mu b/sandbox/005-sandbox.mu
index d8942340..66f3036d 100644
--- a/sandbox/005-sandbox.mu
+++ b/sandbox/005-sandbox.mu
@@ -3,6 +3,9 @@
 # Running code in the sandbox editor prepends its contents to a list of
 # (non-editable) sandboxes below the editor, showing the result and a maybe
 # few other things.
+#
+# This layer draws the menubar buttons non-editable sandboxes but they don't
+# do anything yet. Later layers implement each button.
 
 container programming-environment-data [
   sandbox:address:sandbox-data  # list of sandboxes, from top to bottom
@@ -43,7 +46,7 @@ scenario run-and-show-results [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .divide-with-remainder 11, 3                       .
     .3                                                 .
     .2                                                 .
@@ -65,20 +68,13 @@ scenario run-and-show-results [
     .                                                  .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                 x.
+    .                                                  .
     .                                                  .
     .3                                                 .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # sandbox title in reverse video
-  screen-should-contain-in-color 240/dark-grey, [
-    .                                                  .
-    .                                                  .
-    .                                                  .
-    .0                                                 .
-  ]
   # run another command
   assume-console [
     left-click 1, 80
@@ -93,11 +89,11 @@ scenario run-and-show-results [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .divide-with-remainder 11, 3                       .
     .3                                                 .
     .2                                                 .
@@ -260,10 +256,7 @@ def render-sandboxes screen:address:screen, sandbox:address:sandbox-data, left:n
     # render sandbox menu
     row <- add row, 1
     screen <- move-cursor screen, row, left
-    print screen, idx, 240/dark-grey
-    clear-line-until screen, right
-    delete-icon:character <- copy 120/x
-    print screen, delete-icon, 245/grey
+    screen <- render-sandbox-menu screen, idx, left, right
     # save menu row so we can detect clicks to it later
     *sandbox <- put *sandbox, starting-row-on-screen:offset, row
     # render sandbox contents
@@ -305,6 +298,46 @@ def render-sandboxes screen:address:screen, sandbox:address:sandbox-data, left:n
   row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx, env
 ]
 
+def render-sandbox-menu screen:address:screen, sandbox-index:number, left:number, right:number -> screen:address:screen [
+  local-scope
+  load-ingredients
+  move-cursor-to-column screen, left
+  edit-button-left:number, edit-button-right:number, copy-button-left:number, copy-button-right:number, delete-button-left:number <- sandbox-menu-columns left, right
+  print screen, sandbox-index, 232/dark-grey, 245/grey
+  start-buttons:number <- subtract edit-button-left, 1
+  clear-line-until screen, start-buttons, 245/grey
+  print screen, [edit], 232/black, 94/background-orange
+  clear-line-until screen, edit-button-right, 94/background-orange
+  _, col:number <- cursor-position screen
+  at-start-of-copy-button?:boolean <- equal col, copy-button-left
+  assert at-start-of-copy-button?, [aaa]
+  print screen, [copy], 232/black, 58/background-green
+  clear-line-until screen, copy-button-right, 58/background-green
+  _, col:number <- cursor-position screen
+  at-start-of-delete-button?:boolean <- equal col, delete-button-left
+  assert at-start-of-delete-button?, [bbb]
+  print screen, [delete], 232/black, 52/background-red
+  clear-line-until screen, right, 52/background-red
+]
+
+# divide up the menu bar for a sandbox into 3 segments, for edit/copy/delete buttons
+# delete-button-right == right
+# all left/right pairs are inclusive
+def sandbox-menu-columns left:number, right:number -> edit-button-left:number, edit-button-right:number, copy-button-left:number, copy-button-right:number, delete-button-left:number [
+  local-scope
+  load-ingredients
+  start-buttons:number <- add left, 4/space-for-sandbox-index
+  buttons-space:number <- subtract right, start-buttons
+  button-width:number <- divide-with-remainder buttons-space, 3  # integer division
+  buttons-wide-enough?:boolean <- greater-or-equal button-width, 8
+  assert buttons-wide-enough?, [sandbox must be at least 30 or so characters wide]
+  edit-button-left:number <- copy start-buttons
+  copy-button-left:number <- add start-buttons, button-width
+  edit-button-right:number <- subtract copy-button-left, 1
+  delete-button-left:number <- subtract right, button-width
+  copy-button-right:number <- subtract delete-button-left, 1
+]
+
 # assumes programming environment has no sandboxes; restores them from previous session
 def! restore-sandboxes env:address:programming-environment-data -> env:address:programming-environment-data [
   local-scope
@@ -427,7 +460,7 @@ return z
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -451,7 +484,7 @@ return z
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .5                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -477,7 +510,7 @@ scenario run-instruction-manages-screen-per-sandbox [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .print-integer screen, 4                           .
     .screen:                                           .
     .  .4                             .                .
@@ -545,7 +578,7 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -565,7 +598,7 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .␣                                                x.
+    .␣   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -585,7 +618,7 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
     .                               run (F4)           .
     .␣                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -687,11 +720,11 @@ scenario scrolling-through-multiple-sandboxes [
     .                               run (F4)           .
     .␣                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -711,11 +744,11 @@ scenario scrolling-through-multiple-sandboxes [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .␣                                                x.
+    .␣   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -732,7 +765,7 @@ scenario scrolling-through-multiple-sandboxes [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -749,7 +782,7 @@ scenario scrolling-through-multiple-sandboxes [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -766,11 +799,11 @@ scenario scrolling-through-multiple-sandboxes [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -790,11 +823,11 @@ scenario scrolling-through-multiple-sandboxes [
     .                               run (F4)           .
     .␣                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -814,11 +847,11 @@ scenario scrolling-through-multiple-sandboxes [
     .                               run (F4)           .
     .␣                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -844,7 +877,7 @@ scenario scrolling-manages-sandbox-index-correctly [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -862,7 +895,7 @@ scenario scrolling-manages-sandbox-index-correctly [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -880,7 +913,7 @@ scenario scrolling-manages-sandbox-index-correctly [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -898,7 +931,7 @@ scenario scrolling-manages-sandbox-index-correctly [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.  # no change
+    .0   edit           copy           delete          .  # no change
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
diff --git a/sandbox/007-sandbox-delete.mu b/sandbox/007-sandbox-delete.mu
index f8ea3450..252b6708 100644
--- a/sandbox/007-sandbox-delete.mu
+++ b/sandbox/007-sandbox-delete.mu
@@ -7,7 +7,6 @@ scenario deleting-sandboxes [
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
   # run a few commands
   assume-console [
-    left-click 1, 0
     type [divide-with-remainder 11, 3]
     press F4
     type [add 2, 2]
@@ -18,20 +17,20 @@ scenario deleting-sandboxes [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .divide-with-remainder 11, 3                       .
     .3                                                 .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # delete second sandbox
+  # delete second sandbox by clicking on left edge of 'delete' button
   assume-console [
-    left-click 7, 49
+    left-click 7, 34
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -40,14 +39,13 @@ scenario deleting-sandboxes [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
-    .                                                  .
   ]
-  # delete first sandbox
+  # delete first sandbox by clicking at right edge of 'delete' button
   assume-console [
     left-click 3, 49
   ]
@@ -59,15 +57,16 @@ scenario deleting-sandboxes [
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
-    .                                                  .
   ]
 ]
 
 after <global-touch> [
-  # on a sandbox delete icon? process delete
+  # support 'delete' button
   {
-    was-delete?:boolean <- delete-sandbox t, env
-    break-unless was-delete?
+    delete?:boolean <- should-attempt-delete? click-row, click-column, env
+    break-unless delete?
+    delete?, env <- try-delete-sandbox click-row, env
+    break-unless delete?
     hide-screen screen
     screen <- render-sandbox-side screen, env
     screen <- update-cursor screen, current-sandbox, env
@@ -76,70 +75,77 @@ after <global-touch> [
   }
 ]
 
-def delete-sandbox t:touch-event, env:address:programming-environment-data -> was-delete?:boolean, env:address:programming-environment-data [
+# some preconditions for attempting to delete a sandbox
+def should-attempt-delete? click-row:number, click-column:number, env:address:programming-environment-data -> result:boolean [
+  local-scope
+  load-ingredients
+  # are we below the sandbox editor?
+  click-sandbox-area?:boolean <- click-on-sandbox-area? click-row, env
+  reply-unless click-sandbox-area?, 0/false
+  # narrower, is the click in the columns spanning the 'copy' button?
+  first-sandbox:address:editor-data <- get *env, current-sandbox:offset
+  assert first-sandbox, [!!]
+  sandbox-left-margin:number <- get *first-sandbox, left:offset
+  sandbox-right-margin:number <- get *first-sandbox, right:offset
+  _, _, _, _, delete-button-left:number <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin
+  result <- within-range? click-column, delete-button-left, sandbox-right-margin
+]
+
+def try-delete-sandbox click-row:number, env:address:programming-environment-data -> clicked-on-delete-button?:boolean, env:address:programming-environment-data [
+  local-scope
+  load-ingredients
+  # identify the sandbox to delete, if the click was actually on the 'delete' button
+  sandbox:address:sandbox-data <- find-sandbox env, click-row
+  return-unless sandbox, 0/false
+  clicked-on-delete-button? <- copy 1/true
+  env <- delete-sandbox env, sandbox
+]
+
+def delete-sandbox env:address:programming-environment-data, sandbox:address:sandbox-data -> env:address:programming-environment-data [
   local-scope
   load-ingredients
-  click-column:number <- get t, column:offset
-  current-sandbox:address:editor-data <- get *env, current-sandbox:offset
-  right:number <- get *current-sandbox, right:offset
-  at-right?:boolean <- equal click-column, right
-  return-unless at-right?, 0/false
-  click-row:number <- get t, row:offset
+  curr-sandbox:address:sandbox-data <- get *env, sandbox:offset
+  first-sandbox?:boolean <- equal curr-sandbox, sandbox
   {
-    first:address:sandbox-data <- get *env, sandbox:offset
-    reply-unless first, 0/false
-    target-row:number <- get *first, starting-row-on-screen:offset
-    delete-first?:boolean <- equal target-row, click-row
-    break-unless delete-first?
-    new-first:address:sandbox-data <- get *first, next-sandbox:offset
-    *env <- put *env, sandbox:offset, new-first
-    env <- fixup-delete env, new-first
-    return 1/true  # force rerender
+    # first sandbox? pop
+    break-unless first-sandbox?
+    next-sandbox:address:sandbox-data <- get *curr-sandbox, next-sandbox:offset
+    *env <- put *env, sandbox:offset, next-sandbox
   }
-  prev:address:sandbox-data <- get *env, sandbox:offset
-  assert prev, [failed to find any sandboxes!]
-  curr:address:sandbox-data <- get *prev, next-sandbox:offset
   {
-    break-unless curr
-    # more sandboxes to check
+    # not first sandbox?
+    break-if first-sandbox?
+    prev-sandbox:address:sandbox-data <- copy curr-sandbox
+    curr-sandbox <- get *curr-sandbox, next-sandbox:offset
     {
-      target-row:number <- get *curr, starting-row-on-screen:offset
-      delete-curr?:boolean <- equal target-row, click-row
-      break-unless delete-curr?
-      # delete this sandbox
-      next:address:sandbox-data <- get *curr, next-sandbox:offset
-      *prev <- put *prev, next-sandbox:offset, next
-      env <- fixup-delete env, next
-      return 1/true  # force rerender
+      assert curr-sandbox, [sandbox not found! something is wrong.]
+      found?:boolean <- equal curr-sandbox, sandbox
+      break-if found?
+      prev-sandbox <- copy curr-sandbox
+      curr-sandbox <- get *curr-sandbox, next-sandbox:offset
+      loop
     }
-    prev <- copy curr
-    curr <- get *curr, next-sandbox:offset
-    loop
+    # snip sandbox out of its list
+    next-sandbox:address:sandbox-data <- get *curr-sandbox, next-sandbox:offset
+    *prev-sandbox <- put *prev-sandbox, next-sandbox:offset, next-sandbox
   }
-  return 0/false
-]
-
-def fixup-delete env:address:programming-environment-data, next:address:sandbox-data -> env:address:programming-environment-data [
-  local-scope
-  load-ingredients
   # update sandbox count
   sandbox-count:number <- get *env, number-of-sandboxes:offset
   sandbox-count <- subtract sandbox-count, 1
   *env <- put *env, number-of-sandboxes:offset, sandbox-count
+  # reset scroll if deleted sandbox was last
   {
-    break-if next
-    # deleted sandbox was last
+    break-if next-sandbox
     render-from:number <- get *env, render-from:offset
     reset-scroll?:boolean <- equal render-from, sandbox-count
     break-unless reset-scroll?
-    # deleted sandbox was only sandbox rendered, so reset scroll
     *env <- put *env, render-from:offset, -1
   }
 ]
 
 scenario deleting-sandbox-after-scroll [
   trace-until 100/app  # trace too long
-  assume-screen 30/width, 10/height
+  assume-screen 50/width, 10/height
   # initialize environment
   1:address:array:character <- new []
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
@@ -155,39 +161,39 @@ scenario deleting-sandbox-after-scroll [
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                              .  # menu
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 1, 1                      .
-    .2                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                            x.
-    .add 2, 2                      .
-    .4                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                               run (F4)           .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 1, 1                                          .
+    .2                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .1   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
   ]
   # delete the second sandbox
   assume-console [
-    left-click 6, 29
+    left-click 6, 34
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   ]
   # second sandbox shows in editor; scroll resets to display first sandbox
   screen-should-contain [
-    .                              .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 1, 1                      .
-    .2                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                              .
+    .                               run (F4)           .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 1, 1                                          .
+    .2                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
 
 scenario deleting-top-sandbox-after-scroll [
   trace-until 100/app  # trace too long
-  assume-screen 30/width, 10/height
+  assume-screen 50/width, 10/height
   # initialize environment
   1:address:array:character <- new []
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
@@ -203,39 +209,39 @@ scenario deleting-top-sandbox-after-scroll [
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                              .  # menu
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 1, 1                      .
-    .2                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                            x.
-    .add 2, 2                      .
-    .4                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                               run (F4)           .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 1, 1                                          .
+    .2                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .1   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
   ]
   # delete the second sandbox
   assume-console [
-    left-click 2, 29
+    left-click 2, 34
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   ]
   # second sandbox shows in editor; scroll resets to display first sandbox
   screen-should-contain [
-    .                              .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 2, 2                      .
-    .4                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                              .
+    .                               run (F4)           .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
 
 scenario deleting-final-sandbox-after-scroll [
   trace-until 100/app  # trace too long
-  assume-screen 30/width, 10/height
+  assume-screen 50/width, 10/height
   # initialize environment
   1:address:array:character <- new []
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
@@ -252,37 +258,37 @@ scenario deleting-final-sandbox-after-scroll [
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                              .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                            x.
-    .add 2, 2                      .
-    .4                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                              .
+    .                               run (F4)           .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .1   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   # delete the second sandbox
   assume-console [
-    left-click 2, 29
+    left-click 2, 34
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   ]
   # implicitly scroll up to first sandbox
   screen-should-contain [
-    .                              .
-    .                              .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 1, 1                      .
-    .2                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                              .
+    .                               run (F4)           .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 1, 1                                          .
+    .2                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
 
 scenario deleting-updates-sandbox-count [
   trace-until 100/app  # trace too long
-  assume-screen 30/width, 10/height
+  assume-screen 50/width, 10/height
   # initialize environment
   1:address:array:character <- new []
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
@@ -297,20 +303,20 @@ scenario deleting-updates-sandbox-count [
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                              .
-    .                              .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 1, 1                      .
-    .2                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                            x.
-    .add 2, 2                      .
-    .4                             .
+    .                               run (F4)           .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 1, 1                                          .
+    .2                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .1   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
   ]
   # delete the second sandbox, then try to scroll down twice
   assume-console [
-    left-click 3, 29
+    left-click 3, 34
     press page-down
     press page-down
   ]
@@ -319,12 +325,12 @@ scenario deleting-updates-sandbox-count [
   ]
   # shouldn't go past last sandbox
   screen-should-contain [
-    .                              .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                            x.
-    .add 2, 2                      .
-    .4                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                              .
+    .                               run (F4)           .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
diff --git a/sandbox/006-sandbox-edit.mu b/sandbox/008-sandbox-edit.mu
index 5f527444..192ed391 100644
--- a/sandbox/006-sandbox-edit.mu
+++ b/sandbox/008-sandbox-edit.mu
@@ -2,7 +2,7 @@
 
 scenario clicking-on-a-sandbox-moves-it-to-editor [
   trace-until 100/app  # trace too long
-  assume-screen 40/width, 10/height
+  assume-screen 50/width, 10/height
   # run something
   1:address:array:character <- new [add 2, 2]
   assume-console [
@@ -11,36 +11,28 @@ scenario clicking-on-a-sandbox-moves-it-to-editor [
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                     run (F4)           .
-    .                                        .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                      x.
-    .add 2, 2                                .
-    .4                                       .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
-    .                                        .
-    .                                        .
+    .                               run (F4)           .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   # click somewhere on the sandbox
   assume-console [
-    left-click 3, 0
+    left-click 3, 4
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   ]
   # it pops back into editor
   screen-should-contain [
-    .                     run (F4)           .
-    .add 2, 2                                .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
+    .                               run (F4)           .
+    .add 2, 2                                          .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   # cursor should be in the right place
   assume-console [
@@ -50,40 +42,20 @@ scenario clicking-on-a-sandbox-moves-it-to-editor [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   ]
   screen-should-contain [
-    .                     run (F4)           .
-    .0add 2, 2                               .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
-    .                                        .
+    .                               run (F4)           .
+    .0add 2, 2                                         .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
 
 after <global-touch> [
-  # below editor? pop appropriate sandbox contents back into sandbox editor provided it's empty
+  # support 'edit' button
   {
-    sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get t, column:offset
-    on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
-    break-unless on-sandbox-side?
-    first-sandbox:address:sandbox-data <- get *env, sandbox:offset
-    break-unless first-sandbox
-    first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get t, row:offset
-    below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
-    break-unless below-sandbox-editor?
-    empty-sandbox-editor?:boolean <- empty-editor? current-sandbox
-    break-unless empty-sandbox-editor?  # don't clobber existing contents
-    # identify the sandbox to edit and remove it from the sandbox list
-    sandbox:address:sandbox-data <- extract-sandbox env, click-row
-    break-unless sandbox
-    text:address:array:character <- get *sandbox, data:offset
-    current-sandbox <- insert-text current-sandbox, text
-    *env <- put *env, render-from:offset, -1
+    edit?:boolean <- should-attempt-edit? click-row, click-column, env
+    break-unless edit?
+    edit?, env <- try-edit-sandbox click-row, env
+    break-unless edit?
     hide-screen screen
     screen <- render-sandbox-side screen, env
     screen <- update-cursor screen, current-sandbox, env
@@ -92,52 +64,40 @@ after <global-touch> [
   }
 ]
 
-def empty-editor? editor:address:editor-data -> result:boolean [
+# some preconditions for attempting to edit a sandbox
+def should-attempt-edit? click-row:number, click-column:number, env:address:programming-environment-data -> result:boolean [
   local-scope
   load-ingredients
-  head:address:duplex-list:character <- get *editor, data:offset
-  first:address:duplex-list:character <- next head
-  result <- not first
+  # are we below the sandbox editor?
+  click-sandbox-area?:boolean <- click-on-sandbox-area? click-row, env
+  reply-unless click-sandbox-area?, 0/false
+  # narrower, is the click in the columns spanning the 'edit' button?
+  first-sandbox:address:editor-data <- get *env, current-sandbox:offset
+  assert first-sandbox, [!!]
+  sandbox-left-margin:number <- get *first-sandbox, left:offset
+  sandbox-right-margin:number <- get *first-sandbox, right:offset
+  edit-button-left:number, edit-button-right:number, _ <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin
+  edit-button-vertical-area?:boolean <- within-range? click-column, edit-button-left, edit-button-right
+  reply-unless edit-button-vertical-area?, 0/false
+  # finally, is sandbox editor empty?
+  current-sandbox:address:editor-data <- get *env, current-sandbox:offset
+  result <- empty-editor? current-sandbox
 ]
 
-def extract-sandbox env:address:programming-environment-data, click-row:number -> result:address:sandbox-data, env:address:programming-environment-data [
+def try-edit-sandbox click-row:number, env:address:programming-environment-data -> clicked-on-edit-button?:boolean, env:address:programming-environment-data [
   local-scope
   load-ingredients
-  curr-sandbox:address:sandbox-data <- get *env, sandbox:offset
-  start:number <- get *curr-sandbox, starting-row-on-screen:offset
-  in-editor?:boolean <- lesser-than click-row, start
-  return-if in-editor?, 0
-  first-sandbox?:boolean <- equal click-row, start
-  {
-    # first sandbox? pop
-    break-unless first-sandbox?
-    next-sandbox:address:sandbox-data <- get *curr-sandbox, next-sandbox:offset
-    *env <- put *env, sandbox:offset, next-sandbox
-  }
-  {
-    # not first sandbox?
-    break-if first-sandbox?
-    prev-sandbox:address:sandbox-data <- copy curr-sandbox
-    curr-sandbox <- get *curr-sandbox, next-sandbox:offset
-    {
-      next-sandbox:address:sandbox-data <- get *curr-sandbox, next-sandbox:offset
-      break-unless next-sandbox
-      # if click-row < sandbox.next-sandbox.starting-row-on-screen, break
-      next-start:number <- get *next-sandbox, starting-row-on-screen:offset
-      found?:boolean <- lesser-than click-row, next-start
-      break-if found?
-      prev-sandbox <- copy curr-sandbox
-      curr-sandbox <- copy next-sandbox
-      loop
-    }
-    # snip sandbox out of its list
-    *prev-sandbox <- put *prev-sandbox, next-sandbox:offset, next-sandbox
-  }
-  result <- copy curr-sandbox
-  # update sandbox count
-  sandbox-count:number <- get *env, number-of-sandboxes:offset
-  sandbox-count <- subtract sandbox-count, 1
-  *env <- put *env, number-of-sandboxes:offset, sandbox-count
+  # identify the sandbox to edit, if the click was actually on the 'edit' button
+  sandbox:address:sandbox-data <- find-sandbox env, click-row
+  return-unless sandbox, 0/false
+  clicked-on-edit-button? <- copy 1/true
+  # 'edit' button = 'copy' button + 'delete' button
+  text:address:array:character <- get *sandbox, data:offset
+  current-sandbox:address:editor-data <- get *env, current-sandbox:offset
+  current-sandbox <- insert-text current-sandbox, text
+  env <- delete-sandbox env, sandbox
+  # reset scroll
+  *env <- put *env, render-from:offset, -1
 ]
 
 scenario sandbox-with-print-can-be-edited [
@@ -155,7 +115,7 @@ scenario sandbox-with-print-can-be-edited [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .print-integer screen, 4                           .
     .screen:                                           .
     .  .4                             .                .
@@ -168,7 +128,7 @@ scenario sandbox-with-print-can-be-edited [
   ]
   # edit the sandbox
   assume-console [
-    left-click 3, 70
+    left-click 3, 18
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -203,7 +163,7 @@ scenario editing-sandbox-after-scrolling-resets-scroll [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -211,7 +171,7 @@ scenario editing-sandbox-after-scrolling-resets-scroll [
   ]
   # edit the second sandbox
   assume-console [
-    left-click 2, 20
+    left-click 2, 10
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -221,7 +181,7 @@ scenario editing-sandbox-after-scrolling-resets-scroll [
     .                               run (F4)           .
     .add 2, 2                                          .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -249,15 +209,15 @@ scenario editing-sandbox-updates-sandbox-count [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
   ]
   # edit the second sandbox, then resave
   assume-console [
-    left-click 3, 20
+    left-click 3, 10
     press F4
   ]
   run [
@@ -268,11 +228,11 @@ scenario editing-sandbox-updates-sandbox-count [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 1, 1                                          .
     .2                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
   ]
   # now try to scroll past end
   assume-console [
@@ -287,7 +247,7 @@ scenario editing-sandbox-updates-sandbox-count [
   screen-should-contain [
     .                               run (F4)           .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .1                                                x.
+    .1   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
diff --git a/sandbox/008-sandbox-test.mu b/sandbox/009-sandbox-test.mu
index e88d7240..2578e1ab 100644
--- a/sandbox/008-sandbox-test.mu
+++ b/sandbox/009-sandbox-test.mu
@@ -19,7 +19,7 @@ def foo [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -51,7 +51,7 @@ def foo [
     .                               run (F4)           .
     .␣                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
diff --git a/sandbox/009-sandbox-trace.mu b/sandbox/010-sandbox-trace.mu
index b759313e..63d56787 100644
--- a/sandbox/009-sandbox-trace.mu
+++ b/sandbox/010-sandbox-trace.mu
@@ -2,7 +2,7 @@
 
 scenario sandbox-click-on-code-toggles-app-trace [
   trace-until 100/app  # trace too long
-  assume-screen 40/width, 10/height
+  assume-screen 50/width, 10/height
   # run a stash instruction
   1:address:array:character <- new [stash [abc]]
   assume-console [
@@ -11,13 +11,13 @@ scenario sandbox-click-on-code-toggles-app-trace [
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                     run (F4)           .
-    .                                        .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                      x.
-    .stash [abc]                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
+    .                               run (F4)           .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .stash [abc]                                       .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   # click on the code in the sandbox
   assume-console [
@@ -30,24 +30,24 @@ scenario sandbox-click-on-code-toggles-app-trace [
   ]
   # trace now printed and cursor shouldn't have budged
   screen-should-contain [
-    .                     run (F4)           .
-    .␣                                       .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                      x.
-    .stash [abc]                             .
-    .abc                                     .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
+    .                               run (F4)           .
+    .␣                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .stash [abc]                                       .
+    .abc                                               .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   screen-should-contain-in-color 245/grey, [
-    .                                        .
-    .                                        .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                       x.
-    .                                        .
-    .abc                                     .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
+    .                                                  .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
+    .                                                  .
+    .abc                                               .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   # click again on the same region
   assume-console [
@@ -59,19 +59,19 @@ scenario sandbox-click-on-code-toggles-app-trace [
   ]
   # trace hidden again
   screen-should-contain [
-    .                     run (F4)           .
-    .␣                                       .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                      x.
-    .stash [abc]                             .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
+    .                               run (F4)           .
+    .␣                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .stash [abc]                                       .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
 
 scenario sandbox-shows-app-trace-and-result [
   trace-until 100/app  # trace too long
-  assume-screen 40/width, 10/height
+  assume-screen 50/width, 10/height
   # run a stash instruction and some code
   1:address:array:character <- new [stash [abc]
 add 2, 2]
@@ -81,15 +81,15 @@ add 2, 2]
   2:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
-    .                     run (F4)           .
-    .                                        .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                      x.
-    .stash [abc]                             .
-    .add 2, 2                                .
-    .4                                       .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
+    .                               run (F4)           .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .stash [abc]                                       .
+    .add 2, 2                                          .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
   # click on the code in the sandbox
   assume-console [
@@ -100,16 +100,16 @@ add 2, 2]
   ]
   # trace now printed above result
   screen-should-contain [
-    .                     run (F4)           .
-    .                                        .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                      x.
-    .stash [abc]                             .
-    .add 2, 2                                .
-    .abc                                     .
-    .4                                       .
-    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                        .
+    .                               run (F4)           .
+    .                                                  .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .0   edit           copy           delete          .
+    .stash [abc]                                       .
+    .add 2, 2                                          .
+    .abc                                               .
+    .4                                                 .
+    .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .                                                  .
   ]
 ]
 
diff --git a/sandbox/010-errors.mu b/sandbox/011-errors.mu
index c67790f3..8dfedf96 100644
--- a/sandbox/010-errors.mu
+++ b/sandbox/011-errors.mu
@@ -143,7 +143,7 @@ def foo [
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .foo: unknown element foo in container number      .
     .foo: first ingredient of 'get' should be a contai↩.
@@ -233,7 +233,7 @@ scenario run-hides-errors-from-past-sandboxes [
     .                               run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .add 2, 2                                          .
     .4                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -261,7 +261,7 @@ z <- add x, y
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo 2                                             .
     .foo_2: 'add' requires number ingredients, but got↩.
     . y                                                .
@@ -280,7 +280,7 @@ z <- add x, y
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo 2                                             .
     .foo_3: 'add' requires number ingredients, but got↩.
     . y                                                .
@@ -356,7 +356,7 @@ def foo [
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .foo: missing type for x in 'x <- copy 0'          .
     .foo: can't copy 0 to x; types don't match         .
@@ -388,7 +388,7 @@ def foo «
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .9: unbalanced '\\\[' for recipe                      .
     .9: unbalanced '\\\[' for recipe                      .
@@ -418,7 +418,7 @@ def foo [
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .foo: first ingredient of 'get' should be a contai↩.
     .ner, but got x:address:point                      .
@@ -447,7 +447,7 @@ def foo [
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .foo: second ingredient of 'get' should have type ↩.
     .'offset', but got x:number                        .
@@ -473,7 +473,7 @@ def foo [
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .foo: use before set: y                            .
   ]
@@ -488,7 +488,7 @@ def foo [
     .  errors found                 run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo                                               .
     .foo: use before set: y                            .
   ]
@@ -509,7 +509,7 @@ scenario run-instruction-and-print-errors [
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .get 1:address:point, 1:offset                     .
     .first ingredient of 'get' should be a container, ↩.
     .but got 1:address:point                           .
@@ -548,7 +548,7 @@ scenario run-instruction-and-print-errors-only-once [
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .get 1234:number, foo:offset                       .
     .unknown element foo in container number           .
     .first ingredient of 'get' should be a container, ↩.
@@ -577,7 +577,7 @@ loop
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .{                                                 .
     .loop                                              .
     .}                                                 .
@@ -611,7 +611,7 @@ return b
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo 4, 0                                          .
     .foo: divide by zero in '_, c:number <- divide-wit↩.
     .h-remainder a, b'                                 .
@@ -630,7 +630,7 @@ return b
     .  errors found (0)             run (F4)           .
     .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .0                                                x.
+    .0   edit           copy           delete          .
     .foo 4, 0                                          .
     .dividing by 0                                     .
     .foo: divide by zero in '_, c:number <- divide-wit↩.
diff --git a/sandbox/011-editor-undo.mu b/sandbox/012-editor-undo.mu
index 14c156e9..14c156e9 100644
--- a/sandbox/011-editor-undo.mu
+++ b/sandbox/012-editor-undo.mu