diff --git a/.gitignore b/.gitignore
index 6dafe23..239f615 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,9 @@ examples/textbox/textbox
 examples/cloudflare/cloudflare
 examples/*/helloconsole
 
+# protobuf compiled files
+protobuf/*.pb.go
+
 # temporary files when building debian packages
 /*.deb
 /files
diff --git a/common.go b/common.go
index 6f42ce4..462f37d 100644
--- a/common.go
+++ b/common.go
@@ -170,6 +170,13 @@ func (n *Node) Unpad() *Node {
 	return n
 }
 
+func (n *Node) Expand() *Node {
+	a := newAction(n, toolkit.Pad)
+	a.Expand = true
+	sendAction(a)
+	return n
+}
+
 // is this better?
 // yes, this is better. it allows Internationalization very easily
 //  me.window = myGui.New2().Window("DNS and IPv6 Control Panel").Standard()
@@ -182,8 +189,9 @@ func (n *Node) Window(title string) *Node {
 
 // This should not really do anything. as per the docs, the "Standard()" way
 // should be the default way
+/*
 func (n *Node) Standard() *Node {
-	log(debugError, "Standard() not implemented yet")
+	log(debugInfo, "Standard() not implemented yet")
 	return n
 }
 
@@ -191,3 +199,4 @@ func (n *Node) SetMargin() *Node {
 	log(debugError, "DoMargin() not implemented yet")
 	return n
 }
+*/
diff --git a/examples/cloudflare/api.go b/examples/cloudflare/api.go
index 8cf550e..9522b07 100644
--- a/examples/cloudflare/api.go
+++ b/examples/cloudflare/api.go
@@ -4,10 +4,11 @@ package main
 import 	(
 	"os"
 	"log"
+	"fmt"
 	"encoding/json"
 	"io/ioutil"
 	"net/http"
-	"strconv"
+//	"strconv"
 	"bytes"
 
 	"github.com/davecgh/go-spew/spew"
@@ -19,7 +20,17 @@ func doChange(dnsRow *RRT) {
 	log.Println("Content", dnsRow.Content, "vs", dnsRow.valueNode.S)
 	if (dnsRow.Content != dnsRow.valueNode.S) {
 		log.Println("UPDATE VALUE", dnsRow.nameNode.Name, dnsRow.typeNode.Name, "to", dnsRow.valueNode.S)
-		httpPut(dnsRow)
+		stuff, result := httpPut(dnsRow)
+		if (dnsRow.curlNode != nil) {
+			pretty, _ := formatJSON(stuff)
+			log.Println("http PUT curl =", pretty)
+			dnsRow.curlNode.SetText(pretty)
+		}
+		if (dnsRow.resultNode != nil) {
+			pretty, _ := formatJSON(result)
+			log.Println("http PUT result =", pretty)
+			dnsRow.resultNode.SetText(pretty)
+		}
 	}
 	dnsRow.saveNode.Disable()
 }
@@ -73,24 +84,26 @@ func getZonefile(c *configT) *DNSRecords {
 	go.wit.com. 3600 IN A 1.1.1.9
 	test.wit.com. 3600 IN NS ns1.wit.com.
 */
-func httpPut(dnsRow *RRT) {
-	var url string = cloudflareURL + os.Getenv("CLOUDFLARE_ZONEID") + "/dns_records/" + dnsRow.ID
-	var authKey string = os.Getenv("CLOUDFLARE_AUTHKEY")
-	var email string = os.Getenv("CLOUDFLARE_EMAIL")
+func httpPut(dnsRow *RRT) (string, string) {
+	var url string = cloudflareURL + os.Getenv("CF_API_ZONEID") + "/dns_records/" + dnsRow.ID
+	var authKey string = os.Getenv("CF_API_KEY")
+	var email string = os.Getenv("CF_API_EMAIL")
 
-	// make a json record to send on port 90 to cloudflare
+	// make a json record to send on port 80 to cloudflare
 	var tmp string
 	tmp = `{"content": "` + dnsRow.valueNode.S + `", `
 	tmp += `"name": "` + dnsRow.Name + `", `
 	tmp += `"type": "` + dnsRow.Type + `", `
-	tmp+= `"ttl": "` +  strconv.Itoa(dnsRow.TTL) + `", `
+	tmp+= `"ttl": "` +  "1" + `", `
 	tmp += `"comment": "WIT DNS Control Panel"`
 	tmp +=  `}`
 	data := []byte(tmp)
 
 	log.Println("http PUT url =", url)
-	log.Println("http PUT data =", data)
-	spew.Dump(data)
+	// log.Println("http PUT data =", data)
+	// spew.Dump(data)
+	pretty, _ := formatJSON(string(data))
+	log.Println("http PUT data =", pretty)
 
 	req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(data))
 
@@ -103,19 +116,19 @@ func httpPut(dnsRow *RRT) {
 	resp, err := client.Do(req)
 	if err != nil {
 		log.Println(err)
-		return
+		return tmp, fmt.Sprintf("blah err =", err)
 	}
 	defer resp.Body.Close()
 
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 		log.Println(err)
-		return
+		return tmp, fmt.Sprintf("blah err =", err)
 	}
-	log.Println("http PUT body =", body)
-	spew.Dump(body)
+	// log.Println("http PUT body =", body)
+	// spew.Dump(body)
 
-	return
+	return tmp, string(body)
 }
 
 // https://api.cloudflare.com/client/v4/zones
@@ -185,3 +198,22 @@ func getZones(auth, email string) *DNSRecords {
 
 	return &records
 }
+
+// formatJSON takes an unformatted JSON string and returns a formatted version.
+func formatJSON(unformattedJSON string) (string, error) {
+	var jsonData interface{}
+
+	// Decode the JSON string into an interface
+	err := json.Unmarshal([]byte(unformattedJSON), &jsonData)
+	if err != nil {
+		return "", err
+	}
+
+	// Re-encode the JSON with indentation for formatting
+	formattedJSON, err := json.MarshalIndent(jsonData, "", "    ")
+	if err != nil {
+		return "", err
+	}
+
+	return string(formattedJSON), nil
+}
diff --git a/examples/cloudflare/curl.sh b/examples/cloudflare/curl.sh
old mode 100644
new mode 100755
index ec8c014..1edd53e
--- a/examples/cloudflare/curl.sh
+++ b/examples/cloudflare/curl.sh
@@ -1,3 +1,28 @@
-curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-     -H "Authorization: Bearer AAAPutYourTokenInHereSoYouCanTestItL5Cl3" \
-     -H "Content-Type:application/json"
+#curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
+#     -H "Authorization: Bearer AAAPutYourTokenInHereSoYouCanTestItL5Cl3" \
+#     -H "Content-Type:application/json"
+
+# https://api.cloudflare.com/client/v4/zones/27b900d9e05cfb9f3a64fecff2497f90/dns_records
+#
+# {
+#    "comment": "WIT DNS Control Panel",
+#    "content": "2001:4860:4860::8888",
+#    "name": "www",
+#    "proxied": false,
+#    "ttl": 3600,
+#    "type": "AAAA"
+#}
+
+curl --request POST \
+  --url https://api.cloudflare.com/client/v4/zones/27b900d9e05cfb9f3a64fecff2497f90/dns_records \
+  --header 'Content-Type: application/json' \
+  --header 'X-Auth-Key: e08806ad85ef97aebaacd2d7fa462a7d417a7x' \
+  --header 'X-Auth-Email: basilarchia@gmail.com' \
+  --data '{
+  "comment": "WIT DNS Control Panel",
+  "content": "2001:4860:4860::5555",
+  "name": "www5",
+  "proxied": false,
+  "ttl": 3600,
+  "type": "AAAA"
+}'
diff --git a/examples/cloudflare/gui.go b/examples/cloudflare/gui.go
index 60fd5fe..ed08482 100644
--- a/examples/cloudflare/gui.go
+++ b/examples/cloudflare/gui.go
@@ -4,6 +4,8 @@ package main
 import 	(
 	"log"
 	"strconv"
+
+	"git.wit.org/jcarr/control-panel-dns/cloudflare"
 )
 
 func loadDNS(c *configT) {
@@ -11,7 +13,8 @@ func loadDNS(c *configT) {
 	log.Println("adding DNS record", hostname)
 
 	newt := mainWindow.NewTab(hostname)
-	newg := newt.NewGroup("more")
+	vb := newt.NewBox("vBox", false)
+	newg := vb.NewGroup("more zoneID = " + c.zoneID)
 
 	// make a grid 6 things wide
 	grid := newg.NewGrid("gridnuts", 6, gridH)
@@ -19,41 +22,22 @@ func loadDNS(c *configT) {
 //	grid.NewButton("Type", func () {
 //		log.Println("sort by Type")
 //	})
-	typedrop := grid.NewDropdown("type")
-	typedrop.AddText("A")
-	typedrop.AddText("AAAA")
-	typedrop.AddText("CNAME")
-	typedrop.Custom = func () {
-		log.Println("custom dropdown() a =", typedrop.Name, typedrop.S)
-	}
-	nb := grid.NewButton("Name", func () {
-		log.Println("sort by Name")
-	})
-	nb.Disable()
+	grid.NewLabel("RR type")
+	grid.NewLabel("hostname")
 
-	grid.NewButton("Protection", func () {
-		log.Println("sort proxied")
-	})
-	grid.NewButton("TTL", func () {
-		log.Println("sort by TTL")
-	})
-	nb = grid.NewButton("Value", func () {
-		log.Println("sort by Value")
-	})
-	nb.Disable()
-	nb = grid.NewButton("Save", func () {
-		log.Println("click below to save")
-	})
-	nb.Disable()
+	grid.NewLabel("Proxy")
+	grid.NewLabel("TTL")
+	grid.NewLabel("Value")
+	grid.NewLabel("Save")
 
-	masterSave = newt.NewButton("Master Save", func () {
+	masterSave = vb.NewButton("Master Save", func () {
 		log.Println("save stuff to cloudflare")
 	})
 	masterSave.Disable()
 
 	records := getZonefile(c)
 	for _, record := range records.Result {
-		var rr RRT // dns zonefile resource record
+		var rr cloudflare.RRT // dns zonefile resource record
 
 		// copy all the JSON values into the row record.
 		rr.ID = record.ID
@@ -62,59 +46,34 @@ func loadDNS(c *configT) {
 		rr.Content = record.Content
 		rr.Proxied = record.Proxied
 		rr.Proxiable = record.Proxiable
-		rr.TTL = record.TTL
+		// rr.Ttl = record.TTL
 
-		rr.typeNode = grid.NewLabel(record.Type)
-		rr.nameNode = grid.NewEntryLine(record.Name)
-		rr.nameNode.SetText(record.Name)
-		rr.nameNode.Disable()
+		grid.NewLabel(record.Type)
+		grid.NewLabel(record.Name)
 
-		// set proxy or unproxied
-		rr.proxyNode = grid.NewDropdown("proxy")
+		proxy := grid.NewLabel("proxy")
 		if (record.Proxied) {
-			rr.proxyNode.AddText("Proxied")
-			rr.proxyNode.AddText("DNS")
+			proxy.SetText("On")
 		} else {
-			rr.proxyNode.AddText("DNS")
-			rr.proxyNode.AddText("Proxied")
-		}
-		rr.proxyNode.Custom = func () {
-			log.Println("proxy dropdown() a =", rr.proxyNode.Name, rr.proxyNode.S, rr.ID)
-			rr.saveNode.Enable()
-			masterSave.Enable()
+			proxy.SetText("Off")
 		}
 
-		var ttl, short  string
+		var ttl  string
 		if (record.TTL == 1) {
 			ttl = "Auto"
 		} else {
 			ttl = strconv.Itoa(record.TTL)
 		}
-		rr.ttlNode = grid.NewLabel(ttl)
-		// short = fmt.Sprintf("%80s", record.Content)
-		short = record.Content
-		if len(short) > 40 {
-			short = short[:40] // Slice the first 20 characters
-		}
+		grid.NewLabel(ttl)
 
-		rr.valueNode = grid.NewEntryLine(short)
-		rr.valueNode.SetText(record.Content)
+		val := grid.NewLabel("Value")
+		val.SetText(record.Content)
 
-		rr.valueNode.Custom = func () {
-			log.Println("value changed =", rr.valueNode.Name, rr.proxyNode.S, rr.ID)
-			rr.saveNode.Enable()
-			masterSave.Enable()
-		}
-
-		// fmt.Printf("ID: %s, Type: %s, Name: %s, short Content: %s\n", record.ID, record.Type, record.Name, short)
-		// fmt.Printf("\tproxied: %b, %b, string TTL: %i\n", record.Proxied, record.Proxiable, ttl)
-
-		rr.saveNode = grid.NewButton("Save", nil)
-		rr.saveNode.Disable()
-		rr.saveNode.Custom = func () {
+		load := grid.NewButton("Load", nil)
+		load.Custom = func () {
 			name := "save stuff to cloudflare for " + rr.ID
 			log.Println(name)
-			doChange(&rr)
+			// doChange(&rr)
 		}
 	}
 
diff --git a/examples/cloudflare/main.go b/examples/cloudflare/main.go
index 4e72668..aeb8570 100644
--- a/examples/cloudflare/main.go
+++ b/examples/cloudflare/main.go
@@ -7,7 +7,9 @@ import 	(
 	"log"
 	"bufio"
 	"strings"
+
 	"git.wit.org/wit/gui"
+	"git.wit.org/jcarr/control-panel-dns/cloudflare"
 )
 
 var title string = "Cloudflare DNS Control Panel"
@@ -44,7 +46,8 @@ func makeCloudflareWindow() {
 	makeConfigTab(mainWindow)
 
 	t = mainWindow.NewTab("Zones")
-	g1 := t.NewGroup("zones")
+	vb := t.NewBox("vBox", false)
+	g1 := vb.NewGroup("zones")
 
 	// make dropdown list of zones
 	zonedrop = g1.NewDropdown("zone")
@@ -52,6 +55,7 @@ func makeCloudflareWindow() {
 	for d, _ := range config {
 		zonedrop.AddText(d)
 	}
+	zonedrop.AddText("stablesid.org")
 
 	zonedrop.Custom = func () {
 		domain := zonedrop.S
@@ -82,18 +86,18 @@ func makeConfigTab(window *gui.Node) {
 	vb := t.NewBox("vBox", false)
 	g1 := vb.NewGroup("Cloudflare API Config")
 
-	g1.NewLabel("If you have an API key with access to list all of /n your zone files, enter it here. \n \n Alternatively, you can set the enviroment variables: \n env $CLOUDFLARE_AUTHKEY \n env $CLOUDFLARE_EMAIL \n env $CLOUDFLARE_URL \n")
+	g1.NewLabel("If you have an API key with access to list all of /n your zone files, enter it here. \n \n Alternatively, you can set the enviroment variables: \n env $CF_API_KEY \n env $CF_API_EMAIL\n")
 
 	// make grid to display credentials
 	grid := g1.NewGrid("credsGrid", 2, 4) // width = 2
 
 	grid.NewLabel("Auth Key")
-	aw := grid.NewEntryLine("CLOUDFLARE_AUTHKEY")
-	aw.SetText(os.Getenv("CLOUDFLARE_AUTHKEY"))
+	aw := grid.NewEntryLine("CF_API_KEY")
+	aw.SetText(os.Getenv("CF_API_KEY"))
 
 	grid.NewLabel("Email")
-	ew := grid.NewEntryLine("CLOUDFLARE_EMAIL")
-	ew.SetText(os.Getenv("CLOUDFLARE_EMAIL"))
+	ew := grid.NewEntryLine("CF_API_EMAIL")
+	ew.SetText(os.Getenv("CF_API_EMAIL"))
 
 	var url string = "https://api.cloudflare.com/client/v4/zones/"
 	grid.NewLabel("Cloudflare API")
@@ -106,6 +110,10 @@ func makeConfigTab(window *gui.Node) {
 		getZones(aw.S, ew.S)
 	})
 
+	vb.NewButton("cloudflare wit.com", func () {
+		cloudflare.CreateRR(myGui, "wit.com", "3777302ac4a78cd7fa4f6d3f72086d06")
+	})
+
 	t.Pad()
 	t.Margin()
 	vb.Pad()
@@ -144,16 +152,16 @@ func showCloudflareCredentials(box *gui.Node) {
 	grid := box.NewGrid("credsGrid", 2, 4) // width = 2
 
 	grid.NewLabel("Domain")
-	domainWidget = grid.NewEntryLine("CLOUDFLARE_DOMAIN")
+	domainWidget = grid.NewEntryLine("CF_API_DOMAIN")
 
 	grid.NewLabel("Zone ID")
-	zoneWidget = grid.NewEntryLine("CLOUDFLARE_ZONEID")
+	zoneWidget = grid.NewEntryLine("CF_API_ZONEID")
 
 	grid.NewLabel("Auth Key")
-	authWidget = grid.NewEntryLine("CLOUDFLARE_AUTHKEY")
+	authWidget = grid.NewEntryLine("CF_API_KEY")
 
 	grid.NewLabel("Email")
-	emailWidget = grid.NewEntryLine("CLOUDFLARE_EMAIL")
+	emailWidget = grid.NewEntryLine("CF_API_EMAIL")
 
 	var url string = "https://api.cloudflare.com/client/v4/zones/"
 	grid.NewLabel("Cloudflare API")
@@ -161,10 +169,6 @@ func showCloudflareCredentials(box *gui.Node) {
 
 	grid.Pad()
 
-	saveButton = box.NewButton("Save to config", func () {
-	})
-	saveButton.Disable()
-
 	loadButton = box.NewButton("Load Cloudflare DNS zonefile", func () {
 		var domain configT
 		domain.domain = domainWidget.S
diff --git a/examples/cloudflare/structs.go b/examples/cloudflare/structs.go
index af4d7f3..50647d7 100644
--- a/examples/cloudflare/structs.go
+++ b/examples/cloudflare/structs.go
@@ -34,20 +34,23 @@ var zonedrop *gui.Node
 
 // Resource Record (used in a DNS zonefile)
 type RRT struct {
-	typeNode *gui.Node
-	nameNode *gui.Node
-	proxyNode *gui.Node
-	ttlNode *gui.Node
-	valueNode *gui.Node
-	saveNode *gui.Node
+	typeNode *gui.Node	// CNAME, A, AAAA, ...
+	nameNode *gui.Node	// www, mail, ...
+	proxyNode *gui.Node	// If cloudflare is a port 80 & 443 proxy
+	ttlNode *gui.Node	// just set to 1 which means automatic to cloudflare
+	valueNode *gui.Node	// 4.2.2.2, "dkim stuff", etc
+	curlNode *gui.Node	// shows you what you could run via curl
+	resultNode *gui.Node	// what the cloudflare API returned
+	saveNode *gui.Node	// button to send it to cloudflare
 
 	ID     string
 	Type   string
 	Name   string
 	Content string
+	ProxyS string
 	Proxied bool
 	Proxiable bool
-	TTL int
+	Ttl string
 }
 
 /*
diff --git a/main.go b/main.go
index 03479bba..f529539 100644
--- a/main.go
+++ b/main.go
@@ -37,17 +37,17 @@ func init() {
 func watchCallback() {
 	log(logInfo, "watchCallback() START")
 	for {
-		log(logNow, "watchCallback() restarted select for toolkit user events")
+		log(logInfo, "watchCallback() restarted select for toolkit user events")
 	    	select {
 		case a := <-me.guiChan:
 			if (a.ActionType == toolkit.UserQuit) {
-				log(logNow, "doUserEvent() User sent Quit()")
+				log(logInfo, "doUserEvent() User sent Quit()")
 				me.rootNode.doCustom()
 				exit("wit/gui toolkit.UserQuit")
 				break
 			}
 			if (a.ActionType == toolkit.EnableDebug) {
-				log(logNow, "doUserEvent() Enable Debugging Window")
+				log(logInfo, "doUserEvent() Enable Debugging Window")
 				DebugWindow()
 				break
 			}
@@ -56,7 +56,7 @@ func watchCallback() {
 			if (n == nil) {
 				log(logError, "watchCallback() UNKNOWN widget id =", a.WidgetId, a.Name)
 			} else {
-				log(logNow, "watchCallback() FOUND widget id =", n.id, n.Name)
+				log(logInfo, "watchCallback() FOUND widget id =", n.id, n.Name)
 				n.doUserEvent(a)
 			}
 			// this maybe a good idea?
@@ -67,7 +67,7 @@ func watchCallback() {
 }
 
 func (n *Node) doCustom() {
-	log(logNow, "doUserEvent() widget =", n.id, n.Name, n.WidgetType, n.B)
+	log(logInfo, "doUserEvent() widget =", n.id, n.Name, n.WidgetType, n.B)
 	if (n.Custom == nil) {
 		log(debugError, "Custom() = nil. SKIPPING")
 		return
@@ -76,40 +76,40 @@ func (n *Node) doCustom() {
 }
 
 func (n *Node) doUserEvent(a toolkit.Action) {
-	log(logNow, "doUserEvent() node =", n.id, n.Name)
+	log(logInfo, "doUserEvent() node =", n.id, n.Name)
 	switch n.WidgetType {
 	case toolkit.Checkbox:
 		n.B = a.B
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.B)
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.B)
 		n.doCustom()
 	case toolkit.Button:
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "button clicked")
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "button clicked")
 		n.doCustom()
 	case toolkit.Combobox:
 		n.S = a.S
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
 		n.doCustom()
 	case toolkit.Dropdown:
 		n.S = a.S
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
 		n.doCustom()
 	case toolkit.Textbox:
 		n.S = a.S
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.S)
 		n.doCustom()
 	case toolkit.Spinner:
 		n.I = a.I
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.I)
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.I)
 		n.doCustom()
 	case toolkit.Slider:
 		n.I = a.I
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "set to:", n.I)
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "set to:", n.I)
 		n.doCustom()
 	case toolkit.Window:
-		log(logNow, "doUserEvent() node =", n.id, n.Name, "window closed")
+		log(logInfo, "doUserEvent() node =", n.id, n.Name, "window closed")
 		n.doCustom()
 	default:
-		log(logNow, "doUserEvent() type =", n.WidgetType)
+		log(logInfo, "doUserEvent() type =", n.WidgetType)
 	}
 }
 
diff --git a/protobuf/Makefile b/protobuf/Makefile
new file mode 100644
index 0000000..7dd70be
--- /dev/null
+++ b/protobuf/Makefile
@@ -0,0 +1,34 @@
+all:
+	# You must use the current protoc-gen-go
+	# protoc --version 3.6++ does not mean that protoc will generate version3 .go files
+	#
+	# apt remove golang-goprotobuf-dev
+	# apt install protobuf-compiler
+	#
+	# Then:
+	# go get -u github.com/golang/protobuf/protoc-gen-go
+	# cd ~/go/src/github.com/golang/protobuf/protoc-gen-go
+	# go install
+	#
+	# Then:
+	protoc --version
+	make widget.pb.go
+
+clean:
+	rm -f *.pb.go
+
+widget.pb.go: widget.proto
+	protoc --go_out=. widget.proto
+
+compile:
+	protoc --go_out=. *.proto
+
+deps:
+	apt install golang-goprotobuf-dev
+	apt install protobuf-compiler
+
+push:
+	git pull
+	git add --all
+	git commit -a -s
+	git push
diff --git a/protobuf/widget.proto b/protobuf/widget.proto
new file mode 100644
index 0000000..e20354b
--- /dev/null
+++ b/protobuf/widget.proto
@@ -0,0 +1,117 @@
+syntax = "proto3";
+package guiProtobuf;
+
+message Action {
+	WidgetType widgetType	= 1;
+	ActionType actionType	= 2;
+	int64	widgetId	= 3;
+	int64	parentId	= 4;
+	string	text		= 5; // what is visable to the user
+	string	name		= 6; // a name useful for programming
+
+	// This is how the values are passed back and forth
+	// values from things like checkboxes & dropdown's
+	bool	b		= 7;
+	int64	i		= 8;
+	string	s		= 9;
+
+	// This is used for things like a slider(0,100)
+	int64	x		= 10;
+	int64	y		= 11;
+
+	// This is for the grid size & widget position
+	int64	w		= 12;
+	int64	h		= 13;
+	int64	atw		= 14;
+	int64	ath		= 15;
+
+	bool	margin		= 16; // Put space around elements to improve look & feel
+	bool	expand		= 17; // Make widgets fill up the space available
+
+	repeated Response results = 18;
+	repeated Network networks = 19;
+	repeated VM vms = 20;
+
+	enum WidgetType {
+		Unknown	= 0;
+		Root	= 1;	// the master 'root' node of the binary tree
+		Flag	= 2;	// used to send configuration values to plugins
+		Window	= 3;	// in certain gui's (ncurses), these are tabs
+		Tab	= 4;	// internally, this is a window
+		Frame	= 5;	// deprecate?
+		Grid	= 6;	// like drawers in a chest
+		Group	= 7;	// like the 'Appetizers' section on a menu
+		Box	= 8;	// a vertical or horizontal stack of widgets
+		Button	= 9;
+		Checkbox = 10;	// select 'on' or 'off'
+		Dropdown = 11;
+		Combobox  = 12;	// dropdown with edit=true
+		Label = 13;
+		Textbox = 14;	// is this a Label with edit=true
+		Slider = 15;	// like a progress bar
+		Spinner = 16;	// like setting the oven temperature
+		Separator = 17;	// TODO
+		Image = 18;	// TODO
+		Area = 19;	// TODO
+		Form = 20;	// TODO
+		Font = 21;	// TODO
+		Color = 22;	// TODO
+		Dialog = 23;	// TODO
+		Stdout = 24;	// can be used to capture and display log output
+	}
+
+	enum ActionType {
+		Health	= 0;
+		Add	= 1;
+		Delete	= 2;
+		Get	= 3;
+		Set	= 4;
+		GetText	= 5;
+		SetText	= 6;
+		AddText	= 7;
+		Show	= 8;
+		Hide	= 9;
+		Enable	= 10;
+		Disable	= 11;
+		Margin	= 12;
+		Unmargin	= 13;
+		Pad	= 14;
+		Unpad	= 15;
+		Append	= 16;
+		Move	= 17;
+		Dump	= 18;
+		User	= 19; // the user did something (mouse, keyboard, etc)
+		InitToolkit	= 20; // initializes the toolkit
+		CloseToolkit	= 21; // closes the toolkit
+		UserQuit	= 22; // the user closed the GUI
+		EnableDebug	= 23; // open the debugging window
+	}
+
+	message Response {
+		// ActionType type	= 1;
+		int64	id	= 2;
+		string	name	= 3;
+		string	error	= 4;
+		repeated string snippets = 5;
+	}
+
+	message Network {
+		int64	id	= 1;
+		string	name	= 2;
+		int64	total_cpu = 3;
+		int64	total_mem = 4;
+		string	login_url = 5;
+	}
+
+	message VM {
+		int64	id	= 1;
+		string	name	= 2;
+		string	hostname = 3;
+		int64	cpus	= 4;
+		int64	memory	= 5;
+		int64	disk	= 6;
+		string	IPv6	= 7;
+		string	role	= 8;
+		string	baseImage = 9;
+	}
+}
diff --git a/toolkit/andlabs/action.go b/toolkit/andlabs/action.go
index 29b797a..5352fc8 100644
--- a/toolkit/andlabs/action.go
+++ b/toolkit/andlabs/action.go
@@ -38,11 +38,11 @@ func (n *node) enable(b bool) {
 }
 
 func (n *node) pad(at toolkit.ActionType) {
-	log(debugError, "pad()")
+	log(logInfo, "pad() on WidgetId =", n.WidgetId)
 
 	t := n.tk
 	if (t == nil) {
-		log(debugError, "pad() toolkit struct == nil. for", n.WidgetId)
+		log(logError, "pad() toolkit struct == nil. for", n.WidgetId)
 		return
 	}
 
diff --git a/toolkit/andlabs/add.go b/toolkit/andlabs/add.go
index 9963b79..82d962f 100644
--- a/toolkit/andlabs/add.go
+++ b/toolkit/andlabs/add.go
@@ -127,15 +127,16 @@ func (p *node) place(n *node) bool {
 		return true
 	case toolkit.Tab:
 		if (p.tk.uiTab == nil) {
-			log(logError, "p.tk.uiTab == nil", p.tk)
+			log(logError, "p.tk.uiTab == nil for n.WidgetId =", n.WidgetId, "p.tk =", p.tk)
 			panic("p.tk.uiTab == nil")
 		}
 		if (n.tk.uiControl == nil) {
-			log(logError, "n.tk.uiControl == nil", n.tk)
+			log(logError, "n.tk.uiControl == nil for n.WidgetId =", n.WidgetId, "n.tk =", n.tk)
 			panic("n.tk.uiControl == nil")
 		}
-		log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() node=", n.WidgetId, n.Name, n.Text, n.WidgetType)
-		log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() on parent=", p.WidgetId, p.Name, p.Text, p.WidgetType)
+		log(logError, "CHECK LOGIC ON THIS. APPENDING directly into a window without a tab")
+		// log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() node=", n.WidgetId, n.Name, n.Text, n.WidgetType)
+		// log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() on parent=", p.WidgetId, p.Name, p.Text, p.WidgetType)
 		// panic("n.tk.uiControl == nil")
 		p.tk.uiTab.Append(n.Text, n.tk.uiControl)
 		p.tk.boxC += 1
diff --git a/toolkit/andlabs/debug.go b/toolkit/andlabs/debug.go
index 33f8c44..d9f8e58 100644
--- a/toolkit/andlabs/debug.go
+++ b/toolkit/andlabs/debug.go
@@ -14,12 +14,12 @@ var stretchy bool // expand things like buttons to the maximum size
 var padded bool // add space between things like buttons
 var margin bool // add space around the frames of windows
 
-var debugToolkit bool = true
-var debugChange bool = true
-var debugPlugin bool = true
-var debugAction bool = true
-var debugFlags bool = true
-var debugGrid bool = true
+var debugToolkit bool = false
+var debugChange bool = false
+var debugPlugin bool = false
+var debugAction bool = false
+var debugFlags bool = false
+var debugGrid bool = false
 var debugNow bool = true
 var debugError bool = true
 
diff --git a/toolkit/andlabs/main.go b/toolkit/andlabs/main.go
index e2d34a3..3430769 100644
--- a/toolkit/andlabs/main.go
+++ b/toolkit/andlabs/main.go
@@ -40,13 +40,18 @@ func init() {
 	// log(debugToolkit, "init() Setting defaultBehavior = true")
 	setDefaultBehavior(true)
 
+
+	// TODO: this is messed up. run ui.Main() from the first add? Initialize it with an empty thing first?
+	// fake out the OS toolkit by making a fake window. This is probably needed for macos & windows
+	// actually, this probably breaks the macos build
+	go ui.Main(func() {
+		demoUI()
+	})
+
 	// andlabs = make(map[int]*andlabsT)
 	pluginChan = make(chan toolkit.Action, 1)
 
 	log(logNow, "Init() start channel reciever")
-	go ui.Main(func() {
-		demoUI()
-	})
 	go catchActionChannel()
 	log(logNow, "Init() END")
 }
diff --git a/toolkit/andlabs/updateui.go b/toolkit/andlabs/updateui.go
index 4752d67..c43e15f 100644
--- a/toolkit/andlabs/updateui.go
+++ b/toolkit/andlabs/updateui.go
@@ -86,7 +86,8 @@ func demoUI() {
 		messageLabel.SetText("")
 	})
 
-	mainWindow.Show()
+	// this is messed up.
+	// mainWindow.Show()
 }
 
 /*
diff --git a/toolkit/gocui/debug.go b/toolkit/gocui/debug.go
index 7512c1e..2669067 100644
--- a/toolkit/gocui/debug.go
+++ b/toolkit/gocui/debug.go
@@ -45,7 +45,8 @@ func (n *node) showWidgetPlacement(b bool, s string) {
 			s1 += fmt.Sprintf("At(%2d,%2d) ", n.AtW, n.AtH)
 		}
 	}
-	log(b, s1, s, n.WidgetType, ",", n.Name) // , "text=", w.text)
+	tmp := "." + n.Name + "."
+	log(b, s1, s, n.WidgetType, ",", tmp) // , "text=", w.text)
 }
 
 func (n *node) dumpWidget(pad string) {
diff --git a/toolkit/gocui/main.go b/toolkit/gocui/main.go
index 04e1255..11f525a 100644
--- a/toolkit/gocui/main.go
+++ b/toolkit/gocui/main.go
@@ -43,7 +43,7 @@ func catchActionChannel() {
 				log(logError,"ERROR: console did not initialize")
 				continue
 			}
-			log(logNow, "catchActionChannel()", a.WidgetId, a.ActionType, a.WidgetType, a.Name)
+			log(logInfo, "catchActionChannel()", a.WidgetId, a.ActionType, a.WidgetType, a.Name)
 			action(&a)
 		}
 	}
diff --git a/toolkit/gocui/plugin.go b/toolkit/gocui/plugin.go
index eae811d..18cb71f 100644
--- a/toolkit/gocui/plugin.go
+++ b/toolkit/gocui/plugin.go
@@ -51,8 +51,22 @@ func action(a *toolkit.Action) {
 	case toolkit.CloseToolkit:
 		log(logNow, "attempting to close the plugin and release stdout and stderr")
 		standardExit()
+	case toolkit.Enable:
+		if n.Visible() {
+			// widget was already shown
+		} else {
+			log(logInfo, "Setting Visable to true", a.Name)
+			n.SetVisible(true)
+		}
+	case toolkit.Disable:
+		if n.Visible() {
+			log(logInfo, "Setting Visable to false", a.Name)
+			n.SetVisible(false)
+		} else {
+			// widget was already hidden
+		}
 	default:
-		log(logError, "action() Unknown =", a.ActionType, a.WidgetType, a.Name)
+		log(logError, "action() ActionType =", a.ActionType, "WidgetType =", a.WidgetType, "Name =", a.Name)
 	}
 	log(logInfo, "action() END")
 }
@@ -70,16 +84,28 @@ func (n *node) AddText(text string) {
 }
 
 func (n *node) SetText(text string) {
+	var changed bool = false
 	if (n == nil) {
 		log(logNow, "widget is nil")
 		return
 	}
-	n.S = text
-	n.Text = text
+	if (n.Text != text) {
+		n.Text = text
+		changed = true
+	}
+	if (n.S != text) {
+		n.S = text
+		changed = true
+	}
+	if (! changed) {
+		return
+	}
 
-	n.textResize()
-	n.deleteView()
-	n.showView()
+	if (n.Visible()) {
+		n.textResize()
+		n.deleteView()
+		n.showView()
+	}
 }
 
 func (n *node) Set(val any) {
diff --git a/toolkit/gocui/view.go b/toolkit/gocui/view.go
index e69bf7c..28b80e0 100644
--- a/toolkit/gocui/view.go
+++ b/toolkit/gocui/view.go
@@ -20,20 +20,30 @@ func splitLines(s string) []string {
 	return lines
 }
 
-func (n *node) textResize() {
+func (n *node) textResize() bool {
 	w := n.tk
 	var width, height int = 0, 0
+	var changed bool = false
 
 	for i, s := range splitLines(n.Text) {
-		log(logNow, "textResize() len =", len(s), i, s)
+		log(logInfo, "textResize() len =", len(s), i, s)
 		if (width < len(s)) {
 			width = len(s)
 		}
 		height += 1
 	}
-	w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW
-	w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH
-	n.showWidgetPlacement(logNow, "textResize()")
+	if (w.gocuiSize.w1 != w.gocuiSize.w0 + width + me.FramePadW) {
+		w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW
+		changed = true
+	}
+	if (w.gocuiSize.h1 != w.gocuiSize.h0 + height + me.FramePadH) {
+		w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH
+		changed = true
+	}
+	if (changed) {
+		n.showWidgetPlacement(logNow, "textResize() changed")
+	}
+	return changed
 }
 
 func (n *node) hideView() {
@@ -58,17 +68,38 @@ func (n *node) showView() {
 	x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName)
 	log(logInfo, "showView() w.v already defined for widget", n.Name, err)
 
+	// n.smartGocuiSize()
+	changed := n.textResize()
+
+	if (changed) {
+		log(logNow, "showView() textResize() changed. Should recreateView here wId =", w.cuiName)
+	} else {
+		log(logNow, "showView() Clear() and Fprint() here wId =", w.cuiName)
+		w.v.Clear()
+		fmt.Fprint(w.v, n.Text)
+		n.SetVisible(false)
+		n.SetVisible(true)
+		return
+	}
+
 	// if the gocui element has changed where it is supposed to be on the screen
 	// recreate it
-	if (x0 != w.gocuiSize.w0) || (y0 != w.gocuiSize.h0) {
-		log(logError, "showView() w.v.w0 != x0", n.Name, w.gocuiSize.w0, x0)
-		log(logError, "showView() w.v.h0 != y0", n.Name, w.gocuiSize.h0, y0)
+	if (x0 != w.gocuiSize.w0) {
 		n.recreateView()
 		return
 	}
-	if (x1 != w.gocuiSize.w1) || (y1 != w.gocuiSize.h1) {
-		log(logError, "showView() w.v.w1 != x1", n.Name, w.gocuiSize.w1, x1)
-		log(logError, "showView() w.v.h1 != y1", n.Name, w.gocuiSize.h1, y1)
+	if (y0 != w.gocuiSize.h0) {
+		log(logError, "showView() start hight mismatch id=", w.cuiName, "gocui h vs computed h =", w.gocuiSize.h0, y0)
+		n.recreateView()
+		return
+	}
+	if (x1 != w.gocuiSize.w1) {
+		log(logError, "showView() too wide", w.cuiName, "w,w", w.gocuiSize.w1, x1)
+		n.recreateView()
+		return
+	}
+	if (y1 != w.gocuiSize.h1) {
+		log(logError, "showView() too high", w.cuiName, "h,h", w.gocuiSize.h1, y1)
 		n.recreateView()
 		return
 	}