diff --git a/README.md b/README.md
index 7234f83427e2f6b7045707a54c748553e340a4f9..a039fb36eeef9ca910520c399bba2610ade75867 100644
--- a/README.md
+++ b/README.md
@@ -26,9 +26,10 @@ This project serves the developement environment / api we use to write and repre
   - change settings 
 
  - right now
-  - add module 
-  - l for load
-  - s for save
+  - push state down & up 
+   - note below : 
+   - not actually setting, do node main, change ms, watch console 
+  - some wholistic test ... 
 
  - *don't forget*
   - onload, load state... 
@@ -84,15 +85,18 @@ Modules-that-represent-remote-computing also have
 To assemble a representation of these, we want to have a kind of 'netlist' that, for convenience, we'll treat like a JSON object. We want heirarchy, so consider the representation having 'top-level' outputs / inputs / state as well ? 
 
 
+## Programming Notes
 
+### 15 Minute Tasks 
+ - @ views.js, uiRequestModuleMenu and uiRequestProgramMenu don't properly build trees from folder structure. similarly, reciprical fn's in client.js do the same 
+ - @ these load / save functions could also reach into the modules' source to retrieve their proper names, as spec'd in description ... 
 
-## Programming Notes
+ - 's' for save program uses hack-asf DOM alert to ask for path
 
 title bar
 L for load prgmem
 M for add module
 
-
 - once we can plan gcode sequence, work towards more
  - better hardware abstraction, i.e.
    stepper.port = bridge.port('0,1')
diff --git a/client/client.js b/client/client.js
index e17fda15d216f39221c579991fb1113ef4783e23..69c0c79a6afcb4ad9ed70f6a67b9de19c6d216c0 100644
--- a/client/client.js
+++ b/client/client.js
@@ -101,9 +101,13 @@ function socketRecv(evt) {
             console.log('RECV CONSOLE:', data)
             break
         case 'put module menu':
-            console.log('RECV MENU')
+            console.log('RECV MODULE MENU')
             heapSendsModuleMenu(data)
             break
+        case 'put program menu':
+            console.log('RECV PRG MENU')
+            heapSendsProgramMenu(data)
+            break
         case 'put program':
             console.log('RECV PROGRAM')
             heapSendsNewProgram(data)
@@ -112,10 +116,16 @@ function socketRecv(evt) {
             console.log('RECV NEW MODULE')
             heapSendsNewModule(data)
             break
-        case 'put state':
+        case 'put module change':
+            console.log('RECV MODULE CHANGE')
+            heapSendsModuleChange(data)
+            break 
+        case 'put state change':
             console.log('RECV STATE CHANGE')
-            heapSendsNewState(data)
+            heapSendsStateChange(data)
             break
+        case 'restart':
+            location.reload()
         default:
             console.log('ERR recv with non recognized type', recv)
             break
@@ -129,9 +139,10 @@ MISC ---------------------------------------------------
 */
 
 // return ul element with name and alt and link? 
+// TODO: not properly a tree, see note @ reciprocal fn in views.js
 function heapSendsModuleMenu(tree) {
     var menuDom = document.createElement('div')
-    menuDom.id = 'menu'
+    menuDom.id = 'moduleMenu'
     menuDom.style.left = lastPos.x + 'px'
     menuDom.style.top = lastPos.y + 'px'
     for (key in tree) {
@@ -145,7 +156,7 @@ function heapSendsModuleMenu(tree) {
             li.addEventListener('click', function(evt) {
                 var data = this.id
                 socketSend('put module', data)
-                wrapper.removeChild(document.getElementById('menu'))
+                wrapper.removeChild(document.getElementById('moduleMenu'))
             })
             ul.appendChild(li)
         }
@@ -154,10 +165,42 @@ function heapSendsModuleMenu(tree) {
     wrapper.append(menuDom)
 
     function rmListener(evt) {
-        var findMenu = document.getElementById('menu')
-        if (findMenu !== null && findMenu.id == 'menu') {
+        var findMenu = document.getElementById('moduleMenu')
+        if (findMenu !== null && findMenu.id == 'moduleMenu') {
+            wrapper.removeChild(findMenu)
+        }
+        evt.target.removeEventListener(evt.type, arguments.callee)
+    }
+
+    document.addEventListener('click', rmListener)
+}
+
+function heapSendsProgramMenu(tree) {
+    var menuDom = document.createElement('div')
+    menuDom.id = 'programMenu'
+    menuDom.style.left = lastPos.x + 'px'
+    menuDom.style.top = lastPos.y + 'px'
+    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){
+            var data = this.id
+            socketSend('load program', data)
+            wrapper.removeChild(document.getElementById('programMenu'))
+        })
+        menuDom.appendChild(li)
+    }
+    wrapper.append(menuDom) 
+
+    function rmListener(evt){
+        var findMenu = document.getElementById('programMenu') 
+        if(findMenu !== null && findMenu.id == 'programMenu'){
             wrapper.removeChild(findMenu)
         }
+        // rm this listner... 
+        evt.target.removeEventListener(evt.type, arguments.callee)
     }
 
     document.addEventListener('click', rmListener)
@@ -201,7 +244,6 @@ function heapSendsNewModule(mdl) {
     redrawLinks()
 }
 
-
 // writes DOM elements to represent the module, appends to the wrapper
 // and appends to the rep object a .ui object 
 // containing references to those DOM objects 
@@ -220,23 +262,19 @@ function addRepToView(rep) {
     // more html: the title
     var title = document.createElement('div')
     title.className = 'modname'
-    title.innerHTML = rep.description.name + ' id: ' + rep.id
+    title.innerHTML = rep.description.id
     title.alt = rep.description.alt
     domElem.appendChild(title)
 
     var uiSetFlag
     // place in pos if info present 
     // the rep.ui object will store references to the module's related DOM elements
-    console.log(rep.description.position)
     if (rep.description.position != null) {
-        console.log("FOUND POS")
         uiSetFlag = false
         if (rep.description.position.left != null) {
-            console.log("FOUND LEFT")
             domElem.style.left = rep.description.position.left + 'px'
         }
         if (rep.description.position.top != null) {
-            console.log("FOUND TOP")
             domElem.style.top = rep.description.position.top + 'px'
         }
     } else {
@@ -317,24 +355,35 @@ function addRepToView(rep) {
     }
 }
 
-// update state from server to UI
-function heapSendsNewState(data) {
-    console.log('HEAP SENDS CHANGE STATE IN MODULE', data)
-    var rep = reps[data.id]
-    for (var key in data.state) {
-        if (data.state[key].type == 'button') {
-            console.log('BUTTON UPDATE')
-        } else if (data.state[key].type == 'multiline') {
-            console.log('MULTILINE UPDATE')
-            rep.ui.state[key].value = data.state[key].value
-        } else {
-            // two ?
-            rep.state[key] = data.state[key]
-            if (rep.ui.state[key] != null) {
-                rep.ui.state[key].value = rep.state[key]
-            }
+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){
+        var output = rep.outputs[key]
+        if(output.calls.length !== data.outputs[key].calls.length){
+            rep.outputs = data.outputs
+        }
+    }
+    // ok
+    for(key in rep.state){
+        var stateItem = rep.state[key]
+        if(stateItem != data.state[key]){
+            stateItem = data.state[key]
+            rep.ui.state[key].value = data.state[key]
         }
     }
+    // wreckless or wonderful?
+    //clear(rep)
+    redrawLinks()
+}
+
+// update state from server to UI
+function heapSendsStateChange(data) {
+    console.log('HEAP SENDS CHANGE STATE IN MODULE', data)
+    heapSendsModuleChange(data)
 }
 
 /*
@@ -344,11 +393,16 @@ UI -> HEAP ---------------------------------------------------
 */
 
 // push new state from UI to server 
-function putState(rep) {
+function putState(rep, key) {
     // ship it all home: not perfect, but hey 
     var data = {
-        id: rep.id,
-        state: rep.state
+        description: {
+            id: rep.description.id
+        },
+        state: {
+            key: key,
+            val: rep.state[key]
+        }
     }
     socketSend('put state', data)
 }
@@ -419,7 +473,7 @@ var svg = {}
 
 /*
 
-UI EVENTS ---------------------------------------------------
+UI EVENTS ---------------------------------------------------------------------
 
 */
 
@@ -455,7 +509,7 @@ document.onkeydown = function(evt){
             socketSend('save program', path)
             break
         case 'l':
-            console.log('LOADID')
+            socketSend('get program menu', '')
             break 
         case 'm':
             socketSend('get module menu', '')
diff --git a/client/divtools.js b/client/divtools.js
index fb99fc95f2f751966ae58b2dcae357417e463410..ed2866c32d1ddd58cccd84a5a2f0d0a8b3458557 100644
--- a/client/divtools.js
+++ b/client/divtools.js
@@ -14,7 +14,7 @@ function writeStateRep(container, rep, key) {
                 } else {
                     rep.state[key].isPressed = true
                 }
-                putState(rep)
+                putState(rep, key)
             })
             container.appendChild(li)
             return li
@@ -30,7 +30,7 @@ function writeStateRep(container, rep, key) {
             txtArea.value = variable.value
             txtArea.addEventListener('change', function() {
                 rep.state[key].value = txtArea.value
-                putState(rep)
+                putState(rep, key)
             })
             li.appendChild(txtArea)
             container.appendChild(li)
@@ -46,7 +46,7 @@ function writeStateRep(container, rep, key) {
                 input.value = variable
                 input.addEventListener('change', function() {
                     rep.state[key] = input.value
-                    putState(rep)
+                    putState(rep, key)
                 })
                 li.appendChild(input)
                 container.appendChild(li)
@@ -60,7 +60,7 @@ function writeStateRep(container, rep, key) {
                 input.value = variable.toString()
                 input.addEventListener('change', function() {
                     rep.state[key] = parseFloat(input.value)
-                    putState(rep)
+                    putState(rep, key)
                 })
                 li.appendChild(input)
                 container.appendChild(li)
@@ -79,7 +79,7 @@ function writeStateRep(container, rep, key) {
                             array[index] = parseFloat(element)
                         })
                         rep.state[key] = arr
-                        putState(rep)
+                        putState(rep, key)
                     })
                     li.appendChild(input)
                     container.appendChild(li)
diff --git a/client/style.css b/client/style.css
index da2e4569a93e28414bbbd9dcb94a51aeab5d3ad0..eaa14125a28ffa18ce81b2ee9adc89560a149914 100644
--- a/client/style.css
+++ b/client/style.css
@@ -98,9 +98,16 @@ li:active{
 	background-color: #d1d1d1;
 }
 
-#menu {
+#moduleMenu {
 	position: absolute;
 	width: 245px;
 	padding: 10px;
     background-color: #303030;
 }
+
+#programMenu {
+	position: absolute;
+	width: 245px;
+	padding: 10px;
+    background-color: #303030;
+}
\ No newline at end of file
diff --git a/lib/jsunit.js b/lib/jsunit.js
index 714505339514196d7e457a238356d7ae16ba8b11..071b68d602d3c409561eff213eb88b10d8226673 100644
--- a/lib/jsunit.js
+++ b/lib/jsunit.js
@@ -79,11 +79,11 @@ function isStateKey(key) {
 }
 
 // a coupl'a fancy UI state hooks 
-function Button(label) {
+function Button(label, onClick) {
     var button = {
         type: 'button',
-        isPressed: false,
-        label: label
+        label: label,
+        onClick: onClick
     }
 
     return button
diff --git a/programs/hookup.json b/programs/hookup.json
new file mode 100644
index 0000000000000000000000000000000000000000..b73809adcb3e415c4e0c87dbf084812eb4b5c2f9
--- /dev/null
+++ b/programs/hookup.json
@@ -0,0 +1,136 @@
+{
+  "description": {
+    "name": "tstprgmem",
+    "counter": 4
+  },
+  "modules": {
+    "gate-1": {
+      "description": {
+        "id": "gate-1",
+        "name": "gate",
+        "alt": "in ... out",
+        "path": "./src/util/gate.js",
+        "position": {
+          "left": 10,
+          "top": 10
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": [
+            {
+              "parentId": "delay-2",
+              "key": "thru"
+            },
+            {
+              "parentId": "logger-3",
+              "key": "thru"
+            },
+            {
+              "parentId": "delay-4",
+              "key": "thru"
+            }
+          ]
+        }
+      },
+      "state": {
+        "toggle": {
+          "type": "button",
+          "isPressed": false,
+          "label": "Open / Close"
+        },
+        "message": "closed"
+      }
+    },
+    "delay-2": {
+      "description": {
+        "id": "delay-2",
+        "name": "delay",
+        "alt": "in ... out",
+        "path": "./src/util/delay.js",
+        "position": {
+          "left": 132,
+          "top": 225
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": [
+            {
+              "parentId": "logger-3",
+              "key": "thru"
+            }
+          ]
+        }
+      },
+      "state": {
+        "ms": 100
+      }
+    },
+    "logger-3": {
+      "description": {
+        "id": "logger-3",
+        "name": "logger",
+        "alt": "in ... out to console",
+        "path": "./src/util/log.js",
+        "position": {
+          "left": 123,
+          "top": 367
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "throughput": {
+          "emits": "any",
+          "calls": []
+        }
+      },
+      "state": {
+        "prefix": "LOGGER:",
+        "message": "---"
+      }
+    },
+    "delay-4": {
+      "description": {
+        "id": "delay-4",
+        "name": "delay",
+        "alt": "in ... out",
+        "path": "./src/util/delay.js",
+        "position": {
+          "left": 721,
+          "top": 309
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": []
+        }
+      },
+      "state": {
+        "ms": 100
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/views.js b/views.js
index 951bf74180f085dd9cbea1165be8c137570a4223..44a6514608e94e736dfb8b259153b942fbe1754a 100644
--- a/views.js
+++ b/views.js
@@ -87,12 +87,15 @@ function socketRecv(evt) {
         case 'get module menu':
             uiRequestModuleMenu()
             break
+        case 'get program menu':
+            uiRequestProgramMenu()
+            break
         case 'load program':
             uiRequestLoadProgram(data)
             break
         case 'save program':
             uiRequestSaveProgram(data)
-            break 
+            break
         case 'put module':
             uiRequestNewModule(data)
             break
@@ -148,6 +151,9 @@ function uiRequestCurrentProgram() {
     socketSend('put program', prgRep)
 }
 
+
+// TODO: proper heirarchy, with both of these ... 
+
 function uiRequestModuleMenu() {
     var availableSourceRep = {}
     var dir = fs.readdirSync('./src')
@@ -166,24 +172,48 @@ function uiRequestModuleMenu() {
     socketSend('put module menu', availableSourceRep)
 }
 
+function uiRequestProgramMenu() {
+    var availableProgramRep = {}
+    var paths = fs.readdirSync('./programs')
+    for (i in paths) {
+        if (paths[i].slice(-5) === '.json') {
+            var name = paths[i].slice(0, -5)
+            var path = './programs/' + paths[i]
+            availableProgramRep[paths[i]] = {
+                name: name,
+                path: path
+            }
+        }
+    }
+    socketSend('put program menu', availableProgramRep)
+}
+
 /*
 
 UI -> HEAP HANDLES ---------------------------------------------------
 
 */
 
-function uiRequestLoadProgram(data){
+function uiRequestLoadProgram(data) {
     console.log('UI REQUEST TO OPEN NEW PROGRAM', data)
+    // gonna tear it doooown, maybe just kick page afterwards to restart it?
+    program = null
+    program = Programs.open(data)
+
+    socketSend('restart', '')
 }
 
-function uiRequestSaveProgram(data){
+function uiRequestSaveProgram(data) {
     console.log('UI REQUEST TO SAVE PROGRAM', data)
     // is data a path? add .json ? 
-    if(!data.includes('.json')){
-        data = data + '.json'
+    if (data) {
+        if (!data.includes('.json')) {
+            data = data + '.json'
+        }
+        path = 'programs/' + data
+        Programs.save(program, path)
+        socketSend('console', ('saved program at' + path))
     }
-    path = 'programs/' + data 
-    Programs.save(program, path)
 }
 
 function uiRequestNewModule(data) {
@@ -196,11 +226,54 @@ function uiRequestNewModule(data) {
 
 function uiRequestStateChange(data) {
     console.log('UI REQUEST CHANGE STATE IN MODULE', data)
-    // and don't forget state.obj.emit 
+    // there should only be one ! 
+    var id = data.description.id
+    var key = data.state.key
+    var val = data.state.val
+
+    var mdlState = program.modules[id].state
+    console.log('mdlState', mdlState)
+    var mdlStateItem = program.modules[id].state[key]
+    console.log('mdlStateItem', mdlStateItem)
+
+    if (mdlStateItem) {
+        switch (mdlState.type) {
+            case 'button':
+                mdlStateItem.onClick()
+                break
+            case 'multiline':
+                mdlStateItem.value = val
+                mdlState.emitChange(key)
+                break
+            default:
+                mdlStateItem = val
+                mdlState.emitChange(key)
+                break
+        }
+    } else {
+        console.log("ERR no state key,", key, "found here", data)
+    }
+
+    console.log('mdlStateItem', mdlStateItem)
+    console.log(program.modules[id])
 }
 
 function uiRequestLinkChange(data) {
     console.log('UI REQUEST ADD EVENT LINK', data)
+    var fromId = data.from.id
+    var outputName = data.from.output
+    var toId = data.to.id
+    var inputName = data.to.input
+
+    // HERE: check if hooked already 
+
+    var fromMdl = program.modules[fromId]
+    var toMdl = program.modules[toId]
+    fromMdl.outputs[outputName].attach(toMdl.inputs[inputName])
+
+    var nRep = Reps.makeFromModule(fromMdl)
+
+    socketSend('put module change', nRep)
 }
 
 function uiRequestUiChange(data) {