diff --git a/README.md b/README.md
index 24010e3..102387a 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # Zoom Quilt Machine
 
 
-A [game-engine](https://en.wikipedia.org/wiki/Game_engine)-like system 
+A [game-engine](https://en.wikipedia.org/wiki/Game_engine)-like system
   written in [Rust](https://www.rust-lang.org/),
   for the [Internet Computer](https://dfinity.org/faq).
 
@@ -21,12 +21,43 @@ Initially, we focus on [PoC](https://en.wikipedia.org/wiki/Proof_of_concept) con
 - [ ] HTTP-client support: Connect to an IC canister holding saved media; send/receive media data to/from that canister.
 
 
-### See also: Existing Zoom Quilts:
+### See also:
+
+This project draws inspiration from many sources.
+
+#### Computer graphics standards (2D)
+
+At its core, ZQM is an experimental computer graphics project
+that draws inspiration from existing standards for 2D computer graphics.
+
+For instance, the [SVG standard](https://www.w3.org/TR/SVGTiny12/) shares some goals and has some
+comparable concepts and parts.
+
+As a shared goal, both ZQM and SVG offer a human-computer language
+for authoring portable 2D graphics with interaction (scripting).
+
+##### SVG standard details:
+
+- [Scalable Vector Graphics (SVG) Tiny 1.2 Specification](https://www.w3.org/TR/SVGTiny12/)
+  - [Basic data types](https://www.w3.org/TR/SVGTiny12/types.html)
+  - [DOM IDL, for interactive scripts](https://www.w3.org/TR/SVGTiny12/svgudomidl.html)
+
+ZQM aspires to produce SVG media artifacts from its own media.
+
+#### Media/content editors/IDEs
+
+ - https://p5stamper.com/
+
+
+#### Existing "Zoom Quilts" (art projects):
+
+The name _"Zoom Quilt Machine"_ was inspired by the experiences
+of watching these art projects,
+and imagining an answer to the question:
+_"What tools could express the authorship of these quilts, as living data structures?"_:
 
  - https://www.zoomquilt.org/
  - https://zoomquilt2.com/
  - https://arkadia.xyz/
  - https://www.adultswim.com/etcetera/zoom/
 
-#### Media/content editors/IDEs
- - https://p5stamper.com/
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index c382c5e..b75c900 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -13,7 +13,14 @@ serde_bytes = "0.11"
 serde_cbor = "0.9"
 serde_json = "1.0"
 hashcons = "0.1"
+tokio = "0.2.10"
 #sdl2 = "0.32"
+delay = "0.1.0"
+
+serde-idl = { path = "/Users/matthew/dfn/sdk/src/serde_idl" }
+dfx_derive = { path = "/Users/matthew/dfn/sdk/src/dfx_derive" }
+dfx_info = { path = "/Users/matthew/dfn/sdk/src/dfx_info" }
+ic-agent = { path = "/Users/matthew/dfn/sdk/src/agent/rust" }
 
 [lib]
 name = "zqm_engine"
diff --git a/engine/src/lib/bitmap.rs b/engine/src/lib/bitmap.rs
index 2ae93ec..bb58da2 100644
--- a/engine/src/lib/bitmap.rs
+++ b/engine/src/lib/bitmap.rs
@@ -48,7 +48,7 @@ pub enum AutoCommand {
 // Define a canonical editor for the structure in question.  Again, use simplified, affine Rust.
 
 /// the history-_independent_ state of the editor
-#[derive(Debug, Serialize, Deserialize, Hash)]
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub struct EditorState {
     /// created by an Init command; affected by Auto and Edit commands
     pub bitmap: Bitmap,
@@ -62,7 +62,7 @@ pub struct EditorState {
 //   includes the command history, and any "pre-states" before initialization completes.
 
 /// the history-_dependent_ state of the editoro
-#[derive(Debug, Serialize, Deserialize, Hash)]
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub struct Editor {
     /// full linear history of this bitmap's evolution, as a sequence of commands
     pub history: Vec<Command>,
diff --git a/engine/src/lib/candid.rs b/engine/src/lib/candid.rs
new file mode 100644
index 0000000..7929b19
--- /dev/null
+++ b/engine/src/lib/candid.rs
@@ -0,0 +1,955 @@
+use serde::{Deserialize, Serialize};
+extern crate serde_idl;
+
+use menu;
+use menu::MenuType;
+use render;
+use types::lang::{Atom, Name};
+
+use ic_agent::{Agent, AgentConfig, Blob, CanisterId};
+use serde_idl::grammar::IDLProgParser;
+use serde_idl::lexer::Lexer;
+use serde_idl::{
+    types::{Dec, IDLProg, IDLType, Label, PrimType},
+    value::IDLArgs,
+    value::IDLField,
+    value::IDLValue,
+};
+
+
+use std::collections::HashMap;
+pub type Env = HashMap<String, MenuType>;
+
+pub fn parse_idl(input: &str) -> IDLProg {
+    let lexer = Lexer::new(input);
+    IDLProgParser::new().parse(lexer).unwrap()
+}
+
+fn name_of_idllabel(l: &Label) -> Name {
+    match l {
+        Label::Id(n) => Name::Atom(Atom::Usize(*n as usize)),
+        Label::Named(n) => Name::Atom(Atom::String(n.clone())),
+        Label::Unnamed(_) => Name::Void,
+    }
+}
+
+fn menutype_of_idltype(env: &Env, t: &IDLType) -> MenuType {
+    match t {
+        IDLType::RecordT(fields) => {
+            let mut out = vec![];
+            for field in fields.iter() {
+                let n = name_of_idllabel(&field.label);
+                let t = menutype_of_idltype(env, &field.typ);
+                out.push((n, t))
+            }
+            MenuType::Product(out)
+        }
+        IDLType::VariantT(fields) => {
+            let mut out = vec![];
+            for field in fields.iter() {
+                let n = name_of_idllabel(&field.label);
+                let t = menutype_of_idltype(env, &field.typ);
+                out.push((n, t))
+            }
+            MenuType::Variant(out)
+        }
+        IDLType::OptT(t) => {
+            let mt = menutype_of_idltype(env, t);
+            MenuType::Option(Box::new(mt))
+        }
+        IDLType::VarT(v) => menutype_resolve_var(env, v, 0),
+        IDLType::VecT(t) => {
+            let t = menutype_of_idltype(env, &*t);
+            MenuType::Vec(Box::new(t))
+        }
+        IDLType::FuncT(ft) => {
+            let args = ft
+                .args
+                .iter()
+                .map(|t| menutype_of_idltype(env, t))
+                .collect();
+            let rets = ft
+                .rets
+                .iter()
+                .map(|t| menutype_of_idltype(env, t))
+                .collect();
+            MenuType::Func(menu::FuncType { args, rets })
+        }
+        IDLType::PrimT(PrimType::Nat) => MenuType::Prim(menu::PrimType::Nat),
+        IDLType::PrimT(PrimType::Int) => MenuType::Prim(menu::PrimType::Int),
+        IDLType::PrimT(PrimType::Nat8) => MenuType::Prim(menu::PrimType::Nat),
+        IDLType::PrimT(PrimType::Text) => MenuType::Prim(menu::PrimType::Text),
+        IDLType::PrimT(PrimType::Bool) => MenuType::Prim(menu::PrimType::Bool),
+        IDLType::PrimT(PrimType::Null) => MenuType::Prim(menu::PrimType::Null),
+        IDLType::ServT(_) => {
+            // To do (!):
+            MenuType::Prim(menu::PrimType::Null)
+        }
+        _ => unimplemented!("{:?}", t),
+    }
+}
+
+pub fn string_of_field_id(id: u32) -> Option<String> {
+    match id {
+        24860 => Some("ok".to_string()),
+        1269255460 => Some("rect".to_string()),
+        4996424 => Some("dim".to_string()),
+        5594516 => Some("pos".to_string()),
+        38537191 => Some("height".to_string()),
+        3395466758 => Some("width".to_string()),
+        120 => Some("x".to_string()),
+        121 => Some("y".to_string()),
+        240232876 => Some("closed".to_string()),
+        _ => None,
+    }
+}
+
+pub fn get_nat(v: &IDLValue) -> Option<usize> {
+    match v {
+        IDLValue::Nat(n) => Some(*n as usize),
+        _ => {
+            error!("expected nat: {:?}", v);
+            None
+        }
+    }
+}
+
+pub fn get_text(v: &IDLValue) -> Option<String> {
+    match v {
+        IDLValue::Text(t) => Some(t.clone()),
+        _ => {
+            error!("expected text: {:?}", v);
+            None
+        }
+    }
+}
+
+pub fn get_pos(pos: &IDLValue) -> Option<render::Pos> {
+    match pos {
+        IDLValue::Record(fields) => match (get_nat(&fields[0].val), get_nat(&fields[1].val)) {
+            (Some(x), Some(y)) => {
+                let x = x as isize;
+                let y = y as isize;
+                Some(render::Pos { x, y })
+            }
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+pub fn get_dim(dim: &IDLValue) -> Option<render::Dim> {
+    match dim {
+        IDLValue::Record(fields) => match (get_nat(&fields[1].val), get_nat(&fields[0].val)) {
+            (Some(width), Some(height)) => Some(render::Dim { width, height }),
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+pub fn get_rect(rect: &IDLValue) -> Option<render::Rect> {
+    match rect {
+        IDLValue::Record(fields) => {
+            if fields.len() != 2 {
+                return None;
+            };
+            match (get_pos(&fields[1].val), get_dim(&fields[0].val)) {
+                (Some(pos), Some(dim)) => Some(render::Rect { pos, dim }),
+                _ => None,
+            }
+        }
+        _ => None,
+    }
+}
+
+pub fn get_color(color: &IDLValue) -> Option<render::Color> {
+    match color {
+        IDLValue::Record(fields) => {
+            if fields.len() != 3 {
+                return None;
+            };
+            match (
+                get_nat(&fields[0].val),
+                get_nat(&fields[1].val),
+                get_nat(&fields[2].val),
+            ) {
+                (Some(r), Some(g), Some(b)) => Some(render::Color::RGB(r, g, b)),
+                _ => None,
+            }
+        }
+        _ => None,
+    }
+}
+
+pub fn get_color_nat(color_nat: &IDLValue) -> Option<(render::Color, usize)> {
+    match color_nat {
+        IDLValue::Record(fields) => {
+            if fields.len() != 2 {
+                return None;
+            };
+            match (get_color(&fields[0].val), get_nat(&fields[1].val)) {
+                (Some(c), Some(n)) => Some((c, n)),
+                _ => None,
+            }
+        }
+        _ => None,
+    }
+}
+
+pub fn get_fill_(f: &IDLField) -> Option<render::Fill> {
+    match f.id {
+        // #closed : Color
+        240232876 => match get_color(&f.val) {
+            Some(c) => Some(Fill::Closed(c)),
+            None => None,
+        },
+        // #open : (Color, Nat)
+        1236534218 => match get_color_nat(&f.val) {
+            Some((c, n)) => Some(Fill::Open(c, n)),
+            None => None,
+        },
+        // #none
+        1225396920 => Some(Fill::None),
+        _ => {
+            error!("unrecognized fill tag: {:?}", f);
+            None
+        }
+    }
+}
+
+pub fn get_fill(v: &IDLValue) -> Option<render::Fill> {
+    match v {
+        IDLValue::Variant(v) => get_fill_(&*v),
+        _ => {
+            error!("unrecognized fill: {:?}", v);
+            None
+        }
+    }
+}
+
+pub fn get_dir2d_(f: &IDLField) -> Option<Dir2D> {
+    match f.id {
+        3915647964 => Some(Dir2D::Right),
+        _ => {
+            error!("unrecognized dir2d tag: {:?}", f.id);
+            None
+        }
+    }
+}
+
+pub fn get_dir2d(v: &IDLValue) -> Option<Dir2D> {
+    match v {
+        IDLValue::Variant(v) => get_dir2d_(&*v),
+        _ => {
+            error!("unrecognized dir2d: {:?}", v);
+            None
+        }
+    }
+}
+
+pub fn get_flow_atts(v: &IDLValue) -> Option<render::FlowAtts> {
+    match v {
+        IDLValue::Record(fields) => {
+            if fields.len() != 3 {
+                return None;
+            };
+            match (
+                get_dir2d(&fields[0].val),
+                get_nat(&fields[2].val),
+                get_nat(&fields[1].val),
+            ) {
+                (Some(dir), Some(intra_pad), Some(inter_pad)) => Some(FlowAtts {
+                    dir,
+                    intra_pad,
+                    inter_pad,
+                }),
+                _ => {
+                    error!("unrecognized flow_atts: {:?}", v);
+                    None
+                }
+            }
+        }
+        _ => None,
+    }
+}
+
+pub fn get_text_atts(v: &IDLValue) -> Option<render::TextAtts> {
+    match v {
+        IDLValue::Record(fields) => {
+            if fields.len() != 5 {
+                return None;
+            };
+            // to do -- fix these indices;
+            // get indicies from field names somehow.
+            match (
+                get_nat(&fields[1].val),
+                get_fill(&fields[0].val),
+                get_fill(&fields[3].val),
+                get_dim(&fields[4].val),
+                get_flow_atts(&fields[2].val),
+            ) {
+                (Some(zoom), Some(fg_fill), Some(bg_fill), Some(glyph_dim), Some(glyph_flow)) => {
+                    Some(TextAtts {
+                        zoom,
+                        fg_fill,
+                        bg_fill,
+                        glyph_dim,
+                        glyph_flow,
+                    })
+                }
+                _ => {
+                    error!("could not recognize text_atts {:?}", v);
+                    None
+                }
+            }
+        }
+        _ => {
+            error!("could not recognize text_atts {:?}", v);
+            None
+        }
+    }
+}
+
+pub fn get_render_text(elm: &IDLValue) -> Option<render::Elm> {
+    match elm {
+        IDLValue::Record(fields) => {
+            if fields.len() != 2 {
+                return None;
+            };
+            // to do -- decompose these text atts and use them!
+            match (get_text(&fields[0].val), get_text_atts(&fields[1].val)) {
+                (Some(t), Some(ta)) => {
+                    let mut r = Render::new();
+                    r.text(&t, &ta);
+                    Some(r.into_elms()[0].clone())
+                }
+                _ => None,
+            }
+        }
+        _ => None,
+    }
+}
+
+pub fn get_render_rect_fill(elm: &IDLValue) -> Option<render::Elm> {
+    match elm {
+        IDLValue::Record(fields) => {
+            if fields.len() != 2 {
+                return None;
+            };
+            match (get_rect(&fields[0].val), get_fill(&fields[1].val)) {
+                (Some(r), Some(f)) => Some(render::Elm::Rect(r, f)),
+                _ => None,
+            }
+        }
+        _ => None,
+    }
+}
+
+pub fn get_render_node(elm: &IDLValue) -> Option<render::Elm> {
+    match elm {
+        IDLValue::Record(fields) => {
+            if fields.len() != 3 {
+                return None;
+            };
+            let rect = get_rect(&fields[2].val);
+            let fill = get_fill(&fields[1].val);
+            // to do -- decompose these text atts and use them!
+            match &fields[0].val {
+                IDLValue::Vec(vals) => match (rect, fill, get_render_elms(&vals)) {
+                    (None, _, _) => {
+                        warn!("failed to parse rect of node elm");
+                        None
+                    }
+                    (_, None, _) => {
+                        warn!("failed to parse fill of node elm");
+                        None
+                    }
+                    (Some(rect), Some(fill), Some(node_elms)) => {
+                        trace!("node with elements: {:?}", node_elms);
+                        Some(render::Elm::Node(Box::new(render::Node {
+                            name: Name::Void,
+                            children: node_elms,
+                            rect: rect,
+                            fill: fill,
+                        })))
+                    }
+                    _ => None,
+                },
+                _ => None,
+            }
+        }
+        _ => None,
+    }
+}
+
+/*
+Variant(
+    // node
+    IDLField { id: 1225394690, val:
+               Record(
+                   [
+                       // elms
+                       IDLField {
+                           id: 1125441421, val:
+                           // elms vector
+                           Vec(
+                               [
+                                   // #rect
+                                   Variant(IDLField { id: 1269255460,
+                                                      // (rect, fill)
+                                                      val: Record([
+                                                          // 0: rect
+                                                          IDLField { id: 0, val: Record([
+                                                              IDLField { id: 4996424, val: Record([
+                                                                  IDLField { id: 38537191, val: Nat(10) },
+                                                                  IDLField { id: 3395466758, val: Nat(10) }]) },
+                                                              IDLField { id: 5594516, val: Record([
+                                                                  IDLField { id: 120, val: Nat(2) },
+                                                                  IDLField { id: 121, val: Nat(2) }]) }]) },
+                                                          // 1: fill
+                                                          IDLField { id: 1, val: Variant(
+                                                              IDLField { id: 240232876, val: Record([
+                                                                  IDLField { id: 0, val: Nat(0) },
+                                                                  IDLField { id: 1, val: Nat(0) },
+                                                                  IDLField { id: 2, val: Nat(0) }]) }) }
+                                                      ])
+                                   })
+                               ]
+                           )
+                       },
+                       // fill
+                       IDLField { id: 1136381571, val: Variant(IDLField { id: 1225396920, val: Null }) },
+                       // rect
+                       IDLField { id: 1269255460, val:
+                                  Record([
+                                      IDLField { id: 4996424, val: Record([
+                                          IDLField { id: 38537191, val: Nat(0) },
+                                          IDLField { id: 3395466758, val: Nat(0) }
+                                      ])
+                                      },
+                                      IDLField { id: 5594516, val: Record([
+                                          IDLField { id: 120, val: Nat(2) },
+                                          IDLField { id: 121, val: Nat(2) }]) }])
+                       }
+                   ]
+               )
+    })
+*/
+
+pub fn get_render_elm_(elm: &IDLField) -> Option<render::Elm> {
+    match elm.id {
+        // #node : Node
+        1225394690 => get_render_node(&elm.val),
+        // #text : (String, TextAtts)
+        1291439277 => get_render_text(&elm.val),
+        // #rect : (Rect, Fill)
+        1269255460 => get_render_rect_fill(&elm.val),
+        tag => {
+            info!(
+                "warning: recognized element tag {}, for element {:?}",
+                tag, elm
+            );
+            None
+        }
+    }
+}
+
+pub fn get_render_elm(v: &IDLValue) -> Option<render::Elm> {
+    // extra debugger messages here:
+    match v {
+        IDLValue::Variant(f) => match get_render_elm_(f) {
+            None => {
+                warn!("failed to parse element {:?}", v);
+                return None;
+            }
+            Some(elm) => Some(elm),
+        },
+        _ => {
+            warn!("unrecognized element {:?}", v);
+            return None;
+        }
+    }
+}
+
+pub fn get_render_elms(vals: &Vec<IDLValue>) -> Option<render::Elms> {
+    let mut out: render::Elms = vec![];
+    for v in vals.iter() {
+        match get_render_elm(v) {
+            Some(elm) => out.push(elm),
+            None => return None,
+        }
+    }
+    Some(out)
+}
+
+pub fn get_render_named_elms_(vals: &Vec<IDLValue>) -> Option<render::NamedElms> {
+    let mut out: render::NamedElms = vec![];
+    for v in vals.iter() {
+        match v {
+            IDLValue::Record(fields) => {
+                if fields.len() != 2 {
+                    return None;
+                };
+                match (get_text(&fields[0].val), get_render_elm(&fields[1].val)) {
+                    (Some(name), Some(elm)) => out.push((Name::Atom(Atom::String(name)), elm)),
+                    _ => return None,
+                };
+            }
+            _ => return None,
+        }
+    }
+    Some(out)
+}
+
+pub fn get_render_named_elms(vals: &IDLValue) -> Option<render::NamedElms> {
+    match vals {
+        IDLValue::Vec(vals) => get_render_named_elms_(vals),
+        _ => None,
+    }
+}
+
+pub fn get_render_out(v: &IDLValue) -> Option<render::Out> {
+    match v {
+        IDLValue::Variant(field) => match field.id {
+            // #draw
+            1114647556 => match get_render_elm(&field.val) {
+                Some(elm) => Some(render::Out::Draw(elm)),
+                None => None,
+            },
+            // #redraw
+            4271367479 => match get_render_named_elms(&field.val) {
+                Some(named_elms) => Some(render::Out::Redraw(named_elms)),
+                None => None,
+            },
+            // #renderStreams -- to do
+            // otherwise
+            id => {
+                warn!("unexpected render_out id {:?}", id);
+                None
+            }
+        },
+        _ => None,
+    }
+}
+
+pub fn get_result_render_out(v: &IDLValue) -> Option<render::Out> {
+    // todo: add rendering for Ok/Err themselves
+    match v {
+        IDLValue::Variant(f) => match f.id {
+            // #ok
+            24860 => get_render_out(&f.val),
+            // #err
+            5048165 => get_render_out(&f.val),
+            _ => {
+                warn!("unexpected field {:?}", f);
+                None
+            }
+        },
+        _ => None,
+    }
+}
+
+pub fn find_render_out(vs: &Vec<IDLValue>) -> Option<render::Out> {
+    let mut outs: Vec<render::Out> = vec![];
+    for v in vs.iter() {
+        match get_result_render_out(v) {
+            None => {}
+            Some(o) => outs.push(o),
+        }
+    }
+    if outs.len() == 0 {
+        None
+    } else if outs.len() == 1 {
+        outs.pop()
+    } else {
+        error!("unexpected: multiple rendering outputs; using the last");
+        outs.pop()
+    }
+}
+
+pub fn idlargs_of_menutree(mt: &menu::MenuTree) -> String {
+    format!("{}", mt)
+}
+
+pub fn blob_of_menutree(mt: &menu::MenuTree) -> Blob {
+    let args: String = idlargs_of_menutree(mt);
+    let args: IDLArgs = args.parse().unwrap();
+    Blob(args.to_bytes().unwrap())
+}
+
+pub fn menutype_resolve_var(env: &Env, v: &String, depth: usize) -> MenuType {
+    // to do -- what is the real threshold in the def again?
+    if depth > 100 {
+        MenuType::Var(Name::Atom(Atom::String(v.clone())))
+    } else {
+        match env.get(v) {
+            None => MenuType::Var(Name::Atom(Atom::String(v.clone()))),
+            Some(MenuType::Var(Name::Atom(Atom::String(v)))) => {
+                menutype_resolve_var(env, v, depth + 1)
+            }
+            Some(MenuType::Var(_)) => unreachable!(),
+            Some(t) => t.clone(),
+        }
+    }
+}
+
+pub fn tuple_of_product(t: &menu::MenuTree) -> menu::MenuTree {
+    // to do
+    t.clone()
+}
+
+pub fn menutype_of_idlprog(p: &IDLProg) -> menu::MenuType {
+    let mut emp = HashMap::new();
+    let mut env = HashMap::new();
+    for dec in p.decs.iter() {
+        match dec {
+            Dec::TypD(ref b) => {
+                let t = menutype_of_idltype(&emp, &b.typ);
+                //print!("{:?}", b);
+                drop(env.insert(b.id.clone(), t))
+            }
+            _ => unimplemented!(),
+        }
+    }
+    match p.actor {
+        Some(IDLType::ServT(ref methods)) => {
+            let mut choices = vec![];
+            for method in methods.iter() {
+                //eprint!("method {:?} : {:?}", method.id, method.typ);
+                let i = method.id.clone();
+                let t = match method.typ {
+                    IDLType::FuncT(ref ft) => {
+                        if ft.args.len() > 0 {
+                            let arg_types: Vec<MenuType> = ft
+                                .args
+                                .iter()
+                                .map(|a| menutype_of_idltype(&env, a))
+                                .collect();
+                            let mut fields = vec![];
+                            for i in 0..arg_types.len() {
+                                fields.push(arg_types[i].clone())
+                            }
+                            MenuType::Tup(fields)
+                        } else {
+                            MenuType::Prim(menu::PrimType::Unit)
+                        }
+                    }
+                    _ => unreachable!(),
+                };
+                choices.push((Name::Atom(Atom::String(i)), t));
+            }
+            MenuType::Variant(choices)
+        }
+        _ => panic!("expected a service type"),
+    }
+}
+
+pub fn agent(url: &str) -> Result<Agent, ic_agent::AgentError> {
+    Agent::new(AgentConfig {
+        url: format!("http://{}", url).as_str(),
+        ..AgentConfig::default()
+    })
+}
+
+use std::time::{Duration, SystemTime};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+pub struct Call {
+    pub method: String,
+    pub args: menu::MenuTree,
+    pub args_idl: String,
+    pub rets_idl: Result<String, String>,
+    pub render_out: Option<render::Out>,
+    pub timestamp: SystemTime,
+    pub duration: Duration,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+pub struct Repl {
+    pub config: Config,
+    pub display: Vec<(Name, render::Elm)>,
+    pub history: Vec<Call>,
+    // todo: log of results, parsed into MenuTree's according to the MenuType of the result type
+}
+
+impl Repl {
+    pub fn update_display(&mut self, out: &Option<render::Out>) {
+        match out {
+            None => (), // nothing
+            Some(render::Out::Draw(named_elms)) => {
+                // nothing
+            }
+            Some(render::Out::Redraw(named_elms)) => {
+                let mut cells = std::collections::HashMap::new();
+                for (name, elm) in self.display.iter() {
+                    cells.insert(name, elm);
+                }
+                for (name, elm) in named_elms.iter() {
+                    cells.insert(name, elm);
+                }
+                let mut display: render::NamedElms = vec![];
+                for (name, elm) in cells.drain().take(1) {
+                    display.push((name.clone(), elm.clone()))
+                }
+                self.display = display;
+            }
+        }
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+pub struct Config {
+    //idl_prog: IDLProg,
+    pub replica_url: String,
+    pub canister_id: String,
+    pub menu_type: MenuType,
+}
+
+use types::lang::{Command, Editor, Frame, State};
+pub fn init(url: &str, cid_text: &str, p: &IDLProg) -> Result<State, String> {
+    use eval;
+    let cid: CanisterId = CanisterId::from_text(cid_text).unwrap();
+    assert_eq!(cid.to_text(), cid_text);
+    let mt: menu::MenuType = menutype_of_idlprog(p);
+    let mut st = State {
+        stack: vec![Frame::from_editor(Editor::CandidRepl(Box::new(Repl {
+            history: vec![],
+            display: vec![],
+            config: Config {
+                //idl_prog: p.clone(),
+                menu_type: mt.clone(),
+                replica_url: url.to_string(),
+                canister_id: cid.to_string(),
+            },
+        })))],
+        frame: Frame::from_editor(Editor::Menu(Box::new(menu::Editor {
+            state: None,
+            history: vec![],
+        }))),
+    };
+    let cmd = Command::Menu(menu::Command::Init(menu::InitCommand::Default(
+        menu::MenuTree::Blank(mt.clone()),
+        mt,
+    )));
+    eval::command_eval(&mut st, None, &cmd)?;
+    Ok(st)
+}
+
+use crate::render::{FlowAtts, FrameType, Render, TextAtts};
+use types::{
+    lang::Dir2D,
+    render::{Color, Dim, Elms, Fill},
+};
+
+fn text_zoom() -> usize {
+    2
+}
+
+fn horz_flow() -> FlowAtts {
+    FlowAtts {
+        dir: Dir2D::Right,
+        intra_pad: 2,
+        inter_pad: 2,
+    }
+}
+
+fn vert_flow() -> FlowAtts {
+    FlowAtts {
+        dir: Dir2D::Down,
+        intra_pad: 2,
+        inter_pad: 2,
+    }
+}
+
+fn glyph_padding() -> usize {
+    1
+}
+
+// eventaually we get these atts from
+//  some environment-determined settings
+fn glyph_flow() -> FlowAtts {
+    FlowAtts {
+        dir: Dir2D::Right,
+        intra_pad: glyph_padding(),
+        inter_pad: glyph_padding(),
+    }
+}
+
+fn glyph_dim() -> Dim {
+    Dim {
+        width: 5,
+        height: 5,
+    }
+}
+
+fn kw_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom(),
+        fg_fill: Fill::Closed(Color::RGB(255, 230, 255)),
+        bg_fill: Fill::None,
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn dim_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom(),
+        fg_fill: Fill::Closed(Color::RGB(150, 100, 150)),
+        bg_fill: Fill::None,
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn dim2_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom(),
+        fg_fill: Fill::Closed(Color::RGB(200, 180, 200)),
+        bg_fill: Fill::None,
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn data_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom(),
+        fg_fill: Fill::Closed(Color::RGB(230, 230, 230)),
+        bg_fill: Fill::None,
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn err_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom(),
+        fg_fill: Fill::Closed(Color::RGB(255, 100, 100)),
+        bg_fill: Fill::None,
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn msg_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom(),
+        fg_fill: Fill::Closed(Color::RGB(200, 200, 255)),
+        bg_fill: Fill::None,
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn big_msg_atts() -> TextAtts {
+    TextAtts {
+        zoom: text_zoom() * 2,
+        fg_fill: Fill::Closed(Color::RGB(220, 220, 255)),
+        bg_fill: Fill::Closed(Color::RGB(50, 50, 50)),
+        glyph_dim: glyph_dim(),
+        glyph_flow: glyph_flow(),
+    }
+}
+
+fn box_fill() -> Fill {
+    Fill::Open(Color::RGB(50, 100, 50), 1)
+}
+
+pub fn render_elms(repl: &Repl, r: &mut Render) {
+    if repl.display.len() > 0 {
+        r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+        r.str("Render cells:", &msg_atts());
+        for (name, elm) in repl.display.iter() {
+            r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+            r.fill(Fill::Open(Color::RGB(255, 255, 255), 1));
+            r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+            r.name(name, &data_atts());
+            r.str("= ", &data_atts());
+            r.end();
+            r.elm(elm.clone());
+            r.end();
+        }
+        r.end()
+    }
+    if repl.history.len() > 0 {
+        r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+        r.str("Message log:", &msg_atts());
+        r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+        r.fill(box_fill());
+        for call in repl.history.iter().rev().take(10) {
+            r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+            r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+            r.str(&call.method, &msg_atts());
+            r.text(&call.args_idl, &data_atts());
+            r.end();
+            match call.rets_idl {
+                Ok(ref rets_idl) => {
+                    r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+                    r.text(&format!(" {:?}", &call.duration), &dim_atts());
+                    r.str("━━►", &dim2_atts());
+                    // to do -- if len of text overflows, then wrap it
+                    match call.render_out {
+                        None => {
+                            if rets_idl.len() > 80 {
+                                let mut rets_idl = rets_idl.clone();
+                                rets_idl.truncate(80);
+                                r.text(&rets_idl, &data_atts());
+                                r.str("...", &data_atts())
+                            } else {
+                                r.text(rets_idl, &data_atts())
+                            }
+                        }
+                        Some(ref out) => match out {
+                            render::Out::Draw(elm) => r.elm(elm.clone()),
+                            render::Out::Redraw(named_elms) => {
+                                for (name, elm) in named_elms.iter() {
+                                    r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+                                    r.fill(Fill::Open(Color::RGB(255, 255, 255), 1));
+                                    r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+                                    r.name(name, &data_atts());
+                                    r.str("= ", &data_atts());
+                                    r.end();
+                                    r.elm(elm.clone());
+                                    r.end();
+                                }
+                            }
+                        },
+                    };
+                    r.end()
+                }
+                Err(ref msg) => {
+                    r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+                    r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+                    r.text(&format!(" {:?}", &call.duration), &dim_atts());
+                    r.str("━━► ", &err_atts());
+                    r.end();
+                    r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+                    r.str(&" ", &err_atts());
+                    // to do -- if len of text overflows, then wrap it
+                    r.text(msg, &err_atts());
+                    r.end();
+                    r.end();
+                }
+            }
+            r.end()
+        }
+        r.end();
+        r.end()
+    }
+}
+
+#[test]
+fn doit() {
+    let prog = r#"
+service server : {
+  f : (a: nat, b:nat) -> () oneway;
+  g : (a: text, b:nat) -> () oneway;
+  h : (a: nat, b:record { nat; nat; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; }) -> () oneway;
+}
+    "#;
+    let ast = parse_idl(&prog);
+    let menu = menutype_of_idlprog_service(&ast);
+    drop(menu);
+}
diff --git a/engine/src/lib/chain.rs b/engine/src/lib/chain.rs
index f0150ee..f84f331 100644
--- a/engine/src/lib/chain.rs
+++ b/engine/src/lib/chain.rs
@@ -131,7 +131,7 @@ pub struct EditorState {
     pub tail: Chain,
 }
 
-#[derive(Debug, Serialize, Deserialize, Hash)]
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub struct Editor {
     pub history: Vec<Command>,
     pub state: Option<EditorState>,
diff --git a/engine/src/lib/eval.rs b/engine/src/lib/eval.rs
index 98619e2..e1b54a5 100644
--- a/engine/src/lib/eval.rs
+++ b/engine/src/lib/eval.rs
@@ -1,17 +1,30 @@
 // to-do/question: rename this module to 'engine'?
 
 use bitmap;
+use candid;
 use menu;
 
+use delay::Delay;
+use std::time::Duration;
+
+const RETRY_PAUSE: Duration = Duration::from_millis(100);
+const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
+
+pub enum LoadState {
+    CandidFile { file: String },
+    Resume,
+}
+
 pub use super::types::{
     event::Event,
-    lang::{Command, Editor, State},
+    lang::{Command, Editor, Media, State},
     render,
 };
 
 pub fn commands_of_event(state: &mut State, event: &Event) -> Result<Vec<Command>, ()> {
     debug!("commands_of_event {:?}", event);
-    let res = match &mut state.editor {
+    let res = match &mut state.frame.editor {
+        &mut Editor::CandidRepl(ref _repl) => unimplemented!(),
         &mut Editor::Bitmap(ref _ed) => {
             // to do -- insert a name into each command that is unique,
             // but whose structure encodes a wallclock timestamp, among other sequence numbers.
@@ -45,45 +58,204 @@ pub fn commands_of_event(state: &mut State, event: &Event) -> Result<Vec<Command
     res
 }
 
-pub fn command_eval(state: &mut State, command: &Command) -> Result<(), String> {
-    debug!("command_eval {:?}", command);
-    let res = match (command, &mut state.editor) {
-        (&Command::Bitmap(ref bc), &mut Editor::Bitmap(ref mut be)) => {
-            super::bitmap::semantics::editor_eval(be, bc)
-        }
-        (&Command::Bitmap(ref _bc), _) => Err("bitmap editor expected bitmap command".to_string()),
-        (_, &mut Editor::Bitmap(ref mut _be)) => {
-            Err("bitmap command for non-bitmap editor".to_string())
+use serde_idl::value::{IDLArgs, IDLField, IDLValue};
+
+pub fn command_eval(
+    state: &mut State,
+    orig_event: Option<Event>,
+    command: &Command,
+) -> Result<(), String> {
+    if let &Command::Return(ref media) = command {
+        debug!("command_eval: return...");
+
+        if state.stack.len() == 0 {
+            Err(format!("cannot return; empty stack"))
+        } else {
+            let old_top = state.frame.clone();
+            state.frame = state.stack.pop().unwrap();
+            let resume = Command::Resume(media.clone(), old_top);
+            command_eval(state, None, &resume)
         }
+    } else if let &Command::Resume(Media::MenuTree(ref mt), ref old_frame) = command {
+        // to do: clean, refactor and move this logic (e.g., into the candid module)
+        match &**mt {
+            &menu::MenuTree::Variant(ref ch) => match &ch.choice {
+                None => {
+                    warn!("no choice selected; try again...");
+                }
+                &Some((ref lab, ref tree, ref typ)) => {
+                    let method = format!("{}", lab);
+                    let str = format!("{}", tree);
+                    info!("Sending message \"{}\", with args {}", method, str);
+                    if let Ok(args) = &str.parse::<IDLArgs>() {
+                        debug!("...as {}({})", method, args);
+
+                        if let &mut Editor::CandidRepl(ref mut repl) = &mut state.frame.editor {
+                            use ic_agent::{Blob, CanisterId};
+                            use std::time::Duration;
+                            use tokio::runtime::Runtime;
 
-        (&Command::Menu(ref c), &mut Editor::Menu(ref mut e)) => {
-            super::menu::semantics::editor_eval(e, c)
+                            info!(
+                                "...to canister_id {:?} at replica_url {:?}",
+                                repl.config.canister_id, repl.config.replica_url
+                            );
+
+                            let mut runtime = Runtime::new().expect("Unable to create a runtime");
+                            let delay = Delay::builder()
+                                .throttle(RETRY_PAUSE)
+                                .timeout(REQUEST_TIMEOUT)
+                                .build();
+                            let agent = candid::agent(&repl.config.replica_url).unwrap();
+                            let canister_id =
+                                CanisterId::from_text(repl.config.canister_id.clone()).unwrap();
+                            let timestamp = std::time::SystemTime::now();
+                            let blob_res = runtime.block_on(agent.call_and_wait(
+                                &canister_id,
+                                &method,
+                                &Blob(args.to_bytes().unwrap()),
+                                delay,
+                            ));
+
+                            let elapsed = timestamp.elapsed().unwrap();
+                            if let Ok(blob_res) = blob_res {
+                                let result =
+                                    serde_idl::IDLArgs::from_bytes(&(*blob_res.unwrap().0));
+                                let idl_rets = result.unwrap().args;
+                                let render_out = candid::find_render_out(&idl_rets);
+                                repl.update_display(&render_out);
+                                let res = format!("{:?}", &idl_rets);
+                                let mut res_log = res.clone();
+                                if res_log.len() > 80 {
+                                    res_log.truncate(80);
+                                    res_log.push_str("...(truncated)");
+                                }
+                                info!("..successful result {:?}", res_log);
+                                trace!("..render out {:?}", render_out);
+                                let call = candid::Call {
+                                    timestamp: timestamp,
+                                    duration: elapsed,
+                                    method: method,
+                                    args: tree.clone(),
+                                    args_idl: str.to_string(),
+                                    rets_idl: Ok(res),
+                                    render_out,
+                                };
+                                repl.history.push(call)
+                            } else {
+                                let res = format!("{:?}", blob_res);
+                                info!("..error result {:?}", res);
+                                let call = candid::Call {
+                                    timestamp: timestamp,
+                                    duration: elapsed,
+                                    method: method,
+                                    args: tree.clone(),
+                                    args_idl: str.to_string(),
+                                    rets_idl: Err(res),
+                                    render_out: None,
+                                };
+                                repl.history.push(call)
+                            }
+                        } else {
+                            unreachable!()
+                        }
+                    } else {
+                        error!("Cannot send incomplete message; please fill remaining blanks.")
+                    }
+                }
+            },
+            _ => unreachable!("broken invariants"),
         }
-        (&Command::Menu(ref _c), _) => Err("menu editor expected menu command".to_string()),
-        (_, &mut Editor::Menu(ref mut _e)) => Err("menu command for non-menu editor".to_string()),
+        state.stack.push(state.frame.clone());
+        state.frame = old_frame.clone();
+        Ok(())
+    } else {
+        debug!("command_eval {:?}", command);
+        match orig_event {
+            Some(ev) => info!("event {:?} ==> command {:?}", ev, command),
+            None => info!("unknown event ==> command {:?}", command),
+        };
+        let res = match (command, &mut state.frame.editor) {
+            (&Command::Bitmap(ref bc), &mut Editor::Bitmap(ref mut be)) => {
+                super::bitmap::semantics::editor_eval(be, bc)
+            }
+            (&Command::Bitmap(ref _bc), _) => {
+                Err("bitmap editor expected bitmap command".to_string())
+            }
+            (_, &mut Editor::Bitmap(ref mut _be)) => {
+                Err("bitmap command for non-bitmap editor".to_string())
+            }
 
-        (&Command::Chain(ref _ch), _) => unimplemented!(),
-        (&Command::Grid(ref _gr), _) => unimplemented!(),
-    };
-    debug!("command_eval {:?} ==> {:?}", command, res);
-    res
+            (&Command::Menu(ref c), &mut Editor::Menu(ref mut e)) => {
+                match super::menu::semantics::editor_eval(e, c) {
+                    Ok(()) => Ok(()),
+                    Err(menu::Halt::Commit(mt)) => {
+                        command_eval(state, None, &Command::Return(Media::MenuTree(Box::new(mt))))
+                    }
+                    Err(menu::Halt::Message(m)) => Err(m),
+                }
+            }
+            (&Command::Menu(ref _c), _) => Err("menu editor expected menu command".to_string()),
+            (_, &mut Editor::Menu(ref mut _e)) => {
+                Err("menu command for non-menu editor".to_string())
+            }
+
+            (&Command::Chain(ref _ch), _) => unimplemented!(),
+            (&Command::Grid(ref _gr), _) => unimplemented!(),
+            (&Command::Return(_), _) => unimplemented!(),
+            (&Command::Resume(_, _), _) => unimplemented!(),
+        };
+        debug!("command_eval {:?} ==> {:?}", command, res);
+        res
+    }
 }
 
-pub fn render_elms(state: &State) -> Result<render::Elms, String> {
-    match &state.editor {
+use crate::render::{FlowAtts, FrameType, Render, TextAtts};
+use types::lang::{Dir2D, Name};
+
+pub fn render_elms_of_editor(editor: &Editor, r: &mut Render) {
+    match editor {
+        &Editor::CandidRepl(ref repl) => candid::render_elms(repl, r),
         &Editor::Bitmap(ref ed) => match ed.state {
-            None => Ok(vec![]),
-            Some(ref ed) => super::bitmap::io::render_elms(ed),
+            None => warn!("to do: render empty bitmap editor?"),
+            Some(ref ed) => unimplemented!(), //super::bitmap::io::render_elms(ed, r),
         },
         &Editor::Menu(ref ed) => match ed.state {
-            None => Ok(vec![]),
-            Some(ref st) => super::menu::io::render_elms(st),
+            None => warn!("to do: render empty bitmap editor?"),
+            Some(ref st) => menu::io::render_elms(st, r),
         },
         &Editor::Chain(ref _ch) => unimplemented!(),
         &Editor::Grid(ref _gr) => unimplemented!(),
     }
 }
 
+pub fn render_elms(state: &State) -> Result<render::Elms, String> {
+    let mut r = Render::new();
+    fn vert_flow() -> FlowAtts {
+        FlowAtts {
+            dir: Dir2D::Down,
+            intra_pad: 2,
+            inter_pad: 2,
+        }
+    }
+    fn horz_flow() -> FlowAtts {
+        FlowAtts {
+            dir: Dir2D::Right,
+            intra_pad: 2,
+            inter_pad: 2,
+        }
+    }
+    // to do: acquire and use the screen dimension for "clip flows".
+    r.begin(&Name::Void, FrameType::Flow(horz_flow()));
+    r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+    for frame in state.stack.iter() {
+        render_elms_of_editor(&frame.editor, &mut r);
+    }
+    r.end();
+    render_elms_of_editor(&state.frame.editor, &mut r);
+    r.end();
+    Ok(r.into_elms())
+}
+
 pub fn get_persis_state_path() -> String {
     let dir: String = std::env::current_dir().unwrap().to_str().unwrap().into();
     format!("{}/zqm.json", dir)
diff --git a/engine/src/lib/glyph.rs b/engine/src/lib/glyph.rs
index bd1b16b..2e3c505 100644
--- a/engine/src/lib/glyph.rs
+++ b/engine/src/lib/glyph.rs
@@ -22,6 +22,20 @@ pub mod cap5x5 {
     // the first set of glyphs to bootstrap zqm's interface
     // editor: emacs (Rust major mode, in ovewrite minor mode)
     glyph_map! {
+        "☺2" => [
+            0 8 8 8 0 ;
+            8 0 0 0 8 ;
+            8 0 0 0 8 ;
+            8 0 0 0 8 ;
+            0 8 8 8 0
+        ],
+        "☺" => [
+            0 8 8 8 0 ;
+            8 0 8 0 8 ;
+            0 8 8 8 0 ;
+            8 0 0 0 8 ;
+            0 8 8 8 0
+        ],
         " " => [
             0 0 0 0 0 ;
             0 0 0 0 0 ;
@@ -29,6 +43,78 @@ pub mod cap5x5 {
             0 0 0 0 0 ;
             0 0 0 0 0
         ],
+        // hack: lock glyph for mazeGame
+        "ļ" => [
+            8 8 8 8 8 ;
+            8 8 0 8 8 ;
+            8 0 8 0 8 ;
+            8 8 0 8 8 ;
+            8 8 8 8 8
+        ],
+        // hack: key glyph for mazeGame
+        "ķ" => [
+            0 0 8 0 0 ;
+            0 8 0 8 0 ;
+            0 0 8 0 0 ;
+            0 0 8 8 0 ;
+            0 0 8 8 0
+        ],
+        "◊" => [
+            0 0 8 0 0 ;
+            0 8 0 8 0 ;
+            8 0 0 0 8 ;
+            0 8 0 8 0 ;
+            0 0 8 0 0
+        ],
+        "⇲" => [
+            8 0 8 0 0 ;
+            0 8 8 0 8 ;
+            8 8 8 0 8 ;
+            0 0 0 0 8 ;
+            0 8 8 8 8
+        ],
+        "█" => [
+            8 8 8 8 8 ;
+            8 8 8 8 8 ;
+            8 8 8 8 8 ;
+            8 8 8 8 8 ;
+            8 8 8 8 8
+        ],
+        "░" => [
+            8 0 8 0 8 ;
+            0 8 0 8 0 ;
+            8 0 8 0 8 ;
+            0 8 0 8 0 ;
+            8 0 8 0 8
+        ],
+        "━" => [
+            0 0 0 0 0 ;
+            0 0 0 0 0 ;
+            8 0 8 0 8 ;
+            0 0 0 0 0 ;
+            0 0 0 0 0
+        ],
+        "►" => [
+            0 0 8 0 0 ;
+            0 0 8 8 0 ;
+            8 0 8 8 8 ;
+            0 0 8 8 0 ;
+            0 0 8 0 0
+        ],
+        "→" => [
+            0 0 8 0 0 ;
+            0 0 0 8 0 ;
+            8 8 8 8 8 ;
+            0 0 0 8 0 ;
+            0 0 8 0 0
+        ],
+        "?" => [
+            0 8 8 8 0 ;
+            8 0 0 0 8 ;
+            0 0 8 8 0 ;
+            0 0 0 0 0 ;
+            0 0 8 0 0
+        ],
         "**" => [
             0 0 8 0 0 ;
             0 8 8 8 0 ;
diff --git a/engine/src/lib/init.rs b/engine/src/lib/init.rs
index 9beb6ca..098f410 100644
--- a/engine/src/lib/init.rs
+++ b/engine/src/lib/init.rs
@@ -1,17 +1,52 @@
-use eval;
+extern crate serde_idl;
+
+use candid;
 use menu;
-use types::lang::{Atom, Command, Editor, Name, State};
+
+use eval;
+use types::lang::{Atom, Command, Editor, Frame, FrameCont, Name, State};
 
 pub fn init_state() -> State {
     let (mut state_init, init_command) = {
-        if false {
-            use crate::bitmap;
+        if true {
+            let idl_spec = r#"
+service server : {
+  f : (a: nat, b:nat) -> () oneway;
+  g : (a: text, b:nat) -> () oneway;
+  h : (a: nat, b:record { nat; nat; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; }) -> () oneway;
+}
+    "#;
+            let idlprog = candid::parse_idl(&idl_spec);
+            let menu_type = candid::menutype_of_idlprog(&idlprog);
             (
                 State {
-                    editor: Editor::Bitmap(Box::new(bitmap::Editor {
+                    stack: vec![],
+                    frame: Frame::from_editor(Editor::Menu(Box::new(menu::Editor {
                         state: None,
                         history: vec![],
-                    })),
+                    }))),
+                },
+                Command::Menu(menu::Command::Init(menu::InitCommand::Default(
+                    menu::MenuTree::Blank(menu_type.clone()),
+                    menu_type,
+                ))),
+            )
+        } else if false {
+            use crate::bitmap;
+            (
+                State {
+                    stack: vec![],
+                    frame: Frame {
+                        name: Name::Void,
+                        editor: Editor::Bitmap(Box::new(bitmap::Editor {
+                            state: None,
+                            history: vec![],
+                        })),
+                        cont: FrameCont {
+                            var: Name::Void,
+                            commands: vec![],
+                        },
+                    },
                 },
                 Command::Bitmap(bitmap::Command::Init(bitmap::InitCommand::Make16x16)),
             )
@@ -79,10 +114,11 @@ pub fn init_state() -> State {
             ]);
             (
                 State {
-                    editor: Editor::Menu(Box::new(menu::Editor {
+                    stack: vec![],
+                    frame: Frame::from_editor(Editor::Menu(Box::new(menu::Editor {
                         state: None,
                         history: vec![],
-                    })),
+                    }))),
                 },
                 Command::Menu(menu::Command::Init(menu::InitCommand::Default(
                     menu::MenuTree::Blank(root.clone()),
@@ -91,7 +127,7 @@ pub fn init_state() -> State {
             )
         }
     };
-    let r = eval::command_eval(&mut state_init, &init_command);
+    let r = eval::command_eval(&mut state_init, None, &init_command);
     match r {
         Ok(()) => {}
         Err(err) => eprintln!("Failed to initialize the editor: {:?}", err),
diff --git a/engine/src/lib/menu.rs b/engine/src/lib/menu.rs
index 2fdf1e2..810fa38 100644
--- a/engine/src/lib/menu.rs
+++ b/engine/src/lib/menu.rs
@@ -4,6 +4,7 @@ use types::lang::{Atom, Dir1D, Name};
 
 pub type Text = String;
 pub type Nat = usize; // todo -- use a bignum rep
+pub type Int = isize; // todo -- use a bignum rep
 pub type Label = Name;
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
@@ -14,16 +15,26 @@ pub enum MenuType {
     Option(Box<MenuType>),
     Vec(Box<MenuType>),
     Tup(Vec<MenuType>),
+    Var(Name),
+    Func(FuncType),
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
 pub enum PrimType {
+    Null,
     Unit,
     Nat,
+    Int,
     Text,
     Bool,
 }
 
+#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
+pub struct FuncType {
+    pub args: Vec<MenuType>,
+    pub rets: Vec<MenuType>,
+}
+
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub enum MenuTree {
     Product(Vec<(Label, MenuTree, MenuType)>),
@@ -33,31 +44,33 @@ pub enum MenuTree {
     Tup(Vec<(MenuTree, MenuType)>),
     Blank(MenuType),
     Nat(Nat),
+    Int(Int),
     Text(Text),
     Bool(bool),
     Unit,
+    Null,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub struct LabelSelect {
-    before: Vec<(Label, MenuTree, MenuType)>,
-    ctx: MenuCtx,
-    label: Label,
-    after: Vec<(Label, MenuTree, MenuType)>,
+    pub before: Vec<(Label, MenuTree, MenuType)>,
+    pub ctx: MenuCtx,
+    pub label: Label,
+    pub after: Vec<(Label, MenuTree, MenuType)>,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub struct PosSelect {
-    before: Vec<(Label, MenuTree, MenuType)>,
-    ctx: MenuCtx,
-    after: Vec<(Label, MenuTree, MenuType)>,
+    pub before: Vec<(MenuTree, MenuType)>,
+    pub ctx: MenuCtx,
+    pub after: Vec<(MenuTree, MenuType)>,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub struct LabelChoice {
-    before: Vec<(Label, MenuTree, MenuType)>,
-    choice: Option<(Label, MenuTree, MenuType)>,
-    after: Vec<(Label, MenuTree, MenuType)>,
+    pub before: Vec<(Label, MenuTree, MenuType)>,
+    pub choice: Option<(Label, MenuTree, MenuType)>,
+    pub after: Vec<(Label, MenuTree, MenuType)>,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
@@ -68,6 +81,7 @@ pub enum Error {
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
 pub enum Tag {
+    Root,
     Prim(PrimType),
     Variant,
     Product,
@@ -75,6 +89,8 @@ pub enum Tag {
     Vec,
     Tup,
     Blank,
+    Var,
+    Func,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
@@ -91,22 +107,23 @@ pub enum AutoCommand {
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub enum EditCommand {
+    Commit,
     Descend,
     Ascend,
     PrevSibling,
     NextSibling,
-    GotoRoot,       // ---?
-    AutoFill,       // Tab
-    Clear,          // Backspace
-    NextTree,       // ArrowRight
-    PrevTree,       // ArrowLeft
-    NextBlank,      // ---?
-    PrevBlank,      // ---?
-    NextVariant,    // ArrowRight
-    PrevVariant,    // ArrowLefet
-    AcceptVariant,  // Enter
-    VecInsertBlank, // ---?
-    VecInsertAuto,  // ---?
+    GotoRoot,
+    AutoFill,
+    Clear,
+    NextTree,
+    PrevTree,
+    NextBlank,
+    PrevBlank,
+    NextVariant,
+    PrevVariant,
+    AcceptVariant,
+    VecInsertBlank,
+    VecInsertAuto,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, Hash)]
@@ -119,11 +136,11 @@ pub enum Command {
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
 pub enum MenuCtx {
     Root(MenuType),
+    Tup(Box<PosSelect>),
     Product(Box<LabelSelect>),
     Variant(Box<LabelSelect>),
     Option(bool, Box<MenuCtx>),
     Vec(Box<PosSelect>),
-    Tup(Box<PosSelect>),
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
@@ -140,11 +157,16 @@ pub struct Editor {
     pub history: Vec<Command>,
 }
 
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+pub enum Halt {
+    Commit(MenuTree),
+    Message(String),
+}
+
 pub mod semantics {
     use super::*;
 
-    pub type Err = String;
-    pub type Res = Result<(), Err>;
+    pub type Res = Result<(), Halt>;
 
     pub fn editor_eval(menu: &mut Editor, command: &Command) -> Res {
         trace!("editor_eval({:?}) begin", command);
@@ -159,7 +181,7 @@ pub mod semantics {
                 Ok(())
             }
             Command::Edit(ref c) => match menu.state {
-                None => Err("Invalid editor state".to_string()),
+                None => Err(Halt::Message("Invalid editor state".to_string())),
                 Some(ref mut st) => state_eval_command(st, c),
             },
             Command::Auto(ref _c) => unimplemented!(),
@@ -171,6 +193,10 @@ pub mod semantics {
 
     pub fn state_eval_command(menu: &mut MenuState, command: &EditCommand) -> Res {
         match command {
+            &EditCommand::Commit => {
+                goto_root(menu)?;
+                Err(Halt::Commit(menu.tree.clone()))
+            }
             &EditCommand::GotoRoot => goto_root(menu),
             &EditCommand::AutoFill => {
                 let tree = auto_fill(&menu.tree_typ, 1);
@@ -222,15 +248,17 @@ pub mod semantics {
             MenuTree::Tup(_) => Tag::Tup,
             MenuTree::Blank(_) => Tag::Blank,
             MenuTree::Nat(_) => Tag::Prim(PrimType::Nat),
+            MenuTree::Int(_) => Tag::Prim(PrimType::Int),
             MenuTree::Bool(_) => Tag::Prim(PrimType::Bool),
             MenuTree::Text(_) => Tag::Prim(PrimType::Text),
             MenuTree::Unit => Tag::Prim(PrimType::Unit),
+            MenuTree::Null => Tag::Prim(PrimType::Null),
         }
     }
 
     pub fn ctx_tag(ctx: &MenuCtx) -> Tag {
         match ctx {
-            MenuCtx::Root(typ) => typ_tag(typ),
+            MenuCtx::Root(typ) => Tag::Root,
             MenuCtx::Product(_) => Tag::Product,
             MenuCtx::Variant(_) => Tag::Variant,
             MenuCtx::Option(_, _) => Tag::Option,
@@ -247,6 +275,8 @@ pub mod semantics {
             MenuType::Option(_) => Tag::Option,
             MenuType::Tup(_) => Tag::Tup,
             MenuType::Vec(_) => Tag::Vec,
+            MenuType::Var(_) => Tag::Var,
+            MenuType::Func(_) => Tag::Func,
         }
     }
 
@@ -255,7 +285,10 @@ pub mod semantics {
         if &tt == tag {
             Ok(())
         } else {
-            Err(format!("expected {:?} but found {:?}: {:?}", tag, tt, tree))
+            Err(Halt::Message(format!(
+                "expected {:?} but found {:?}: {:?}",
+                tag, tt, tree
+            )))
         }
     }
 
@@ -269,7 +302,7 @@ pub mod semantics {
         }
     }
 
-    pub fn next_blank(menu: &mut MenuState) -> Result<MenuType, Err> {
+    pub fn next_blank(menu: &mut MenuState) -> Result<MenuType, Halt> {
         match menu.tree {
             MenuTree::Blank(ref t) => Ok(t.clone()),
             _ => {
@@ -279,7 +312,7 @@ pub mod semantics {
         }
     }
 
-    pub fn prev_blank(menu: &mut MenuState) -> Result<MenuType, Err> {
+    pub fn prev_blank(menu: &mut MenuState) -> Result<MenuType, Halt> {
         match menu.tree {
             MenuTree::Blank(ref t) => Ok(t.clone()),
             _ => {
@@ -317,7 +350,26 @@ pub mod semantics {
 
     pub fn ascend(menu: &mut MenuState) -> Res {
         match menu.ctx.clone() {
-            MenuCtx::Root(_) => Err("cannot ascend: already at root".to_string()),
+            MenuCtx::Root(_) => Err(Halt::Message("cannot ascend: already at root".to_string())),
+            MenuCtx::Tup(sel) => {
+                let mut arms: Vec<(MenuTree, MenuType)> = sel
+                    .before
+                    .iter()
+                    .map(|(t, tt)| (t.clone(), tt.clone()))
+                    .collect();
+                arms.push((menu.tree.clone(), menu.tree_typ.clone()));
+                let mut after = sel
+                    .after
+                    .iter()
+                    .map(|(t, tt)| (t.clone(), tt.clone()))
+                    .collect();
+                arms.append(&mut after);
+                let fields: Vec<MenuType> = arms.iter().map(|(_, t)| t.clone()).collect();
+                menu.tree = MenuTree::Tup(arms);
+                menu.tree_typ = MenuType::Tup(fields);
+                menu.ctx = sel.ctx;
+                Ok(())
+            }
             MenuCtx::Product(mut sel) => {
                 let mut arms = sel.before;
                 arms.push((sel.label, menu.tree.clone(), menu.tree_typ.clone()));
@@ -352,14 +404,45 @@ pub mod semantics {
             }
             MenuCtx::Option(_flag, _menu) => unimplemented!(),
             MenuCtx::Vec(_sel) => unimplemented!(),
-            MenuCtx::Tup(_sel) => unimplemented!(),
         }
     }
 
     pub fn descend(menu: &mut MenuState, dir: Dir1D) -> Res {
         // navigate product field structure; ignore unchosen variant options.
         match menu.tree {
-            MenuTree::Blank(_) => Err("no subtrees".to_string()),
+            MenuTree::Blank(_) => Err(Halt::Message("no subtrees".to_string())),
+            MenuTree::Tup(ref trees) => {
+                let mut trees = trees.clone();
+                if trees.len() > 0 {
+                    match dir {
+                        Dir1D::Forward => {
+                            trees.rotate_left(1);
+                            let (tree, tree_t) = trees.pop().unwrap();
+                            menu.tree = tree;
+                            menu.tree_typ = tree_t;
+                            menu.ctx = MenuCtx::Tup(Box::new(PosSelect {
+                                before: vec![],
+                                ctx: menu.ctx.clone(),
+                                after: trees,
+                            }));
+                            Ok(())
+                        }
+                        Dir1D::Backward => {
+                            let (tree, tree_t) = trees.pop().unwrap();
+                            menu.tree = tree;
+                            menu.tree_typ = tree_t;
+                            menu.ctx = MenuCtx::Tup(Box::new(PosSelect {
+                                before: trees,
+                                ctx: menu.ctx.clone(),
+                                after: vec![],
+                            }));
+                            Ok(())
+                        }
+                    }
+                } else {
+                    Err(Halt::Message("no subtrees".to_string()))
+                }
+            }
             MenuTree::Product(ref trees) => {
                 let mut trees = trees.clone();
                 if trees.len() > 0 {
@@ -391,9 +474,14 @@ pub mod semantics {
                         }
                     }
                 } else {
-                    Err("no subtrees".to_string())
+                    Err(Halt::Message("no subtrees".to_string()))
                 }
             }
+            MenuTree::Nat(ref n) => {
+                // to do -- move this logic
+                menu.tree = MenuTree::Nat(*n + 1);
+                Ok(())
+            }
             MenuTree::Variant(ref trees) => {
                 let trees = trees.clone();
                 match trees.choice {
@@ -410,12 +498,12 @@ pub mod semantics {
                             Ok(())
                         }
                     },
-                    None => Err("no choice subtree".to_string()),
+                    None => Err(Halt::Message("no choice subtree".to_string())),
                 }
             }
             _ => {
                 // to do
-                Err("not implemented".to_string())
+                Err(Halt::Message("not implemented".to_string()))
             }
         }
     }
@@ -470,12 +558,26 @@ pub mod semantics {
                     }
                 }
             }
-            _ => Err("expected tree to be a variant".to_string()),
+            _ => Err(Halt::Message("expected tree to be a variant".to_string())),
         }
     }
 
     pub fn next_sibling(menu: &mut MenuState) -> Res {
         match menu.ctx.clone() {
+            MenuCtx::Tup(mut sel) => {
+                sel.before.push((menu.tree.clone(), menu.tree_typ.clone()));
+                if sel.after.len() > 0 {
+                    sel.after.rotate_left(1);
+                    let (tree, tree_typ) = sel.after.pop().unwrap();
+                    menu.tree = tree;
+                    menu.tree_typ = tree_typ;
+                    menu.ctx = MenuCtx::Tup(sel);
+                    Ok(())
+                } else {
+                    ascend(menu)?;
+                    descend(menu, Dir1D::Forward)
+                }
+            }
             MenuCtx::Product(mut sel) => {
                 sel.before
                     .push((sel.label, menu.tree.clone(), menu.tree_typ.clone()));
@@ -519,6 +621,20 @@ pub mod semantics {
                     descend(menu, Dir1D::Backward)
                 }
             }
+            MenuCtx::Tup(mut sel) => {
+                sel.after.push((menu.tree.clone(), menu.tree_typ.clone()));
+                sel.after.rotate_left(1);
+                if sel.before.len() > 0 {
+                    let (tree, tree_typ) = sel.before.pop().unwrap();
+                    menu.tree = tree;
+                    menu.tree_typ = tree_typ;
+                    menu.ctx = MenuCtx::Tup(sel);
+                    Ok(())
+                } else {
+                    ascend(menu)?;
+                    descend(menu, Dir1D::Backward)
+                }
+            }
             MenuCtx::Root(_) => Ok(()),
             MenuCtx::Variant(_) => {
                 ascend(menu)?;
@@ -562,9 +678,13 @@ pub mod semantics {
         } else {
             match typ {
                 &MenuType::Prim(PrimType::Unit) => MenuTree::Unit,
+                &MenuType::Prim(PrimType::Null) => MenuTree::Null,
                 &MenuType::Prim(PrimType::Nat) => MenuTree::Nat(0),
+                &MenuType::Prim(PrimType::Int) => MenuTree::Int(0),
                 &MenuType::Prim(PrimType::Text) => MenuTree::Text("".to_string()),
                 &MenuType::Prim(PrimType::Bool) => MenuTree::Bool(false),
+                &MenuType::Var(ref n) => MenuTree::Blank(typ.clone()),
+                &MenuType::Func(ref f) => MenuTree::Blank(typ.clone()),
                 &MenuType::Variant(ref labtyps) => {
                     let after_choices: Vec<(Label, MenuTree, MenuType)> = labtyps
                         .iter()
@@ -623,6 +743,8 @@ pub mod io {
             (&Event::Quit { .. }, _, _) => Err(()),
             (&Event::KeyDown(ref kei), ref ctx, ref tree) => match (kei.key.as_str(), ctx, tree) {
                 ("Escape", _, _) => Err(()),
+                //("Enter", Tag::Root, _) => Ok(vec![EditCommand::Commit]),
+                ("Enter", _, _) => Ok(vec![EditCommand::Commit]),
                 ("Backspace", _, _) => Ok(vec![EditCommand::Clear]),
 
                 ("Tab", _, Tag::Blank) => Ok(vec![EditCommand::AutoFill]),
@@ -631,13 +753,14 @@ pub mod io {
                 ("ArrowLeft", _, _) => Ok(vec![EditCommand::Ascend]),
                 ("ArrowRight", _, _) => Ok(vec![EditCommand::Descend]),
 
-                ("ArrowUp", Tag::Variant, _) => Ok(vec![EditCommand::PrevVariant]),
-                ("ArrowDown", Tag::Variant, _) => Ok(vec![EditCommand::NextVariant]),
+                ("ArrowUp", _, Tag::Variant) => Ok(vec![EditCommand::PrevVariant]),
+                ("ArrowDown", _, Tag::Variant) => Ok(vec![EditCommand::NextVariant]),
 
                 ("ArrowUp", Tag::Product, _) => Ok(vec![EditCommand::PrevSibling]),
                 ("ArrowDown", Tag::Product, _) => Ok(vec![EditCommand::NextSibling]),
 
-                ("Enter", _, _) => Ok(vec![EditCommand::Descend]),
+                ("ArrowUp", Tag::Tup, _) => Ok(vec![EditCommand::PrevSibling]),
+                ("ArrowDown", Tag::Tup, _) => Ok(vec![EditCommand::NextSibling]),
 
                 (key, ctx, tree) => {
                     warn!(
@@ -654,9 +777,9 @@ pub mod io {
         }
     }
 
-    pub fn render_elms(menu: &MenuState) -> Result<Elms, String> {
-        use crate::render::{FlowAtts, FrameType, TextAtts};
+    use crate::render::{FlowAtts, FrameType, TextAtts};
 
+    pub fn render_elms(menu: &MenuState, r: &mut Render) {
         fn black_fill() -> Fill {
             //Fill::Closed(Color::RGB(0, 0, 0))
             Fill::None
@@ -736,7 +859,7 @@ pub mod io {
                 glyph_flow: glyph_flow(),
             }
         };
-        fn typ_atts() -> TextAtts {
+        fn typ_lab_atts() -> TextAtts {
             TextAtts {
                 zoom: 2,
                 fg_fill: Fill::Closed(Color::RGB(200, 255, 255)),
@@ -745,6 +868,15 @@ pub mod io {
                 glyph_flow: glyph_flow(),
             }
         };
+        fn typ_sym_atts() -> TextAtts {
+            TextAtts {
+                zoom: 2,
+                fg_fill: Fill::Closed(Color::RGB(180, 200, 200)),
+                bg_fill: Fill::None,
+                glyph_dim: glyph_dim(),
+                glyph_flow: glyph_flow(),
+            }
+        };
         fn typ_vflow() -> FlowAtts {
             FlowAtts {
                 dir: Dir2D::Down,
@@ -845,6 +977,29 @@ pub mod io {
                     r_out.end();
                     return;
                 }
+                &MenuCtx::Tup(ref sel) => {
+                    next_ctx = Some(sel.ctx.clone());
+                    r.begin(&Name::Void, FrameType::Flow(vert_flow()));
+                    for (t, _ty) in sel.before.iter() {
+                        begin_item(&mut r);
+                        //render_product_label(l, &mut r);
+                        render_tree(t, false, &ctx_box_fill(), &mut r);
+                        r.end();
+                    }
+                    {
+                        begin_item(&mut r);
+                        //render_product_label(&sel.label, &mut r);
+                        r.nest(&Name::Void, r_tree);
+                        r.end();
+                    }
+                    for (t, _ty) in sel.after.iter() {
+                        begin_item(&mut r);
+                        //render_product_label(&l, &mut r);
+                        render_tree(t, false, &ctx_box_fill(), &mut r);
+                        r.end();
+                    }
+                    r.end();
+                }
                 &MenuCtx::Product(ref sel) => {
                     next_ctx = Some(sel.ctx.clone());
                     r.begin(&Name::Void, FrameType::Flow(vert_flow()));
@@ -877,7 +1032,6 @@ pub mod io {
                 }
                 &MenuCtx::Option(_flag, ref _body) => unimplemented!(),
                 &MenuCtx::Vec(ref _ch) => unimplemented!(),
-                &MenuCtx::Tup(ref _ch) => unimplemented!(),
             };
             r.end();
             // continue rendering the rest of the context, in whatever flow we are using for that purpose.
@@ -893,6 +1047,7 @@ pub mod io {
         fn render_type(
             typ: &MenuType,
             text: &TextAtts,
+            text2: &TextAtts,
             vflow: &FlowAtts,
             hflow: &FlowAtts,
             r: &mut Render,
@@ -900,7 +1055,9 @@ pub mod io {
             let mut first = true;
             match typ {
                 MenuType::Prim(PrimType::Unit) => r.str("()", text),
+                MenuType::Prim(PrimType::Null) => r.str("null", text),
                 MenuType::Prim(PrimType::Nat) => r.str("nat", text),
+                MenuType::Prim(PrimType::Int) => r.str("int", text),
                 MenuType::Prim(PrimType::Text) => r.str("text", text),
                 MenuType::Prim(PrimType::Bool) => r.str("bool", text),
                 MenuType::Variant(fields) => {
@@ -910,16 +1067,18 @@ pub mod io {
                             if first {
                                 first = false;
                                 begin_flow(r, hflow);
-                                r.str("{", text);
+                                r.str("{ ", text2);
                             } else {
                                 r.end();
                                 begin_flow(r, hflow);
-                                r.str("; ", text);
+                                r.str("; ", text2);
                             };
-                            r.str(&format!("#{}: ", l), text);
-                            render_type(t, text, vflow, hflow, r);
+                            r.str("#", text2);
+                            r.str(&format!("{}", l), text);
+                            r.str(": ", text2);
+                            render_type(t, text, text2, vflow, hflow, r);
                         }
-                        r.str("}", text);
+                        r.str(" }", text2);
                         r.end();
                     } else {
                         unimplemented!()
@@ -933,25 +1092,76 @@ pub mod io {
                             if first {
                                 first = false;
                                 begin_flow(r, hflow);
-                                r.str("{", text);
+                                r.str("{ ", text2);
                             } else {
                                 r.end();
                                 begin_flow(r, hflow);
-                                r.str("; ", text);
+                                r.str("; ", text2);
                             };
-                            r.str(&format!("{}: ", l), text);
-                            render_type(t, text, vflow, hflow, r);
+                            r.str(&format!("{}", l), text);
+                            r.str(":", text2);
+                            render_type(t, text, text2, vflow, hflow, r);
                         }
-                        r.str("}", text);
+                        r.str(" }", text2);
                         r.end();
                     } else {
-                        unimplemented!()
+                        r.str("{ }", text2);
+                    }
+                    r.end()
+                }
+                MenuType::Var(n) => r.name(n, text),
+                MenuType::Option(t) => {
+                    begin_flow(r, hflow);
+                    r.str("?", text2);
+                    render_type(t, text, text2, vflow, hflow, r);
+                    r.end()
+                }
+                MenuType::Vec(t) => {
+                    begin_flow(r, hflow);
+                    r.str("[", text2);
+                    render_type(t, text, text2, vflow, hflow, r);
+                    r.str("]", text2);
+                    r.end()
+                }
+                MenuType::Tup(typs) => {
+                    begin_flow(r, hflow);
+                    r.str("(", text2);
+                    let mut not_first = false;
+                    for t in typs.iter() {
+                        if not_first {
+                            r.str(", ", text2);
+                        }
+                        render_type(t, text, text2, vflow, hflow, r);
+                        not_first = true;
+                    }
+                    r.str(")", text2);
+                    r.end()
+                }
+                MenuType::Func(ft) => {
+                    begin_flow(r, hflow);
+                    r.str("(", text2);
+                    let mut not_first = false;
+                    for t in ft.args.iter() {
+                        if not_first {
+                            r.str(", ", text2);
+                        }
+                        render_type(t, text, text2, vflow, hflow, r);
+                        not_first = true;
                     }
+                    r.str(")", text2);
+                    r.str("→", text2);
+                    r.str("(", text2);
+                    let mut not_first = false;
+                    for t in ft.rets.iter() {
+                        if not_first {
+                            r.str(", ", text2);
+                        }
+                        render_type(t, text, text2, vflow, hflow, r);
+                        not_first = true;
+                    }
+                    r.str(")", text2);
                     r.end()
                 }
-                MenuType::Option(_t) => unimplemented!(),
-                MenuType::Vec(_t) => unimplemented!(),
-                MenuType::Tup(_fields) => unimplemented!(),
             }
         }
 
@@ -974,7 +1184,7 @@ pub mod io {
                     r.begin(&Name::Void, FrameType::Flow(vert_flow()));
                     if show_detailed {
                         begin_item(r);
-                        r.text(&format!("Choice:"), &msg_atts());
+                        r.text(&format!("choice:"), &msg_atts());
                         if let Some(_) = ch.choice {
                             // nothing
                         } else {
@@ -1048,19 +1258,21 @@ pub mod io {
                 }
                 &MenuTree::Blank(ref _typ) => r.text(&format!("___"), &blank_atts()),
                 &MenuTree::Nat(n) => r.text(&format!("{}", n), &text_atts()),
+                &MenuTree::Int(i) => r.text(&format!("{}", i), &text_atts()),
                 &MenuTree::Bool(b) => r.text(&format!("{}", b), &text_atts()),
                 &MenuTree::Text(ref t) => r.text(&format!("{:?}", t), &text_atts()),
                 &MenuTree::Unit => r.str("()", &text_atts()),
+                &MenuTree::Null => r.str("null", &text_atts()),
             };
             r.end();
         };
-        let mut r = Render::new();
         r.begin(&Name::Void, FrameType::Flow(vert_flow()));
         if true {
-            r.str("hello world!", &text_atts());
-            r.str(" please, enter a value to submit:", &msg_atts());
-            r.str(" (Auto-fill and navigate with arrow keys)", &msg_atts());
-
+            if false {
+                r.str("hello world!", &text_atts());
+                r.str(" please, enter a value to submit:", &msg_atts());
+                r.str(" (Auto-fill and navigate with arrow keys)", &msg_atts());
+            }
             let r_tree = {
                 let mut r_tree = Render::new();
                 r_tree.begin(&Name::Void, FrameType::Flow(vert_flow()));
@@ -1068,7 +1280,8 @@ pub mod io {
                 render_tree(&menu.tree, true, &detailed_tree_box_fill(), &mut r_tree);
                 render_type(
                     &menu.tree_typ,
-                    &typ_atts(),
+                    &typ_lab_atts(),
+                    &typ_sym_atts(),
                     &typ_vflow(),
                     &typ_hflow(),
                     &mut r_tree,
@@ -1076,15 +1289,78 @@ pub mod io {
                 r_tree.end();
                 r_tree
             };
-            render_ctx(&menu.ctx, &mut r, r_tree);
+            render_ctx(&menu.ctx, r, r_tree);
         }
         r.end();
-        Ok(r.into_elms())
     }
 }
 
 use std::fmt;
 
+impl fmt::Display for MenuTree {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let mut first = true;
+        match self {
+            MenuTree::Product(ref fields) => {
+                write!(f, "record {{")?;
+                for (l, t, _) in fields.iter() {
+                    if !first {
+                        write!(f, "; ")?;
+                    };
+                    first = false;
+                    write!(f, "{}={}", l, t)?;
+                }
+                write!(f, "}}")
+            }
+            MenuTree::Variant(ref choice) => {
+                write!(f, "variant {{")?;
+                if let Some((ref l, ref t, _)) = choice.choice {
+                    write!(f, "{}={}", l, t)?;
+                } else {
+                    // nothing
+                }
+                write!(f, "}}")
+            }
+            MenuTree::Option(ref b, ref t, _) => {
+                if *b {
+                    write!(f, "opt {}", t)
+                } else {
+                    write!(f, "opt null")
+                }
+            }
+            MenuTree::Vec(ref ts, _) => {
+                write!(f, "vec{{")?;
+                for t in ts.iter() {
+                    if !first {
+                        write!(f, "; ")?;
+                    };
+                    first = false;
+                    write!(f, "{}", t)?;
+                }
+                write!(f, "}}")
+            }
+            MenuTree::Tup(ref ts) => {
+                write!(f, "(")?;
+                for (t, _) in ts.iter() {
+                    if !first {
+                        write!(f, ", ")?;
+                    };
+                    first = false;
+                    write!(f, "{}", t)?;
+                }
+                write!(f, ")")
+            }
+            MenuTree::Blank(_) => write!(f, "variant {{BLANK=()}}"),
+            MenuTree::Nat(n) => write!(f, "{}", n),
+            MenuTree::Int(i) => write!(f, "{}", i),
+            MenuTree::Text(t) => write!(f, "{:?}", t),
+            MenuTree::Bool(b) => write!(f, "{}", b),
+            MenuTree::Unit => write!(f, "()"),
+            MenuTree::Null => write!(f, "null"),
+        }
+    }
+}
+
 // move elsewhere
 impl fmt::Display for Name {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1114,8 +1390,11 @@ impl fmt::Display for MenuType {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let mut first = true;
         match self {
+            MenuType::Var(ref n) => write!(f, "{:?}", n),
             MenuType::Prim(PrimType::Unit) => write!(f, "()"),
+            MenuType::Prim(PrimType::Null) => write!(f, "null"),
             MenuType::Prim(PrimType::Nat) => write!(f, "nat"),
+            MenuType::Prim(PrimType::Int) => write!(f, "int"),
             MenuType::Prim(PrimType::Text) => write!(f, "text"),
             MenuType::Prim(PrimType::Bool) => write!(f, "bool"),
             MenuType::Variant(fields) => {
@@ -1153,6 +1432,25 @@ impl fmt::Display for MenuType {
                 }
                 write!(f, ")")
             }
+            MenuType::Func(ft) => {
+                write!(f, "(")?;
+                for t in ft.args.iter() {
+                    if !first {
+                        write!(f, ", ")?;
+                    };
+                    write!(f, "{}", t)?;
+                    first = false;
+                }
+                write!(f, ") -> (");
+                for t in ft.rets.iter() {
+                    if !first {
+                        write!(f, ", ")?;
+                    };
+                    write!(f, "{}", t)?;
+                    first = false;
+                }
+                write!(f, ")")
+            }
         }
     }
 }
diff --git a/engine/src/lib/mod.rs b/engine/src/lib/mod.rs
index e3e4f47..d165591 100644
--- a/engine/src/lib/mod.rs
+++ b/engine/src/lib/mod.rs
@@ -7,7 +7,11 @@ extern crate hashcons;
 extern crate serde;
 extern crate serde_bytes;
 
-// -------- eval semantics ---------
+extern crate ic_agent;
+extern crate serde_idl;
+
+extern crate tokio;
+extern crate delay;
 
 pub mod eval;
 pub mod init;
@@ -34,3 +38,7 @@ pub type GlyphMap = std::collections::HashMap<types::lang::Name, Glyph>;
 
 pub mod glyph;
 pub mod render;
+
+// -------- Candid (IC input/output) ---------
+
+pub mod candid;
diff --git a/engine/src/lib/render.rs b/engine/src/lib/render.rs
index cedd4ea..64a1e5d 100644
--- a/engine/src/lib/render.rs
+++ b/engine/src/lib/render.rs
@@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
 
 use bitmap;
 use glyph;
-use types::{
+pub use types::{
     lang::{Atom, Dir2D, Name},
-    render::{Dim, Elm, Elms, Fill, Node, Pos, Rect},
+    render::{Color, Dim, Elm, Elms, Fill, NamedElms, Node, Out, Pos, Rect},
 };
 
 #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
@@ -99,6 +99,10 @@ impl Render {
         self.frame.elms.push(Elm::Rect(r.clone(), f))
     }
 
+    pub fn elm(&mut self, e: Elm) {
+        self.frame.elms.push(e)
+    }
+
     pub fn bitmap(&mut self, bm: &bitmap::Bitmap, ba: &BitmapAtts) {
         let (width, height) = bitmap::semantics::bitmap_get_size(bm);
         for y in 0..height {
@@ -160,6 +164,11 @@ impl Render {
         self.end();
     }
 
+    pub fn add(&mut self, elms: Elms) {
+        let mut elms = elms;
+        self.frame.elms.append(&mut elms)
+    }
+
     pub fn into_elms(self) -> Elms {
         assert_eq!(self.stack.len(), 0);
         self.frame.elms
diff --git a/engine/src/lib/types.rs b/engine/src/lib/types.rs
index 23d2f9f..f6cf8c5 100644
--- a/engine/src/lib/types.rs
+++ b/engine/src/lib/types.rs
@@ -1,6 +1,6 @@
 /// The ZQM language: abstract syntax
 pub mod lang {
-    use crate::{bitmap, chain, grid, menu};
+    use crate::{bitmap, candid, chain, grid, menu};
     use hashcons::merkle::Merkle;
     use serde::{Deserialize, Serialize};
 
@@ -12,6 +12,7 @@ pub mod lang {
         Atom(Atom),
         Name(Name),
         Location(Location),
+        MenuTree(Box<menu::MenuTree>),
         Bitmap(Box<bitmap::Bitmap>),
         Chain(Box<chain::Chain>),
         Grid(Box<grid::Grid>),
@@ -100,18 +101,34 @@ pub mod lang {
         pub content: Media,
     }
 
-    #[derive(Debug, Serialize, Deserialize, Hash)]
+    #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
     pub enum Editor {
+        CandidRepl(Box<candid::Repl>),
         Bitmap(Box<bitmap::Editor>),
         Menu(Box<menu::Editor>),
         Chain(Box<chain::Editor>),
         Grid(Box<grid::Editor>),
     }
 
+    #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+    pub struct Frame {
+        pub name: Name,
+        pub editor: Editor,
+        pub cont: FrameCont,
+    }
+
+    #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+    pub struct FrameCont {
+        pub var: Name,
+        pub commands: Vec<Command>,
+    }
+
     // to do -- eventually, we may want these to be "open" wrt the exp environment;
     // for expressing scripts, etc; then we'd need to do substitution, or more env-passing, or both.
     #[derive(Debug, Clone, Serialize, Deserialize, Hash)]
     pub enum Command {
+        Return(Media),
+        Resume(Media, Frame),
         Menu(menu::Command),
         Bitmap(bitmap::Command),
         Chain(chain::Command),
@@ -134,7 +151,8 @@ pub mod lang {
 
     #[derive(Debug, Serialize, Deserialize, Hash)]
     pub struct State {
-        pub editor: Editor,
+        pub stack: Vec<Frame>,
+        pub frame: Frame,
     }
 
     pub type Hash = u64;
@@ -163,6 +181,19 @@ pub mod lang {
         pub time: Name,
         pub place: Name,
     }
+
+    impl Frame {
+        pub fn from_editor(editor: Editor) -> Frame {
+            Frame {
+                name: Name::Void,
+                editor: editor,
+                cont: FrameCont {
+                    var: Name::Void,
+                    commands: vec![],
+                },
+            }
+        }
+    }
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -314,6 +345,7 @@ pub mod event {
         Quit,
         KeyDown(KeyEventInfo),
         KeyUp(KeyEventInfo),
+        WindowSizeChange(super::render::Dim),
     }
     #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
     pub struct KeyEventInfo {
@@ -379,6 +411,13 @@ pub mod render {
         Node(Box<Node>),
     }
     pub type Elms = Vec<Elm>;
+    pub type NamedElms = Vec<(Name, Elm)>;
+
+    #[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+    pub enum Out {
+        Draw(Elm),
+        Redraw(NamedElms),
+    }
 }
 
 /// Deprecated?
@@ -406,143 +445,3 @@ pub mod util {
         Name::Atom(atom)
     }
 }
-
-/*
-
-/// a state store tree cursor
-///
-/// the "cursor position" is a distinct subtree;
-/// together with the tree context, the pair forms a "tree zipper"
-/// that represents a cursor in a tree, permitting O(1) local navigation.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct StateStoreTreeCursor {
-    pub ctx: StateStoreTreeCtx,
-    pub pos: StateStoreTree,
-}
-
-/// historical tree of state stores
-///
-/// a state store tree organizes a set of meta commands' state stores into a
-/// historical tree of concurrent evolution; each "node" is a store of
-/// states; each "edge" is a MetaCommand that relates two nodes' state stores.
-#[derive(Debug, Serialize, Deserialize)]
-pub enum StateStoreTree {
-    Empty,
-    Singleton(StateStore),
-    Linear(StateStore, Vec<LinearStateTrans>, Box<StateStoreTree>),
-    Branching(StateStore, Vec<BranchingStateTrans>),
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub enum StateStoreTreeCtx {
-    Linear(StateStore, Vec<LinearStateTrans>, Vec<LinearStateTrans>),
-    Branching(StateStore, Vec<BranchingStateTransCtx>),
-}
-
-/// a linear state transition consists of a `Command` and target `StateStore`
-#[derive(Debug, Serialize, Deserialize)]
-pub struct LinearStateTrans {
-    command: Command,
-    target: StateStore,
-}
-
-/// a branching state transition consists of a `Command` and target `StateStoreTree`
-///
-/// two sister state transitions, each of type `BranchingStateTrans`,
-/// arise together when a `Command` is undone, and a distinct `Command` is issued;
-/// this pattern can repeat, giving rise to more siblings.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct BranchingStateTrans {
-    command: Command,
-    subtree: Box<StateStoreTree>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct BranchingStateTransCtx {
-    ctx: StateStoreTreeCtx,
-    command: Command,
-}
-*/
-
-/*
-
-/// A `History` generalizes the relationship between interactive
-/// `Command`s and the `State`s that they describe and inter-relate.
-///
-/// - Commands
-///
-/// - Each "non-historical" Command's semantics (e.g., in bitmap
-/// editor: move the cursor, toggle bits, etc),
-///
-/// - their "History" (the ambient stack or tree of past editor
-/// states),
-///
-/// - Each "historical" Command's semantics, e.g., push/pop editor
-/// states; undo/redo editor states, including the _mathematical_
-/// meaning of "undo/redo", which generalizes and nests.
-///
-
-use std::hash::Hash;
-use std::fmt::Debug;
-
-trait History : Eq + Hash + Debug {
-    /// State of an abstracted "editor"
-    type State    : Eq + Hash + Debug + Clone;
-    /// Commands of an abstracted "editor"
-    type Command  : Eq + Hash + Debug;
-    /// Historical (re-)focusing commands of an abstracted "editor"
-    type HCommand : Eq + Hash + Debug;
-    /// Ok responses for HCommand evaluation
-    type Resp     : Eq + Hash + Debug;
-    /// Err responses for HCommand evaluation
-    type Error    : Eq + Hash + Debug;
-
-    /// project/translate any historical command from the (more general) command type
-    ///
-    /// the environment uses this operation to test whether the
-    /// command is recognized/defined, and should be evaluated here.
-    ///
-    /// to do: enforce "well-formed ensembles", where no two distinct
-    /// command histories translate the same command; "well-formed
-    /// ensembles" enjoy nice properties: composition order does not
-    /// matter, and there are no (potentially very strange) "double
-    /// effects" of any one command.
-    fn translate_command(&mut self, &Self::Command) -> Option<Self::HCommand>;
-
-    /// internal semantics of this command history.
-    ///
-    /// this method gives the "historical semantics" for the given "historical command"
-    fn intern_eval(&mut self,
-                   state: &mut Self::State,
-                   command: &Self::HCommand)
-                   -> Result<Self::Resp, Self::Error>;
-
-    /// external semantics of this command history
-    ///
-    /// this method records a state-transition triple in its representation, which
-    /// happens when the environment changes the editor state.
-    ///
-    /// Q: What assumptions can we make about the state triples?
-    ///    e.g., do we ever miss state changes, or do they always sequence entirely?
-    ///
-    /// provisionally: the sequence of triples is always "well
-    /// formed", where each "after" state becomes the "before" state
-    /// in the subsequent triple, if any.
-    ///
-    fn extern_eval(&mut self,
-                   before: &Self::State,
-                   command: &Self::Command,
-                   result: Result<Self::Resp, Self::Error>,
-                   after: &Self::State);
-}
-*/
-
-/////////////////////////////////
-
-/*
-impl FromStr for Name {
-    fn from_str(s:& str) -> Name {
-        Name::Atom(NameAtom::String(s.to_string()))
-    }
-}
-*/
diff --git a/shell/Cargo.toml b/shell/Cargo.toml
index 499b28e..419542f 100644
--- a/shell/Cargo.toml
+++ b/shell/Cargo.toml
@@ -16,6 +16,11 @@ hashcons = "0.1"
 sdl2 = "0.32"
 zqm-engine = { path = "../engine" }
 
+serde-idl = { path = "/Users/matthew/dfn/sdk/src/serde_idl" }
+dfx_derive = { path = "/Users/matthew/dfn/sdk/src/dfx_derive" }
+dfx_info = { path = "/Users/matthew/dfn/sdk/src/dfx_info" }
+ic-agent = { path = "/Users/matthew/dfn/sdk/src/agent/rust" }
+
 [[bin]]
 name = "zqm-shell"
 path = "src/bin/zqm.rs"
diff --git a/shell/src/bin/zqm.rs b/shell/src/bin/zqm.rs
index 3ba05f8..71ddcc0 100644
--- a/shell/src/bin/zqm.rs
+++ b/shell/src/bin/zqm.rs
@@ -15,13 +15,14 @@ extern crate structopt;
 use structopt::StructOpt;
 
 use sdl2::event::Event as SysEvent;
+use sdl2::event::WindowEvent;
 use sdl2::keyboard::Keycode;
 use std::io;
 
 // ZQM:
 extern crate zqm_engine;
 use zqm_engine::{
-    eval,
+    candid, eval, init,
     types::{self, event, render},
 };
 
@@ -44,21 +45,19 @@ struct CliOpt {
 
 #[derive(StructOpt, Debug)]
 enum CliCommand {
+    #[structopt(name = "candid", about = "Start an interactive Candid session.")]
+    Candid {
+        replica_url: String,
+        canister_id: String,
+        did_file: String,
+    },
+
     #[structopt(name = "start", about = "Start interactively.")]
     Start,
 
     #[structopt(name = "resume", about = "Resume last interaction.")]
     Resume,
 
-    #[structopt(name = "replay", about = "Replay last interaction.")]
-    Replay,
-
-    #[structopt(
-        name = "history",
-        about = "Interact with history, the list of all prior interactions."
-    )]
-    History,
-
     #[structopt(name = "version", about = "Display version.")]
     Version,
 
@@ -157,6 +156,16 @@ pub fn draw_elms<T: RenderTarget>(
 
 fn translate_system_event(event: SysEvent) -> Option<event::Event> {
     match &event {
+        SysEvent::Window {
+            win_event: WindowEvent::SizeChanged(w, h),
+            ..
+        } => {
+            let dim = render::Dim {
+                width: *w as usize,
+                height: *h as usize,
+            };
+            Some(event::Event::WindowSizeChange(dim))
+        }
         SysEvent::Quit { .. }
         | SysEvent::KeyDown {
             keycode: Some(Keycode::Escape),
@@ -191,22 +200,32 @@ fn translate_system_event(event: SysEvent) -> Option<event::Event> {
     }
 }
 
+pub fn redraw<T: RenderTarget>(
+    canvas: &mut Canvas<T>,
+    state: &mut types::lang::State,
+    dim: &render::Dim,
+) -> Result<(), String> {
+    let pos = render::Pos { x: 0, y: 0 };
+    let fill = render::Fill::Closed(render::Color::RGB(0, 0, 0));
+    let elms = eval::render_elms(state)?;
+    draw_elms(canvas, &pos, dim, &fill, &elms)?;
+    canvas.present();
+    drop(elms);
+    Ok(())
+}
+
 pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> {
     use sdl2::event::EventType;
-
-    let pos = render::Pos { x: 0, y: 0 };
-    let dim = render::Dim {
-        width: 888,
+    let mut dim = render::Dim {
+        width: 1000,
         height: 666,
     };
-    let fill = render::Fill::Closed(render::Color::RGB(0, 0, 0));
-
     let sdl_context = sdl2::init()?;
     let video_subsystem = sdl_context.video()?;
     let window = video_subsystem
-        .window("zoom-quilt-machine", dim.width as u32, dim.height as u32)
+        .window("thin-ic-agent", dim.width as u32, dim.height as u32)
         .position_centered()
-        //.resizable()
+        .resizable()
         //.input_grabbed()
         //.fullscreen()
         //.fullscreen_desktop()
@@ -223,10 +242,7 @@ pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> {
 
     {
         // draw initial frame, before waiting for any events
-        let elms = eval::render_elms(state)?;
-        draw_elms(&mut canvas, &pos, &dim, &fill, &elms)?;
-        canvas.present();
-        drop(elms);
+        redraw(&mut canvas, state, &dim);
     }
 
     let mut event_pump = sdl_context.event_pump()?;
@@ -242,12 +258,22 @@ pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> {
             None => continue 'running,
             Some(event) => event,
         };
+        // catch window resize event: redraw and loop:
+        match event {
+            event::Event::WindowSizeChange(new_dim) => {
+                dim = new_dim.clone();
+                redraw(&mut canvas, state, &dim)?;
+                continue 'running;
+            }
+            _ => (),
+        }
         match eval::commands_of_event(state, &event) {
             Ok(commands) => {
                 for c in commands.iter() {
                     // note: we borrow the command here, possibly requiring some cloning when it is evaluated.
                     // todo -- we do nothing with the result; we should log it.
-                    match eval::command_eval(state, c) {
+                    let event = event.clone();
+                    match eval::command_eval(state, Some(event), c) {
                         Ok(()) => {}
                         Err(msg) => {
                             warn!("Command {:?} lead to an error:", c);
@@ -255,10 +281,7 @@ pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> {
                         }
                     }
                 }
-                let elms = eval::render_elms(state)?;
-                draw_elms(&mut canvas, &pos, &dim, &fill, &elms)?;
-                canvas.present();
-                drop(elms);
+                redraw(&mut canvas, state, &dim)?;
             }
             Err(()) => break 'running,
         }
@@ -276,12 +299,32 @@ fn main() {
             (_, _, _) => log::LevelFilter::Info,
         },
     );
-
-    let mut state = eval::load_state();
-
     info!("Evaluating CLI command: {:?} ...", &cliopt.command);
-
+    // - - - - - - - - - - -
     match cliopt.command {
+        CliCommand::Candid {
+            replica_url,
+            canister_id,
+            did_file,
+        } => {
+            use std::fs;
+            let contents = fs::read_to_string(&did_file).expect("reading candid file");
+            let ast = candid::parse_idl(&contents);
+            //let menu = candid::menutype_of_idlprog_service(&ast);
+            let mut state = candid::init(replica_url.as_str(), canister_id.as_str(), &ast).unwrap();
+            do_event_loop(&mut state).unwrap();
+            eval::save_state(&state);
+        }
+        CliCommand::Start => {
+            let mut state = init::init_state();
+            do_event_loop(&mut state).unwrap();
+            eval::save_state(&state);
+        }
+        CliCommand::Resume => {
+            let mut state = eval::load_state();
+            do_event_loop(&mut state).unwrap();
+            eval::save_state(&state);
+        }
         CliCommand::Version => {
             const VERSION: &'static str = env!("CARGO_PKG_VERSION");
             println!("{}", VERSION);
@@ -292,15 +335,5 @@ fn main() {
             CliOpt::clap().gen_completions_to("zqm", s, &mut io::stdout());
             info!("done")
         }
-        CliCommand::Start => {
-            do_event_loop(&mut state).unwrap();
-            eval::save_state(&state);
-        }
-        CliCommand::Resume => {
-            do_event_loop(&mut state).unwrap();
-            eval::save_state(&state);
-        }
-        CliCommand::Replay => unimplemented!(),
-        CliCommand::History => unimplemented!(),
     }
 }
diff --git a/shell/tic-onto b/shell/tic-onto
new file mode 100755
index 0000000..4742cfa
--- /dev/null
+++ b/shell/tic-onto
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo Connecting to $1
+cargo run $2 candid localhost:8000 ic:$1 ../mohammer/canisters/idl/$1.did