on master help sign in
Got some things working
commit3de0c04bce19a73eee405e9ce104bdc904efcf22 authoriko <mail@iko.soy> dateWed 2 Aug 2023, 21:06 (3 years ago) parentb8fd2ca
11 files changed+586−11
dev/elm-live.sh+7 frontend/elm.json+51 frontend/fetch-event-source.js+2 frontend/index.html+18 frontend/script.js+42 frontend/src/Main.elm+279 frontend/src/Pages/Search.elm+149 frontend/src/Route.elm+28 zod/seax/app/seax.hoon+5−5 zod/seax/lib/rank.hoon+4−5 zod/seax/lib/sink.hoon+1−1
dev/elm-live.shnew file+7
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/../frontend"
6+
7+nix run nixpkgs#elmPackages.elm-live -- src/Main.elm --start-page=index.html -- --output=elm.js "$@"
frontend/elm.jsonnew file+51
@@ -0,0 +1,51 @@
1+{
2+ "type": "application",
3+ "source-directories": [
4+ "src",
5+ "../../airlock/src"
6+ ],
7+ "elm-version": "0.19.1",
8+ "dependencies": {
9+ "direct": {
10+ "TSFoster/elm-bytes-extra": "1.3.0",
11+ "cmditch/elm-bigint": "2.0.1",
12+ "elm/browser": "1.0.2",
13+ "elm/bytes": "1.0.8",
14+ "elm/core": "1.0.5",
15+ "elm/html": "1.0.0",
16+ "elm/http": "2.0.0",
17+ "elm/json": "1.1.3",
18+ "elm/svg": "1.0.1",
19+ "elm/time": "1.0.0",
20+ "elm/url": "1.0.0",
21+ "elm-community/list-extra": "8.7.0",
22+ "elm-community/maybe-extra": "5.3.0",
23+ "figbus/elm-urbit-api": "4.0.1",
24+ "jasonliang-dev/elm-heroicons": "2.0.0",
25+ "jxxcarlson/hex": "4.0.0",
26+ "mdgriffith/elm-animator": "1.1.1",
27+ "mdgriffith/elm-ui": "1.1.8",
28+ "rtfeldman/elm-hex": "1.0.0",
29+ "toastal/either": "3.6.3"
30+ },
31+ "indirect": {
32+ "AdrianRibao/elm-derberos-date": "1.2.3",
33+ "avh4/elm-color": "1.0.0",
34+ "bitsoflogic/elm-radixint": "2.0.0",
35+ "elm/file": "1.0.5",
36+ "elm/parser": "1.1.0",
37+ "elm/random": "1.0.0",
38+ "elm/regex": "1.0.0",
39+ "elm/virtual-dom": "1.0.3",
40+ "ianmackenzie/elm-units": "2.10.0",
41+ "ktonon/elm-word": "2.1.2",
42+ "robinheghan/murmur3": "1.0.0"
43+ }
44+ },
45+ "test-dependencies": {
46+ "direct": {},
47+ "indirect": {
48+ "chelovek0v/bbase64": "1.0.1"
49+ }
50+ }
51+}
frontend/fetch-event-source.jsnew file+2
@@ -0,0 +1,2 @@
1+var m=class extends Error{};async function A(r,e,t){let n=r.getReader(),o={done:!1,value:new Uint8Array};for(;o&&!o.done;)o=await Promise.race([n.read(),new Promise((g,i)=>{setTimeout(()=>i(new Error("getBytes timed out")),t)})]),e(o.value)}function R(r){let e,t,n,o=!1;return function(i){e===void 0?(e=i,t=0,n=-1):e=L(e,i);let s=e.length,a=0;for(;t<s;){o&&(e[t]===10&&(a=++t),o=!1);let c=-1;for(;t<s&&c===-1;++t)switch(e[t]){case 58:n===-1&&(n=t-a);break;case 13:o=!0;case 10:c=t;break}if(c===-1)break;r(e.subarray(a,c),n),a=t,n=-1}a===s?e=void 0:a!==0&&(e=e.subarray(a),t-=a)}}function P(r,e,t){let n=k(),o=new TextDecoder;return function(i,s){if(i.length===0)r?.(n),n=k();else if(s>0){let a=o.decode(i.subarray(0,s)),c=s+(i[s+1]===32?2:1),p=o.decode(i.subarray(c));switch(a){case"data":n.data=n.data?n.data+`
2+`+p:p;break;case"event":n.event=p;break;case"id":e?.(n.id=p);break;case"retry":let l=parseInt(p,10);isNaN(l)||t?.(n.retry=l);break}}}}function L(r,e){let t=new Uint8Array(r.length+e.length);return t.set(r),t.set(e,r.length),t}function k(){return{data:"",event:"",id:"",retry:void 0}}var b="text/event-stream",N=1e3,T="last-event-id";function q(r,{signal:e,headers:t,onopen:n,onmessage:o,onclose:g,onerror:i,openWhenHidden:s,fetch:a,responseTimeout:c,...p}){return new Promise((l,M)=>{let y={...t};y.accept||(y.accept=b);let f;function w(){f.abort(),document.hidden||E()}typeof document<"u"&&!s&&document.addEventListener("visibilitychange",w);let S=N,h;function x(){typeof document<"u"&&!s&&document.removeEventListener("visibilitychange",w),clearTimeout(h),f.abort()}e?.addEventListener("abort",()=>{x(),l()});let I=a??fetch,U=n??O,v=!1;async function E(){f=new AbortController;try{let u=await Promise.race([I(r,{...p,headers:y,signal:f.signal}),new Promise((d,C)=>{setTimeout(()=>C(new Error("fetch timed out")),c)})]);if(u.status===404)throw new m("Channel reaped");if(u.status<200||u.status>=300)throw new Error(`Invalid server response: ${u.status}`);await U(u,v),v&&(v=!1),await A(u.body,R(P(o,d=>{d?y[T]=d:delete y[T]},d=>{S=d})),c),g?.(),x(),l()}catch(u){if(!f.signal.aborted)try{v=!0;let d=i?.(u)??S;clearTimeout(h),f.abort(),h=setTimeout(E,d)}catch(d){x(),M(d)}}}E()})}function O(r){let e=r.headers.get("content-type");if(!e?.startsWith(b))throw new Error(`Expected content-type to be ${b}, Actual: ${e}`)}export{b as EventStreamContentType,q as fetchEventSource};
frontend/index.htmlnew file+18
@@ -0,0 +1,18 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="UTF-8" />
5+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+ <style>
7+ body {
8+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
9+ }
10+ </style>
11+ </head>
12+ <body>
13+ <div id="elm"></div>
14+ <script src="/elm.js"></script>
15+ <script type="module" src="/script.js"></script>
16+ </body>
17+</html>
18+
frontend/script.jsnew file+42
@@ -0,0 +1,42 @@
1+import { fetchEventSource } from "/fetch-event-source.js"
2+
3+const uid = `${Math.floor(Date.now() / 1000)}-${Math.random()}`;
4+
5+function searchForInit(obj) {
6+ if (obj.init) {
7+ return obj.init;
8+ } else {
9+ for (let key in obj) {
10+ if (obj.hasOwnProperty(key)) {
11+ const result = searchForInit(obj[key]);
12+ if (result) {
13+ return result;
14+ }
15+ }
16+ }
17+ }
18+}
19+
20+let app = searchForInit(Elm)({ node: document.getElementById("elm"), flags: { uid } });
21+
22+app.ports.createEventSource.subscribe((url) => {
23+ fetchEventSource(url, {
24+ headers: {
25+ Accept: 'application/x-urb-jam',
26+ "x-channel-format": 'application/x-urb-jam',
27+ "content-type": 'application/x-urb-jam'
28+ },
29+ credentials: 'include',
30+ responseTimeout: 25000,
31+ openWhenHidden: true,
32+ onmessage(ev) {
33+ console.log(ev)
34+ app.ports.onEventSourceMessage.send({ message: ev.data });
35+ },
36+ onerror(err) {
37+ console.log(err)
38+ app.ports.onEventSourceMessage.send({ error: err });
39+ }
40+ });
41+
42+});
frontend/src/Main.elmnew file+279
@@ -0,0 +1,279 @@
1+port module Main exposing (main)
2+
3+import Animator
4+import Dict exposing (Dict)
5+import Element exposing (..)
6+import Element.Background as Background
7+import Element.Border as Border
8+import Element.Events as Events
9+import Element.Font as Font
10+import Element.Input as Input
11+import Element.Keyed as Keyed
12+import Heroicons.Outline
13+import Html.Attributes
14+import Html.Events
15+import Json.Decode as JD
16+import List.Extra
17+import Svg.Attributes exposing (opacity)
18+import Time exposing (Posix)
19+import Ur
20+import Ur.Cmd
21+import Ur.Deconstructor as D
22+import Ur.Run
23+import Ur.Sub
24+
25+
26+url : String
27+url =
28+ "http://localhost:8080"
29+
30+
31+type alias Model =
32+ { ship : Maybe String
33+ , search : String
34+ , initiatedSearch : Maybe String
35+ , searchResults : List SearchResult
36+ , resultTimelines : Dict String ( Animator.Timeline Int, Animator.Timeline Bool )
37+ }
38+
39+
40+type alias SearchResult =
41+ { title : String
42+ , link : String
43+ }
44+
45+
46+type Msg
47+ = Noop
48+ | GotShipName String
49+ | UpdateSearch String
50+ | Search String
51+ | UpdateSearchResults (List SearchResult)
52+ | OpenResult String
53+ | Tick Posix
54+
55+
56+main : Ur.Run.Program Model Msg
57+main =
58+ Ur.Run.document
59+ { init =
60+ ( { ship = Nothing
61+ , search = ""
62+ , initiatedSearch = Nothing
63+ , searchResults = []
64+ , resultTimelines = Dict.empty
65+ }
66+ , Cmd.batch
67+ [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
68+ |> Cmd.map (always Noop)
69+ , Ur.getShipName url |> Cmd.map (result (always Noop) GotShipName)
70+ ]
71+ |> Ur.Cmd.cmd
72+ )
73+ , update = update
74+ , view = \model -> { title = "%seax", body = [ view model |> layout [] ] }
75+ , subscriptions = \model -> Animator.toSubscription Tick model (animator model)
76+ , createEventSource = createEventSource
77+ , urbitSubscriptions =
78+ \{ initiatedSearch, ship } ->
79+ case ( initiatedSearch, ship ) of
80+ ( Just query, Just ship_ ) ->
81+ Ur.Sub.sink
82+ { app = "seax"
83+ , path = [ "search", query ]
84+ , ship = ship_
85+ , deconstructor =
86+ D.list (D.cell D.cord D.cord)
87+ |> D.map
88+ (List.map
89+ (\( title, link ) ->
90+ { title = title, link = link }
91+ )
92+ >> UpdateSearchResults
93+ )
94+ }
95+
96+ _ ->
97+ Ur.Sub.none
98+ , onEventSourceMsg = onEventSourceMessage
99+ , urbitUrl = \_ -> url
100+ }
101+
102+
103+view : Model -> Element Msg
104+view model =
105+ case model.initiatedSearch of
106+ Nothing ->
107+ column [ centerX, centerY, padding 8, spacing 16 ]
108+ [ el [ centerX, Font.size 67, Font.heavy ] (text "%seax")
109+ , searchView model
110+ ]
111+
112+ Just query ->
113+ column [ width fill, padding 8, spacing 16 ]
114+ [ searchView model
115+ , Keyed.column [ width fill ]
116+ (model.searchResults
117+ |> List.map
118+ (\{ title, link } ->
119+ ( link
120+ , column
121+ [ spacing 4
122+ , width fill
123+ , pointer
124+ , alpha
125+ (Animator.move
126+ (Dict.get link model.resultTimelines
127+ |> Maybe.map Tuple.second
128+ |> Maybe.withDefault (Animator.init False)
129+ )
130+ (\x ->
131+ if x then
132+ Animator.at 1
133+
134+ else
135+ Animator.at 0
136+ )
137+ )
138+ , Html.Attributes.style "top"
139+ ((Animator.move
140+ (Dict.get link model.resultTimelines
141+ |> Maybe.map Tuple.first
142+ |> Maybe.withDefault (Animator.init 0)
143+ )
144+ (\i -> toFloat i * 47 |> Animator.at)
145+ |> String.fromFloat
146+ )
147+ ++ "px"
148+ )
149+ |> htmlAttribute
150+ , Html.Attributes.style "position" "absolute" |> htmlAttribute
151+ ]
152+ [ [ text title ]
153+ |> paragraph
154+ [ Font.size 16
155+ , width fill
156+ , Html.Attributes.style "text-overflow" "ellipsis" |> htmlAttribute
157+ ]
158+ , [ text link ]
159+ |> paragraph
160+ [ Font.size 12
161+ , Html.Attributes.style "text-overflow" "ellipsis" |> htmlAttribute
162+ , width fill
163+ , Font.color (rgb 0.7 0.7 0.7)
164+ ]
165+ ]
166+ )
167+ )
168+ )
169+ ]
170+
171+
172+searchView : { a | search : String } -> Element Msg
173+searchView model =
174+ row [ spacing 8 ]
175+ [ Input.text []
176+ { onChange = UpdateSearch
177+ , placeholder = Nothing
178+ , text = model.search
179+ , label = Input.labelHidden "search"
180+ }
181+ , Input.button [ height fill ]
182+ { label =
183+ text "search"
184+ |> el
185+ [ Border.width 4
186+ , padding 4
187+ , Font.size 29
188+ , Font.bold
189+ , Border.color (rgb 0 0 0)
190+ , Background.color (rgb 0 0 0)
191+ , Font.color (rgb 1 1 1)
192+ , mouseOver
193+ [ Font.color (rgb 0 0 0)
194+ , Background.color (rgb 1 1 1)
195+ ]
196+ ]
197+ , onPress = Just (Search model.search)
198+ }
199+ ]
200+
201+
202+update : Msg -> Model -> ( Model, Ur.Cmd.Cmd Msg )
203+update msg model =
204+ case msg of
205+ Noop ->
206+ ( model, Ur.Cmd.none )
207+
208+ GotShipName ship ->
209+ ( { model | ship = Just ship }, Ur.Cmd.none )
210+
211+ UpdateSearch search ->
212+ ( { model | search = search }, Ur.Cmd.none )
213+
214+ Search query ->
215+ ( { model | initiatedSearch = Just query }, Ur.Cmd.none )
216+
217+ UpdateSearchResults results ->
218+ let
219+ foo =
220+ List.Extra.zip (List.range 0 (List.length results - 1)) results
221+ |> List.map
222+ (\( index, result_ ) ->
223+ ( result_.link
224+ , case Dict.get result_.link model.resultTimelines of
225+ Just timeline ->
226+ timeline |> Tuple.mapFirst (Animator.go Animator.slowly index)
227+
228+ Nothing ->
229+ ( Animator.init index, Animator.init False |> Animator.go Animator.slowly True )
230+ )
231+ )
232+ |> Dict.fromList
233+ in
234+ ( { model | resultTimelines = foo, searchResults = results }, Ur.Cmd.none )
235+
236+ OpenResult url_ ->
237+ ( model, Ur.Cmd.none )
238+
239+ Tick time ->
240+ ( Animator.update time (animator model) model, Ur.Cmd.none )
241+
242+
243+animator : Model -> Animator.Animator Model
244+animator model =
245+ List.foldl
246+ (\key anim ->
247+ anim
248+ |> Animator.watchingWith
249+ (\m -> Dict.get key m.resultTimelines |> Maybe.map Tuple.first |> Maybe.withDefault (Animator.init 0))
250+ (\index m ->
251+ { m | resultTimelines = m.resultTimelines |> Dict.update key (Maybe.map (\( _, x ) -> ( index, x ))) }
252+ )
253+ (always False)
254+ |> Animator.watchingWith
255+ (\m -> Dict.get key m.resultTimelines |> Maybe.map Tuple.second |> Maybe.withDefault (Animator.init False))
256+ (\shown m ->
257+ { m | resultTimelines = m.resultTimelines |> Dict.update key (Maybe.map (\( x, _ ) -> ( x, shown ))) }
258+ )
259+ (always False)
260+ -- |> Animator.watching
261+ )
262+ Animator.animator
263+ (Dict.keys model.resultTimelines)
264+
265+
266+result : (a -> c) -> (b -> c) -> Result a b -> c
267+result f g res =
268+ case res of
269+ Ok b ->
270+ g b
271+
272+ Err a ->
273+ f a
274+
275+
276+port createEventSource : String -> Cmd msg
277+
278+
279+port onEventSourceMessage : (JD.Value -> msg) -> Sub msg
frontend/src/Pages/Search.elmnew file+149
@@ -0,0 +1,149 @@
1+port module Pages.Search exposing (Model, init, update, view)
2+
3+import Element exposing (..)
4+import Element.Background as Background
5+import Element.Border as Border
6+import Element.Events as Events
7+import Element.Font as Font
8+import Element.Input as Input
9+import Heroicons.Outline
10+import Html.Events
11+import Json.Decode as JD
12+import Svg.Attributes
13+import Ur
14+import Ur.Cmd
15+import Ur.Run
16+import Ur.Sub
17+
18+
19+url : String
20+url =
21+ "http://localhost:8080"
22+
23+
24+type alias Model =
25+ { ship : Maybe String, search : String }
26+
27+
28+type Msg
29+ = Noop
30+ | GotShipName String
31+ | UpdateSearch String
32+ | Search String
33+
34+
35+main : Ur.Run.Program Model Msg
36+main =
37+ Ur.Run.application
38+ { init =
39+ \_ _ ->
40+ ( { ship = Nothing
41+ , search = ""
42+ }
43+ , Cmd.batch
44+ [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
45+ |> Cmd.map (always Noop)
46+ , Ur.getShipName url |> Cmd.map (result (always Noop) GotShipName)
47+
48+ -- , Ur.scry
49+ -- { url = url
50+ -- , agent = "journal"
51+ -- , path = [ "entries", "all" ]
52+ -- , error = Noop
53+ -- , success =
54+ -- D.cell D.ignore
55+ -- (D.cell (D.const D.cord "jrnl")
56+ -- (D.list (D.cell D.bigint D.cord))
57+ -- )
58+ -- |> D.map (\( (), ( (), listings ) ) -> GotListings listings)
59+ -- }
60+ ]
61+ |> Ur.Cmd.cmd
62+ )
63+ , update = update
64+ , view = \model -> { title = "%seax", body = [ view model |> layout [] ] }
65+ , subscriptions = always Sub.none
66+ , createEventSource = createEventSource
67+ , urbitSubscriptions = always Ur.Sub.none
68+
69+ -- \{ entries, shipName } ->
70+ -- case ( entries, shipName ) of
71+ -- ( Just _, Just ship ) ->
72+ -- Ur.Sub.subscribe
73+ -- { ship = ship
74+ -- , app = "journal"
75+ -- , path = [ "updates" ]
76+ -- , deconstructor = decodeJournalUpdate |> D.map GotUpdate
77+ -- }
78+ -- _ ->
79+ -- Ur.Sub.none
80+ , onEventSourceMsg = onEventSourceMessage
81+ , onUrlChange = \_ -> Noop
82+ , onUrlRequest = \_ -> Noop
83+ , urbitUrl = \_ -> url
84+ }
85+
86+
87+view : Model -> Element Msg
88+view model =
89+ column [ centerX, centerY ]
90+ [ el [ centerX, Font.size 67, Font.heavy ] (text "%seax")
91+ , row [ padding 16, spacing 8 ]
92+ [ Input.text []
93+ { onChange = UpdateSearch
94+ , placeholder = Nothing
95+ , text = model.search
96+ , label = Input.labelHidden "search"
97+ }
98+ , Input.button [ height fill ]
99+ { label =
100+ text "search"
101+ |> el
102+ [ Border.width 4
103+ , padding 4
104+ , Font.size 29
105+ , Font.bold
106+ , Border.color (rgb 0 0 0)
107+ , Background.color (rgb 0 0 0)
108+ , Font.color (rgb 1 1 1)
109+ , mouseOver
110+ [ Font.color (rgb 0 0 0)
111+ , Background.color (rgb 1 1 1)
112+ ]
113+ ]
114+ , onPress = Just (Search model.search)
115+ }
116+ ]
117+ ]
118+
119+
120+update : Msg -> Model -> ( Model, Ur.Cmd.Cmd Msg )
121+update msg model =
122+ case msg of
123+ Noop ->
124+ ( model, Ur.Cmd.none )
125+
126+ GotShipName ship ->
127+ ( { model | ship = Just ship }, Ur.Cmd.none )
128+
129+ UpdateSearch search ->
130+ ( { model | search = search }, Ur.Cmd.none )
131+
132+ Search query ->
133+ ( model, Ur.Cmd.none )
134+
135+
136+result : (a -> c) -> (b -> c) -> Result a b -> c
137+result f g res =
138+ case res of
139+ Ok b ->
140+ g b
141+
142+ Err a ->
143+ f a
144+
145+
146+port createEventSource : String -> Cmd msg
147+
148+
149+port onEventSourceMessage : (JD.Value -> msg) -> Sub msg
frontend/src/Route.elmnew file+28
@@ -0,0 +1,28 @@
1+module Route exposing (..)
2+
3+import Url.Builder as Builder
4+import Url.Parser exposing ((</>), (<?>), Parser, map, oneOf, s, string, top)
5+import Url.Parser.Query as Query
6+
7+
8+type Route
9+ = SearchRoute
10+ | ResultsRoute String
11+
12+
13+parser : Parser (Route -> a) a
14+parser =
15+ oneOf
16+ [ map ResultsRoute (s "search" </> string)
17+ , map SearchRoute top
18+ ]
19+
20+
21+printer : Route -> String
22+printer route =
23+ case route of
24+ SearchRoute ->
25+ Builder.absolute [] []
26+
27+ ResultsRoute query ->
28+ Builder.absolute [ "search", query ] []
zod/seax/app/seax.hoon+5−5
@@ -50,8 +50,7 @@ $:
5050 search-results.search-state
5151=* sink
5252 |= =query
53− ((sink:^sink ~[%search query]) (search-results query))
54−:: =/ sinks *(map query _(sink ''))
53+ ((sink:^sink ~[~[%search query]]) (search-results query))
5554
5655^- agent:gall
5756%- agent:dbug
@@ -82,7 +81,7 @@ $:
8281 =. search-subscriptions.state
8382 (~(put by search-subscriptions.state) query [1 ~])
8483 =/ cards (search-cards query)
85− [cards this]
84+ [[flush:(sink query) cards] this]
8685 =/ subscription=search-state +.subscription
8786 =. subscription
8887 [+(number-of-subscriptions.subscription) search-results.subscription]
@@ -94,7 +93,6 @@ $:
9493:: This was not tested because it requires waiting 12 hours.
9594++ on-leave
9695 |= =path
97− ~& path
9896 ?+ path !!
9997 [%search query ~]
10098 =/ query +<.path
@@ -120,6 +118,7 @@ $:
120118 [%search-result query=@t engine=term ~]
121119 ?> ?=([%iris %http-response %finished *] sign-arvo)
122120 =/ query=@t +<.wire
121+ =/ sink (sink query)
123122 =/ engine-name=term +>-.wire
124123 =/ [=engine *] (~(got by engines) engine-name)
125124 =/ search-subscription (~(got by search-subscriptions.state) query)
@@ -131,7 +130,8 @@ $:
131130 (~(put by search-results.search-subscription) engine-name results)
132131 =. search-subscriptions.state
133132 (~(put by search-subscriptions.state) query search-subscription)
134− `this
133+ =^ card sink (sync:sink (search-results query))
134+ [~[card] this]
135135 ==
136136++ on-fail |=([term tang] `..on-init)
137137--
zod/seax/lib/rank.hoon+4−5
@@ -42,22 +42,21 @@
4242 ^- @rs
4343 %+ roll engine-weights
4444 |= [a=_.1 b=_.1]
45− (mul.rs a b)
45+ (mul:rs a b)
4646 =/ position-weights
4747 ^- @rs
48− %+ mul.rs (lent ords)
48+ %+ mul:rs (sun:rs (lent ords))
4949 %+ roll
50− (turn ords |=(ord=@u (div.rs 1 (sun:rs ord))))
50+ (turn ords |=(ord=@u (div:rs .1 (sun:rs ord))))
5151 add
5252 =/ rank
5353 ^- @rs
54− (mul.rs engine-weights position-weights)
54+ (mul:rs engine-weights position-weights)
5555 [ord=rank result=result]
5656=/ sorted-results
5757 %+ sort ranking
5858 |= [[lhs=@rs *] [rhs=@rs *]]
5959 %+ gth lhs rhs
60−~& sorted-results
6160%+ turn sorted-results
6261|= [* result=search-result]
6362result
zod/seax/lib/sink.hoon+1−1
@@ -21,6 +21,6 @@
2121 ++ paths pats
2222 ++ flush
2323 ^- card:agent:gall
24− [%give %fact pats %noun !>(^-((clog) [%flush stat]))]
24+ [%give %fact ~ %noun !>(^-((clog) [%flush stat]))]
2525 --
2626--