diff --git a/README.md b/README.md
index 07dcdda1640cebe9bf5f49c3948efdf16fb211bd..df2ba7a1e8a6fa71a64ae9533fee6ece1ee0ce50 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,15 @@ This project serves the developement environment / api we use to write and repre
 
 ## For MW
 
- - top menu bar instructions 
  - rick click on menu bar to delete module 
 
+ - walk program units and change 
+ - what program units do we want?
+ - try demo machine setup ... want flow control, 
+ - jogging, etc ... keydown units ? 
+
+ - documentation 
+
  - upd8ts to modules:
   - rename inout to jsunit
   - buttons get onClick evt 
@@ -25,6 +31,11 @@ This project serves the developement environment / api we use to write and repre
  - as you test, to track, 
   - weird state types solution: arrays, numbers, buttons, etc ?
 
+ - UI desires
+  - off-screen divs get pointers-to so that we don't get lost
+  - 'h' or something to zoom-to-extents
+  - highlight wires 
+
  - GIFS
   - load a program
   - drag around
@@ -35,39 +46,10 @@ This project serves the developement environment / api we use to write and repre
   - rm modules
   - change settings 
 
- - *don't forget*
-  - onload, load state... 
-  - when hooking back to server, server.emitChange(key) ... from writeStateObject 
-
- - this spiral
-  - entire programs read into UI
-   - hook sever-and-back 
-    - for event hookup
-    - for module add 
-    - for state push-down 
-   - re-hook state push-down 
-  - can req and add to program new modules
-  - can req to load new program 
-  - can req to save this program 
-
  - next spiral
   - programs come in chunk-wise and get placed 
   - programs are modules are heirarchical 
 
- - fundament
-  - the basic unit is a .js file having inputs, outputs, and state objects 
-  - we can compose programs of these modules, being collections of them with connections 
-  - we can represent these programs heirarchically, or in a flat manner ... heirarchy just requires an extra wrap, we do it later
-  
-  - load / save programs (req. prgmem representation) 
-  - rewrite ui & transfers to use this rep. 
- - UI 
-  - title bar descr. how to operate
-  - 'L' for load, 'M' for new module, type for module search ?
-  - right-click delete / copy etc 
-  - evt line highlight on hover 
-  - hover display type
-
 ## WRT Representations
 
 OK
diff --git a/client/client.js b/client/client.js
index 84d0f845f5ef15076597fc85900393ece6328b83..fa9323d9c22bbc6850c36cf58a77fab1bfbffd83 100644
--- a/client/client.js
+++ b/client/client.js
@@ -21,10 +21,11 @@ CLIENT GLOBALS ---------------------------------------------------
 */
 
 var sckt = {}
-var lastPos = { x: 10, y: 10 }
+var lastPos = { x: 10, y: 30 }
 
 // drawing / div-ing
 var wrapper = {}
+var nav = {}
 
 /*
 
@@ -50,6 +51,8 @@ window.onload = function() {
     wrapper.id = 'wrapper'
     document.body.append(wrapper)
 
+    nav = document.getElementById('nav')
+
     const socket = new WebSocket('ws://localhost:8081')
 
     socket.onopen = function(evt) {
@@ -121,7 +124,7 @@ function socketRecv(evt) {
         case 'put module change':
             console.log('RECV MODULE CHANGE')
             heapSendsModuleChange(data)
-            break 
+            break
         case 'put state change':
             console.log('RECV STATE CHANGE')
             heapSendsStateChange(data)
@@ -183,22 +186,22 @@ function heapSendsNewModule(mdl) {
 // containing references to those DOM objects 
 
 
-function heapSendsModuleChange(data){
+function heapSendsModuleChange(data) {
     console.log(data)
     // data should be rep of changed module 
     var rep = program.modules[data.description.id]
     // we want a general case, but for now we know we're looking for 
     // new event hookups or new state items 
-    for(key in rep.outputs){
+    for (key in rep.outputs) {
         var output = rep.outputs[key]
-        if(output.calls.length !== data.outputs[key].calls.length){
+        if (output.calls.length !== data.outputs[key].calls.length) {
             rep.outputs = data.outputs
         }
     }
     // ok
-    for(key in rep.state){
+    for (key in rep.state) {
         var stateItem = rep.state[key]
-        if(stateItem != data.state[key]){
+        if (stateItem != data.state[key]) {
             stateItem = data.state[key]
             rep.ui.state[key].value = data.state[key]
         }
@@ -211,9 +214,9 @@ function heapSendsModuleChange(data){
 // update state from server to UI
 function heapSendsStateChange(data) {
     console.log('HEAP SENDS CHANGE STATE IN MODULE', data)
-    var rep = program.modules[data.id] 
-    rep.state[data.key] = data.val 
-    rep.ui.state[data.key].value = data.val 
+    var rep = program.modules[data.id]
+    rep.state[data.key] = data.val
+    rep.ui.state[data.key].value = data.val
 }
 
 /*
@@ -226,7 +229,7 @@ UI -> HEAP ---------------------------------------------------
 function putState(rep, key) {
     var data = {
         id: rep.description.id,
-        key: key, 
+        key: key,
         val: rep.state[key]
     }
     socketSend('put state change', data)
@@ -326,12 +329,107 @@ UI EVENTS ---------------------------------------------------------------------
 
 */
 
+// drag / pan etc 
+
+document.body.style.overflow = 'hidden'
+document.body.style.transform = 'scale(1) translate(0px, 0px)'
+document.body.style.transformOrigin = '0px 0px'
+// s/o @ Neil 
+function getCurrentTransform() {
+    // a string 
+    var transform = document.body.style.transform
+
+    var index = transform.indexOf('scale')
+    var left = transform.indexOf('(', index)
+    var right = transform.indexOf(')', index)
+    var s = parseFloat(transform.slice(left + 1, right))
+    var index = transform.indexOf('translate')
+    var left = transform.indexOf('(', index)
+    var right = transform.indexOf('px', left)
+    var tx = parseFloat(transform.slice(left + 1, right))
+    var left = transform.indexOf(',', right)
+    var right = transform.indexOf('px', left)
+    var ty = parseFloat(transform.slice(left + 1, right))
+    var origin = document.body.style.transformOrigin
+    var pxx = origin.indexOf('px')
+    var ox = parseFloat(origin.slice(0, pxx))
+    var pxy = origin.indexOf('px', pxx + 2)
+    var oy = parseFloat(origin.slice(pxx + 2, pxy))
+
+    return ({
+        s: s,
+        tx: tx,
+        ty: ty,
+        ox: ox,
+        oy: oy
+    })
+}
+
+function elementIsNotModule(element) {
+    if ((element.tagName == 'HTML') || (element.tagName == 'BODY') || (element.tagName == 'svg')) {
+        return true
+    } else {
+        return false
+    }
+}
+
+onwheel = function(evt) {
+    var el = document.elementFromPoint(evt.pageX, evt.pageY)
+    if (elementIsNotModule(el)) {
+        var cT = getCurrentTransform()
+        evt.preventDefault()
+        evt.stopPropagation()
+        if (evt.deltaY > 0) {
+            var scale = 1.05 * cT.s
+        } else {
+            var scale = 0.95 * cT.s
+        }
+        var tx = cT.tx + (evt.pageX - cT.ox) * (1 - 1 / cT.s)
+        var ty = cT.ty + (evt.pageY - cT.oy) * (1 - 1 / cT.s)
+
+        // body
+        document.body.style.transform = `scale(${scale}) translate(${tx}px,${ty}px)`
+        document.body.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
+
+        // opposite for nav 
+        nav.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
+        nav.style.transform = `scale(${1/scale}) translate(${-tx*scale}px,${-ty*scale}px)`
+    }
+}
+
+onmousedown = function(evt) {
+    var el = document.elementFromPoint(evt.pageX, evt.pageY)
+    if (elementIsNotModule(el)) {
+        window.addEventListener('mousemove', mouseMoveDragListener)
+        window.addEventListener('mouseup', mouseUpDragListener)
+    }
+}
+
+function mouseMoveDragListener(evt) {
+    evt.preventDefault()
+    evt.stopPropagation()
+    var cT = getCurrentTransform()
+    var dx = evt.movementX
+    var dy = evt.movementY
+    var tx = cT.tx + dx / cT.s
+    var ty = cT.ty + dy / cT.s
+
+    // for body 
+    document.body.style.transform = `scale(${cT.s}) translate(${tx}px,${ty}px)`
+
+    // opposite for nav 
+    nav.style.transform = `scale(${1/cT.s}) translate(${-tx*cT.s}px,${-ty*cT.s}px)`
+}
+
+function mouseUpDragListener(evt) {
+    window.removeEventListener('mousemove', mouseMoveDragListener)
+    window.removeEventListener('mouseup', mouseUpDragListener)
+}
+
 // get json menu item and render
 // and ask for module at /obj/key
 oncontextmenu = function(evt) {
     if (sckt) {
-        lastPos.x = evt.pageX
-        lastPos.y = evt.pageY
         socketSend('get module menu', '')
     } else {
         // socket brkn, reload page 
@@ -341,17 +439,17 @@ oncontextmenu = function(evt) {
     return false
 }
 
-document.onmousemove = function(evt){
-    lastPos.x = evt.pageX 
-    lastPos.y = evt.pageY
+onmousemove = function(evt){
+    var cT = getCurrentTransform()
+    lastPos.x = cT.ox - cT.tx + (evt.pageX - cT.ox)/cT.s
+    lastPos.y = cT.oy - cT.ty + (evt.pageY - cT.oy)/cT.s
 }
 
-document.onkeydown = function(evt){
-    console.log(evt)
-    switch(evt.key){
+document.onkeydown = function(evt) {
+    switch (evt.key) {
         case 'Escape':
             location.reload()
-            break 
+            break
         case 's':
             // get path ? 
             var path = prompt("path? starting at atkapi/programs/")
@@ -359,12 +457,12 @@ document.onkeydown = function(evt){
             break
         case 'l':
             socketSend('get program menu', '')
-            break 
+            break
         case 'm':
             socketSend('get module menu', '')
-            break 
+            break
         default:
-            break 
+            break
     }
 }
 
@@ -373,6 +471,7 @@ document.onkeydown = function(evt){
 function heapSendsModuleMenu(tree) {
     var menuDom = document.createElement('div')
     menuDom.id = 'moduleMenu'
+    console.log(lastPos)
     menuDom.style.left = lastPos.x + 'px'
     menuDom.style.top = lastPos.y + 'px'
     for (key in tree) {
@@ -410,23 +509,23 @@ function heapSendsProgramMenu(tree) {
     menuDom.id = 'programMenu'
     menuDom.style.left = lastPos.x + 'px'
     menuDom.style.top = lastPos.y + 'px'
-    for(key in tree){
+    for (key in tree) {
         var li = document.createElement('li')
         var path = tree[key].path
         li.innerHTML = key.toString()
-        li.id = path 
-        li.addEventListener('click', function(evt){
+        li.id = path
+        li.addEventListener('click', function(evt) {
             var data = this.id
             socketSend('load program', data)
             wrapper.removeChild(document.getElementById('programMenu'))
         })
         menuDom.appendChild(li)
     }
-    wrapper.append(menuDom) 
+    wrapper.append(menuDom)
 
-    function rmListener(evt){
-        var findMenu = document.getElementById('programMenu') 
-        if(findMenu !== null && findMenu.id == 'programMenu'){
+    function rmListener(evt) {
+        var findMenu = document.getElementById('programMenu')
+        if (findMenu !== null && findMenu.id == 'programMenu') {
             wrapper.removeChild(findMenu)
         }
         // rm this listner... 
@@ -434,4 +533,4 @@ function heapSendsProgramMenu(tree) {
     }
 
     document.addEventListener('click', rmListener)
-}
+}
\ No newline at end of file
diff --git a/client/divtools.js b/client/divtools.js
index 337a85cf5a4257bf169b8816b462f055be446479..ee000c89689232614b3ffd97b2a155fc348bec5f 100644
--- a/client/divtools.js
+++ b/client/divtools.js
@@ -1,6 +1,5 @@
 // writing representations
 
-
 function addRepToView(rep) {
     // a div to locate it 
     var domElem = document.createElement('div')
@@ -85,21 +84,25 @@ function addRepToView(rep) {
         offsetX = evt.clientX - domElem.getBoundingClientRect().left
         offsetY = evt.clientY - domElem.getBoundingClientRect().top
 
-        function domElemMouseMove(mv) {
-            domElem.style.left = mv.pageX - offsetX + 'px'
-            domElem.style.top = mv.pageY - offsetY + 'px'
+        function domElemMouseMove(evt) {
+            evt.preventDefault()
+            evt.stopPropagation()
+            var cT = getCurrentTransform()
+            domElem.style.left = parseFloat(domElem.style.left) + evt.movementX/cT.s + 'px'
+            domElem.style.top = parseFloat(domElem.style.top) + evt.movementY/cT.s + 'px'
             redrawLinks()
         }
 
-        document.addEventListener('mousemove', domElemMouseMove)
-
-        title.onmouseup = function() {
+        function rmOnMouseUp(evt){
             rep.description.position.left = parseInt(domElem.style.left, 10)
             rep.description.position.top = parseInt(domElem.style.top, 10)
             putUi(rep)
             document.removeEventListener('mousemove', domElemMouseMove)
-            title.onmouseup = null
+            document.removeEventListener('mouseup', rmOnMouseUp)
         }
+
+        document.addEventListener('mousemove', domElemMouseMove)
+        document.addEventListener('mouseup', rmOnMouseUp)
     }
 
     wrapper.appendChild(rep.ui.domElem)
diff --git a/client/index.html b/client/index.html
index 3e1055e3aa21fb877a2f9386dfc3faa06a5bb46f..5008fd782a7e5fbf20d24a38693b0c0207023404 100644
--- a/client/index.html
+++ b/client/index.html
@@ -11,6 +11,9 @@
     <script type="text/javascript" src="dummies.js"></script> -->
     <script type="text/javascript" src="divtools.js"></script>
     <script type="text/javascript" src="client.js"></script>
+    <div id = "nav">
+    	<p>'l' for load program, 's' for save program, right-click or 'm' for add module menu</p>
+    </div>
 </body>
 
 </html>
\ No newline at end of file
diff --git a/programs/default.json b/programs/default.json
index 251b0a4023275c536b03576f62f39469500a647d..1be7d018319dd6940d486a6232f86169bd3ebc37 100644
--- a/programs/default.json
+++ b/programs/default.json
@@ -12,7 +12,7 @@
         "path": "./src/util/gate.js",
         "position": {
           "left": 10,
-          "top": 10
+          "top": 30
         }
       },
       "inputs": {