diff --git a/main/app/philbug/app.js b/main/app/philbug/app.js
new file mode 100644
index 0000000000..0f6d738619
--- /dev/null
+++ b/main/app/philbug/app.js
@@ -0,0 +1,44 @@
+
+window.onload = function() {
+ data = {}
+
+ change = function(_data) {
+ for(i in _data) {
+ data[i] = _data[i]
+ }
+ }
+
+ update = function() {
+ for (var i in data) {
+ if ($('#'+i).length < 1) {
+ var e = document.createElement('tr')
+ e.id = i
+ $('#cont tbody').append(e)
+ }
+ $('#'+i).html("
~"+i+" | "+data[i]+" | ")
+ }
+ $('#cont tbody').append([].sort.call($('#cont tr'), function (a, b) {
+ return parseInt(b.childNodes[1].innerText) -
+ parseInt(a.childNodes[1].innerText)
+ }))
+ }
+
+ goof = function(e) {
+ d = $.map($(".sel"), function(el) {return el.id})
+ window.urb.send(d)
+ }
+
+ window.urb.subscribe("frog","goof", function(err,res) {
+ if(err)
+ return console.log('cannot connect to frog/goof')
+ change(res.data)
+ update()
+
+ return true
+ })
+
+ $('#cont').on('click', 'tr', function (e) {
+ if (!e.ctrlKey) { $('.sel').removeClass('sel') }
+ $(this).addClass('sel')
+ })
+}
diff --git a/main/app/philbug/core.hoon b/main/app/philbug/core.hoon
new file mode 100644
index 0000000000..0372e6c5ce
--- /dev/null
+++ b/main/app/philbug/core.hoon
@@ -0,0 +1,101 @@
+!:
+=> |%
+ ++ axle
+ $% [%0 p=(map ,@p ,@ud)]
+ ==
+ ++ gilt
+ $% [%json p=json]
+ [%hymn p=manx]
+ ==
+ ++ gift
+ $% [%rust gilt]
+ [%rasp gilt]
+ ==
+ ++ move ,[p=bone q=[%give p=gift]]
+ --
+|= *
+|_ [hid=hide vat=axle]
+++ incl
+ |= wal=wall
+ %+ turn wal
+ |= tape ;script(type "text/javascript", src +<);
+::
+++ root
+ /(scot %p our.hid)/main/(scot %da lat.hid)/app/[app.hid]
+::
+++ page
+ ^- manx
+ ;html
+ ;head
+ ;title: Foobug!
+ ;style
+ ; .sel {background: lightgray}
+ ; #cont {border-collapse: collapse}
+ ==
+ ;* %- incl :~
+ "//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"
+ ==
+ ;script ;- (trip ;;(,@ .^(%cx (welp root /urb/js))))
+ ==
+ ;script ;- (trip ;;(,@ .^(%cx (welp root /app/js))))
+ ==
+ ==
+ ;body
+ ;p: Hello.
+ ;table#cont ;tbody
+ ;* %+ turn
+ %+ sort (~(tap by p.vat) ~)
+ |= [p=[u=@p n=@ud] q=[u=@p n=@ud]] (gth n.p n.q)
+ |= [u=@p n=@ud]
+ ;tr(id (slag 1 )):(td:(-) td:(-))
+ == ==
+ ;p: Select a ship
+ ;button(onclick "goof()"): Give 5 points
+ ==
+ ==
+::
+++ peer
+ |= [ost=bone you=ship pax=path]
+ ^- [(list move) _+>]
+ ?: =(~ pax)
+ =. p.vat (~(put by p.vat) you (fall (~(get by p.vat) you) _@ud))
+ [[ost %give %rust %hymn page]~ +>]
+ :_ +>
+ ~[(send-vat ost (turn (~(tap by p.vat)) |=([p=@p q=@ud] p)))]
+::
+++ poke-json
+ |= [ost=bone you=ship jon=json]
+ ^- [(list move) _+>]
+ ~& [%poke [%state p.vat] ost you]
+ =+ j=(,[%a p=(list ,[%s p=@t])] jon)
+ =. p.vat
+ %- ~(tur by p.vat)
+ |= [u=@p n=@ud]
+ ?. (lien p.j |=([%s p=@t] =((slav %p (cat 3 '~' p)) u)))
+ n
+ (add 5 n)
+ :_ +>+
+ :- [ost %give %rasp %json jon]
+ %+ turn
+ ^- (list bone)
+ %+ ~(rep by sup.hid) *(list bone)
+ |= [p=[p=bone q=[ship path]] q=(list bone)] ^- (list bone)
+ ?. =(/goof +.q.p) q
+ [p.p q]
+ |= o=bone
+ %+ send-vat o
+ %+ turn p.j
+ |= [%s p=@t]
+ (slav %p (cat 3 '~' p))
+++ send-vat
+ |= [o=bone l=(list ,@p)]
+ :* o %give %rust %json %o
+ ^- (map ,@t jval)
+ %- mo
+ %+ turn l
+ |= p=@p
+ :- (rsh 3 1 (scot %p p)) :- %n
+ %^ rsh 3 2
+ (scot %ui (fall (~(get by p.vat) p) _@ud))
+ ==
+--
diff --git a/main/app/philbug/urb.js b/main/app/philbug/urb.js
new file mode 100644
index 0000000000..3352a9e6f7
--- /dev/null
+++ b/main/app/philbug/urb.js
@@ -0,0 +1,111 @@
+window.urb = {
+ ship: ship,
+ port: port,
+ auto: auto,
+ oryx: oryx,
+ user: user,
+ appn: "foobug",
+ seqn: 0,
+ seqp: 1,
+ dely: 0,
+
+ req: function(method,url,data,json,cb) {
+ var xhr = new XMLHttpRequest()
+ xhr.open(method.toUpperCase(), url)
+ if(json)
+ xhr.setRequestHeader("content-type", "text/json")
+ if(data)
+ xhr.send(JSON.stringify(data))
+ else
+ xhr.send()
+ if(cb) {
+ xhr.onload = function() {
+ cb(null,{
+ "status":this.status,
+ "data":JSON.parse(this.responseText)
+ })
+ }
+ xhr.onerror = function() {
+ cb({
+ "status":this.status,
+ "data":this.responseText
+ })
+ }
+ }
+ },
+
+ subscribe: function(stream,path,cb) {
+ if(!cb)
+ throw new Error("You must supply a callback to urb.subscribe.")
+
+ var method, perm, url, $this
+
+ method = "post"
+ perm = "pis"
+ url = [this.ship,perm,this.user,this.appn,this.port]
+ if(stream) {
+ url.push(stream)
+ if(path)
+ url.push(path)
+ }
+ url = "/"+url.join("/")
+
+
+ $this = this
+ this.req(method,url,{},true,function(err,data) {
+ cb.apply(this,arguments)
+ if(!err) { $this.poll(stream,cb); }
+ })
+ },
+
+ send: function(data,cb) {
+ if(!data) { data = {}; }
+ if(!cb) { cb = function() {}; }
+
+ var method, perm, url, $this
+
+ method = "post"
+ perm = "pim"
+ url = [this.ship,perm,this.user,this.appn,this.port,this.seqn]
+ url = "/"+url.join("/")
+
+ this.seqn++
+
+ $this = this
+ this.req(method,url,data,true,function(err,data) {
+ if(err) { $this.seqn--; }
+ cb.apply(this,arguments)
+ })
+ },
+
+ poll: function(stream,cb) {
+ if(!stream)
+ throw new Error("You must supply a stream to urb.poll.")
+ if(!cb)
+ throw new Error("You must supply a callback to urb.poll.")
+
+ var method, perm, url, $this
+
+ method = "get"
+ perm = "gie"
+ if(!stream) { return false; }
+ url = [this.ship,perm,this.user,this.appn,this.port,stream,this.seqp]
+ url = "/"+url.join("/")
+
+ $this = this
+ this.req(method,url,null,false,function(err,data) {
+ if(cb.apply(this,arguments) === false) { return; }
+
+ if(err)
+ $this.dely += 1000
+ else {
+ $this.dely = 0
+ $this.seqp++
+ }
+
+ setTimeout(function() {
+ $this.poll(stream,cb)
+ },$this.dely)
+ })
+ }
+}