From a86b7ccc43c76725fa59bbcb5cd921c9385674d6 Mon Sep 17 00:00:00 2001 From: Anton Dyudin Date: Mon, 1 Feb 2016 15:15:10 -0800 Subject: [PATCH 1/9] preliminary refactoring: horns producing cages instead of vases --- arvo/ford.hoon | 66 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/arvo/ford.hoon b/arvo/ford.hoon index 1e80944acf..0800fa1646 100644 --- a/arvo/ford.hoon +++ b/arvo/ford.hoon @@ -1343,7 +1343,7 @@ %+ cope (maim cof pit able) |= [cof=cafe bax=vase] %+ cope (chap cof bax [%fan fan.hyd]) - |= [cof=cafe gox=vase] + |= [cof=cafe mar=mark gox=vase] %+ cope (maim cof (slop gox bax) [%tssg (flop boy)]) |= [cof=cafe fin=vase] (fine cof fin) @@ -1384,7 +1384,7 @@ %+ cope $(poy t.poy) |= [cof=cafe nex=(list (pair ,@ vase))] %+ cope (chap(s.how [p.i.poy s.how]) cof bax hon) - (flux |=(elt=vase [[q.i.poy elt] nex])) + (flux |=([mar=mark elt=vase] [[q.i.poy elt] nex])) %- flux |= yal=(list (pair ,@ vase)) ^- vase ?~ yal [[%cube 0 [%atom %n]] 0] @@ -1402,40 +1402,42 @@ (flue cof) (cope nod (flux some)) %- flux - |= doy=(map ,@ vase) ^- vase + |= doy=(map ,@ cage) ^- vase ?~ doy [[%cube 0 [%atom %n]] 0] %+ slop - (slop [[%atom %ta] p.n.doy] q.n.doy) + (slop [[%atom %ta] p.n.doy] q.q.n.doy) (slop $(doy l.doy) $(doy r.doy)) :: ++ chap :: produce resources |= [cof=cafe bax=vase hon=horn] - ^- (bolt vase) + ^- (bolt cage) ?- -.hon - %ape (maim cof bax p.hon) + %ape (cope (maim cof bax p.hon) (flux |=(a=vase [%noun a]))) %arg %+ cope (maim cof bax p.hon) |= [cof=cafe gat=vase] %+ cope (maim cof !>(~) ((jock |) arg)) |= [cof=cafe val=vase] - (maul cof gat (slop !>(how) val)) + %+ cope (maul cof gat (slop !>(how) val)) + (flux |=(a=vase noun/a)) :: %alt %. cof - |= cof=cafe ^- (bolt vase) + |= cof=cafe ^- (bolt cage) ?~ p.hon (flaw cof leaf/"ford: out of options" ~) (coop ^$(cof cof, hon i.p.hon) ..$(p.hon t.p.hon)) :: %dub %+ cope $(hon q.hon) - (flux |=(vax=vase [[%face p.hon p.vax] q.vax])) + (flux |=([mar=mark vax=vase] [mar [%face p.hon p.vax] q.vax])) :: %fan + %- cope :_ (flux |=(a=vase noun/a)) %+ cope |- ^- (bolt (list vase)) ?~ p.hon (flue cof) %+ cope ^$(cof cof, hon i.p.hon) - |= [cof=cafe vax=vase] + |= [cof=cafe mar=mark vax=vase] %+ cope ^$(cof cof, p.hon t.p.hon) (flux |=(tev=(list vase) [vax tev])) |= [cof=cafe tev=(list vase)] @@ -1446,15 +1448,19 @@ :: %for =+ opt=|.(>(turn p.hon |=([a=path ^] a))<) - |- ^- (bolt vase) + |- ^- (bolt cage) ?~ p.hon (flaw cof leaf/"ford: no match" >(tope how)< *opt ~) ?: =(p.i.p.hon (scag (lent p.i.p.hon) (flop s.how))) ^$(hon q.i.p.hon) $(p.hon t.p.hon) :: %hel $(hon p.hon, lit |) - %hub (chad cof bax %ud p.hon) + %hub + %+ cope (chad cof bax %ud p.hon) + (flux |=(a=vase noun/a)) + :: %man + %- cope :_ (flux |=(a=vase noun/a)) |- ^- (bolt vase) ?~ p.hon (fine cof [[%cube 0 [%atom %n]] 0]) %+ cope $(p.hon l.p.hon) @@ -1462,42 +1468,50 @@ %+ cope ^$(cof cof, p.hon r.p.hon) |= [cof=cafe rig=vase] %+ cope ^^^$(cof cof, hon q.n.p.hon) - |= [cof=cafe vax=vase] + |= [cof=cafe mar=mark vax=vase] %+ fine cof %+ slop (slop [[%atom %tas] p.n.p.hon] vax) (slop lef rig) :: - %now (chad cof bax %da p.hon) - %nap (chai cof bax p.hon) + %now + %+ cope (chad cof bax %da p.hon) + (flux |=(a=vase noun/a)) + :: + %nap + %+ cope (chai cof bax p.hon) + (flux |=(a=vase noun/a)) + :: + %saw + %+ cope $(hon q.hon) + |= [cof=cafe mar=mark sam=vase] + %+ cope (maim cof bax p.hon) + |= [cof=cafe gat=vase] + %+ cope (maul cof gat sam) + (flux |=(a=vase noun/a)) + :: %see =. r.p.hon ?:(?=([%ud 0] r.p.hon) r.how r.p.hon) $(hon q.hon, how p.hon) - :: - %saw - %+ cope $(hon q.hon) - |= [cof=cafe sam=vase] - %+ cope (maim cof bax p.hon) - |= [cof=cafe gat=vase] - (maul cof gat sam) :: %sic %+ cope $(hon q.hon) - |= [cof=cafe vax=vase] + |= [cof=cafe mar=mark vax=vase] %+ cope (maim cof bax [%bctr p.hon]) |= [cof=cafe tug=vase] ?. (~(nest ut p.tug) | p.vax) (flaw cof [%leaf "type error: {} {}"]~) - (fine cof [p.tug q.vax]) + (fine cof [mar p.tug q.vax]) :: %toy ?: p.hon =. arg ?.(lit arg many/~) - (cope (cope (make cof %bake q.hon arg how) furl) feel) + (cope (make cof %bake q.hon arg how) furl) %+ cool |.(leaf/"ford: hook {} {<(tope how)>}") %+ cope (fade cof %hoon how) |= [cof=cafe hyd=hood] - (cope (abut:(meow how arg) cof hyd) (lake q.hon)) + %+ cope (abut:(meow how arg) cof hyd) + ;~(cope (lake q.hon) (flux |=(a=vase [q.hon a]))) == :: ++ head :: consume structures From a2081a7482860f3408a0f78c14358d068278651d Mon Sep 17 00:00:00 2001 From: Anton Dyudin Date: Mon, 1 Feb 2016 15:54:16 -0800 Subject: [PATCH 2/9] transfered /@ to "include by date", /& now "cast to mark" --- arvo/ford.hoon | 20 +++++++++++++++----- arvo/zuse.hoon | 5 ++--- mar/tree/elem.hoon | 8 ++++++++ ren/paste-new.hoon | 2 +- ren/talklog/hymn.hoon | 2 +- ren/tree/combine.hoon | 8 ++++---- ren/tree/elem.hoon | 5 +++++ ren/tree/include.hoon | 2 +- ren/{ => tree}/index.hoon | 6 +++--- web/paste.hoon | 2 +- 10 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 mar/tree/elem.hoon create mode 100644 ren/tree/elem.hoon rename ren/{ => tree}/index.hoon (57%) diff --git a/arvo/ford.hoon b/arvo/ford.hoon index 0800fa1646..09ddae5bbc 100644 --- a/arvo/ford.hoon +++ b/arvo/ford.hoon @@ -677,10 +677,10 @@ (stag %fan ;~(pfix dot fan:read)) (stag %for ;~(pfix com for:read)) (stag %hel ;~(pfix cen day:read)) - (stag %hub ;~(pfix pat day:read)) + (stag %lin ;~(pfix pam lin:read)) (stag %man ;~(pfix tar man:read)) (stag %nap ;~(pfix cab day:read)) - (stag %now ;~(pfix pam day:read)) + (stag %now ;~(pfix pat day:read)) (stag %saw ;~(pfix sem saw:read)) (stag %see ;~(pfix col see:read)) (stag %sic ;~(pfix ket sic:read)) @@ -721,6 +721,12 @@ %+ rail fail =- ;~(sfix (star -) gap duz) ;~(pfix gap fas ;~(plug hath day)) + :: + ++ lin + %+ rail + ;~(plug (plus ;~(sfix sym pam)) day) + =+ (cook |=(a=term [a ~]) sym) + ;~(pfix gap ;~(plug - day)) :: ++ man %+ rail fail @@ -1455,9 +1461,13 @@ $(p.hon t.p.hon) :: %hel $(hon p.hon, lit |) - %hub - %+ cope (chad cof bax %ud p.hon) - (flux |=(a=vase noun/a)) + %lin + %+ cope $(hon q.hon) + |= [cof=cafe cay=cage] ^- (bolt cage) + ?~ p.hon (fine cof cay) + %+ cope $(p.hon t.p.hon) + |= [cof=cafe cay=cage] + (cope (make cof %cast i.p.hon `cay) furl) :: %man %- cope :_ (flux |=(a=vase noun/a)) diff --git a/arvo/zuse.hoon b/arvo/zuse.hoon index ee8dec46b6..c7352b1585 100644 --- a/arvo/zuse.hoon +++ b/arvo/zuse.hoon @@ -2493,15 +2493,14 @@ $% [%ape p=twig] :: /~ twig by hand [%arg p=twig] :: /$ argument [%alt p=(list horn)] :: /| options - :: [%day p=horn] :: list by @dr [%dub p=term q=horn] :: /= apply face [%fan p=(list horn)] :: /. list [%for p=(list (pair path:spur horn))] :: /, switch by path [%hel p=horn] :: /% propagate args - [%hub p=horn] :: /@ list by @ud + [%lin p=(list mark) q=horn] :: /& translates [%man p=(map span horn)] :: /* hetero map [%nap p=horn] :: /_ homo map - [%now p=horn] :: /& list by @da + [%now p=horn] :: /@ list by @da [%saw p=twig q=horn] :: /; operate on [%see p=beam q=horn] :: /: relative to [%sic p=tile q=horn] :: /^ cast diff --git a/mar/tree/elem.hoon b/mar/tree/elem.hoon new file mode 100644 index 0000000000..326ab130a7 --- /dev/null +++ b/mar/tree/elem.hoon @@ -0,0 +1,8 @@ +:: +:::: /hoon/core/elem/mar + :: +/? 314 +|_ own=manx +:: +++ grow |% ++ elem own :: alias +-- -- diff --git a/ren/paste-new.hoon b/ren/paste-new.hoon index d81f0e6a03..58983c56d9 100644 --- a/ren/paste-new.hoon +++ b/ren/paste-new.hoon @@ -1,3 +1,3 @@ -/= all /; flop /^ (list (pair time ,*)) /& /mime/ +/= all /; flop /^ (list (pair time ,*)) /@ /mime/ ^- json ?~(all ~ (joba %u s/(scot %da p.i.all))) diff --git a/ren/talklog/hymn.hoon b/ren/talklog/hymn.hoon index b8da3a42af..464a1cea0e 100644 --- a/ren/talklog/hymn.hoon +++ b/ren/talklog/hymn.hoon @@ -6,7 +6,7 @@ /= mez /; pojo /; |=(a=(list ,[@ p=json]) =.(a (flop a) ?~(a [%a ~] p.i.a))) - /& /json/ + /@ /json/ |% ++ cdnj |=(a=tape ;script(src "//cdnjs.cloudflare.com/ajax/libs/{a}");) -- diff --git a/ren/tree/combine.hoon b/ren/tree/combine.hoon index 269eb140eb..2a0582ad57 100644 --- a/ren/tree/combine.hoon +++ b/ren/tree/combine.hoon @@ -1,9 +1,9 @@ /+ tree, react /= mime /mime/ -/= body /elem/ -/= snip /snip/ -/= meta /front/ -/= sect /index/ +/= body /tree-elem/ +/= sect /tree-index/ +/= snip /&snip&elem&/tree-elem/ +/= meta /&front&elem&/tree-elem/ !: ^- tree-include =+ rj=react-to-json:react diff --git a/ren/tree/elem.hoon b/ren/tree/elem.hoon new file mode 100644 index 0000000000..cbe71140e9 --- /dev/null +++ b/ren/tree/elem.hoon @@ -0,0 +1,5 @@ +/% /, + /web/modules /|(/!elem/ /elem/) + / /elem/ + == +`manx`-.- diff --git a/ren/tree/include.hoon b/ren/tree/include.hoon index d1355b133c..1634a765a6 100644 --- a/ren/tree/include.hoon +++ b/ren/tree/include.hoon @@ -1,5 +1,5 @@ /- tree-include /| /tree-combine/ - /:/===/web/404:/tree-combine/ + /:/===/web/404:/tree-combine/ :: XX merge into tree-elem? == `tree-include`-< diff --git a/ren/index.hoon b/ren/tree/index.hoon similarity index 57% rename from ren/index.hoon rename to ren/tree/index.hoon index 5913615152..0ffc3dc6f3 100644 --- a/ren/index.hoon +++ b/ren/tree/index.hoon @@ -1,12 +1,12 @@ /+ tree /, / - /; (getall:tree /h1/h2/h3/h4/h5/h6) /elem/ + /; (getall:tree /h1/h2/h3/h4/h5/h6) /tree-elem/ :: /pub/docs/dev/hoon/runes /; |= [tip=marl sub=(map span marl) ~] (zing `(list marl)`[tip (turn (~(tap by sub)) tail)]) - /. /; (getall:tree %h1 ~) /elem/ - /_ /; (getall:tree %h1 ~) /elem/ + /. /; (getall:tree %h1 ~) /tree-elem/ + /_ /; (getall:tree %h1 ~) /tree-elem/ == == :: :::: diff --git a/web/paste.hoon b/web/paste.hoon index 099946bc7c..8d99b09363 100644 --- a/web/paste.hoon +++ b/web/paste.hoon @@ -1,4 +1,4 @@ -/= all /; flop /^ (list (pair time ,*)) /& /mime/ +/= all /; flop /^ (list (pair time ,*)) /@ /mime/ ;html ;head:title:"Pastebin" ;body From bf5e3cc73cb632f647cffe10f47333f82b8434be Mon Sep 17 00:00:00 2001 From: Anton Dyudin Date: Mon, 1 Feb 2016 16:54:45 -0800 Subject: [PATCH 3/9] default to elem.hook treatment --- ren/tree/elem.hoon | 4 ++-- ren/urb.hoon | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ren/tree/elem.hoon b/ren/tree/elem.hoon index cbe71140e9..486ff225ff 100644 --- a/ren/tree/elem.hoon +++ b/ren/tree/elem.hoon @@ -1,5 +1,5 @@ /% /, - /web/modules /|(/!elem/ /elem/) - / /elem/ + /web /|(/!elem/ /elem/) + / /elem/ == `manx`-.- diff --git a/ren/urb.hoon b/ren/urb.hoon index 6a0d2c2ec8..dd629dbeff 100644 --- a/ren/urb.hoon +++ b/ren/urb.hoon @@ -1,6 +1,6 @@ /% /, - /talk /talklog-hymn/ - /web /|(/!hymn/ /tree-hymn/) + /talk/log /talklog-hymn/ + /web/app /|(/!hymn/ /tree-hymn/) / /tree-hymn/ == `manx`-.- From 8e87106369bd1fb5dd7745c2a8774a3464030fa1 Mon Sep 17 00:00:00 2001 From: Anton Dyudin Date: Mon, 1 Feb 2016 17:44:11 -0800 Subject: [PATCH 4/9] moved sole protocol coffee to separate repository https://github.com/urbit/sole@e2741c0 --- web/{ => app}/dojo.hoon | 4 +- web/dojo/main.coffee | 306 ---------------- web/dojo/share.coffee | 83 ----- web/lib/js/sole.js | 756 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 757 insertions(+), 392 deletions(-) rename web/{ => app}/dojo.hoon (78%) delete mode 100644 web/dojo/main.coffee delete mode 100644 web/dojo/share.coffee create mode 100644 web/lib/js/sole.js diff --git a/web/dojo.hoon b/web/app/dojo.hoon similarity index 78% rename from web/dojo.hoon rename to web/app/dojo.hoon index ae6bb58f03..5eba7b5bdb 100644 --- a/web/dojo.hoon +++ b/web/app/dojo.hoon @@ -32,8 +32,6 @@ ;body ;div#err; ;div#term:"" - ;script@"dojo/share.coffee"(type "text/coffeescript"); - ;script@"dojo/main.coffee"(type "text/coffeescript"); - ;+ (cdnj "coffee-script/1.7.1/coffee-script.min.js") + ;script@"/lib/js/sole.js"; == == diff --git a/web/dojo/main.coffee b/web/dojo/main.coffee deleted file mode 100644 index e133af4bc0..0000000000 --- a/web/dojo/main.coffee +++ /dev/null @@ -1,306 +0,0 @@ -[DOM,recl,rend] = [React.DOM, React.createClass, React.renderComponent] -[div, pre, span] = [DOM.div, DOM.pre, DOM.span] -str = JSON.stringify - -Prompt = recl render: -> - [pro,cur,buf] = [@props.prompt[@props.appl] ? "X", @props.cursor, @props.input + " "] - pre {}, @props.appl+pro, - span {style: background: 'lightgray'}, buf.slice(0,cur), "\u0332", buf.slice(cur) - -Matr = recl render: -> - lines = @props.rows.map (lin)-> pre {}, lin, " " - lines.push Prompt - appl: @props.appl, - prompt: @props.prompt, - input: @props.input, - cursor: @props.cursor - div {}, lines - -$ -> - - met = $('
').text('m').css(display: 'none').appendTo(term).width()
-  subs = ""
-  # $(window).resize -> 
-  #   window.termWif = ($(term).width() / met).toFixed()
-  #   path =  "/new/#{termWif}"
-  #   if path is subs
-  #     return
-  #   if subs
-  #     urb.unsubscribe path: subs
-  #   subs = path
-  #   urb.subscribe {path}, (err,dat)->
-  #       if err or dat.data.ok
-  #         return;
-  #       syncRev = dat.data.rev
-  #       unless termRev > syncRev
-  #         termRev = syncRev
-  #         matr.setProps rows: dat.data.stak
-  #         document.title = "Matrix"  # XX  debug
-  # $(window).resize()
-
-  flash = ($el, background)->
-    $el.css {background}
-    if background
-      setTimeout (-> flash $el,''), 50
-  bell = -> flash ($ 'body'), 'black'
-
-  matr = rend (Matr
-    rows:[]
-    appl:""
-    prompt:{"": "# "}
-    input:""
-    cursor:0
-    history:[]
-    offset:0  ), term
-  window.matr = matr
-  update = (a) -> matr.setProps a
-  buffer = "": new Share ""
-  window.buffer = buffer
-  choose = (appl)->
-    urb.appl = appl
-    buffer[appl] ?= new Share ""
-    updPrompt '', null
-    update {appl, cursor: 0, input: buffer[appl].buf}
-  print = (txt)-> update rows: [matr.props.rows..., txt]
-  sync = (ted,app)->
-    app ?= matr.props.appl
-    if app isnt matr.props.appl then return
-    b = buffer[app]
-    update input: b.buf, cursor: b.transpose ted, matr.props.cursor
-  updPrompt = (app,pro) ->
-    prompt = $.extend {}, matr.props.prompt
-    if pro? then prompt[app] = pro else delete prompt[app]
-    update {prompt}
-  sysStatus = ()-> updPrompt '', (
-    [app,pro] = [matr.props.appl, (k for k,v of matr.props.prompt when k isnt '')]
-    if app is '' then (pro.join ', ')+'# ' else null
-  )
- 
-  peer = (ruh,app) ->
-    app ?= urb.appl
-    if ruh.map then return ruh.map (rul)-> peer rul, app
-    mapr = matr.props
-    switch Object.keys(ruh)[0]
-      when 'txt' then print ruh.txt
-      when 'tan' then ruh.tan.split("\n").map print
-      when 'pro' then updPrompt app, ruh.pro.cad
-      when 'hop' then update cursor: ruh.hop; bell() # XX buffer.transpose?
-      when 'blk' then console.log "Stub #{str ruh}"
-      when 'det' then buffer[app].receive ruh.det; sync ruh.det.ted, app
-      when 'act' then switch ruh.act
-        when 'clr' then update rows:[]
-        when 'bel' then bell()
-        when 'nex' then update
-          input: ""
-          cursor: 0
-          history: 
-            if !mapr.input then mapr.history
-            else [mapr.input, mapr.history...]
-          offset: 0
-      #   else throw "Unknown "+(JSON.stringify ruh)
-      else v = Object.keys(ruh); console.log v, ruh[v[0]]
-
-  join = (app)->
-    if matr.props.prompt[app]?
-      return print '# already-joined: '+app
-    choose app
-    urb.bind "/sole", {wire:"/"}, (err,d)->
-      if err then console.log err
-      else if d.data then peer d.data, app
-  cycle = ()->
-    apps = Object.keys matr.props.prompt
-    if apps.length < 2 then return
-    choose apps[1 + apps.indexOf urb.appl] ? apps[0]
-  part = (appl)->
-    mapr = matr.props
-    unless mapr.prompt[appl]?
-      return print '# not-joined: '+appl
-    urb.drop "/sole", {appl, wire: "/"}
-    if appl is mapr.appl then cycle()
-    updPrompt appl, null
-    sysStatus()
-  join urb.appl
-  window.join = join; window.part = part
-  
-  pressed = []
-  deltim = null
-  #later = (data)->
-  #  if data
-  #    pressed.push data
-  #  clearTimeout deltim
-  #  setTimeout (->
-  #    if urb.reqq.length > 0 
-  #      return deltim = later()
-  #    urb.send data: pressed
-  #    pressed = []
-  #  ), 500
-
-  urb.send.mark = 'sole-action'
-  sendAction = (data)->
-    if matr.props.appl then urb.send data, (e,res)->
-      if res.status isnt 200 then $('#err')[0].innerText = res.data.mess
-    else if data is 'ret'
-      app = /^[a-z-]+$/.exec(buffer[""].buf.slice(1))
-      unless app? and app[0]?
-        return bell()
-      else switch buffer[""].buf[0]
-        when '+' then doEdit set: ""; join app[0]
-        when '-' then doEdit set: ""; part app[0]
-        else bell()
-  
-  doEdit = (ted)->
-    det = buffer[matr.props.appl].transmit ted
-    sync ted
-    sendAction {det}
-
-  yank = ''
-  eatKyev= (mod, key)->
-    mapr = matr.props
-    switch mod.sort().join '-'
-      when '', 'shift'
-        if key.str
-          doEdit ins: cha: key.str, at: mapr.cursor
-          update cursor: mapr.cursor+1
-        switch key.act
-          when 'entr' then sendAction 'ret'
-          when 'up'
-            history = mapr.history.slice(); offset = mapr.offset
-            if history[offset] == undefined
-              return
-            [input, history[offset]] = [history[offset], mapr.input]
-            offset++
-            doEdit set: input
-            update {offset, history, cursor: input.length}
-          when 'down'
-            history = mapr.history.slice(); offset = mapr.offset
-            offset--
-            if history[offset] == undefined
-              return
-            [input, history[offset]] = [history[offset], mapr.input]
-            doEdit set: input
-            update {offset, history, cursor: input.length}
-          when 'left' then if mapr.cursor > 0 
-            update cursor: mapr.cursor-1
-          when 'right' then if mapr.cursor < mapr.input.length
-            update cursor: mapr.cursor+1
-          when 'baxp' then if mapr.cursor > 0
-            doEdit del: mapr.cursor-1
-          #else (if key.act then console.log key.act)
-      when 'ctrl' then switch key.str || key.act
-        when 'a','left'  then update cursor: 0
-        when 'e','right' then update cursor: mapr.input.length
-        when 'l' then update rows: []
-        when 'entr' then bell()
-        when 'w' then eatKyev ['alt'], act:'baxp'
-        when 'p' then eatKyev [], act: 'up'
-        when 'n' then eatKyev [], act: 'down'
-        when 'b' then eatKyev [], act: 'left'
-        when 'f' then eatKyev [], act: 'right'
-        when 'g' then bell()
-        when 'x' then cycle()
-        when 'v'
-          appl = if mapr.appl isnt '' then '' else urb.appl
-          update {appl, cursor:0, input:buffer[appl].buf}
-          sysStatus()
-        when 't'
-          if mapr.cursor is 0 or mapr.input.length < 2
-            return bell()
-          cursor = mapr.cursor
-          if cursor < mapr.input.length
-            cursor++
-          doEdit [{del:cursor-1},ins:{at:cursor-2,cha:mapr.input[cursor-1]}]
-          update {cursor}
-        when 'u' 
-          yank = mapr.input.slice(0,mapr.cursor)
-          doEdit (del:mapr.cursor - n for n in [1..mapr.cursor])
-        when 'k'
-          yank = mapr.input.slice(mapr.cursor)
-          doEdit (del:mapr.cursor for _ in [mapr.cursor...mapr.input.length])
-        when 'y'
-          doEdit (ins: {cha, at: mapr.cursor + n} for cha,n in yank)
-        else console.log mod, str key
-      when 'alt' then switch key.str || key.act
-        when 'f','right'
-          rest = mapr.input.slice(mapr.cursor)
-          rest = rest.match(/\W*\w*/)[0] # XX unicode
-          update cursor: mapr.cursor + rest.length
-        when 'b','left'
-          prev = mapr.input.slice(0,mapr.cursor)
-          prev = prev.split('').reverse().join('')  # XX
-          prev = prev.match(/\W*\w*/)[0] # XX unicode
-          update cursor: mapr.cursor - prev.length
-        when 'baxp'
-          prev = mapr.input.slice(0,mapr.cursor)
-          prev = prev.split('').reverse().join('')  # XX
-          prev = prev.match(/\W*\w*/)[0] # XX unicode
-          yank = prev
-          doEdit (del: mapr.cursor-1 - n for _,n in prev)
-      else console.log mod, str key
-
-  Mousetrap.handleKey = (char, mod, e)->
-    norm = {
-      capslock:  'caps'
-      pageup:    'pgup'
-      pagedown:  'pgdn'
-      backspace: 'baxp'
-      enter:     'entr'
-    }
-
-    key = switch
-      when char.length is 1
-        if e.type is 'keypress'
-          chac = char.charCodeAt(0)
-          if chac < 32          # normalize ctrl keys
-            char = String.fromCharCode chac | 96
-          str: char
-      when e.type is 'keydown'
-        if char isnt 'space'
-          act: norm[char] ? char
-      when e.type is 'keyup' and norm[key] is 'caps'
-        act: 'uncap'
-    if !key then return
-    if key.act and key.act in mod
-      return
-    e.preventDefault()
-    #[fore, aft] = (
-    #  [sli,cur] = [mapr.input.slice, mapr.cursor]
-    #  [sli(0, cur), sli(cur)]
-    #)
-    eatKyev mod, key
-
-    #amod = (arr)->
-    #  for i in arr
-    #    unless mod.indexOf(i) < 0
-    #      return yes
-    #  no
-    # if key.str or key.act is 'baxp' or key.act is 'entr'
-    #   termRev++
-    #   [bot, rest...] = old = matr.props.rows
-    #   matr.setProps rows:(
-    #     switch  key.act 
-    #       when 'baxp'
-    #         if amod ['ctrl', 'meta']
-    #           ['', rest...]
-    #         else if amod ['alt']
-    #           [(bot.replace /\ *[^ ]*$/, ''), rest...]
-    #         else if bot and bot.length 
-    #           [bot.slice(0, -1), rest...]
-    #         else if rest[0] and rest[0].length
-    #           res = rest.slice()
-    #           res[0] = res[0].slice(0, -1)
-    #           res
-    #         else rest
-    #       when 'entr'
-    #         ['', old...]
-    #       when undefined
-    #         if mod.length > 1 or (mod.length and !amod ['shift'])
-    #           old
-    #         else unless old and bot isnt null
-    #           [key.str]
-    #         #else if bot.length is termWif
-    #         #  [key.str, old...]
-    #         else [bot + key.str, rest...]
-    #   )
-    #   document.title = "Matri"  # XX  debug
-    # later {mod, key}
-
diff --git a/web/dojo/share.coffee b/web/dojo/share.coffee
deleted file mode 100644
index 67679b4fe1..0000000000
--- a/web/dojo/share.coffee
+++ /dev/null
@@ -1,83 +0,0 @@
-# See /hook/core/sole/lib
-str = JSON.stringify
-clog = (a)-> console.log a
-class window.Share
-  constructor: (@buf = "", @ven = [0, 0], @leg = []) ->
-  #
-  abet: -> buf:@buf, leg:@leg.slice(), ven:@ven.slice()
-  apply: (ted)->
-    switch
-      when 'nop' == ted then return
-      when ted.map then ted.map @apply, @
-      else switch Object.keys(ted)[0]
-        when 'set' then @buf = ted.set
-        when 'del' then @buf = @buf.slice(0,ted.del) + @buf.slice(ted.del + 1)
-        when 'ins'
-          {at,cha} = ted.ins
-          @buf = @buf.slice(0,at) + cha + @buf.slice(at)
-        else throw "%sole-edit -lost.#{str ted}"
-  #
-  transmute: (sin,dex)->
-    switch
-      when sin == 'nop' or dex == 'nop' then dex
-      when sin.reduce
-        sin.reduce ((dex,syn) => @transmute(syn,dex)), dex
-      when dex.map then dex.map (dax) => @transmute(sin,dax)
-      when dex.set != undefined then dex
-      else switch Object.keys(sin)[0]
-        when 'set' then 'nop'
-        when 'del'
-          if sin.del is dex.del then return 'nop'
-          dex = $.extend true, {}, dex  # clone
-          switch Object.keys(dex)[0]
-            when 'del' then if sin.del < dex.del    then dex.del--
-            when 'ins' then if sin.del < dex.ins.at then dex.ins.at--
-          return dex
-        when 'ins'
-          dex = $.extend true, {}, dex  # clone
-          {at,cha} = sin.ins
-          switch Object.keys(dex)[0]
-            when 'del' then if at < dex.del then dex.del++
-            when 'ins' then if at < dex.ins.at or
-                              (at == dex.ins.at and !(cha <= dex.ins.cha))
-                dex.ins.at++
-          return dex
-        else throw "%sole-edit -lost.#{str sin}"
-  #
-  commit: (ted)->
-    @ven[0]++
-    @leg.push ted
-    @apply ted
-  #
-  inverse: (ted)->
-    switch
-      when 'nop' == ted then ted
-      when ted.map
-        ted.map( (tad)=> res=@inverse tad; @apply tad; res).reverse()
-      else switch Object.keys(ted)[0]
-        when 'set' then set: @buf
-        when 'ins' then del: ted.ins
-        when 'del' then ins: at: ted.del, cha: @buf[ted.del]
-        else throw "%sole-edit -lost.#{str ted}"
-  #
-  receive: ({ler,ted})->
-    if !(ler[1] is @ven[1]) 
-      throw "-out-of-sync.[#{str ler} #{str @ven}]"
-    @leg = @leg.slice @leg.length + ler[0] - @ven[0] 
-    dat = @transmute @leg, ted
-    @ven[1]++; @apply dat; dat
-  #
-  remit: -> throw 'stub'
-  transmit: (ted)->
-    act = {ted, ler:[@ven[1], @ven[0]]}
-    @commit ted
-    return act
-  #
-  transceive: ({ler,ted})->
-    old = new Share @buf
-    dat = @receive {ler, ted}
-    old.inverse dat
-  #
-  transpose: (ted,pos)->
-    if pos == undefined then @transpose @leg, ted
-    else ((@transmute ted, ins: at: pos).ins ? at:0).at
diff --git a/web/lib/js/sole.js b/web/lib/js/sole.js
new file mode 100644
index 0000000000..00b3829685
--- /dev/null
+++ b/web/lib/js/sole.js
@@ -0,0 +1,756 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o').text('m').css({
+    display: 'none'
+  }).appendTo(term).width();
+  subs = "";
+  flash = function($el, background) {
+    $el.css({
+      background: background
+    });
+    if (background) {
+      return setTimeout((function() {
+        return flash($el, '');
+      }), 50);
+    }
+  };
+  bell = function() {
+    return flash($('body'), 'black');
+  };
+  matr = rend(Matr({
+    rows: [],
+    appl: "",
+    prompt: {
+      "": "# "
+    },
+    input: "",
+    cursor: 0,
+    history: [],
+    offset: 0
+  }), term);
+  window.matr = matr;
+  update = function(a) {
+    return matr.setProps(a);
+  };
+  buffer = {
+    "": new Share("")
+  };
+  window.buffer = buffer;
+  choose = function(appl) {
+    urb.appl = appl;
+    if (buffer[appl] == null) {
+      buffer[appl] = new Share("");
+    }
+    updPrompt('', null);
+    return update({
+      appl: appl,
+      cursor: 0,
+      input: buffer[appl].buf
+    });
+  };
+  print = function(txt) {
+    return update({
+      rows: slice.call(matr.props.rows).concat([txt])
+    });
+  };
+  sync = function(ted, app) {
+    var b;
+    if (app == null) {
+      app = matr.props.appl;
+    }
+    if (app !== matr.props.appl) {
+      return;
+    }
+    b = buffer[app];
+    return update({
+      input: b.buf,
+      cursor: b.transpose(ted, matr.props.cursor)
+    });
+  };
+  updPrompt = function(app, pro) {
+    var prompt;
+    prompt = $.extend({}, matr.props.prompt);
+    if (pro != null) {
+      prompt[app] = pro;
+    } else {
+      delete prompt[app];
+    }
+    return update({
+      prompt: prompt
+    });
+  };
+  sysStatus = function() {
+    var app, k, pro, ref2, v;
+    return updPrompt('', ((ref2 = [
+      matr.props.appl, (function() {
+        var ref2, results;
+        ref2 = matr.props.prompt;
+        results = [];
+        for (k in ref2) {
+          v = ref2[k];
+          if (k !== '') {
+            results.push(k);
+          }
+        }
+        return results;
+      })()
+    ], app = ref2[0], pro = ref2[1], ref2), app === '' ? (pro.join(', ')) + '# ' : null));
+  };
+  peer = function(ruh, app) {
+    var mapr, v;
+    if (app == null) {
+      app = urb.appl;
+    }
+    if (ruh.map) {
+      return ruh.map(function(rul) {
+        return peer(rul, app);
+      });
+    }
+    mapr = matr.props;
+    switch (Object.keys(ruh)[0]) {
+      case 'txt':
+        return print(ruh.txt);
+      case 'tan':
+        return ruh.tan.split("\n").map(print);
+      case 'pro':
+        return updPrompt(app, ruh.pro.cad);
+      case 'hop':
+        update({
+          cursor: ruh.hop
+        });
+        return bell();
+      case 'blk':
+        return console.log("Stub " + (str(ruh)));
+      case 'det':
+        buffer[app].receive(ruh.det);
+        return sync(ruh.det.ted, app);
+      case 'act':
+        switch (ruh.act) {
+          case 'clr':
+            return update({
+              rows: []
+            });
+          case 'bel':
+            return bell();
+          case 'nex':
+            return update({
+              input: "",
+              cursor: 0,
+              history: !mapr.input ? mapr.history : [mapr.input].concat(slice.call(mapr.history)),
+              offset: 0
+            });
+        }
+        break;
+      default:
+        v = Object.keys(ruh);
+        return console.log(v, ruh[v[0]]);
+    }
+  };
+  join = function(app) {
+    if (matr.props.prompt[app] != null) {
+      return print('# already-joined: ' + app);
+    }
+    choose(app);
+    return urb.bind("/sole", {
+      wire: "/"
+    }, function(err, d) {
+      if (err) {
+        return console.log(err);
+      } else if (d.data) {
+        return peer(d.data, app);
+      }
+    });
+  };
+  cycle = function() {
+    var apps, ref2;
+    apps = Object.keys(matr.props.prompt);
+    if (apps.length < 2) {
+      return;
+    }
+    return choose((ref2 = apps[1 + apps.indexOf(urb.appl)]) != null ? ref2 : apps[0]);
+  };
+  part = function(appl) {
+    var mapr;
+    mapr = matr.props;
+    if (mapr.prompt[appl] == null) {
+      return print('# not-joined: ' + appl);
+    }
+    urb.drop("/sole", {
+      appl: appl,
+      wire: "/"
+    });
+    if (appl === mapr.appl) {
+      cycle();
+    }
+    updPrompt(appl, null);
+    return sysStatus();
+  };
+  join(urb.appl);
+  window.join = join;
+  window.part = part;
+  pressed = [];
+  deltim = null;
+  urb.send.mark = 'sole-action';
+  sendAction = function(data) {
+    var app;
+    if (matr.props.appl) {
+      return urb.send(data, function(e, res) {
+        if (res.status !== 200) {
+          return $('#err')[0].innerText = res.data.mess;
+        }
+      });
+    } else if (data === 'ret') {
+      app = /^[a-z-]+$/.exec(buffer[""].buf.slice(1));
+      if (!((app != null) && (app[0] != null))) {
+        return bell();
+      } else {
+        switch (buffer[""].buf[0]) {
+          case '+':
+            doEdit({
+              set: ""
+            });
+            return join(app[0]);
+          case '-':
+            doEdit({
+              set: ""
+            });
+            return part(app[0]);
+          default:
+            return bell();
+        }
+      }
+    }
+  };
+  doEdit = function(ted) {
+    var det;
+    det = buffer[matr.props.appl].transmit(ted);
+    sync(ted);
+    return sendAction({
+      det: det
+    });
+  };
+  yank = '';
+  eatKyev = function(mod, key) {
+    var _, appl, cha, cursor, history, input, mapr, n, offset, prev, ref2, ref3, rest;
+    mapr = matr.props;
+    switch (mod.sort().join('-')) {
+      case '':
+      case 'shift':
+        if (key.str) {
+          doEdit({
+            ins: {
+              cha: key.str,
+              at: mapr.cursor
+            }
+          });
+          update({
+            cursor: mapr.cursor + 1
+          });
+        }
+        switch (key.act) {
+          case 'entr':
+            return sendAction('ret');
+          case 'up':
+            history = mapr.history.slice();
+            offset = mapr.offset;
+            if (history[offset] === void 0) {
+              return;
+            }
+            ref2 = [history[offset], mapr.input], input = ref2[0], history[offset] = ref2[1];
+            offset++;
+            doEdit({
+              set: input
+            });
+            return update({
+              offset: offset,
+              history: history,
+              cursor: input.length
+            });
+          case 'down':
+            history = mapr.history.slice();
+            offset = mapr.offset;
+            offset--;
+            if (history[offset] === void 0) {
+              return;
+            }
+            ref3 = [history[offset], mapr.input], input = ref3[0], history[offset] = ref3[1];
+            doEdit({
+              set: input
+            });
+            return update({
+              offset: offset,
+              history: history,
+              cursor: input.length
+            });
+          case 'left':
+            if (mapr.cursor > 0) {
+              return update({
+                cursor: mapr.cursor - 1
+              });
+            }
+            break;
+          case 'right':
+            if (mapr.cursor < mapr.input.length) {
+              return update({
+                cursor: mapr.cursor + 1
+              });
+            }
+            break;
+          case 'baxp':
+            if (mapr.cursor > 0) {
+              return doEdit({
+                del: mapr.cursor - 1
+              });
+            }
+        }
+        break;
+      case 'ctrl':
+        switch (key.str || key.act) {
+          case 'a':
+          case 'left':
+            return update({
+              cursor: 0
+            });
+          case 'e':
+          case 'right':
+            return update({
+              cursor: mapr.input.length
+            });
+          case 'l':
+            return update({
+              rows: []
+            });
+          case 'entr':
+            return bell();
+          case 'w':
+            return eatKyev(['alt'], {
+              act: 'baxp'
+            });
+          case 'p':
+            return eatKyev([], {
+              act: 'up'
+            });
+          case 'n':
+            return eatKyev([], {
+              act: 'down'
+            });
+          case 'b':
+            return eatKyev([], {
+              act: 'left'
+            });
+          case 'f':
+            return eatKyev([], {
+              act: 'right'
+            });
+          case 'g':
+            return bell();
+          case 'x':
+            return cycle();
+          case 'v':
+            appl = mapr.appl !== '' ? '' : urb.appl;
+            update({
+              appl: appl,
+              cursor: 0,
+              input: buffer[appl].buf
+            });
+            return sysStatus();
+          case 't':
+            if (mapr.cursor === 0 || mapr.input.length < 2) {
+              return bell();
+            }
+            cursor = mapr.cursor;
+            if (cursor < mapr.input.length) {
+              cursor++;
+            }
+            doEdit([
+              {
+                del: cursor - 1
+              }, {
+                ins: {
+                  at: cursor - 2,
+                  cha: mapr.input[cursor - 1]
+                }
+              }
+            ]);
+            return update({
+              cursor: cursor
+            });
+          case 'u':
+            yank = mapr.input.slice(0, mapr.cursor);
+            return doEdit((function() {
+              var i, ref4, results;
+              results = [];
+              for (n = i = 1, ref4 = mapr.cursor; 1 <= ref4 ? i <= ref4 : i >= ref4; n = 1 <= ref4 ? ++i : --i) {
+                results.push({
+                  del: mapr.cursor - n
+                });
+              }
+              return results;
+            })());
+          case 'k':
+            yank = mapr.input.slice(mapr.cursor);
+            return doEdit((function() {
+              var i, ref4, ref5, results;
+              results = [];
+              for (_ = i = ref4 = mapr.cursor, ref5 = mapr.input.length; ref4 <= ref5 ? i < ref5 : i > ref5; _ = ref4 <= ref5 ? ++i : --i) {
+                results.push({
+                  del: mapr.cursor
+                });
+              }
+              return results;
+            })());
+          case 'y':
+            return doEdit((function() {
+              var i, len, results;
+              results = [];
+              for (n = i = 0, len = yank.length; i < len; n = ++i) {
+                cha = yank[n];
+                results.push({
+                  ins: {
+                    cha: cha,
+                    at: mapr.cursor + n
+                  }
+                });
+              }
+              return results;
+            })());
+          default:
+            return console.log(mod, str(key));
+        }
+        break;
+      case 'alt':
+        switch (key.str || key.act) {
+          case 'f':
+          case 'right':
+            rest = mapr.input.slice(mapr.cursor);
+            rest = rest.match(/\W*\w*/)[0];
+            return update({
+              cursor: mapr.cursor + rest.length
+            });
+          case 'b':
+          case 'left':
+            prev = mapr.input.slice(0, mapr.cursor);
+            prev = prev.split('').reverse().join('');
+            prev = prev.match(/\W*\w*/)[0];
+            return update({
+              cursor: mapr.cursor - prev.length
+            });
+          case 'baxp':
+            prev = mapr.input.slice(0, mapr.cursor);
+            prev = prev.split('').reverse().join('');
+            prev = prev.match(/\W*\w*/)[0];
+            yank = prev;
+            return doEdit((function() {
+              var i, len, results;
+              results = [];
+              for (n = i = 0, len = prev.length; i < len; n = ++i) {
+                _ = prev[n];
+                results.push({
+                  del: mapr.cursor - 1 - n
+                });
+              }
+              return results;
+            })());
+        }
+        break;
+      default:
+        return console.log(mod, str(key));
+    }
+  };
+  return Mousetrap.handleKey = function(char, mod, e) {
+    var chac, key, norm, ref2;
+    norm = {
+      capslock: 'caps',
+      pageup: 'pgup',
+      pagedown: 'pgdn',
+      backspace: 'baxp',
+      enter: 'entr'
+    };
+    key = (function() {
+      var ref2;
+      switch (false) {
+        case char.length !== 1:
+          if (e.type === 'keypress') {
+            chac = char.charCodeAt(0);
+            if (chac < 32) {
+              char = String.fromCharCode(chac | 96);
+            }
+            return {
+              str: char
+            };
+          }
+          break;
+        case e.type !== 'keydown':
+          if (char !== 'space') {
+            return {
+              act: (ref2 = norm[char]) != null ? ref2 : char
+            };
+          }
+          break;
+        case !(e.type === 'keyup' && norm[key] === 'caps'):
+          return {
+            act: 'uncap'
+          };
+      }
+    })();
+    if (!key) {
+      return;
+    }
+    if (key.act && (ref2 = key.act, indexOf.call(mod, ref2) >= 0)) {
+      return;
+    }
+    e.preventDefault();
+    return eatKyev(mod, key);
+  };
+});
+
+
+},{"./share.coffee":2}],2:[function(require,module,exports){
+var clog, str;
+
+str = JSON.stringify;
+
+clog = function(a) {
+  return console.log(a);
+};
+
+module.exports = (function() {
+  function exports(buf, ven, leg) {
+    this.buf = buf != null ? buf : "";
+    this.ven = ven != null ? ven : [0, 0];
+    this.leg = leg != null ? leg : [];
+  }
+
+  exports.prototype.abet = function() {
+    return {
+      buf: this.buf,
+      leg: this.leg.slice(),
+      ven: this.ven.slice()
+    };
+  };
+
+  exports.prototype.apply = function(ted) {
+    var at, cha, ref;
+    switch (false) {
+      case 'nop' !== ted:
+        break;
+      case !ted.map:
+        return ted.map(this.apply, this);
+      default:
+        switch (Object.keys(ted)[0]) {
+          case 'set':
+            return this.buf = ted.set;
+          case 'del':
+            return this.buf = this.buf.slice(0, ted.del) + this.buf.slice(ted.del + 1);
+          case 'ins':
+            ref = ted.ins, at = ref.at, cha = ref.cha;
+            return this.buf = this.buf.slice(0, at) + cha + this.buf.slice(at);
+          default:
+            throw "%sole-edit -lost." + (str(ted));
+        }
+    }
+  };
+
+  exports.prototype.transmute = function(sin, dex) {
+    var at, cha, ref;
+    switch (false) {
+      case !(sin === 'nop' || dex === 'nop'):
+        return dex;
+      case !sin.reduce:
+        return sin.reduce(((function(_this) {
+          return function(dex, syn) {
+            return _this.transmute(syn, dex);
+          };
+        })(this)), dex);
+      case !dex.map:
+        return dex.map((function(_this) {
+          return function(dax) {
+            return _this.transmute(sin, dax);
+          };
+        })(this));
+      case dex.set === void 0:
+        return dex;
+      default:
+        switch (Object.keys(sin)[0]) {
+          case 'set':
+            return 'nop';
+          case 'del':
+            if (sin.del === dex.del) {
+              return 'nop';
+            }
+            dex = $.extend(true, {}, dex);
+            switch (Object.keys(dex)[0]) {
+              case 'del':
+                if (sin.del < dex.del) {
+                  dex.del--;
+                }
+                break;
+              case 'ins':
+                if (sin.del < dex.ins.at) {
+                  dex.ins.at--;
+                }
+            }
+            return dex;
+          case 'ins':
+            dex = $.extend(true, {}, dex);
+            ref = sin.ins, at = ref.at, cha = ref.cha;
+            switch (Object.keys(dex)[0]) {
+              case 'del':
+                if (at < dex.del) {
+                  dex.del++;
+                }
+                break;
+              case 'ins':
+                if (at < dex.ins.at || (at === dex.ins.at && !(cha <= dex.ins.cha))) {
+                  dex.ins.at++;
+                }
+            }
+            return dex;
+          default:
+            throw "%sole-edit -lost." + (str(sin));
+        }
+    }
+  };
+
+  exports.prototype.commit = function(ted) {
+    this.ven[0]++;
+    this.leg.push(ted);
+    return this.apply(ted);
+  };
+
+  exports.prototype.inverse = function(ted) {
+    switch (false) {
+      case 'nop' !== ted:
+        return ted;
+      case !ted.map:
+        return ted.map((function(_this) {
+          return function(tad) {
+            var res;
+            res = _this.inverse(tad);
+            _this.apply(tad);
+            return res;
+          };
+        })(this)).reverse();
+      default:
+        switch (Object.keys(ted)[0]) {
+          case 'set':
+            return {
+              set: this.buf
+            };
+          case 'ins':
+            return {
+              del: ted.ins
+            };
+          case 'del':
+            return {
+              ins: {
+                at: ted.del,
+                cha: this.buf[ted.del]
+              }
+            };
+          default:
+            throw "%sole-edit -lost." + (str(ted));
+        }
+    }
+  };
+
+  exports.prototype.receive = function(arg) {
+    var dat, ler, ted;
+    ler = arg.ler, ted = arg.ted;
+    if (!(ler[1] === this.ven[1])) {
+      throw "-out-of-sync.[" + (str(ler)) + " " + (str(this.ven)) + "]";
+    }
+    this.leg = this.leg.slice(this.leg.length + ler[0] - this.ven[0]);
+    dat = this.transmute(this.leg, ted);
+    this.ven[1]++;
+    this.apply(dat);
+    return dat;
+  };
+
+  exports.prototype.remit = function() {
+    throw 'stub';
+  };
+
+  exports.prototype.transmit = function(ted) {
+    var act;
+    act = {
+      ted: ted,
+      ler: [this.ven[1], this.ven[0]]
+    };
+    this.commit(ted);
+    return act;
+  };
+
+  exports.prototype.transceive = function(arg) {
+    var dat, ler, old, ted;
+    ler = arg.ler, ted = arg.ted;
+    old = new Share(this.buf);
+    dat = this.receive({
+      ler: ler,
+      ted: ted
+    });
+    return old.inverse(dat);
+  };
+
+  exports.prototype.transpose = function(ted, pos) {
+    var ref;
+    if (pos === void 0) {
+      return this.transpose(this.leg, ted);
+    } else {
+      return ((ref = (this.transmute(ted, {
+        ins: {
+          at: pos
+        }
+      })).ins) != null ? ref : {
+        at: 0
+      }).at;
+    }
+  };
+
+  return exports;
+
+})();
+
+
+},{}]},{},[1]);

From f1b3451c1d7efae62c32e86f8775a44763a3fa84 Mon Sep 17 00:00:00 2001
From: Anton Dyudin 
Date: Tue, 2 Feb 2016 13:24:20 -0800
Subject: [PATCH 5/9] minor cleaning

---
 lib/tree.hoon | 64 +++++++++++++++------------------------------------
 1 file changed, 19 insertions(+), 45 deletions(-)

diff --git a/lib/tree.hoon b/lib/tree.hoon
index 4f4a676832..9161cd6fcf 100644
--- a/lib/tree.hoon
+++ b/lib/tree.hoon
@@ -1,64 +1,38 @@
 /-  tree-include
 !:
 |%
-++  extract
-  |=  a=marl  ^-  tape
-  ?~  a  ~
-  %-  weld  :_  $(a t.a)
-  ?.  ?=(_:/(**) i.a)
-    $(a c.i.a)
-  v.i.a.g.i.a
-::
-++  getall
+++  getall                                              :: search in manx
   |=  tag=(list mane)
   |=  ele=manx  ^-  marl
   ?:  (lien tag |=(a=mane =(a n.g.ele)))
     ~[ele]
   (zing (turn c.ele ..$))
-++  baff  |*([a=(unit) b=(trap)] ?^(a a *b))
-++  find-in-tree
-  |*  [paz=fist:jo fun=$+(* (unit))]
-  |=  jon=json
-  =+  a=`(list json)`~[jon]
-  |^  (try)
-  ++  try
-    |.  ^+  *fun
-    ?~  a  ~
-    %+  biff  (paz i.a)
-    |*  [b=(list json) c=*]  ^+  *fun
-    (baff (baff (fun c) try(a b)) try(a t.a))
-  --
 ::
-++  map-to-json
+++  map-to-json                                         :: hoon data to json 
   |*  [a=$+(* cord) b=$+(* json)]
-  |*  c=(map)  ^-  json
+  |*  c=(map)  ^-  json               :: XX c=(map _+<.a _+<.b)
   ~!  c
   (jobe (turn (~(tap by c)) |*([k=* v=*] [(a k) (b v)])))
-:: 
-++  json-front
-  |=  a=json  ^-  json
-  =-  (fall `(unit json)`- ~)
-  %.  a
-  %+  find-in-tree  (ot c/(ar some) gn/so ga/(om so) ~):jo
-  |=  [nom=span atr=(map span cord)]  ^-  (unit json)
-  ?.  (~(has by atr) 'urb:front')  ~
-  ?>  ?=(%meta nom)
-  (biff (~(get by atr) %value) poja)
 ::
-++  read-schem  
-  =<  (cook to-noun (cook to-tree apex))
+::  a.b_c.d => [[%a %b] [%c %d]]
+::  a.b_c, a_b__c => [[%a %b] %c]
+::  a_b_c, a__b_c => [%a [%b %c]]
+++  read-schem                                          :: decode gapped noun
+  =<  (cook to-noun (cook build-grove apex))
   |%
   ++  noun  $|(term [noun noun])       ::  shadow
-  ++  data  $|(term [n=@ l=noun r=data])
+  ::  improper list of possible entry points
+  ++  grove  $|(term [gap=@ sealed=noun pending=grove])
   ++  apex  ;~(plug sym (star ;~(plug delim sym)))
   ++  delim  ;~(pose (cold 0 dot) (cook lent (plus cab)))
-  ++  to-noun  |=(a=data ?@(a a [l.a $(a r.a)]))
-  ++  to-tree
-    |=  [acc=data a=(list ,[p=@u q=term])]
-    %+  roll  a  =<  .(acc ^acc)
-    |=  [[n=@u v=term] acc=data]
-    ?@  acc            [n acc v]
-    ?:  (gth n n.acc)  [n (to-noun acc) v]
-    acc(r $(acc r.acc))
+  ++  to-noun  |=(a=grove ?@(a a [sealed.a $(a pending.a)]))
+  ++  build-grove
+    |=  [a=grove b=(list ,[p=@u q=term])]  ^-  grove
+    %+  roll  b  =<  .(acc a)
+    |=  [[gap=@u v=term] acc=grove]  ^-  grove
+    ?@  acc            [gap acc v]
+    ?:  (gth gap gap.acc)  [gap (to-noun acc) v]
+    acc(pending $(acc pending.acc))
   --
 --
+

From 80d5288cd22bf1db5e2ed04084459a0981b81dc4 Mon Sep 17 00:00:00 2001
From: Anton Dyudin 
Date: Tue, 2 Feb 2016 15:25:15 -0800
Subject: [PATCH 6/9] moved subnav props to module, reactify now async

  https://github.com/urbit/talk@3eba6c8
  https://github.com/urbit/tree@04cc845
---
 web/module/mod.hoon |   7 +-
 web/talk/main.js    | 175 ++++++++++++++++++---------
 web/tree/main.js    | 282 +++++++++++++++++++++++++++-----------------
 3 files changed, 300 insertions(+), 164 deletions(-)

diff --git a/web/module/mod.hoon b/web/module/mod.hoon
index fbd09914b9..226dc0d542 100644
--- a/web/module/mod.hoon
+++ b/web/module/mod.hoon
@@ -1 +1,6 @@
-;module(js "/talk/main.js", css "/talk/main.css", component "talk");
+;module(nav_title "Talk", nav_dpad "", nav_sibs "", nav_subnav "talk-station")
+  ;script@"/~~/~/at/lib/urb.js";
+  ;script@"/talk/main.js";
+  ;link/"/talk/main.css"(rel "stylesheet");
+  ;talk;
+==
diff --git a/web/talk/main.js b/web/talk/main.js
index be99cd160f..0d0d934ff0 100644
--- a/web/talk/main.js
+++ b/web/talk/main.js
@@ -664,6 +664,7 @@ Load = require('./LoadComponent.coffee');
 module.exports = recl({
   displayName: "Station",
   stateFromStore: function() {
+    var ref1;
     return {
       audi: StationStore.getAudience(),
       members: StationStore.getMembers(),
@@ -672,7 +673,8 @@ module.exports = recl({
       configs: StationStore.getConfigs(),
       fetching: MessageStore.getFetching(),
       typing: StationStore.getTyping(),
-      listening: StationStore.getListening()
+      listening: StationStore.getListening(),
+      open: (((ref1 = this.state) != null ? ref1.open : void 0) ? this.state.open : null)
     };
   },
   getInitialState: function() {
@@ -691,9 +693,11 @@ module.exports = recl({
   _onChangeStore: function() {
     return this.setState(this.stateFromStore());
   },
-  _toggleOpen: function(e) {
-    if ($(e.target).closest('.sour-ctrl').length === 0) {
-      return $("#station-container").toggleClass('open');
+  componentWillReceiveProps: function(nextProps) {
+    if (this.props.open === true && nextProps.open === false) {
+      return this.setState({
+        open: null
+      });
     }
   },
   validateSource: function(s) {
@@ -721,6 +725,18 @@ module.exports = recl({
       }
     }
   },
+  _openStation: function(e) {
+    var $t;
+    $t = $(e.target);
+    return this.setState({
+      open: $t.attr('data-station')
+    });
+  },
+  _closeStation: function() {
+    return this.setState({
+      open: null
+    });
+  },
   _remove: function(e) {
     var _sources, _station;
     e.stopPropagation();
@@ -731,9 +747,59 @@ module.exports = recl({
     return StationActions.setSources(this.state.station, _sources);
   },
   render: function() {
-    var _clas, members, parts, source, sources, sourcesSum;
+    var _clas, member, members, obj, parts, source, sources, sourcesSum, station;
     parts = [];
     members = [];
+    if (this.state.station && this.state.configs[this.state.station]) {
+      members = (function() {
+        var ref1, results;
+        ref1 = this.state.members;
+        results = [];
+        for (station in ref1) {
+          members = ref1[station];
+          _clas = clas({
+            open: this.state.open === station,
+            closed: !(this.state.open === station),
+            'col-md-4': true,
+            'col-md-offset-6': true,
+            menu: true,
+            'depth-2': true
+          });
+          results.push(div({
+            className: _clas,
+            "data-members": station
+          }, [
+            div({
+              className: "contents",
+              onClick: this._closeStation
+            }, [
+              div({
+                className: "close"
+              }, "✕"), h2({}, [
+                span({}, "Members"), label({
+                  className: "sum"
+                }, _.keys(members).length)
+              ]), (function() {
+                var results1;
+                results1 = [];
+                for (member in members) {
+                  obj = members[member];
+                  results1.push(div({}, [
+                    div({
+                      className: "name"
+                    }, ""), div({
+                      className: "planet"
+                    }, member)
+                  ]));
+                }
+                return results1;
+              })()
+            ])
+          ]));
+        }
+        return results;
+      }).call(this);
+    }
     if (this.state.station && this.state.configs[this.state.station]) {
       sources = (function() {
         var i, len, ref1, results;
@@ -744,7 +810,11 @@ module.exports = recl({
           results.push(div({
             className: "room"
           }, [
-            source.slice(1), div({
+            div({
+              className: (this.state.open === source ? "selected" : ""),
+              onClick: this._openStation,
+              "data-station": source
+            }, source.slice(1)), div({
               className: "close",
               onClick: this._remove,
               "data-station": source
@@ -772,21 +842,25 @@ module.exports = recl({
       'depth-1': true
     });
     return div({
-      className: _clas,
-      key: 'station'
+      key: "station-container"
     }, [
       div({
-        className: "contents"
+        className: _clas,
+        key: 'station'
       }, [
         div({
-          className: "close",
-          onClick: this.props.toggle
-        }, "✕"), h2({}, [
-          span({}, "Stations"), label({
-            className: "sum"
-          }, sourcesSum)
-        ]), div({}, sources)
-      ])
+          className: "contents"
+        }, [
+          div({
+            className: "close",
+            onClick: this.props.toggle
+          }, "✕"), h2({}, [
+            span({}, "Stations"), label({
+              className: "sum"
+            }, sourcesSum)
+          ]), div({}, sources)
+        ])
+      ]), members
     ]);
   }
 });
@@ -820,10 +894,12 @@ Audience = recl({
   onKeyDown: function(e) {
     if (e.keyCode === 13) {
       e.preventDefault();
-      setTimeout(function() {
-        return $('#writing').focus();
-      }, 0);
-      return false;
+      if (this.props.validate()) {
+        setTimeout((function() {
+          return $('#writing').focus();
+        }), 0);
+        return false;
+      }
     }
   },
   render: function() {
@@ -909,7 +985,9 @@ module.exports = recl({
   sendMessage: function() {
     var audi, txt;
     if (this._validateAudi() === false) {
-      $('#audience').focus();
+      setTimeout((function() {
+        return $('#audience .input').focus();
+      }), 0);
       return;
     }
     if (this.state.audi.length === 0 && $('#audience').text().trim().length > 0) {
@@ -979,7 +1057,7 @@ module.exports = recl({
   },
   _validateAudi: function() {
     var v;
-    v = $('#audience').text();
+    v = $('#audience .input').text();
     v = v.trim();
     if (v.length === 0) {
       return true;
@@ -994,7 +1072,7 @@ module.exports = recl({
     valid = this._validateAudi();
     StationActions.setValidAudience(valid);
     if (valid === true) {
-      stan = $('#audience').text() || window.util.mainStationPath(window.urb.user);
+      stan = $('#audience .input').text() || window.util.mainStationPath(window.urb.user);
       stan = (stan.split(/\ +/)).map(function(v) {
         if (v[0] === "~") {
           return v;
@@ -1069,6 +1147,7 @@ module.exports = recl({
       React.createElement(Audience, {
         audi: audi,
         valid: this.state.valid,
+        validate: this._validateAudi,
         onBlur: this._setAudi
       }), div({
         className: 'message',
@@ -1129,7 +1208,7 @@ setInterval((function() {
   }
 }), 300);
 
-StationComponent = React.createFactory(require('./components/StationComponent.coffee'));
+StationComponent = require('./components/StationComponent.coffee');
 
 MessagesComponent = React.createFactory(require('./components/MessagesComponent.coffee'));
 
@@ -1143,13 +1222,7 @@ TreeActions.registerComponent("talk", React.createClass({
     require('./utils/util.coffee');
     require('./utils/move.coffee');
     StationActions.listen();
-    StationActions.listenStation(window.util.mainStation());
-    return TreeActions.setNav({
-      title: "Talk",
-      dpad: false,
-      sibs: false,
-      subnav: StationComponent
-    });
+    return StationActions.listenStation(window.util.mainStation());
   },
   render: function() {
     return div({
@@ -1168,6 +1241,8 @@ TreeActions.registerComponent("talk", React.createClass({
   }
 }));
 
+TreeActions.registerComponent("talk-station", StationComponent);
+
 
 },{"./actions/StationActions.coffee":2,"./components/MessagesComponent.coffee":6,"./components/StationComponent.coffee":7,"./components/WritingComponent.coffee":8,"./utils/move.coffee":15,"./utils/util.coffee":16}],11:[function(require,module,exports){
 var send;
@@ -1586,25 +1661,7 @@ StationStore = _.merge(new EventEmitter, {
     };
   },
   loadMembers: function(members) {
-    var list, member, presence, results, station;
-    _members = {};
-    results = [];
-    for (station in members) {
-      list = members[station];
-      results.push((function() {
-        var results1;
-        results1 = [];
-        for (member in list) {
-          presence = list[member];
-          if (!_members[member]) {
-            _members[member] = {};
-          }
-          results1.push(_members[member][station] = presence);
-        }
-        return results1;
-      })());
-    }
-    return results;
+    return _members = members;
   },
   getMembers: function() {
     return _members;
@@ -15828,7 +15885,10 @@ var ReactDOMOption = {
       }
     });
 
-    nativeProps.children = content;
+    if (content) {
+      nativeProps.children = content;
+    }
+
     return nativeProps;
   }
 
@@ -21997,7 +22057,7 @@ module.exports = ReactUpdates;
 
 'use strict';
 
-module.exports = '0.14.6';
+module.exports = '0.14.7';
 },{}],121:[function(require,module,exports){
 /**
  * Copyright 2013-2015, Facebook, Inc.
@@ -23092,6 +23152,7 @@ var warning = require('fbjs/lib/warning');
  */
 var EventInterface = {
   type: null,
+  target: null,
   // currentTarget is set when dispatching; no use in copying it here
   currentTarget: emptyFunction.thatReturnsNull,
   eventPhase: null,
@@ -23125,8 +23186,6 @@ function SyntheticEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEvent
   this.dispatchConfig = dispatchConfig;
   this.dispatchMarker = dispatchMarker;
   this.nativeEvent = nativeEvent;
-  this.target = nativeEventTarget;
-  this.currentTarget = nativeEventTarget;
 
   var Interface = this.constructor.Interface;
   for (var propName in Interface) {
@@ -23137,7 +23196,11 @@ function SyntheticEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEvent
     if (normalize) {
       this[propName] = normalize(nativeEvent);
     } else {
-      this[propName] = nativeEvent[propName];
+      if (propName === 'target') {
+        this.target = nativeEventTarget;
+      } else {
+        this[propName] = nativeEvent[propName];
+      }
     }
   }
 
diff --git a/web/tree/main.js b/web/tree/main.js
index 81cc9910cb..517346e47f 100644
--- a/web/tree/main.js
+++ b/web/tree/main.js
@@ -30,7 +30,19 @@ module.exports = {
     })(this));
   },
   registerComponent: function(name, comp) {
-    return window.tree.components[name] = comp;
+    var obj;
+    return this.addVirtual((
+      obj = {},
+      obj["" + name] = comp,
+      obj
+    ));
+  },
+  addVirtual: function(components) {
+    console.log("addVirtual", components);
+    return TreeDispatcher.handleViewAction({
+      type: "addVirtual",
+      components: components
+    });
   },
   setCurr: function(path) {
     return TreeDispatcher.handleViewAction({
@@ -62,8 +74,9 @@ module.exports = {
 };
 
 
-},{"../dispatcher/Dispatcher.coffee":17,"../persistence/TreePersistence.coffee":19}],2:[function(require,module,exports){
-var BodyComponent, Dpad, Nav, Sibs, TreeActions, TreeStore, a, button, clas, div, li, query, recl, ref, rend, ul, util;
+
+},{"../dispatcher/Dispatcher.coffee":18,"../persistence/TreePersistence.coffee":20}],2:[function(require,module,exports){
+var BodyComponent, Dpad, Nav, Sibs, TreeActions, TreeStore, a, button, clas, div, li, query, reactify, recl, ref, rend, ul, util;
 
 clas = require('classnames');
 
@@ -71,6 +84,8 @@ BodyComponent = React.createFactory(require('./BodyComponent.coffee'));
 
 query = require('./Async.coffee');
 
+reactify = require('./Reactify.coffee');
+
 TreeStore = require('../stores/TreeStore.coffee');
 
 TreeActions = require('../actions/TreeActions.coffee');
@@ -127,6 +142,11 @@ Nav = React.createFactory(query({
     var dt;
     return dt = this.ts - Number(Date.now());
   },
+  _home: function() {
+    if (document.location.pathname !== "/") {
+      return document.location = "/";
+    }
+  },
   toggleFocus: function(state) {
     return $(ReactDOM.findDOMNode(this)).toggleClass('focus', state);
   },
@@ -170,7 +190,8 @@ Nav = React.createFactory(query({
     }, div({
       className: 'icon'
     }, div({
-      className: 'home'
+      className: 'home',
+      onClick: this._home
     }, ""), div({
       className: 'app'
     }, title), dpad, button({
@@ -305,18 +326,23 @@ module.exports = query({
       }, "div")
     ];
     if (this.state.subnav) {
-      kids.push(this.state.subnav({
-        key: "subnav",
-        open: this.state.open,
-        toggle: TreeActions.toggleNav
-      }, ""));
+      kids.push(reactify({
+        gn: this.state.subnav,
+        ga: {
+          key: "subnav",
+          open: this.state.open,
+          toggle: TreeActions.toggleNav
+        },
+        c: []
+      }));
     }
     return div({}, kids);
   }
 }));
 
 
-},{"../actions/TreeActions.coffee":1,"../stores/TreeStore.coffee":20,"../utils/util.coffee":22,"./Async.coffee":3,"./BodyComponent.coffee":4,"./DpadComponent.coffee":7,"./SibsComponent.coffee":15,"classnames":23}],3:[function(require,module,exports){
+
+},{"../actions/TreeActions.coffee":1,"../stores/TreeStore.coffee":21,"../utils/util.coffee":23,"./Async.coffee":3,"./BodyComponent.coffee":4,"./DpadComponent.coffee":7,"./Reactify.coffee":13,"./SibsComponent.coffee":16,"classnames":24}],3:[function(require,module,exports){
 var TreeActions, TreeStore, _load, code, div, recl, ref, span;
 
 _load = require('./LoadComponent.coffee');
@@ -426,7 +452,8 @@ module.exports = function(queries, Child, load) {
 };
 
 
-},{"../actions/TreeActions.coffee":1,"../stores/TreeStore.coffee":20,"./LoadComponent.coffee":11}],4:[function(require,module,exports){
+
+},{"../actions/TreeActions.coffee":1,"../stores/TreeStore.coffee":21,"./LoadComponent.coffee":11}],4:[function(require,module,exports){
 var a, clas, div, extras, img, p, query, reactify, recl, ref, rele, util;
 
 clas = require('classnames');
@@ -560,7 +587,8 @@ module.exports = query({
 }));
 
 
-},{"../utils/util.coffee":22,"./Async.coffee":3,"./Reactify.coffee":13,"classnames":23}],5:[function(require,module,exports){
+
+},{"../utils/util.coffee":23,"./Async.coffee":3,"./Reactify.coffee":13,"classnames":24}],5:[function(require,module,exports){
 var div, recl, ref, textarea;
 
 recl = React.createClass;
@@ -583,6 +611,7 @@ module.exports = recl({
 });
 
 
+
 },{}],6:[function(require,module,exports){
 var div, recl;
 
@@ -598,6 +627,7 @@ module.exports = {
   toc: require('./TocComponent.coffee'),
   email: require('./EmailComponent.coffee'),
   module: require('./ModuleComponent.coffee'),
+  script: require('./ScriptComponent.coffee'),
   lost: recl({
     render: function() {
       return div({}, "");
@@ -606,7 +636,8 @@ module.exports = {
 };
 
 
-},{"./CodeMirror.coffee":5,"./EmailComponent.coffee":8,"./KidsComponent.coffee":9,"./ListComponent.coffee":10,"./ModuleComponent.coffee":12,"./SearchComponent.coffee":14,"./TocComponent.coffee":16}],7:[function(require,module,exports){
+
+},{"./CodeMirror.coffee":5,"./EmailComponent.coffee":8,"./KidsComponent.coffee":9,"./ListComponent.coffee":10,"./ModuleComponent.coffee":12,"./ScriptComponent.coffee":14,"./SearchComponent.coffee":15,"./TocComponent.coffee":17}],7:[function(require,module,exports){
 var a, div, recl, ref, util;
 
 util = require('../utils/util.coffee');
@@ -664,7 +695,8 @@ module.exports = React.createFactory(recl({
 }));
 
 
-},{"../utils/util.coffee":22}],8:[function(require,module,exports){
+
+},{"../utils/util.coffee":23}],8:[function(require,module,exports){
 var button, div, input, p, reactify, recl, ref;
 
 reactify = require('./Reactify.coffee');
@@ -745,6 +777,7 @@ module.exports = recl({
 });
 
 
+
 },{"./Reactify.coffee":13}],9:[function(require,module,exports){
 var a, div, hr, li, query, reactify, recl, ref, ul;
 
@@ -826,6 +859,7 @@ module.exports = query({
 }));
 
 
+
 },{"./Async.coffee":3,"./Reactify.coffee":13}],10:[function(require,module,exports){
 var a, clas, div, h1, li, pre, query, reactify, recl, ref, span, ul, util;
 
@@ -983,7 +1017,8 @@ module.exports = query({
 }));
 
 
-},{"../utils/util.coffee":22,"./Async.coffee":3,"./Reactify.coffee":13,"classnames":23}],11:[function(require,module,exports){
+
+},{"../utils/util.coffee":23,"./Async.coffee":3,"./Reactify.coffee":13,"classnames":24}],11:[function(require,module,exports){
 var div, recl, ref, span;
 
 recl = React.createClass;
@@ -1021,81 +1056,44 @@ module.exports = recl({
 });
 
 
-},{}],12:[function(require,module,exports){
-var code, div, reactify, recl, ref, span;
 
-reactify = require('./Reactify.coffee');
+},{}],12:[function(require,module,exports){
+var TreeActions, div, recl;
 
 recl = React.createClass;
 
-ref = React.DOM, div = ref.div, span = ref.span, code = ref.code;
+div = React.DOM.div;
+
+TreeActions = require('../actions/TreeActions.coffee');
 
 module.exports = recl({
   displayName: "Module",
-  getInitialState: function() {
-    return {
-      loaded: false
-    };
-  },
-  loaded: function() {
-    return this.setState({
-      loaded: true
-    });
-  },
-  componentWillMount: function() {
-    this.js = null;
-    return this.css = null;
-  },
   componentDidMount: function() {
-    var l, s;
-    s = document.createElement('script');
-    s.src = this.props.js;
-    s.async = 1;
-    s.onload = this.loaded;
-    s.onreadystatechange = this.loaded;
-    s.onerror = this.loaded;
-    document.body.appendChild(s);
-    this.js = s;
-    if (this.props.css) {
-      l = document.createElement('link');
-      l.rel = "stylesheet";
-      l.href = this.props.css;
-      document.body.appendChild(l);
-      return this.css = l;
-    }
-  },
-  componentDidUpdate: function() {
-    if (this.state.loaded === true && this.props.component.register) {
-      return this.props.component.register();
-    }
+    return setTimeout((function(_this) {
+      return function() {
+        return TreeActions.setNav({
+          title: _this.props["nav:title"],
+          dpad: !!_this.props["nav:dpad"],
+          sibs: !!_this.props["nav:sibs"],
+          subnav: _this.props["nav:subnav"]
+        }, 0);
+      };
+    })(this));
   },
   componentWillUnmount: function() {
-    if (this.js) {
-      document.body.removeChild(this.js);
-    }
-    if (this.css) {
-      document.body.removeChild(this.css);
-    }
-    return window.tree.actions.clearNav();
+    return TreeActions.clearNav();
   },
   render: function() {
-    if (!this.state.loaded) {
-      return div({
-        key: "module-loading"
-      }, "");
-    } else {
-      return reactify({
-        gn: this.props.component,
-        ga: this.props,
-        c: []
-      });
-    }
+    return div({
+      className: "module"
+    }, this.props.children);
   }
 });
 
 
-},{"./Reactify.coffee":13}],13:[function(require,module,exports){
-var Virtual, div, load, reactify, recl, ref, rele, span, walk;
+
+},{"../actions/TreeActions.coffee":1}],13:[function(require,module,exports){
+var TreeStore, Virtual, div, load, reactify, recl, ref, rele, span, walk;
 
 recl = React.createClass;
 
@@ -1105,6 +1103,8 @@ ref = React.DOM, div = ref.div, span = ref.span;
 
 load = React.createFactory(require('./LoadComponent.coffee'));
 
+TreeStore = require('../stores/TreeStore.coffee');
+
 walk = function(root, _nil, _str, _comp) {
   var _walk;
   _walk = function(elem, key) {
@@ -1131,9 +1131,28 @@ walk = function(root, _nil, _str, _comp) {
 
 Virtual = recl({
   displayName: "Virtual",
+  getInitialState: function() {
+    return this.stateFromStore();
+  },
+  stateFromStore: function() {
+    return {
+      components: TreeStore.getVirtualComponents()
+    };
+  },
+  _onChangeStore: function() {
+    if (this.isMounted()) {
+      return this.setState(this.stateFromStore());
+    }
+  },
+  componentDidMount: function() {
+    return TreeStore.addChangeListener(this._onChangeStore);
+  },
+  componentWillUnmount: function() {
+    return TreeStore.removeChangeListener(this._onChangeStore);
+  },
   render: function() {
     var components;
-    components = window.tree.components;
+    components = this.state.components;
     return walk(this.props.manx, function() {
       return load({}, "");
     }, function(str) {
@@ -1161,7 +1180,34 @@ module.exports = _.extend(reactify, {
 });
 
 
-},{"./LoadComponent.coffee":11}],14:[function(require,module,exports){
+
+},{"../stores/TreeStore.coffee":21,"./LoadComponent.coffee":11}],14:[function(require,module,exports){
+var recl, rele;
+
+recl = React.createClass;
+
+rele = React.createElement;
+
+module.exports = recl({
+  displayName: "Script",
+  componentDidMount: function() {
+    var s;
+    s = document.createElement('script');
+    _.assign(s, this.props);
+    document.body.appendChild(s);
+    return this.js = s;
+  },
+  componentWillUnmount: function() {
+    return document.body.removeChild(this.js);
+  },
+  render: function() {
+    return rele("script", this.props);
+  }
+});
+
+
+
+},{}],15:[function(require,module,exports){
 var a, div, input, query, reactify, recl, ref,
   slice = [].slice;
 
@@ -1299,7 +1345,8 @@ module.exports = query({
 }));
 
 
-},{"./Async.coffee":3,"./Reactify.coffee":13}],15:[function(require,module,exports){
+
+},{"./Async.coffee":3,"./Reactify.coffee":13}],16:[function(require,module,exports){
 var a, clas, li, reactify, recl, ref, ul, util;
 
 util = require('../utils/util.coffee');
@@ -1360,7 +1407,8 @@ module.exports = React.createFactory(recl({
 }));
 
 
-},{"../utils/util.coffee":22,"./Reactify.coffee":13,"classnames":23}],16:[function(require,module,exports){
+
+},{"../utils/util.coffee":23,"./Reactify.coffee":13,"classnames":24}],17:[function(require,module,exports){
 var div, query, reactify, recl,
   slice = [].slice;
 
@@ -1488,7 +1536,8 @@ module.exports = query({
 }));
 
 
-},{"./Async.coffee":3,"./Reactify.coffee":13}],17:[function(require,module,exports){
+
+},{"./Async.coffee":3,"./Reactify.coffee":13}],18:[function(require,module,exports){
 module.exports = _.extend(new Flux.Dispatcher(), {
   handleServerAction: function(action) {
     return this.dispatch({
@@ -1505,7 +1554,8 @@ module.exports = _.extend(new Flux.Dispatcher(), {
 });
 
 
-},{}],18:[function(require,module,exports){
+
+},{}],19:[function(require,module,exports){
 var rend;
 
 rend = ReactDOM.render;
@@ -1514,8 +1564,8 @@ $(function() {
   var body, frag, head, util;
   util = require('./utils/util.coffee');
   require('./utils/scroll.coffee');
-  window.tree.components = require('./components/Components.coffee');
   window.tree.actions = require('./actions/TreeActions.coffee');
+  window.tree.actions.addVirtual(require('./components/Components.coffee'));
   frag = util.fragpath(window.location.pathname.replace(/\.[^\/]*$/, ''));
   window.tree.actions.setCurr(frag);
   window.tree.actions.loadPath(frag, window.tree.data);
@@ -1526,7 +1576,8 @@ $(function() {
 });
 
 
-},{"./actions/TreeActions.coffee":1,"./components/AnchorComponent.coffee":2,"./components/BodyComponent.coffee":4,"./components/Components.coffee":6,"./utils/scroll.coffee":21,"./utils/util.coffee":22}],19:[function(require,module,exports){
+
+},{"./actions/TreeActions.coffee":1,"./components/AnchorComponent.coffee":2,"./components/BodyComponent.coffee":4,"./components/Components.coffee":6,"./utils/scroll.coffee":22,"./utils/util.coffee":23}],20:[function(require,module,exports){
 var dedup, util;
 
 util = require('../utils/util.coffee');
@@ -1586,15 +1637,18 @@ module.exports = {
 };
 
 
-},{"../utils/util.coffee":22}],20:[function(require,module,exports){
-var EventEmitter, MessageDispatcher, QUERIES, TreeStore, _curr, _data, _nav, _tree, clog;
 
-EventEmitter = require('events').EventEmitter;
+},{"../utils/util.coffee":23}],21:[function(require,module,exports){
+var EventEmitter, MessageDispatcher, QUERIES, TreeStore, _curr, _data, _nav, _tree, _virt, clog;
+
+EventEmitter = require('events').EventEmitter.EventEmitter;
 
 MessageDispatcher = require('../dispatcher/Dispatcher.coffee');
 
 clog = console.log.bind(console);
 
+_virt = {};
+
 _tree = {};
 
 _data = {};
@@ -1611,7 +1665,7 @@ QUERIES = {
   meta: 'j'
 };
 
-TreeStore = _.extend(EventEmitter.prototype, {
+TreeStore = _.extend(new EventEmitter, {
   addChangeListener: function(cb) {
     return this.on('change', cb);
   },
@@ -1691,6 +1745,14 @@ TreeStore = _.extend(EventEmitter.prototype, {
   getCurr: function() {
     return _curr;
   },
+  addVirtual: function(arg) {
+    var components;
+    components = arg.components;
+    return _.extend(_virt, components);
+  },
+  getVirtualComponents: function() {
+    return _virt;
+  },
   loadPath: function(arg) {
     var data, path;
     path = arg.path, data = arg.data;
@@ -1840,7 +1902,8 @@ TreeStore.dispatchToken = MessageDispatcher.register(function(p) {
 module.exports = TreeStore;
 
 
-},{"../dispatcher/Dispatcher.coffee":17,"events":24}],21:[function(require,module,exports){
+
+},{"../dispatcher/Dispatcher.coffee":18,"events":25}],22:[function(require,module,exports){
 var scroll;
 
 scroll = {
@@ -1932,7 +1995,8 @@ scroll.init();
 module.exports = scroll;
 
 
-},{}],22:[function(require,module,exports){
+
+},{}],23:[function(require,module,exports){
 var _basepath;
 
 _basepath = window.urb.util.basepath("/");
@@ -1981,7 +2045,8 @@ module.exports = {
 };
 
 
-},{}],23:[function(require,module,exports){
+
+},{}],24:[function(require,module,exports){
 /*!
   Copyright (c) 2016 Jed Watson.
   Licensed under the MIT License (MIT), see
@@ -2031,7 +2096,7 @@ module.exports = {
 	}
 }());
 
-},{}],24:[function(require,module,exports){
+},{}],25:[function(require,module,exports){
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a
@@ -2115,11 +2180,18 @@ EventEmitter.prototype.emit = function(type) {
         break;
       // slower
       default:
-        args = Array.prototype.slice.call(arguments, 1);
+        len = arguments.length;
+        args = new Array(len - 1);
+        for (i = 1; i < len; i++)
+          args[i - 1] = arguments[i];
         handler.apply(this, args);
     }
   } else if (isObject(handler)) {
-    args = Array.prototype.slice.call(arguments, 1);
+    len = arguments.length;
+    args = new Array(len - 1);
+    for (i = 1; i < len; i++)
+      args[i - 1] = arguments[i];
+
     listeners = handler.slice();
     len = listeners.length;
     for (i = 0; i < len; i++)
@@ -2157,6 +2229,7 @@ EventEmitter.prototype.addListener = function(type, listener) {
 
   // Check for listener leak
   if (isObject(this._events[type]) && !this._events[type].warned) {
+    var m;
     if (!isUndefined(this._maxListeners)) {
       m = this._maxListeners;
     } else {
@@ -2278,7 +2351,7 @@ EventEmitter.prototype.removeAllListeners = function(type) {
 
   if (isFunction(listeners)) {
     this.removeListener(type, listeners);
-  } else if (listeners) {
+  } else {
     // LIFO order
     while (listeners.length)
       this.removeListener(type, listeners[listeners.length - 1]);
@@ -2299,20 +2372,15 @@ EventEmitter.prototype.listeners = function(type) {
   return ret;
 };
 
-EventEmitter.prototype.listenerCount = function(type) {
-  if (this._events) {
-    var evlistener = this._events[type];
-
-    if (isFunction(evlistener))
-      return 1;
-    else if (evlistener)
-      return evlistener.length;
-  }
-  return 0;
-};
-
 EventEmitter.listenerCount = function(emitter, type) {
-  return emitter.listenerCount(type);
+  var ret;
+  if (!emitter._events || !emitter._events[type])
+    ret = 0;
+  else if (isFunction(emitter._events[type]))
+    ret = 1;
+  else
+    ret = emitter._events[type].length;
+  return ret;
 };
 
 function isFunction(arg) {
@@ -2331,4 +2399,4 @@ function isUndefined(arg) {
   return arg === void 0;
 }
 
-},{}]},{},[18]);
+},{}]},{},[19]);

From cfff86d1dbcdaf65c6035cada291f2b86d315ac8 Mon Sep 17 00:00:00 2001
From: Anton Dyudin 
Date: Tue, 2 Feb 2016 17:54:46 -0800
Subject: [PATCH 7/9] moved web/app/dojo back to web/dojo as a module

  https://github.com/urbit/sole@080b8b6
---
 web/app/dojo.hoon   |  37 ----
 web/dojo.hoon       |  34 ++++
 web/lib/js/sole.js  | 479 +++++++++++++++++++++++---------------------
 web/module/mod.hoon |   2 +-
 4 files changed, 290 insertions(+), 262 deletions(-)
 delete mode 100644 web/app/dojo.hoon
 create mode 100644 web/dojo.hoon

diff --git a/web/app/dojo.hoon b/web/app/dojo.hoon
deleted file mode 100644
index 5eba7b5bdb..0000000000
--- a/web/app/dojo.hoon
+++ /dev/null
@@ -1,37 +0,0 @@
-::    Console front-end
-::
-::::  /hook/hymn/dojo/web
-  ::
-/?    310
-|%
-++  cdnj  |=(a=tape ;script(src "//cdnjs.cloudflare.com/ajax/libs/{a}");)
---
-::
-::::
-  ::
-^-  manx
-;html
-  ;head 
-    ;title: Sole
-    ;*  %-  turn  :_  cdnj  ^-  wall
-        :~  "jquery/2.1.1/jquery.min.js"
-            "mousetrap/1.4.6/mousetrap.js"
-            "react/0.11.0/react.js"
-        ==
-    ;script@"/~~/~/at/lib/js/urb.js";
-    ;script: urb.appl = 'dojo'
-    ;style:'''
-           #term {
-             width: 100%;
-           }
-           #term * {
-             margin: 0px;
-           }
-           '''
-  ==
-  ;body
-    ;div#err;
-    ;div#term:""
-    ;script@"/lib/js/sole.js";
-  ==  
-==
diff --git a/web/dojo.hoon b/web/dojo.hoon
new file mode 100644
index 0000000000..6c0d1399e4
--- /dev/null
+++ b/web/dojo.hoon
@@ -0,0 +1,34 @@
+::    Console front-end
+::
+::::  /hook/hymn/dojo/web
+  ::
+/?    310
+|%
+++  cdnj  |=(a=tape ;script(src "//cdnjs.cloudflare.com/ajax/libs/{a}");)
+--
+::
+::::
+  ::
+^-  manx
+;module
+    =nav_title   "Sole"
+    =nav_subnav  "div"
+  ;*  %-  turn  :_  cdnj  ^-  wall
+      :~  :: "jquery/2.1.1/jquery.min.js"
+          "mousetrap/1.4.6/mousetrap.js"
+          :: "react/0.11.0/react.js"
+      ==
+  ::;script@"/~~/~/at/lib/js/urb.js";
+  ;style:'''
+         #term {
+           width: 100%;
+         }
+         #term * {
+           margin: 0px;
+         }
+         '''
+  ;div#err;
+  ;div#term:""
+  ;script@"/lib/js/sole.js";
+  ;sole(appl "dojo");
+==
diff --git a/web/lib/js/sole.js b/web/lib/js/sole.js
index 00b3829685..59286a96ae 100644
--- a/web/lib/js/sole.js
+++ b/web/lib/js/sole.js
@@ -1,35 +1,48 @@
 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o').text('m').css({
-    display: 'none'
-  }).appendTo(term).width();
-  subs = "";
-  flash = function($el, background) {
+TreeActions.registerComponent("sole", recl({
+  displayName: "Sole",
+  getInitialState: function() {
+    return {
+      rows: [],
+      appl: this.props.appl,
+      prompt: {
+        "": "# "
+      },
+      input: "",
+      cursor: 0,
+      history: [],
+      offset: 0,
+      error: ""
+    };
+  },
+  render: function() {
+    return div({}, div({
+      id: "err"
+    }, this.state.error), rele(Matr, this.state));
+  },
+  flash: function($el, background) {
     $el.css({
       background: background
     });
     if (background) {
-      return setTimeout((function() {
-        return flash($el, '');
-      }), 50);
+      return setTimeout(((function(_this) {
+        return function() {
+          return _this.flash($el, '');
+        };
+      })(this)), 50);
     }
-  };
-  bell = function() {
-    return flash($('body'), 'black');
-  };
-  matr = rend(Matr({
-    rows: [],
-    appl: "",
-    prompt: {
-      "": "# "
-    },
-    input: "",
-    cursor: 0,
-    history: [],
-    offset: 0
-  }), term);
-  window.matr = matr;
-  update = function(a) {
-    return matr.setProps(a);
-  };
-  buffer = {
-    "": new Share("")
-  };
-  window.buffer = buffer;
-  choose = function(appl) {
-    urb.appl = appl;
+  },
+  bell: function() {
+    return this.flash($('body'), 'black');
+  },
+  choose: function(appl) {
     if (buffer[appl] == null) {
       buffer[appl] = new Share("");
     }
-    updPrompt('', null);
-    return update({
+    this.updPrompt('', null);
+    return this.setState({
       appl: appl,
       cursor: 0,
       input: buffer[appl].buf
     });
-  };
-  print = function(txt) {
-    return update({
-      rows: slice.call(matr.props.rows).concat([txt])
+  },
+  print: function(txt) {
+    return this.setState({
+      rows: slice.call(this.state.rows).concat([txt])
     });
-  };
-  sync = function(ted, app) {
+  },
+  sync: function(ted, app) {
     var b;
     if (app == null) {
-      app = matr.props.appl;
+      app = this.state.appl;
     }
-    if (app !== matr.props.appl) {
-      return;
+    if (app === this.state.appl) {
+      b = buffer[app];
+      return this.setState({
+        input: b.buf,
+        cursor: b.transpose(ted, this.state.cursor)
+      });
     }
-    b = buffer[app];
-    return update({
-      input: b.buf,
-      cursor: b.transpose(ted, matr.props.cursor)
-    });
-  };
-  updPrompt = function(app, pro) {
+  },
+  updPrompt: function(app, pro) {
     var prompt;
-    prompt = $.extend({}, matr.props.prompt);
+    prompt = $.extend({}, this.state.prompt);
     if (pro != null) {
       prompt[app] = pro;
     } else {
       delete prompt[app];
     }
-    return update({
+    return this.setState({
       prompt: prompt
     });
-  };
-  sysStatus = function() {
+  },
+  sysStatus: function() {
     var app, k, pro, ref2, v;
-    return updPrompt('', ((ref2 = [
-      matr.props.appl, (function() {
+    return this.updPrompt('', ((ref2 = [
+      this.state.appl, (function() {
         var ref2, results;
-        ref2 = matr.props.prompt;
+        ref2 = this.state.prompt;
         results = [];
         for (k in ref2) {
           v = ref2[k];
@@ -134,47 +143,49 @@ $(function() {
           }
         }
         return results;
-      })()
+      }).call(this)
     ], app = ref2[0], pro = ref2[1], ref2), app === '' ? (pro.join(', ')) + '# ' : null));
-  };
-  peer = function(ruh, app) {
+  },
+  peer: function(ruh, app) {
     var mapr, v;
     if (app == null) {
-      app = urb.appl;
+      app = this.state.appl;
     }
     if (ruh.map) {
-      return ruh.map(function(rul) {
-        return peer(rul, app);
-      });
+      return ruh.map((function(_this) {
+        return function(rul) {
+          return _this.peer(rul, app);
+        };
+      })(this));
     }
-    mapr = matr.props;
+    mapr = this.state;
     switch (Object.keys(ruh)[0]) {
       case 'txt':
-        return print(ruh.txt);
+        return this.print(ruh.txt);
       case 'tan':
-        return ruh.tan.split("\n").map(print);
+        return ruh.tan.split("\n").map(this.print);
       case 'pro':
-        return updPrompt(app, ruh.pro.cad);
+        return this.updPrompt(app, ruh.pro.cad);
       case 'hop':
-        update({
+        this.setState({
           cursor: ruh.hop
         });
-        return bell();
+        return this.bell();
       case 'blk':
         return console.log("Stub " + (str(ruh)));
       case 'det':
         buffer[app].receive(ruh.det);
-        return sync(ruh.det.ted, app);
+        return this.sync(ruh.det.ted, app);
       case 'act':
         switch (ruh.act) {
           case 'clr':
-            return update({
+            return this.setState({
               rows: []
             });
           case 'bel':
-            return bell();
+            return this.bell();
           case 'nex':
-            return update({
+            return this.setState({
               input: "",
               cursor: 0,
               history: !mapr.input ? mapr.history : [mapr.input].concat(slice.call(mapr.history)),
@@ -186,111 +197,122 @@ $(function() {
         v = Object.keys(ruh);
         return console.log(v, ruh[v[0]]);
     }
-  };
-  join = function(app) {
-    if (matr.props.prompt[app] != null) {
-      return print('# already-joined: ' + app);
+  },
+  join: function(app) {
+    if (this.state.prompt[app] != null) {
+      return this.print('# already-joined: ' + app);
     }
-    choose(app);
+    this.choose(app);
     return urb.bind("/sole", {
+      appl: this.state.appl,
       wire: "/"
-    }, function(err, d) {
-      if (err) {
-        return console.log(err);
-      } else if (d.data) {
-        return peer(d.data, app);
-      }
-    });
-  };
-  cycle = function() {
+    }, (function(_this) {
+      return function(err, d) {
+        if (err) {
+          return console.log(err);
+        } else if (d.data) {
+          return _this.peer(d.data, app);
+        }
+      };
+    })(this));
+  },
+  cycle: function() {
     var apps, ref2;
-    apps = Object.keys(matr.props.prompt);
+    apps = Object.keys(this.state.prompt);
     if (apps.length < 2) {
       return;
     }
-    return choose((ref2 = apps[1 + apps.indexOf(urb.appl)]) != null ? ref2 : apps[0]);
-  };
-  part = function(appl) {
+    return this.choose((ref2 = apps[1 + apps.indexOf(this.state.appl)]) != null ? ref2 : apps[0]);
+  },
+  part: function(appl) {
     var mapr;
-    mapr = matr.props;
+    mapr = this.state;
     if (mapr.prompt[appl] == null) {
-      return print('# not-joined: ' + appl);
+      return this.print('# not-joined: ' + appl);
     }
     urb.drop("/sole", {
       appl: appl,
       wire: "/"
     });
     if (appl === mapr.appl) {
-      cycle();
+      this.cycle();
     }
-    updPrompt(appl, null);
-    return sysStatus();
-  };
-  join(urb.appl);
-  window.join = join;
-  window.part = part;
-  pressed = [];
-  deltim = null;
-  urb.send.mark = 'sole-action';
-  sendAction = function(data) {
-    var app;
-    if (matr.props.appl) {
-      return urb.send(data, function(e, res) {
-        if (res.status !== 200) {
-          return $('#err')[0].innerText = res.data.mess;
-        }
-      });
+    this.updPrompt(appl, null);
+    return this.sysStatus();
+  },
+  componentWillUnmount: function() {
+    return this.mousetrapStop();
+  },
+  componentDidMount: function() {
+    this.mousetrapInit();
+    return this.join(this.state.appl);
+  },
+  sendAction: function(data) {
+    var app, appl;
+    appl = this.state.appl;
+    if (appl) {
+      return urb.send(data, {
+        appl: appl,
+        mark: 'sole-action'
+      }, (function(_this) {
+        return function(e, res) {
+          if (res.status !== 200) {
+            return _this.setState({
+              error: res.data.mess
+            });
+          }
+        };
+      })(this));
     } else if (data === 'ret') {
       app = /^[a-z-]+$/.exec(buffer[""].buf.slice(1));
       if (!((app != null) && (app[0] != null))) {
-        return bell();
+        return this.bell();
       } else {
         switch (buffer[""].buf[0]) {
           case '+':
-            doEdit({
+            this.doEdit({
               set: ""
             });
-            return join(app[0]);
+            return this.join(app[0]);
           case '-':
-            doEdit({
+            this.doEdit({
               set: ""
             });
-            return part(app[0]);
+            return this.part(app[0]);
           default:
-            return bell();
+            return this.bell();
         }
       }
     }
-  };
-  doEdit = function(ted) {
+  },
+  doEdit: function(ted) {
     var det;
-    det = buffer[matr.props.appl].transmit(ted);
-    sync(ted);
-    return sendAction({
+    det = buffer[this.state.appl].transmit(ted);
+    this.sync(ted);
+    return this.sendAction({
       det: det
     });
-  };
-  yank = '';
-  eatKyev = function(mod, key) {
+  },
+  eatKyev: function(mod, key) {
     var _, appl, cha, cursor, history, input, mapr, n, offset, prev, ref2, ref3, rest;
-    mapr = matr.props;
+    mapr = this.state;
     switch (mod.sort().join('-')) {
       case '':
       case 'shift':
         if (key.str) {
-          doEdit({
+          this.doEdit({
             ins: {
               cha: key.str,
               at: mapr.cursor
             }
           });
-          update({
+          this.setState({
             cursor: mapr.cursor + 1
           });
         }
         switch (key.act) {
           case 'entr':
-            return sendAction('ret');
+            return this.sendAction('ret');
           case 'up':
             history = mapr.history.slice();
             offset = mapr.offset;
@@ -299,10 +321,10 @@ $(function() {
             }
             ref2 = [history[offset], mapr.input], input = ref2[0], history[offset] = ref2[1];
             offset++;
-            doEdit({
+            this.doEdit({
               set: input
             });
-            return update({
+            return this.setState({
               offset: offset,
               history: history,
               cursor: input.length
@@ -315,31 +337,31 @@ $(function() {
               return;
             }
             ref3 = [history[offset], mapr.input], input = ref3[0], history[offset] = ref3[1];
-            doEdit({
+            this.doEdit({
               set: input
             });
-            return update({
+            return this.setState({
               offset: offset,
               history: history,
               cursor: input.length
             });
           case 'left':
             if (mapr.cursor > 0) {
-              return update({
+              return this.setState({
                 cursor: mapr.cursor - 1
               });
             }
             break;
           case 'right':
             if (mapr.cursor < mapr.input.length) {
-              return update({
+              return this.setState({
                 cursor: mapr.cursor + 1
               });
             }
             break;
           case 'baxp':
             if (mapr.cursor > 0) {
-              return doEdit({
+              return this.doEdit({
                 del: mapr.cursor - 1
               });
             }
@@ -349,61 +371,61 @@ $(function() {
         switch (key.str || key.act) {
           case 'a':
           case 'left':
-            return update({
+            return this.setState({
               cursor: 0
             });
           case 'e':
           case 'right':
-            return update({
+            return this.setState({
               cursor: mapr.input.length
             });
           case 'l':
-            return update({
+            return this.setState({
               rows: []
             });
           case 'entr':
-            return bell();
+            return this.bell();
           case 'w':
-            return eatKyev(['alt'], {
+            return this.eatKyev(['alt'], {
               act: 'baxp'
             });
           case 'p':
-            return eatKyev([], {
+            return this.eatKyev([], {
               act: 'up'
             });
           case 'n':
-            return eatKyev([], {
+            return this.eatKyev([], {
               act: 'down'
             });
           case 'b':
-            return eatKyev([], {
+            return this.eatKyev([], {
               act: 'left'
             });
           case 'f':
-            return eatKyev([], {
+            return this.eatKyev([], {
               act: 'right'
             });
           case 'g':
-            return bell();
+            return this.bell();
           case 'x':
-            return cycle();
+            return this.cycle();
           case 'v':
-            appl = mapr.appl !== '' ? '' : urb.appl;
-            update({
+            appl = mapr.appl !== '' ? '' : this.state.appl;
+            this.setState({
               appl: appl,
               cursor: 0,
               input: buffer[appl].buf
             });
-            return sysStatus();
+            return this.sysStatus();
           case 't':
             if (mapr.cursor === 0 || mapr.input.length < 2) {
-              return bell();
+              return this.bell();
             }
             cursor = mapr.cursor;
             if (cursor < mapr.input.length) {
               cursor++;
             }
-            doEdit([
+            this.doEdit([
               {
                 del: cursor - 1
               }, {
@@ -413,12 +435,12 @@ $(function() {
                 }
               }
             ]);
-            return update({
+            return this.setState({
               cursor: cursor
             });
           case 'u':
-            yank = mapr.input.slice(0, mapr.cursor);
-            return doEdit((function() {
+            this.yank = mapr.input.slice(0, mapr.cursor);
+            return this.doEdit((function() {
               var i, ref4, results;
               results = [];
               for (n = i = 1, ref4 = mapr.cursor; 1 <= ref4 ? i <= ref4 : i >= ref4; n = 1 <= ref4 ? ++i : --i) {
@@ -429,8 +451,8 @@ $(function() {
               return results;
             })());
           case 'k':
-            yank = mapr.input.slice(mapr.cursor);
-            return doEdit((function() {
+            this.yank = mapr.input.slice(mapr.cursor);
+            return this.doEdit((function() {
               var i, ref4, ref5, results;
               results = [];
               for (_ = i = ref4 = mapr.cursor, ref5 = mapr.input.length; ref4 <= ref5 ? i < ref5 : i > ref5; _ = ref4 <= ref5 ? ++i : --i) {
@@ -441,11 +463,12 @@ $(function() {
               return results;
             })());
           case 'y':
-            return doEdit((function() {
-              var i, len, results;
+            return this.doEdit((function() {
+              var i, len, ref4, ref5, results;
+              ref5 = (ref4 = this.yank) != null ? ref4 : '';
               results = [];
-              for (n = i = 0, len = yank.length; i < len; n = ++i) {
-                cha = yank[n];
+              for (n = i = 0, len = ref5.length; i < len; n = ++i) {
+                cha = ref5[n];
                 results.push({
                   ins: {
                     cha: cha,
@@ -454,7 +477,7 @@ $(function() {
                 });
               }
               return results;
-            })());
+            }).call(this));
           default:
             return console.log(mod, str(key));
         }
@@ -465,7 +488,7 @@ $(function() {
           case 'right':
             rest = mapr.input.slice(mapr.cursor);
             rest = rest.match(/\W*\w*/)[0];
-            return update({
+            return this.setState({
               cursor: mapr.cursor + rest.length
             });
           case 'b':
@@ -473,15 +496,15 @@ $(function() {
             prev = mapr.input.slice(0, mapr.cursor);
             prev = prev.split('').reverse().join('');
             prev = prev.match(/\W*\w*/)[0];
-            return update({
+            return this.setState({
               cursor: mapr.cursor - prev.length
             });
           case 'baxp':
             prev = mapr.input.slice(0, mapr.cursor);
             prev = prev.split('').reverse().join('');
             prev = prev.match(/\W*\w*/)[0];
-            yank = prev;
-            return doEdit((function() {
+            this.yank = prev;
+            return this.doEdit((function() {
               var i, len, results;
               results = [];
               for (n = i = 0, len = prev.length; i < len; n = ++i) {
@@ -497,53 +520,61 @@ $(function() {
       default:
         return console.log(mod, str(key));
     }
-  };
-  return Mousetrap.handleKey = function(char, mod, e) {
-    var chac, key, norm, ref2;
-    norm = {
-      capslock: 'caps',
-      pageup: 'pgup',
-      pagedown: 'pgdn',
-      backspace: 'baxp',
-      enter: 'entr'
-    };
-    key = (function() {
-      var ref2;
-      switch (false) {
-        case char.length !== 1:
-          if (e.type === 'keypress') {
-            chac = char.charCodeAt(0);
-            if (chac < 32) {
-              char = String.fromCharCode(chac | 96);
-            }
-            return {
-              str: char
-            };
+  },
+  mousetrapStop: function() {
+    return Mousetrap.handleKey = this._defaultHandleKey;
+  },
+  mousetrapInit: function() {
+    this._defaultHandleKey = Mousetrap.handleKey;
+    return Mousetrap.handleKey = (function(_this) {
+      return function(char, mod, e) {
+        var chac, key, norm, ref2;
+        norm = {
+          capslock: 'caps',
+          pageup: 'pgup',
+          pagedown: 'pgdn',
+          backspace: 'baxp',
+          enter: 'entr'
+        };
+        key = (function() {
+          var ref2;
+          switch (false) {
+            case char.length !== 1:
+              if (e.type === 'keypress') {
+                chac = char.charCodeAt(0);
+                if (chac < 32) {
+                  char = String.fromCharCode(chac | 96);
+                }
+                return {
+                  str: char
+                };
+              }
+              break;
+            case e.type !== 'keydown':
+              if (char !== 'space') {
+                return {
+                  act: (ref2 = norm[char]) != null ? ref2 : char
+                };
+              }
+              break;
+            case !(e.type === 'keyup' && norm[key] === 'caps'):
+              return {
+                act: 'uncap'
+              };
           }
-          break;
-        case e.type !== 'keydown':
-          if (char !== 'space') {
-            return {
-              act: (ref2 = norm[char]) != null ? ref2 : char
-            };
-          }
-          break;
-        case !(e.type === 'keyup' && norm[key] === 'caps'):
-          return {
-            act: 'uncap'
-          };
-      }
-    })();
-    if (!key) {
-      return;
-    }
-    if (key.act && (ref2 = key.act, indexOf.call(mod, ref2) >= 0)) {
-      return;
-    }
-    e.preventDefault();
-    return eatKyev(mod, key);
-  };
-});
+        })();
+        if (!key) {
+          return;
+        }
+        if (key.act && (ref2 = key.act, indexOf.call(mod, ref2) >= 0)) {
+          return;
+        }
+        e.preventDefault();
+        return _this.eatKyev(mod, key);
+      };
+    })(this);
+  }
+}));
 
 
 },{"./share.coffee":2}],2:[function(require,module,exports){
diff --git a/web/module/mod.hoon b/web/module/mod.hoon
index 226dc0d542..eb3efaf0ee 100644
--- a/web/module/mod.hoon
+++ b/web/module/mod.hoon
@@ -1,4 +1,4 @@
-;module(nav_title "Talk", nav_dpad "", nav_sibs "", nav_subnav "talk-station")
+;module(nav_title "Talk", nav_no-dpad "", nav_no-sibs "", nav_subnav "talk-station")
   ;script@"/~~/~/at/lib/urb.js";
   ;script@"/talk/main.js";
   ;link/"/talk/main.css"(rel "stylesheet");

From 5316ca03d8748fb97be992a93be8ec46568dac8e Mon Sep 17 00:00:00 2001
From: Anton Dyudin 
Date: Tue, 2 Feb 2016 17:56:37 -0800
Subject: [PATCH 8/9] exposed urb.waspElem, urb.waspURL

  https://github.com/urbit/tree@3b360ff
---
 arvo/eyre.hoon   | 33 ++++++++++++++++++++-------------
 web/tree/main.js |  9 ++++-----
 2 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/arvo/eyre.hoon b/arvo/eyre.hoon
index a0cbdfc87e..b120459dfe 100644
--- a/arvo/eyre.hoon
+++ b/arvo/eyre.hoon
@@ -441,19 +441,26 @@
   ++  etag
     '''
     if(!window.urb) window.urb = {}
-    urb.waspFrom = function(sel,attr){
-      Array.prototype.map.call(document.querySelectorAll(sel), 
-        function(ele){
-          if(!ele[attr] || (new URL(ele[attr])).host != document.location.host)
-            return;
-          var xhr = new XMLHttpRequest()
-          xhr.open("HEAD", ele[attr])
-          xhr.send()
-          xhr.onload = function(){
-            var dep = this.getResponseHeader("etag")
-            if(dep) urb.wasp(JSON.parse(dep.substr(2)))
-    }})}
-    if(urb.wasp){urb.waspFrom('script','src'); urb.waspFrom('link','href')}
+    urb.waspAll = function(sel){
+      Array.prototype.map.call(document.querySelectorAll(sel), urb.waspElem)
+    }
+    urb.waspElem = function(ele){
+      url = ele.src || ele.href
+      if(!url || (new URL(url)).host != document.location.host)
+        return;
+      urb.waspUrl(url)
+    }
+    urb.waspUrl = function(url){
+      var xhr = new XMLHttpRequest()
+      xhr.open("HEAD", url)
+      xhr.send()
+      xhr.onload = urb.waspLoadedXHR
+    }
+    urb.waspLoadedXHR = function(){
+      var dep = this.getResponseHeader("etag")
+      if(dep) urb.wasp(JSON.parse(dep.substr(2)))
+    }
+    if(urb.wasp){urb.waspAll('script'); urb.waspAll('link')}
     '''
   --
 ++  xml
diff --git a/web/tree/main.js b/web/tree/main.js
index 517346e47f..04f673d9ac 100644
--- a/web/tree/main.js
+++ b/web/tree/main.js
@@ -38,7 +38,6 @@ module.exports = {
     ));
   },
   addVirtual: function(components) {
-    console.log("addVirtual", components);
     return TreeDispatcher.handleViewAction({
       type: "addVirtual",
       components: components
@@ -329,12 +328,11 @@ module.exports = query({
       kids.push(reactify({
         gn: this.state.subnav,
         ga: {
-          key: "subnav",
           open: this.state.open,
           toggle: TreeActions.toggleNav
         },
         c: []
-      }));
+      }, "subnav"));
     }
     return div({}, kids);
   }
@@ -1073,8 +1071,8 @@ module.exports = recl({
       return function() {
         return TreeActions.setNav({
           title: _this.props["nav:title"],
-          dpad: !!_this.props["nav:dpad"],
-          sibs: !!_this.props["nav:sibs"],
+          dpad: _this.props["nav:no-dpad"] != null ? false : void 0,
+          sibs: _this.props["nav:no-sibs"] != null ? false : void 0,
           subnav: _this.props["nav:subnav"]
         }, 0);
       };
@@ -1194,6 +1192,7 @@ module.exports = recl({
     var s;
     s = document.createElement('script');
     _.assign(s, this.props);
+    urb.waspElem(s);
     document.body.appendChild(s);
     return this.js = s;
   },

From 4593025ec1f429d4f8ee7c66d751903379d11315 Mon Sep 17 00:00:00 2001
From: Anton Dyudin 
Date: Tue, 2 Feb 2016 18:06:39 -0800
Subject: [PATCH 9/9] moved single-page apps to web/app

---
 web/{ => app}/bit.hoon       | 0
 web/{ => app}/cloud.hoon     | 4 ++--
 web/{ => app}/cloud/main.css | 0
 web/{ => app}/cloud/main.js  | 0
 web/{ => app}/coin.hoon      | 0
 web/{ => app}/oct3.hoon      | 4 ++--
 web/{ => app}/oct3/main.css  | 0
 web/{ => app}/oct3/main.js   | 0
 web/{ => app}/paste.hoon     | 2 +-
 web/{ => app}/paste/main.css | 0
 10 files changed, 5 insertions(+), 5 deletions(-)
 rename web/{ => app}/bit.hoon (100%)
 rename web/{ => app}/cloud.hoon (86%)
 rename web/{ => app}/cloud/main.css (100%)
 rename web/{ => app}/cloud/main.js (100%)
 rename web/{ => app}/coin.hoon (100%)
 rename web/{ => app}/oct3.hoon (90%)
 rename web/{ => app}/oct3/main.css (100%)
 rename web/{ => app}/oct3/main.js (100%)
 rename web/{ => app}/paste.hoon (96%)
 rename web/{ => app}/paste/main.css (100%)

diff --git a/web/bit.hoon b/web/app/bit.hoon
similarity index 100%
rename from web/bit.hoon
rename to web/app/bit.hoon
diff --git a/web/cloud.hoon b/web/app/cloud.hoon
similarity index 86%
rename from web/cloud.hoon
rename to web/app/cloud.hoon
index 27efbd6f81..0ad743803f 100644
--- a/web/cloud.hoon
+++ b/web/app/cloud.hoon
@@ -16,7 +16,7 @@
     ;script: urb.appl = 'cloud'
     ;script@"https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js";
     ;script@"https://cdnjs.cloudflare.com/ajax/libs/react/0.12.2/react.js";
-    ::;link/"/cloud/main.css"(rel "stylesheet");
+    ::;link/"cloud/main.css"(rel "stylesheet");
     ;title: DO & GCE Manager
   ==
   ;body
@@ -25,6 +25,6 @@
           authcode.do='{?~(do ~ (trip u.do))}'
           """
     ;div#container;
-    ;script@"/cloud/main.js";
+    ;script@"cloud/main.js";
   ==
 ==
diff --git a/web/cloud/main.css b/web/app/cloud/main.css
similarity index 100%
rename from web/cloud/main.css
rename to web/app/cloud/main.css
diff --git a/web/cloud/main.js b/web/app/cloud/main.js
similarity index 100%
rename from web/cloud/main.js
rename to web/app/cloud/main.js
diff --git a/web/coin.hoon b/web/app/coin.hoon
similarity index 100%
rename from web/coin.hoon
rename to web/app/coin.hoon
diff --git a/web/oct3.hoon b/web/app/oct3.hoon
similarity index 90%
rename from web/oct3.hoon
rename to web/app/oct3.hoon
index 8e6cd19bfd..6d1d657fe2 100644
--- a/web/oct3.hoon
+++ b/web/app/oct3.hoon
@@ -24,7 +24,7 @@
     ;link
       =type  "text/css"
       =rel   "stylesheet"
-      =href  "/oct3/main.css"
+      =href  "oct3/main.css"
       ;
     ==
     ;title: :oct3
@@ -43,6 +43,6 @@
     ==
     ;div#bord;
     ;div#audi;
-    ;script(type "text/javascript", src "/oct3/main.js");
+    ;script(type "text/javascript", src "oct3/main.js");
   ==
 ==
diff --git a/web/oct3/main.css b/web/app/oct3/main.css
similarity index 100%
rename from web/oct3/main.css
rename to web/app/oct3/main.css
diff --git a/web/oct3/main.js b/web/app/oct3/main.js
similarity index 100%
rename from web/oct3/main.js
rename to web/app/oct3/main.js
diff --git a/web/paste.hoon b/web/app/paste.hoon
similarity index 96%
rename from web/paste.hoon
rename to web/app/paste.hoon
index 8d99b09363..ed9b4410ce 100644
--- a/web/paste.hoon
+++ b/web/app/paste.hoon
@@ -3,7 +3,7 @@
   ;head:title:"Pastebin"
   ;body
     ;link(rel "stylesheet", href "/lib/base.css");
-    ;link(rel "stylesheet", href "/paste/main.css");
+    ;link(rel "stylesheet", href "paste/main.css");
     ;script@"//code.jquery.com/jquery-2.1.4.min.js";
     ;script@"/~/as/own/~/at/lib/js/urb.js";
     ;script:'''
diff --git a/web/paste/main.css b/web/app/paste/main.css
similarity index 100%
rename from web/paste/main.css
rename to web/app/paste/main.css