objective categorical abstract machine language personal data server

Nicer root page

futur.blue 604d88cd 118d429f

verified
+100 -72
+2 -1
frontend/client/Router.mlx
··· 13 type route = {path: string; template: (module Template)} 14 15 let routes = 16 - [ {path= "/oauth/authorize"; template= (module OauthAuthorizePage)} 17 ; {path= "/account/login"; template= (module LoginPage)} 18 ; {path= "/account/signup"; template= (module SignupPage)} 19 ; {path= "/account"; template= (module AccountPage)}
··· 13 type route = {path: string; template: (module Template)} 14 15 let routes = 16 + [ {path= "/"; template= (module RootPage)} 17 + ; {path= "/oauth/authorize"; template= (module OauthAuthorizePage)} 18 ; {path= "/account/login"; template= (module LoginPage)} 19 ; {path= "/account/signup"; template= (module SignupPage)} 20 ; {path= "/account"; template= (module AccountPage)}
+29 -31
frontend/src/components/HandleInput.mlx
··· 2 3 open React 4 5 - type handle_status = 6 - | Idle 7 - | Checking 8 - | Valid 9 - | Invalid of string 10 11 let[@react.component] make ~name ?(label = "handle") ?(sr_only = false) 12 - ?(required = false) ?(showIndicator = true) ?(subdomainOnly = false) ?placeholder ?hostname ?value ?onChange () = 13 let internalValue, setInternalValue = useState (fun () -> "") in 14 let handleValue = Option.value value ~default:internalValue in 15 let handleStatus, setHandleStatus = useState (fun () -> Idle) in ··· 20 setHandleStatus (fun _ -> Checking) ; 21 let fullHandle = 22 match hostname with 23 - | Some host -> handle ^ "." ^ host 24 - | None -> handle 25 in 26 let _ = 27 Fetch.fetch 28 - ("/account/signup/check-handle?handle=" 29 ^ Js.Global.encodeURIComponent handle ) 30 |> Js.Promise.then_ (fun response -> 31 - if Fetch.Response.ok response then 32 - Fetch.Response.json response 33 else Js.Promise.reject (Js.Exn.raiseError "Request failed") ) 34 |> Js.Promise.then_ (fun json -> 35 let valid = ··· 42 |> Option.map Obj.magic 43 |> Option.value ~default:false 44 in 45 - let error = Option.bind 46 - (Js.Dict.get (Obj.magic json) "error" 47 - |> Option.map Obj.magic) 48 - (fun x -> 49 - if Js.Nullable.isNullable (Obj.magic x) then None 50 - else Some x ) 51 in 52 - ( if valid && available then setHandleStatus (fun _ -> Valid) 53 - else 54 - setHandleStatus (fun _ -> 55 - Invalid (Option.value error ~default:"Invalid handle") 56 - ) ) ; 57 Js.Promise.resolve () ) 58 |> Js.Promise.catch (fun _ -> 59 setHandleStatus (fun _ -> ··· 67 fun e -> 68 let inputValue = (Event.Form.target e)##value in 69 ( match onChange with 70 - | Some f -> f e 71 - | None -> setInternalValue (fun _ -> inputValue) ) ; 72 ( match checkTimeoutRef.current with 73 | Some id -> 74 Js.Global.clearTimeout id ··· 84 let trailing = 85 match hostname with 86 | Some h -> 87 - Some ( 88 <span className="font-serif text-mist-80 whitespace-nowrap"> 89 (string ("." ^ h)) 90 </span> 91 - ) 92 - | _ -> None 93 in 94 <div> 95 <Input ··· 110 (string "Checking availability...") 111 </span> 112 | Valid -> 113 - <span 114 - className="inline-flex items-center text-mana-100 text-sm mt-1"> 115 <CheckmarkIcon className="w-4 h-4 mr-1" /> 116 (string "Handle is available") 117 </span> 118 | Invalid msg -> 119 <span 120 className="inline-flex items-center text-phoenix-100 text-sm mt-1"> 121 - <CircleAlertIcon className="w-4 h-4 mr-1" /> 122 - (string msg) 123 </span> 124 | Idle -> 125 null )
··· 2 3 open React 4 5 + type handle_status = Idle | Checking | Valid | Invalid of string 6 7 let[@react.component] make ~name ?(label = "handle") ?(sr_only = false) 8 + ?(required = false) ?(showIndicator = true) ?(subdomainOnly = false) 9 + ?placeholder ?hostname ?value ?onChange () = 10 let internalValue, setInternalValue = useState (fun () -> "") in 11 let handleValue = Option.value value ~default:internalValue in 12 let handleStatus, setHandleStatus = useState (fun () -> Idle) in ··· 17 setHandleStatus (fun _ -> Checking) ; 18 let fullHandle = 19 match hostname with 20 + | Some host -> 21 + handle ^ "." ^ host 22 + | None -> 23 + handle 24 in 25 let _ = 26 Fetch.fetch 27 + ( "/account/signup/check-handle?handle=" 28 ^ Js.Global.encodeURIComponent handle ) 29 |> Js.Promise.then_ (fun response -> 30 + if Fetch.Response.ok response then Fetch.Response.json response 31 else Js.Promise.reject (Js.Exn.raiseError "Request failed") ) 32 |> Js.Promise.then_ (fun json -> 33 let valid = ··· 40 |> Option.map Obj.magic 41 |> Option.value ~default:false 42 in 43 + let error = 44 + Option.bind 45 + ( Js.Dict.get (Obj.magic json) "error" 46 + |> Option.map Obj.magic ) 47 + (fun x -> 48 + if Js.Nullable.isNullable (Obj.magic x) then None 49 + else Some x ) 50 in 51 + if valid && available then setHandleStatus (fun _ -> Valid) 52 + else 53 + setHandleStatus (fun _ -> 54 + Invalid (Option.value error ~default:"Invalid handle") ) ; 55 Js.Promise.resolve () ) 56 |> Js.Promise.catch (fun _ -> 57 setHandleStatus (fun _ -> ··· 65 fun e -> 66 let inputValue = (Event.Form.target e)##value in 67 ( match onChange with 68 + | Some f -> 69 + f e 70 + | None -> 71 + setInternalValue (fun _ -> inputValue) ) ; 72 ( match checkTimeoutRef.current with 73 | Some id -> 74 Js.Global.clearTimeout id ··· 84 let trailing = 85 match hostname with 86 | Some h -> 87 + Some 88 <span className="font-serif text-mist-80 whitespace-nowrap"> 89 (string ("." ^ h)) 90 </span> 91 + | _ -> 92 + None 93 in 94 <div> 95 <Input ··· 110 (string "Checking availability...") 111 </span> 112 | Valid -> 113 + <span className="inline-flex items-center text-mana-100 text-sm mt-1"> 114 <CheckmarkIcon className="w-4 h-4 mr-1" /> 115 (string "Handle is available") 116 </span> 117 | Invalid msg -> 118 <span 119 className="inline-flex items-center text-phoenix-100 text-sm mt-1"> 120 + <CircleAlertIcon className="w-4 h-4 mr-1" /> (string msg) 121 </span> 122 | Idle -> 123 null )
+3 -2
frontend/src/components/Input.mlx
··· 4 let req_marker = " *" 5 6 let[@react.component] make ?id ~name ?(className = "") ?(type_ = "text") ?label 7 - ?(sr_only = false) ?value ?defaultValue ?placeholder ?autoComplete ?(required = false) 8 - ?(disabled = false) ?trailing ?(showIndicator = true) ?onChange () = 9 let id = Option.value id ~default:name in 10 let placeholder = if label <> None && sr_only then label else placeholder in 11 let input =
··· 4 let req_marker = " *" 5 6 let[@react.component] make ?id ~name ?(className = "") ?(type_ = "text") ?label 7 + ?(sr_only = false) ?value ?defaultValue ?placeholder ?autoComplete 8 + ?(required = false) ?(disabled = false) ?trailing ?(showIndicator = true) 9 + ?onChange () = 10 let id = Option.value id ~default:name in 11 let placeholder = if label <> None && sr_only then label else placeholder in 12 let input =
+2 -1
frontend/src/templates/Layout.mlx
··· 1 open React 2 3 - let[@react.component] make ?(title = "Pegasus") ?(children = null) ?(favicon = "/public/favicon.ico") () = 4 <html lang="en"> 5 <head> 6 <meta charSet="utf-8" />
··· 1 open React 2 3 + let[@react.component] make ?(title = "Pegasus") ?(children = null) 4 + ?(favicon = "/public/favicon.ico") () = 5 <html lang="en"> 6 <head> 7 <meta charSet="utf-8" />
+2 -1
frontend/src/templates/OauthAuthorizePage.mlx
··· 562 in 563 let add_account_url = "/account/login" ^ query_string in 564 let favicon_url, set_favicon_url = 565 - useState (fun () -> (Option.value logo_uri ~default:("https://" ^ host ^ "/favicon.ico")) ) 566 in 567 <form className="w-full h-auto max-w-lg px-4 sm:px-0"> 568 <h1 className="text-2xl font-serif text-mana-200 mb-2">
··· 562 in 563 let add_account_url = "/account/login" ^ query_string in 564 let favicon_url, set_favicon_url = 565 + useState (fun () -> 566 + Option.value logo_uri ~default:("https://" ^ host ^ "/favicon.ico") ) 567 in 568 <form className="w-full h-auto max-w-lg px-4 sm:px-0"> 569 <h1 className="text-2xl font-serif text-mana-200 mb-2">
+60
frontend/src/templates/RootPage.mlx
···
··· 1 + open Melange_json.Primitives 2 + open React 3 + 4 + let pegasus = 5 + string 6 + {js| 7 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢀⣠⣴⡆⠀⠀⠀⠀⠀⠀ 8 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣶⣶⠿⠿⢛⡛⠉⠀⠀⠀⠀⠀⠀⠀ 9 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⣴⣶⣿⢻⣿⣿⣶⣶⣢⣆⠀⠀⠀⣀⣀⣤⣤⣶⣶⣾⣿⡿⠿⣛⣛⣭⣵⣶⠾⢟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀ 10 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⣾⣿⡟⣻⣿⣿⣿⣿⣿⣿⣭⣭⢠⣾⣿⣯⣭⣤⡬⣩⣭⣽⡶⠿⢟⣛⣯⣭⡶⣶⡛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 11 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠁⠿⣿⡿⣿⣿⣿⣿⣿⣯⡄⣿⣿⣿⣿⡟⠃⠈⠱⠶⠾⠛⢋⣭⣿⠶⣟⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 12 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⣾⣿⣿⣿⣿⣟⡓⢠⣾⣿⠿⣛⣛⡛⣟⣛⣛⡛⢶⣶⣶⣶⣶⣶⣥⣤⣤⣤⣤⣄⣀⣀⣀⣀⠀⠀⠀ 13 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁⣴⣿⣿⣿⣿⣿⡿⠯⢁⣿⢟⣴⣿⣿⡿⢳⣿⣿⣿⠏⠰⠶⠶⢾⣽⣭⣭⣭⣙⣛⣛⣛⣛⡛⠿⠿⠿⠿⠋ 14 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⢀⣾⣿⣿⣿⣿⣿⣿⣿⠋⣾⡟⣼⣿⣿⢟⣴⣿⡿⢏⣀⣙⣛⣛⣛⡳⠶⠶⠶⣶⣭⣭⣭⣭⣭⣝⠛⠋⠀⠀⠀ 15 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⣼⣿⣿⣿⣿⣿⣿⠟⣂⣼⡟⣱⣿⣿⢏⣼⣿⣿⠁⠬⣭⣙⣛⣛⣛⣛⠛⠿⠿⠿⠶⠶⠶⠶⠆⠀⠀⠀⠀⠀⠀ 16 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⢁⣤⣾⣿⣿⣿⣷⣶⣶⣾⣟⣫⣾⣿⣿⡏⣾⣿⣿⡅⢶⣶⣦⣤⣤⣬⣭⣭⣉⠛⠛⠛⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀ 17 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠱⣿⣛⣋⣤⣴⣶⣶⣦⣭⡉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 18 + ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 19 + ⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⠿⣿⡿⠶⠜⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⠿⢿⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀ 20 + ⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣶⣶⣶⣿⣷⣶⣾⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⠀⠀⠉⢿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀ 21 + ⠀⠀⠀⠀⢀⣼⠟⠉⠀⢸⣿⠛⠛⠛⠿⠿⢿⣿⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡙⢻⣿⣿⣿⣿⣿⣿⡎⣿⡇⠀⠀⠀⠙⠉⠻⣿⣿⣿⣿⣿⣷⡄⠀⠀ 22 + ⠀⠀⠀⣠⣿⠏⠀⠀⠀⠘⣿⣧⡀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⠿⠿⠛⠛⠛⠉⠉⠉⠁⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⡷⠀⠀ 23 + ⠀⢀⣾⡿⠁⠀⠀⠀⠀⠀⠈⠻⢿⣶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣼⣶⠀⠀⠀⠀⠀⠀⠛⠿⠿⠟⠁⠀⠀ 24 + ⣰⣿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣷⣭⣁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 25 + ⠿⠥⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 26 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣇⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀ 27 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡿⢿⣶⠀⠀⠀⠀⠀⠀⠀ 28 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀ 29 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⠄⠀⠀⠀⠀⠀⠀ 30 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠿⠆⠀⠀⠀⠀⠀⠀ 31 + |js} 32 + 33 + type props = unit [@@deriving json] 34 + 35 + let[@react.component] make ~props:(_ : props) () = 36 + <main className="text-mist-100"> 37 + <pre className="text-mana-100">pegasus</pre> 38 + <p> 39 + (string "this is ") 40 + <a 41 + href="https://tangled.org/futur.blue/pegasus" 42 + className="text-mana-100 underline hover:text-mana-200"> 43 + (string "pegasus") 44 + </a> 45 + (string ", an atproto personal data server") 46 + </p> 47 + <p> 48 + (string "manage your account at ") 49 + <a 50 + href="/account" className="text-mana-100 underline hover:text-mana-200"> 51 + (string "/account") 52 + </a> 53 + </p> 54 + <p> 55 + (string "admin panel at ") 56 + <a href="/admin" className="text-mana-100 underline hover:text-mana-200"> 57 + (string "/admin") 58 + </a> 59 + </p> 60 + </main>
+1 -5
frontend/src/templates/SignupPage.mlx
··· 23 <input type_="hidden" name="dream.csrf" value=csrf_token /> 24 ( if invite_required then 25 <Input 26 - sr_only=true 27 - name="invite_code" 28 - type_="text" 29 - label="invite code" 30 - /> 31 else null ) 32 <HandleInput sr_only=true name="handle" hostname /> 33 <Input sr_only=true name="email" type_="email" label="email" />
··· 23 <input type_="hidden" name="dream.csrf" value=csrf_token /> 24 ( if invite_required then 25 <Input 26 + sr_only=true name="invite_code" type_="text" label="invite code" /> 27 else null ) 28 <HandleInput sr_only=true name="handle" hostname /> 29 <Input sr_only=true name="email" type_="email" label="email" />
+1 -31
pegasus/lib/api/root.ml
··· 1 let handler = 2 Xrpc.handler (fun _ -> 3 - Dream.respond 4 - ~headers:[("Content-Type", "text/plain; charset=utf-8")] 5 - {| 6 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢀⣠⣴⡆⠀⠀⠀⠀⠀⠀ 7 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣶⣶⠿⠿⢛⡛⠉⠀⠀⠀⠀⠀⠀⠀ 8 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⣴⣶⣿⢻⣿⣿⣶⣶⣢⣆⠀⠀⠀⣀⣀⣤⣤⣶⣶⣾⣿⡿⠿⣛⣛⣭⣵⣶⠾⢟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀ 9 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⣾⣿⡟⣻⣿⣿⣿⣿⣿⣿⣭⣭⢠⣾⣿⣯⣭⣤⡬⣩⣭⣽⡶⠿⢟⣛⣯⣭⡶⣶⡛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 10 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠁⠿⣿⡿⣿⣿⣿⣿⣿⣯⡄⣿⣿⣿⣿⡟⠃⠈⠱⠶⠾⠛⢋⣭⣿⠶⣟⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 11 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⣾⣿⣿⣿⣿⣟⡓⢠⣾⣿⠿⣛⣛⡛⣟⣛⣛⡛⢶⣶⣶⣶⣶⣶⣥⣤⣤⣤⣤⣄⣀⣀⣀⣀⠀⠀⠀ 12 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁⣴⣿⣿⣿⣿⣿⡿⠯⢁⣿⢟⣴⣿⣿⡿⢳⣿⣿⣿⠏⠰⠶⠶⢾⣽⣭⣭⣭⣙⣛⣛⣛⣛⡛⠿⠿⠿⠿⠋ 13 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⢀⣾⣿⣿⣿⣿⣿⣿⣿⠋⣾⡟⣼⣿⣿⢟⣴⣿⡿⢏⣀⣙⣛⣛⣛⡳⠶⠶⠶⣶⣭⣭⣭⣭⣭⣝⠛⠋⠀⠀⠀ 14 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⣼⣿⣿⣿⣿⣿⣿⠟⣂⣼⡟⣱⣿⣿⢏⣼⣿⣿⠁⠬⣭⣙⣛⣛⣛⣛⠛⠿⠿⠿⠶⠶⠶⠶⠆⠀⠀⠀⠀⠀⠀ 15 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⢁⣤⣾⣿⣿⣿⣷⣶⣶⣾⣟⣫⣾⣿⣿⡏⣾⣿⣿⡅⢶⣶⣦⣤⣤⣬⣭⣭⣉⠛⠛⠛⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀ 16 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠱⣿⣛⣋⣤⣴⣶⣶⣦⣭⡉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 17 - ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 18 - ⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⠿⣿⡿⠶⠜⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⠿⢿⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀ 19 - ⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣶⣶⣶⣿⣷⣶⣾⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⠀⠀⠉⢿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀ 20 - ⠀⠀⠀⠀⢀⣼⠟⠉⠀⢸⣿⠛⠛⠛⠿⠿⢿⣿⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡙⢻⣿⣿⣿⣿⣿⣿⡎⣿⡇⠀⠀⠀⠙⠉⠻⣿⣿⣿⣿⣿⣷⡄⠀⠀ 21 - ⠀⠀⠀⣠⣿⠏⠀⠀⠀⠘⣿⣧⡀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⠿⠿⠛⠛⠛⠉⠉⠉⠁⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⡷⠀⠀ 22 - ⠀⢀⣾⡿⠁⠀⠀⠀⠀⠀⠈⠻⢿⣶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣼⣶⠀⠀⠀⠀⠀⠀⠛⠿⠿⠟⠁⠀⠀ 23 - ⣰⣿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣷⣭⣁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 24 - ⠿⠥⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 25 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣇⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀ 26 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡿⢿⣶⠀⠀⠀⠀⠀⠀⠀ 27 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀ 28 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⠄⠀⠀⠀⠀⠀⠀ 29 - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠿⠆⠀⠀⠀⠀⠀⠀ 30 - 31 - this is [pegasus](https://tangled.org/@futur.blue/pegasus) 32 - manage your account at /account 33 - |} )
··· 1 let handler = 2 Xrpc.handler (fun _ -> 3 + Util.render_html ~title:"Pegasus" (module Frontend.RootPage) ~props:() )