diff --git a/README.md b/README.md
index 05eb06646e70e82007271bc5b6c29410fd6b565f..b2c499452d78f443fc4a3d03b8dab4dde3ed0b54 100644
--- a/README.md
+++ b/README.md
@@ -267,8 +267,10 @@ View.assignProgram(program)
 
 ## Immediately
 
-- go for generic ui class like remote hardware: subscribes etc 
-
+- nice work on generic ui classes / ui.thing.call('function', arg)
+- finish robot flow: offsets, ok, 100ms updates, ok
+ - forward transform object -> three.js canvas
+ - do key-binding ui for robot jogging, write video 
 - three robot canvas 
 - set module display width 
 
diff --git a/client/client.js b/client/client.js
index 445c6a8d3a9d95b8c7d310cb8792c7d6d98c3930..e06abe46a0067037ba568cf943702eb44de7b627 100644
--- a/client/client.js
+++ b/client/client.js
@@ -32,7 +32,7 @@ var wrapper = {}
 var nav = {}
 
 var verbose = false
-var verboseComs = false 
+var verboseComs = false
 
 /*
 
@@ -98,7 +98,7 @@ function socketSend(type, data) {
         type: type,
         data: data
     }
-    if(verboseComs) console.log('SEND', msg)
+    if (verboseComs) console.log('SEND', msg)
     sckt.send(JSON.stringify(msg))
 }
 
@@ -106,7 +106,7 @@ function socketRecv(evt) {
     var recv = JSON.parse(evt.data)
     var type = recv.type
     var data = recv.data
-    if(verboseComs) console.log('RECV', recv)
+    if (verboseComs) console.log('RECV', recv)
     // tree banger
     switch (type) {
         case 'console':
@@ -171,6 +171,9 @@ function heapSendsNewProgram(prgm) {
     // whole hearted replace
     // hello for bugs when we lay this on top of something else 
     program = prgm
+    if (program.description.view != null) {
+        writeTransformToPage(program.description.view)
+    }
     // 1st we want to git rm old files ... 
     // when adding links, we'll have to add all and then draw links
     if (verbose) console.log('LOAD PROGRAM', program)
@@ -397,6 +400,21 @@ function getCurrentTransform() {
     })
 }
 
+function writeTransformToPage(trns) {
+    // console.log('writing transform', trns)
+    /* transform is like {
+        scale: number,
+        translate: [x, y],
+        origin: [x, y]
+    }
+    */
+    document.body.style.transform = `scale(${trns.scale}) translate(${trns.translate[0]}px, ${trns.translate[1]}px)`
+    document.body.style.transformOrigin = `${trns.origin[0]}px ${trns.origin[1]}px`
+    // opposite for nav 
+    nav.style.transformOrigin = `${trns.origin[0]}px ${trns.origin[1]}px`
+    nav.style.transform = `scale(${1/trns.scale}) translate(${-trns.translate[0]*trns.scale}px,${-trns.translate[1]*trns.scale}px)`
+}
+
 function elementIsNotModule(element) {
     if ((element.tagName == 'HTML') || (element.tagName == 'BODY') || (element.tagName == 'svg')) {
         return true
@@ -420,6 +438,11 @@ onwheel = function(evt) {
         var ty = cT.ty + (evt.pageY - cT.oy) * (1 - 1 / cT.s)
 
         // body
+        writeTransformToPage({
+            scale: scale,
+            translate: [tx, ty],
+            origin: [evt.pageX, evt.pageY]
+        })
         document.body.style.transform = `scale(${scale}) translate(${tx}px,${ty}px)`
         document.body.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
 
diff --git a/client/divtools.js b/client/divtools.js
index dc8f3cd2c965ca8680c149b77b0488231e6ee7a0..9f811c3155ede6935adfed5901c2af66c8a622dc 100644
--- a/client/divtools.js
+++ b/client/divtools.js
@@ -3,11 +3,17 @@
 function addRepToView(rep) {
     // a div to locate it 
     var domElem = document.createElement('div')
+
+    if(rep.description.isBigBlock){
+        domElem.className = 'bigblock'
+    } else {
+        domElem.className = 'block'
+    }
     // dif. color for hardwares ?
-    domElem.className = 'block'
     if (rep.description.isHardware) {
         domElem.classList.add('hardware')
     }
+
     domElem.style.left = lastPos.x + 'px'
     domElem.style.top = lastPos.y + 'px'
 
diff --git a/client/style.css b/client/style.css
index e6045c5d8b252eba98e3ce76eea2a4fe82d11779..d891d8440c5c5f2d73a39c3b2d3192cef2768871 100644
--- a/client/style.css
+++ b/client/style.css
@@ -18,6 +18,14 @@ body {
     background-color: #303030;
 }
 
+.bigblock {
+	width: 800px;
+	position: absolute;
+	padding: 0px;
+	padding-bottom: 23px;
+    background-color: #303030;
+}
+
 .hardware {
 	background-color: #4d4c4c;
 }
diff --git a/client/ui/libs/three.js b/client/ui/libs/three.js
index 8e95e397869be9918769ee422af2a4d0c03053cd..2bdb579138f257b0a0b4546088c36942016f2e82 100644
--- a/client/ui/libs/three.js
+++ b/client/ui/libs/three.js
@@ -48754,3 +48754,490 @@ Object.defineProperties( THREE.OrbitControls.prototype, {
 	}
 
 } );
+
+;(function() {
+
+"use strict";
+
+var root = this
+
+var has_require = typeof require !== 'undefined'
+
+var THREE = root.THREE || has_require && require('three')
+if( !THREE )
+	throw new Error( 'MeshLine requires three.js' )
+
+function MeshLine() {
+
+	this.positions = [];
+
+	this.previous = [];
+	this.next = [];
+	this.side = [];
+	this.width = [];
+	this.indices_array = [];
+	this.uvs = [];
+	this.counters = [];
+	this.geometry = new THREE.BufferGeometry();
+
+	this.widthCallback = null;
+
+}
+
+MeshLine.prototype.setGeometry = function( g, c ) {
+
+	this.widthCallback = c;
+
+	this.positions = [];
+	this.counters = [];
+
+	if( g instanceof THREE.Geometry ) {
+		for( var j = 0; j < g.vertices.length; j++ ) {
+			var v = g.vertices[ j ];
+			var c = j/g.vertices.length;
+			this.positions.push( v.x, v.y, v.z );
+			this.positions.push( v.x, v.y, v.z );
+			this.counters.push(c);
+			this.counters.push(c);
+		}
+	}
+
+	if( g instanceof THREE.BufferGeometry ) {
+		// read attribute positions ?
+	}
+
+	if( g instanceof Float32Array || g instanceof Array ) {
+		for( var j = 0; j < g.length; j += 3 ) {
+			var c = j/g.length;
+			this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] );
+			this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] );
+			this.counters.push(c);
+			this.counters.push(c);
+		}
+	}
+
+	this.process();
+
+}
+
+MeshLine.prototype.compareV3 = function( a, b ) {
+
+	var aa = a * 6;
+	var ab = b * 6;
+	return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] );
+
+}
+
+MeshLine.prototype.copyV3 = function( a ) {
+
+	var aa = a * 6;
+	return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ];
+
+}
+
+MeshLine.prototype.process = function() {
+
+	var l = this.positions.length / 6;
+
+	this.previous = [];
+	this.next = [];
+	this.side = [];
+	this.width = [];
+	this.indices_array = [];
+	this.uvs = [];
+
+	for( var j = 0; j < l; j++ ) {
+		this.side.push( 1 );
+		this.side.push( -1 );
+	}
+
+	var w;
+	for( var j = 0; j < l; j++ ) {
+		if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) );
+		else w = 1;
+		this.width.push( w );
+		this.width.push( w );
+	}
+
+	for( var j = 0; j < l; j++ ) {
+		this.uvs.push( j / ( l - 1 ), 0 );
+		this.uvs.push( j / ( l - 1 ), 1 );
+	}
+
+	var v;
+
+	if( this.compareV3( 0, l - 1 ) ){
+		v = this.copyV3( l - 2 );
+	} else {
+		v = this.copyV3( 0 );
+	}
+	this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	for( var j = 0; j < l - 1; j++ ) {
+		v = this.copyV3( j );
+		this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+		this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	}
+
+	for( var j = 1; j < l; j++ ) {
+		v = this.copyV3( j );
+		this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+		this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	}
+
+	if( this.compareV3( l - 1, 0 ) ){
+		v = this.copyV3( 1 );
+	} else {
+		v = this.copyV3( l - 1 );
+	}
+	this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+
+	for( var j = 0; j < l - 1; j++ ) {
+		var n = j * 2;
+		this.indices_array.push( n, n + 1, n + 2 );
+		this.indices_array.push( n + 2, n + 1, n + 3 );
+	}
+
+	if (!this.attributes) {
+		this.attributes = {
+			position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ),
+			previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ),
+			next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ),
+			side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ),
+			width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ),
+			uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ),
+			index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ),
+			counters: new THREE.BufferAttribute( new Float32Array( this.counters ), 1 )
+		}
+	} else {
+		this.attributes.position.copyArray(new Float32Array(this.positions));
+		this.attributes.position.needsUpdate = true;
+		this.attributes.previous.copyArray(new Float32Array(this.previous));
+		this.attributes.previous.needsUpdate = true;
+		this.attributes.next.copyArray(new Float32Array(this.next));
+		this.attributes.next.needsUpdate = true;
+		this.attributes.side.copyArray(new Float32Array(this.side));
+		this.attributes.side.needsUpdate = true;
+		this.attributes.width.copyArray(new Float32Array(this.width));
+		this.attributes.width.needsUpdate = true;
+		this.attributes.uv.copyArray(new Float32Array(this.uvs));
+		this.attributes.uv.needsUpdate = true;
+		this.attributes.index.copyArray(new Uint16Array(this.indices_array));
+		this.attributes.index.needsUpdate = true;
+    }
+
+	this.geometry.addAttribute( 'position', this.attributes.position );
+	this.geometry.addAttribute( 'previous', this.attributes.previous );
+	this.geometry.addAttribute( 'next', this.attributes.next );
+	this.geometry.addAttribute( 'side', this.attributes.side );
+	this.geometry.addAttribute( 'width', this.attributes.width );
+	this.geometry.addAttribute( 'uv', this.attributes.uv );
+	this.geometry.addAttribute( 'counters', this.attributes.counters );
+
+	this.geometry.setIndex( this.attributes.index );
+
+}
+
+function memcpy (src, srcOffset, dst, dstOffset, length) {
+	var i
+
+	src = src.subarray || src.slice ? src : src.buffer
+	dst = dst.subarray || dst.slice ? dst : dst.buffer
+
+	src = srcOffset ? src.subarray ?
+	src.subarray(srcOffset, length && srcOffset + length) :
+	src.slice(srcOffset, length && srcOffset + length) : src
+
+	if (dst.set) {
+		dst.set(src, dstOffset)
+	} else {
+		for (i=0; i<src.length; i++) {
+			dst[i + dstOffset] = src[i]
+		}
+	}
+
+	return dst
+}
+
+/**
+ * Fast method to advance the line by one position.  The oldest position is removed.
+ * @param position
+ */
+MeshLine.prototype.advance = function(position) {
+
+	var positions = this.attributes.position.array;
+	var previous = this.attributes.previous.array;
+	var next = this.attributes.next.array;
+	var l = positions.length;
+
+	// PREVIOUS
+	memcpy( positions, 0, previous, 0, l );
+
+	// POSITIONS
+	memcpy( positions, 6, positions, 0, l - 6 );
+
+	positions[l - 6] = position.x;
+	positions[l - 5] = position.y;
+	positions[l - 4] = position.z;
+	positions[l - 3] = position.x;
+	positions[l - 2] = position.y;
+	positions[l - 1] = position.z;
+
+    // NEXT
+	memcpy( positions, 6, next, 0, l - 6 );
+
+	next[l - 6]  = position.x;
+	next[l - 5]  = position.y;
+	next[l - 4]  = position.z;
+	next[l - 3]  = position.x;
+	next[l - 2]  = position.y;
+	next[l - 1]  = position.z;
+
+	this.attributes.position.needsUpdate = true;
+	this.attributes.previous.needsUpdate = true;
+	this.attributes.next.needsUpdate = true;
+
+};
+
+function MeshLineMaterial( parameters ) {
+
+	var vertexShaderSource = [
+'precision highp float;',
+'',
+'attribute vec3 position;',
+'attribute vec3 previous;',
+'attribute vec3 next;',
+'attribute float side;',
+'attribute float width;',
+'attribute vec2 uv;',
+'attribute float counters;',
+'',
+'uniform mat4 projectionMatrix;',
+'uniform mat4 modelViewMatrix;',
+'uniform vec2 resolution;',
+'uniform float lineWidth;',
+'uniform vec3 color;',
+'uniform float opacity;',
+'uniform float near;',
+'uniform float far;',
+'uniform float sizeAttenuation;',
+'',
+'varying vec2 vUV;',
+'varying vec4 vColor;',
+'varying float vCounters;',
+'',
+'vec2 fix( vec4 i, float aspect ) {',
+'',
+'    vec2 res = i.xy / i.w;',
+'    res.x *= aspect;',
+'	 vCounters = counters;',
+'    return res;',
+'',
+'}',
+'',
+'void main() {',
+'',
+'    float aspect = resolution.x / resolution.y;',
+'	 float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);',
+'',
+'    vColor = vec4( color, opacity );',
+'    vUV = uv;',
+'',
+'    mat4 m = projectionMatrix * modelViewMatrix;',
+'    vec4 finalPosition = m * vec4( position, 1.0 );',
+'    vec4 prevPos = m * vec4( previous, 1.0 );',
+'    vec4 nextPos = m * vec4( next, 1.0 );',
+'',
+'    vec2 currentP = fix( finalPosition, aspect );',
+'    vec2 prevP = fix( prevPos, aspect );',
+'    vec2 nextP = fix( nextPos, aspect );',
+'',
+'	 float pixelWidth = finalPosition.w * pixelWidthRatio;',
+'    float w = 1.8 * pixelWidth * lineWidth * width;',
+'',
+'    if( sizeAttenuation == 1. ) {',
+'        w = 1.8 * lineWidth * width;',
+'    }',
+'',
+'    vec2 dir;',
+'    if( nextP == currentP ) dir = normalize( currentP - prevP );',
+'    else if( prevP == currentP ) dir = normalize( nextP - currentP );',
+'    else {',
+'        vec2 dir1 = normalize( currentP - prevP );',
+'        vec2 dir2 = normalize( nextP - currentP );',
+'        dir = normalize( dir1 + dir2 );',
+'',
+'        vec2 perp = vec2( -dir1.y, dir1.x );',
+'        vec2 miter = vec2( -dir.y, dir.x );',
+'        //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );',
+'',
+'    }',
+'',
+'    //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;',
+'    vec2 normal = vec2( -dir.y, dir.x );',
+'    normal.x /= aspect;',
+'    normal *= .5 * w;',
+'',
+'    vec4 offset = vec4( normal * side, 0.0, 1.0 );',
+'    finalPosition.xy += offset.xy;',
+'',
+'    gl_Position = finalPosition;',
+'',
+'}' ];
+
+	var fragmentShaderSource = [
+		'#extension GL_OES_standard_derivatives : enable',
+'precision mediump float;',
+'',
+'uniform sampler2D map;',
+'uniform sampler2D alphaMap;',
+'uniform float useMap;',
+'uniform float useAlphaMap;',
+'uniform float useDash;',
+'uniform float dashArray;',
+'uniform float dashOffset;',
+'uniform float dashRatio;',
+'uniform float visibility;',
+'uniform float alphaTest;',
+'uniform vec2 repeat;',
+'',
+'varying vec2 vUV;',
+'varying vec4 vColor;',
+'varying float vCounters;',
+'',
+'void main() {',
+'',
+'    vec4 c = vColor;',
+'    if( useMap == 1. ) c *= texture2D( map, vUV * repeat );',
+'    if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV * repeat ).a;',
+'    if( c.a < alphaTest ) discard;',
+'    if( useDash == 1. ){',
+'        c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));',
+'    }',
+'    gl_FragColor = c;',
+'    gl_FragColor.a *= step(vCounters, visibility);',
+'}' ];
+
+	function check( v, d ) {
+		if( v === undefined ) return d;
+		return v;
+	}
+
+	THREE.Material.call( this );
+
+	parameters = parameters || {};
+
+	this.lineWidth = check( parameters.lineWidth, 1 );
+	this.map = check( parameters.map, null );
+	this.useMap = check( parameters.useMap, 0 );
+	this.alphaMap = check( parameters.alphaMap, null );
+	this.useAlphaMap = check( parameters.useAlphaMap, 0 );
+	this.color = check( parameters.color, new THREE.Color( 0xffffff ) );
+	this.opacity = check( parameters.opacity, 1 );
+	this.resolution = check( parameters.resolution, new THREE.Vector2( 1, 1 ) );
+	this.sizeAttenuation = check( parameters.sizeAttenuation, 1 );
+	this.near = check( parameters.near, 1 );
+	this.far = check( parameters.far, 1 );
+	this.dashArray = check( parameters.dashArray, 0 );
+	this.dashOffset = check( parameters.dashOffset, 0 );
+	this.dashRatio = check( parameters.dashRatio, 0.5 );
+	this.useDash = ( this.dashArray !== 0 ) ? 1 : 0;
+	this.visibility = check( parameters.visibility, 1 );
+	this.alphaTest = check( parameters.alphaTest, 0 );
+	this.repeat = check( parameters.repeat, new THREE.Vector2( 1, 1 ) );
+
+	var material = new THREE.RawShaderMaterial( {
+		uniforms:{
+			lineWidth: { type: 'f', value: this.lineWidth },
+			map: { type: 't', value: this.map },
+			useMap: { type: 'f', value: this.useMap },
+			alphaMap: { type: 't', value: this.alphaMap },
+			useAlphaMap: { type: 'f', value: this.useAlphaMap },
+			color: { type: 'c', value: this.color },
+			opacity: { type: 'f', value: this.opacity },
+			resolution: { type: 'v2', value: this.resolution },
+			sizeAttenuation: { type: 'f', value: this.sizeAttenuation },
+			near: { type: 'f', value: this.near },
+			far: { type: 'f', value: this.far },
+			dashArray: { type: 'f', value: this.dashArray },
+			dashOffset: { type: 'f', value: this.dashOffset },
+			dashRatio: { type: 'f', value: this.dashRatio },
+			useDash: { type: 'f', value: this.useDash },
+			visibility: {type: 'f', value: this.visibility},
+			alphaTest: {type: 'f', value: this.alphaTest},
+			repeat: { type: 'v2', value: this.repeat }
+		},
+		vertexShader: vertexShaderSource.join( '\r\n' ),
+		fragmentShader: fragmentShaderSource.join( '\r\n' )
+	});
+
+	delete parameters.lineWidth;
+	delete parameters.map;
+	delete parameters.useMap;
+	delete parameters.alphaMap;
+	delete parameters.useAlphaMap;
+	delete parameters.color;
+	delete parameters.opacity;
+	delete parameters.resolution;
+	delete parameters.sizeAttenuation;
+	delete parameters.near;
+	delete parameters.far;
+	delete parameters.dashArray;
+	delete parameters.dashOffset;
+	delete parameters.dashRatio;
+	delete parameters.visibility;
+	delete parameters.alphaTest;
+	delete parameters.repeat;
+
+	material.type = 'MeshLineMaterial';
+
+	material.setValues( parameters );
+
+	return material;
+
+};
+
+MeshLineMaterial.prototype = Object.create( THREE.Material.prototype );
+MeshLineMaterial.prototype.constructor = MeshLineMaterial;
+
+MeshLineMaterial.prototype.copy = function ( source ) {
+
+	THREE.Material.prototype.copy.call( this, source );
+
+	this.lineWidth = source.lineWidth;
+	this.map = source.map;
+	this.useMap = source.useMap;
+	this.alphaMap = source.alphaMap;
+	this.useAlphaMap = source.useAlphaMap;
+	this.color.copy( source.color );
+	this.opacity = source.opacity;
+	this.resolution.copy( source.resolution );
+	this.sizeAttenuation = source.sizeAttenuation;
+	this.near = source.near;
+	this.far = source.far;
+	this.dashArray.copy( source.dashArray );
+	this.dashOffset.copy( source.dashOffset );
+	this.dashRatio.copy( source.dashRatio );
+	this.useDash = source.useDash;
+	this.visibility = source.visibility;
+	this.alphaTest = source.alphaTest;
+	this.repeat.copy( source.repeat );
+
+	return this;
+
+};
+
+if( typeof exports !== 'undefined' ) {
+	if( typeof module !== 'undefined' && module.exports ) {
+		exports = module.exports = { MeshLine: MeshLine, MeshLineMaterial: MeshLineMaterial };
+	}
+	exports.MeshLine = MeshLine;
+	exports.MeshLineMaterial = MeshLineMaterial;
+}
+else {
+	root.MeshLine = MeshLine;
+	root.MeshLineMaterial = MeshLineMaterial;
+}
+
+}).call(this);
diff --git a/client/ui/robotRepresentation.js b/client/ui/robotRepresentation.js
new file mode 100644
index 0000000000000000000000000000000000000000..2664eb18f8523a8e2d91b1b4a234462f82986be9
--- /dev/null
+++ b/client/ui/robotRepresentation.js
@@ -0,0 +1,125 @@
+(function() {
+    // generic / example three.js canvas 
+    /* KRITICAL STEP */
+    // this is our object, that we will later append
+    // to the .lump position 
+    var threeCanvas = {}
+
+    var verbose = false 
+
+    // now three stuff 
+    var container = document.createElement('div')
+    var scene = new THREE.Scene()
+    scene.background = new THREE.Color(0xd6d6d6)
+    var camera = new THREE.PerspectiveCamera(75, 1, 0.01, 1000)
+    camera.up.set(0, 0, 1)
+    var renderer = new THREE.WebGLRenderer()
+    renderer.setSize(696, 796)
+    container.appendChild(renderer.domElement)
+
+    // axes 
+    var axesHelper = new THREE.AxesHelper(0.1)
+    scene.add(axesHelper)
+
+    // grid 
+    /*
+    var gridHelper = new THREE.GridHelper(10, 100)
+    gridHelper.translateOnAxis(new THREE.Vector3(0, 0, 1), -0.01)
+    gridHelper.rotateX(-Math.PI / 2)
+    scene.add(gridHelper)
+    */
+
+    // one line 
+    var material = new THREE.LineBasicMaterial({ color: 0x000000 })
+    var geometry = new THREE.Geometry()
+    geometry.vertices.push(new THREE.Vector3(0, 0, 0))
+    geometry.vertices.push(new THREE.Vector3(0.5, 0, 0.5))
+    geometry.vertices.push(new THREE.Vector3(1, 0, 0.5))
+    var meshline = new MeshLine()
+    meshline.setGeometry(geometry, function(p) { return 3 })
+    var material = new MeshLineMaterial({
+        useMap: false,
+        color: new THREE.Color(0x50514f),
+        opacity: 1,
+        sizeAttenuation: true, 
+        lineWidth: 0.01,
+        near: camera.near,
+        far: camera.far
+    })
+    var mesh = new THREE.Mesh(meshline.geometry, material)
+    scene.add(mesh)
+
+    var controls = new THREE.OrbitControls(camera, container)
+
+    camera.position.set(0.5, -1, 0.5)
+    controls.update()
+
+    var animate = function() {
+        requestAnimationFrame(animate)
+        /*
+        line.geometry.vertices[1].x += 0.01
+        line.geometry.verticesNeedUpdate = true
+        */
+        controls.update()
+
+        renderer.render(scene, camera)
+    }
+    // kickstart 
+    animate()
+
+    // set class, etc, put canvas in class, etc 
+    // and then 
+    /* KRITICAL STEP */
+    // we have to give our 'lump' object a .domElem, this
+    // will be referenced upstream to append to the right module 
+    // append it to the dom so that it can be appended on init 
+    threeCanvas.domElem = container
+
+    /* KRITICAL STEP */
+    // whatever is posted to .lump will receive messages through
+    // .onMessage, so if we don't write one, we'll cause errors
+    // upstream, and besides, wouldn't be able to get anything from
+    // the server 
+    threeCanvas.onMessage = function(msg) {
+        //console.log('got message in client side ui object', msg)
+        if (msg.calls == 'updateXY1') {
+            if(verbose) console.log('updateXY1', msg.argument)
+            geometry.vertices[0].set(msg.argument[0], msg.argument[1], msg.argument[2])
+            meshline.setGeometry(geometry, function(p) { return 3 })
+            geometry.verticesNeedUpdate = true 
+        } else if (msg.calls == 'updateXY2') {
+            if(verbose) console.log('updateXY2', msg.argument)
+            geometry.vertices[1].set(msg.argument[0], msg.argument[1], msg.argument[2])
+            meshline.setGeometry(geometry, function(p) { return 3 })
+            geometry.verticesNeedUpdate = true 
+        } else if (msg.calls == 'updateXY3') {
+            if(verbose) console.log('updateXY3', msg.argument)
+            geometry.vertices[2].set(msg.argument[0], msg.argument[1], msg.argument[2])
+            meshline.setGeometry(geometry, function(p) { return 3 })
+            geometry.verticesNeedUpdate = true 
+        } else {
+            console.log('default msg because none from sys at threejs clientside')
+            console.log(msg)
+        }
+    }
+
+    /* KRITICAL STEP */
+    // expect this to only be used once
+    // it's basically our init function 
+    // and gets called once the module is loaded 
+    window.registerNewModule = function(id, key) {
+        threeCanvas.parentId = id
+        threeCanvas.key = key
+        // affectionately named lump of code, insert ourselves here 
+        program.modules[id].ui[key].lump = threeCanvas
+        // and call-back to do onload things
+        var data = {
+            id: threeCanvas.parentId,
+            key: threeCanvas.key,
+            msg: {
+                key: 'onload'
+            }
+        }
+        socketSend('put ui change', data)
+    }
+})()
\ No newline at end of file
diff --git a/client/ui/threeCanvas.js b/client/ui/threeCanvas.js
index 0beeaf0855c01b038898df89c59563137fa0858c..9da96fe9ef7810c975d8df26f2cc8ddb2894eed6 100644
--- a/client/ui/threeCanvas.js
+++ b/client/ui/threeCanvas.js
@@ -1,4 +1,5 @@
 (function() {
+    // generic / example three.js canvas 
     /* KRITICAL STEP */
     // this is our object, that we will later append
     // to the .lump position 
diff --git a/modules/robot/forwardTransform.js b/modules/robot/forwardTransform.js
index 33894b71719bcd3b0378537ce5ef0374564b1ab0..2cdd66b9166ec5060c59ffc16212f4437b7bd3cc 100644
--- a/modules/robot/forwardTransform.js
+++ b/modules/robot/forwardTransform.js
@@ -7,9 +7,6 @@ let Output = JSUnit.Output
 let State = JSUnit.State
 
 function forwardTransform() {
-    var theta1 = 0
-    var theta2 = 0
-
     var forwardTransform = {
         description: {
             name: 'forwardTransform Parser',
@@ -24,73 +21,68 @@ function forwardTransform() {
     // or they don't get getter / settered when we do
     forwardTransform.state = State()
     var state = forwardTransform.state
-    state.l1 = 0.4
-    state.l2 = 0.2
+    state.tA = 0 
+    state.tB = 0
+    state.tC = 0 
+    state.lA = 0.4
+    state.lB = 0.2
+    state.onUiChange('tA', () => {
+        doForwardTransform()
+    })
 
     forwardTransform.inputs = {
-        theta1: Input('any', intakeTheta1),
-        theta2: Input('any', intakeTheta2),
-        l1: Input('any', intakeL1),
-        l2: Input('any', intakeL2)
+        theta_A: Input('any', (num) => {
+            state.tA = num
+            doForwardTransform()
+        }),
+        theta_B: Input('any', (num) => {
+            state.tB = num
+            doForwardTransform()
+        }),
+        theta_C: Input('number', (num) => {
+            state.tC = num
+            doForwardTransform()
+        }),
+        len_A: Input('any', (num) => {
+            state.lA = num
+            doForwardTransform()
+        }),
+        len_B: Input('any', (num) => {
+            state.lB = num
+            doForwardTransform()
+        })
     }
 
     forwardTransform.outputs = {
-        originpt: Output('array'),
-        jointpt: Output('array'),
-        touchpt: Output('array')
-    }
-
-    function intakeTheta1(num){
-        theta1 = num
-        var points = parseforwardTransform()
-        forwardTransform.outputs.originpt.emit(points.originpt)
-        forwardTransform.outputs.jointpt.emit(points.jointpt)
-        forwardTransform.outputs.touchpt.emit(points.touchpt)
-        
-    }
-
-    function intakeTheta2(num){
-        theta2 = num
-        var points = parseforwardTransform()
-        forwardTransform.outputs.originpt.emit(points.originpt)
-        forwardTransform.outputs.jointpt.emit(points.jointpt)
-        forwardTransform.outputs.touchpt.emit(points.touchpt)
-        //console.log('theta2 input')
-    }
-
-    function intakeL1(num){
-        state.l1 = num
-        var points = parseforwardTransform()
-        forwardTransform.outputs.originpt.emit(points.originpt)
-        forwardTransform.outputs.jointpt.emit(points.jointpt)
-        forwardTransform.outputs.touchpt.emit(points.touchpt)
-        //console.log('state.l1 input')
-    }
-
-    function intakeL2(num){
-        state.l2 = num
-        var points = parseforwardTransform()
-        forwardTransform.outputs.originpt.emit(points.originpt)
-        forwardTransform.outputs.jointpt.emit(points.jointpt)
-        forwardTransform.outputs.touchpt.emit(points.touchpt)
-        //console.log('state.l2 input')
+        ptA: Output('array'),
+        ptB: Output('array'),
+        ptC: Output('array')
     }
 
     // TODO: test, can we link global vars to ui objects ... 
     // forwardTransform.ui.mode.value = var ? no bc set / get etc 
     // more like var = forwardTransform.ui.mode.value ? is this referential?
 
-    function parseforwardTransform() {
-        var points = {
-            originpt: [0,0],
-            jointpt: [0,0],
-            touchpt: [0,0]
-        }
-
-        points.jointpt = [state.l1*Math.cos(theta1), state.l1*Math.sin(theta1)]
-        points.touchpt = [state.l1*Math.cos(theta1)+state.l2*Math.cos(theta2+theta1), state.l1*Math.sin(theta1)+state.l2*Math.sin(theta2+   theta1)]
-
-        return points
+    function doForwardTransform() {
+        var lenA = state.lA*Math.cos(state.tB)
+        var lenB = state.lB*Math.cos(state.tB + state.tC)
+
+        var knuckle = [ lenA*Math.cos(state.tA), 
+                        lenA*Math.sin(state.tA), 
+                        state.lA*Math.sin(state.tB)]
+
+        var tip = [ knuckle[0] + lenB*Math.cos(state.tA),
+                    knuckle[1] + lenB*Math.sin(state.tA),
+                    knuckle[2] + state.lB*Math.sin(state.tB + state.tC)]
+        /*
+        [     state.lA*Math.cos(state.tA) + state.lB*Math.cos(state.tA + state.tB), 
+                        state.lA*Math.sin(state.tA) + state.lB*Math.sin(state.tA + state.tB), 
+                        state.lA*Math.sin(state.tB) + state.lB*Math.sin(state.tA + state.tB)]
+        */
+
+        forwardTransform.outputs.ptA.emit([0,0,0])
+        forwardTransform.outputs.ptB.emit(knuckle)
+        forwardTransform.outputs.ptC.emit(tip)
     }
 
     return forwardTransform
diff --git a/modules/ui/robotCanvas.js b/modules/ui/robotCanvas.js
index 1250f4bc2bba95b04af7fc27612acbb995839a55..28b0d7c47d86c766013d738b906094eeece12bd8 100644
--- a/modules/ui/robotCanvas.js
+++ b/modules/ui/robotCanvas.js
@@ -14,8 +14,9 @@ function RobotCanvas() {
     var rbtCanvas = {
         // descriptions are used in UI
         description: {
-            name: 'ThreeJS-Canvas',
-            alt: 'graphix'
+            name: 'RobotArm-Canvas',
+            alt: 'graphix',
+            isBigBlock: true 
         }
     }
 
@@ -26,7 +27,7 @@ function RobotCanvas() {
     rbtCanvas.inputs = {
         xy1: Input('array', onNewXY1),
         xy2: Input('array', onNewXY2),
-        // do some canvas stuff 
+        xy3: Input('array', onNewXY3)
     }
 
     rbtCanvas.outputs = {
@@ -35,30 +36,34 @@ function RobotCanvas() {
 
     rbtCanvas.ui = UI()
     var ui = rbtCanvas.ui
-    ui.addElement('threeCanvas', 'ui/threeCanvas.js')
+    ui.addElement('canvas', 'ui/robotRepresentation.js')
     // add bonus lib path 
-    ui.threeCanvas.libPath = 'ui/libs/three.js'
-    ui.threeCanvas.subscribe('onload', function(msg){
+    ui.canvas.libPath = 'ui/libs/three.js'
+    ui.canvas.subscribe('onload', function(msg){
         console.log('catch canvas load', msg)
+        onNewXY1([0,0,0])
+        onNewXY2([0.3,0.3,0.6])
+        onNewXY3([0.6,0.6,0.3])
     })
-
-    /*
-    ui.threeCanvas.onload = function() {
-        console.log('canvas is loaded')
-    }
-    */
     
     function onNewXY1(array) {
-        ui.threeCanvas.send({
+        ui.canvas.send({
             calls: "updateXY1",
-            argument: [array[0], 0, array[1]]
+            argument: array
         })
     }
 
     function onNewXY2(array){
-        ui.threeCanavs.send({
+        ui.canvas.send({
             calls: "updateXY2",
-            argument: [array[0], 0, array[1]]
+            argument: array
+        })
+    }
+
+    function onNewXY3(array){
+        ui.canvas.send({
+            calls: "updateXY3",
+            argument: array
         })
     }
 
diff --git a/modules/util/gate.js b/modules/util/gate.js
index d4e5f174e8a6bca94c5f9fb7c328b48f8aa93ae9..cf3af9bb6a422f53678535c9b80726f22729f7d8 100644
--- a/modules/util/gate.js
+++ b/modules/util/gate.js
@@ -28,7 +28,7 @@ function Gate() {
     gate.ui = UI()
     var ui = gate.ui
     ui.addElement('openButton', 'ui/uiButton.js')
-    ui.openButton.subscribe('onload', function(msg){
+    ui.openButton.subscribe('onload', function(msg) {
         ui.openButton.send({
             calls: 'setText',
             argument: 'click to open gate'
@@ -51,11 +51,17 @@ function Gate() {
         if (gate.isOpen) {
             gate.isOpen = false
             state.message = 'closed'
-            ui.openButton.setText('click to open gate')
+            ui.openButton.send({
+                calls: 'setText',
+                argument: 'click to open gate'
+            })
         } else {
             gate.isOpen = true
             state.message = 'open'
-            ui.openButton.setText('click to close gate')
+            ui.openButton.send({
+                calls: 'setText',
+                argument: 'click to close gate'
+            })
         }
     }
 
diff --git a/modules/util/log.js b/modules/util/log.js
index eb9ad295a1f96feb084b940eab3769be72ddec5b..9dff8924aebba38f92756b5f62c12060f9a81f1b 100644
--- a/modules/util/log.js
+++ b/modules/util/log.js
@@ -17,10 +17,11 @@ function Logger() {
 
     logger.state = State()
     // alias !
-    var state = logger.state 
+    var state = logger.state
 
     state.prefix = 'LOGGER:'
     state.message = '---'
+    state.logToggle = true
 
     logger.inputs = {
         thru: Input('any', onInput) // makes anything into '1' event
@@ -30,9 +31,11 @@ function Logger() {
         throughput: Output('any')
     }
 
-    function onInput(input){
-        state.message = input.toString()
-        console.log(state.prefix, input)
+    function onInput(input) {
+        if (state.logToggle) {
+            state.message = input.toString()
+            console.log(state.prefix, input)
+        }
     }
 
     return logger
diff --git a/programs.js b/programs.js
index fef3606d80fe22884d8ac8fe05ee15115246d434..8285824f367790420181ab9b1e99d088390bdb21 100644
--- a/programs.js
+++ b/programs.js
@@ -305,6 +305,10 @@ function SetUI(module, left, top){
     module.description.position.top = top
 }
 
+function SetView(program, transform){
+    program.description.view = transform
+}
+
 module.exports = {
     new: newProgram,
     open: openProgram,
@@ -312,5 +316,6 @@ module.exports = {
     loadModuleFromSource: loadModuleFromSource,
     removeModule: removeModuleFromProgram,
     assignSocket: assignSocket,
-    setUI: SetUI
+    setUI: SetUI,
+    setView: SetView
 }
\ No newline at end of file
diff --git a/robot.js b/robot.js
index e33085da118b33b9cca4a677033c6d761ed88fac..77c9b52372f352fcd8678a316e2cf675a155f541 100644
--- a/robot.js
+++ b/robot.js
@@ -22,19 +22,34 @@ var program = Programs.new('new program')
 var link = Programs.loadModuleFromSource(program, './modules/hardware/atkseriallink.js')
 link.startUp()
 link.state.log = false 
-Programs.setUI(link, 1050, 50)
+Programs.setUI(link, 1050, 150)
 
 var mrbotone = Programs.loadModuleFromSource(program, './modules/hardware/atkmrobot.js')
+mrbotone.route.route = '0,0'
+mrbotone.state.enc_reverse = true 
+mrbotone.state.enc_offset = 8875 
 Programs.setUI(mrbotone, 600, 50)
+
 var mrbottwo = Programs.loadModuleFromSource(program, './modules/hardware/atkmrobot.js')
+mrbottwo.state.enc_reverse = true 
+mrbottwo.state.enc_offset = 4000 
+mrbottwo.route.route = '0,1'
 Programs.setUI(mrbottwo, 600, 550)
+
 var mrbotthree = Programs.loadModuleFromSource(program, './modules/hardware/atkmrobot.js')
+mrbotthree.route.route = '0,2'
+mrbotthree.state.enc_reverse = false 
+mrbotthree.state.enc_offset = 1700 
 Programs.setUI(mrbotthree, 600, 1050)
 
 var rbtcanvas = Programs.loadModuleFromSource(program, './modules/ui/robotCanvas.js')
-Programs.setUI(rbtcanvas, 1200, 50)
+Programs.setUI(rbtcanvas, 1500, 1050)
 
-/*
+Programs.setView(program, {
+	scale: 0.5,
+	translate: [-160, -30],
+	origin: [200, 120]
+})
 
 var button = Programs.loadModuleFromSource(program, './modules/ui/button.js')
 var delay = Programs.loadModuleFromSource(program, './modules/util/delay.js')
@@ -46,17 +61,38 @@ Programs.setUI(delay, 90, 250)
 Programs.setUI(gate, 90, 400)
 
 button.outputs.whammy.attach(mrbotone.inputs.get_pos)
+button.outputs.whammy.attach(mrbottwo.inputs.get_pos)
+button.outputs.whammy.attach(mrbotthree.inputs.get_pos)
 button.outputs.whammy.attach(delay.inputs.thru)
 delay.outputs.out.attach(gate.inputs.thru)
 gate.outputs.out.attach(button.inputs.thru)
 
-var log = Programs.loadModuleFromSource(program, './modules/util/log.js')
-log.state.prefix = "jnt1:"
-Programs.setUI(log, 1050, 520)
-mrbotone.outputs.pos.attach(log.inputs.thru)
+var transform = Programs.loadModuleFromSource(program, './modules/robot/forwardTransform.js')
+Programs.setUI(transform, 1225, 650)
+
+var log1 = Programs.loadModuleFromSource(program, './modules/util/log.js')
+log1.state.prefix = "jnt1:"
+Programs.setUI(log1, 1500, 50)
+mrbotone.outputs.pos.attach(log1.inputs.thru)
+mrbotone.outputs.pos.attach(transform.inputs.theta_A)
 
-var canvas = Programs.loadModuleFromSource(program, './modules/ui/threeCanvas.js')
-Programs.setUI(canvas, 1500, 650)
+var log2= Programs.loadModuleFromSource(program, './modules/util/log.js')
+log2.state.prefix = "jnt2:"
+Programs.setUI(log2, 1500, 250)
+mrbottwo.outputs.pos.attach(log2.inputs.thru)
+mrbottwo.outputs.pos.attach(transform.inputs.theta_B)
+
+var log3 = Programs.loadModuleFromSource(program, './modules/util/log.js')
+log3.state.prefix = "jnt3:"
+Programs.setUI(log3, 1500, 450)
+mrbotthree.outputs.pos.attach(log3.inputs.thru)
+mrbotthree.outputs.pos.attach(transform.inputs.theta_C)
+
+transform.outputs.ptA.attach(rbtcanvas.inputs.xy1)
+transform.outputs.ptB.attach(rbtcanvas.inputs.xy2)
+transform.outputs.ptC.attach(rbtcanvas.inputs.xy3)
+
+/*
 
 var collector = Programs.loadModuleFromSource(program, './modules/util/collector.js')
 Programs.setUI(collector, 1050, 800)
@@ -70,7 +106,7 @@ Programs.setUI(gateCounter, 600, 850)
 var stest = Programs.loadModuleFromSource(program, './modules/ui/stest.js')
 
 var rep = Reps.makeFromModule(stest)
-console.log('rep', rep)
+console.log1('rep', rep)
 
 /* example program-like-an-api 
 // load some modules
diff --git a/views.js b/views.js
index b1efc2d8b77779634f59e7d3130cc9085357edc9..43b447d01de379b71187c70368c5fda0366a6f56 100644
--- a/views.js
+++ b/views.js
@@ -163,6 +163,9 @@ function uiRequestCurrentProgram() {
         },
         modules: {}
     }
+    if(program.description.view != null){
+        prgRep.description.view = program.description.view
+    }
     for (mdlName in program.modules) {
         var mdlRep = Reps.makeFromModule(program.modules[mdlName])
         prgRep.modules[mdlName] = mdlRep