on master help sign in
Simplify client app: remove URL from ConnStatus, use PollReceived record, extract error banner, disable URL edit while connected.
commit72c9efec96879f9b4e2a59c12a3c9c558ede81e5 authoriko <mail@iko.soy> dateSat 11 Apr 2026, 03:48 (6 months ago) parentb94da82
2 files changed+39−48
client/App.fs+26−35 server.knot+13−13
client/App.fs+26−35
@@ -49,7 +49,7 @@ module App =
4949 type ConnStatus =
5050 | Offline
5151 | Connecting
52− | Online of url: string * token: string
52+ | Online of token: string
5353
5454 type AuthState =
5555 | NoIdentity
@@ -57,10 +57,12 @@ module App =
5757
5858 type IncomingMessage = { SenderPubkey: string; Message: ChatMessage }
5959
60+ type PollReceived = { Events: int; Messages: int; Errors: int; FirstError: string option }
61+
6062 type PollStatus =
6163 | Idle
6264 | Polling
63− | Received of events: int * messages: int * errors: int * firstError: string option
65+ | Received of PollReceived
6466 | PollError of string
6567
6668 // ── Model ──
@@ -160,7 +162,7 @@ module App =
160162
161163 let private trySession model =
162164 match model.Auth with
163− | Identified(id, Online(url, token)) -> Some { Url = url; Token = token; Identity = id }
165+ | Identified(id, Online token) -> Some { Url = model.ServerUrl; Token = token; Identity = id }
164166 | _ -> None
165167
166168 let private setConn status model =
@@ -225,7 +227,7 @@ module App =
225227 match model.Auth with
226228 | Identified(id, Connecting) ->
227229 let session = { Url = model.ServerUrl; Token = token; Identity = id }
228− { model with Auth = Identified(id, Online(model.ServerUrl, token)); Page = Conversations; Error = None },
230+ { model with Auth = Identified(id, Online token); Page = Conversations; Error = None },
229231 [ CmdPoll(session, model.PollCursor) ]
230232 | _ -> model, []
231233
@@ -392,7 +394,7 @@ module App =
392394 if not ackIds.IsEmpty then
393395 do! ackMessages session.Url session.Token ackIds
394396
395− let status = Received(response.Events.Length, messages.Length, errors.Length, List.tryHead errors)
397+ let status = Received { Events = response.Events.Length; Messages = messages.Length; Errors = errors.Length; FirstError = List.tryHead errors }
396398 return PollResult(messages, status, response.Cursor)
397399 with
398400 | :? TimeoutException ->
@@ -429,11 +431,20 @@ module App =
429431 let private formatPollStatus = function
430432 | Idle -> ""
431433 | Polling -> "polling..."
432− | Received(evts, msgs, errs, firstErr) ->
433− $"evts:{evts} msgs:{msgs} errs:{errs}"
434− + (match firstErr with Some e -> $" [{e}]" | None -> "")
434+ | Received r ->
435+ $"evts:{r.Events} msgs:{r.Messages} errs:{r.Errors}"
436+ + (match r.FirstError with Some e -> $" [{e}]" | None -> "")
435437 | PollError msg -> $"poll error: {msg}"
436438
439+ let private viewErrorBanner (error: string option) =
440+ VStack(spacing = 4.) {
441+ match error with
442+ | Some err ->
443+ Label(err).textColor(Colors.Red).font(size = 12.)
444+ Button("Dismiss", DismissError)
445+ | None -> ()
446+ }
447+
437448 let private viewSetup () =
438449 ContentPage(
439450 (VStack(spacing = 24.) {
@@ -471,16 +482,11 @@ module App =
471482
472483 Label("Server:")
473484
474− Entry(model.ServerUrl, SetServerUrl)
475−
476− match model.Error with
477− | Some err ->
478− Label(err)
479− .textColor(Colors.Red)
480− .font(size = 12.)
485+ match conn with
486+ | Online _ -> Label(model.ServerUrl).font(size = 14.)
487+ | _ -> Entry(model.ServerUrl, SetServerUrl)
481488
482− Button("Dismiss", DismissError)
483− | None -> ()
489+ viewErrorBanner model.Error
484490
485491 match conn with
486492 | Offline -> Button("Connect", DoConnect)
@@ -516,11 +522,7 @@ module App =
516522 .font(size = 10.)
517523 .textColor(Colors.DimGray)
518524
519− match model.Error with
520− | Some err ->
521− Label(err).textColor(Colors.Red).font(size = 12.)
522− Button("Dismiss", DismissError)
523− | None -> ()
525+ viewErrorBanner model.Error
524526
525527 if model.Contacts.IsEmpty then
526528 Label("No contacts yet. Tap + to add one.")
@@ -554,14 +556,7 @@ module App =
554556 .centerTextHorizontal()
555557 }
556558
557− match model.Error with
558− | Some err ->
559− Label(err)
560− .textColor(Colors.Red)
561− .font(size = 12.)
562−
563− Button("Dismiss", DismissError)
564− | None -> ()
559+ viewErrorBanner model.Error
565560
566561 if msgs.IsEmpty then
567562 Label("No messages yet")
@@ -593,11 +588,7 @@ module App =
593588 .font(size = 20.)
594589 }
595590
596− match model.Error with
597− | Some err ->
598− Label(err).textColor(Colors.Red).font(size = 12.)
599− Button("Dismiss", DismissError)
600− | None -> ()
591+ viewErrorBanner model.Error
601592
602593 Label("Public Key:")
603594 Entry(pubkey, SetContactPubkey)
server.knot+13−13
@@ -184,6 +184,7 @@ whenOk = \result action -> case result of
184184 Err _ -> yield {}
185185clamp = \lo hi x -> if x < lo then lo else if x > hi then hi else x
186186min = \a b -> if a < b then a else b
187+max = \a b -> if a > b then a else b
187188headOf = \items -> fold (\acc x -> case acc of
188189 Nothing {} -> Just {value: x}
189190 _ -> acc) Nothing {} items
@@ -397,9 +398,9 @@ boundedSmallest = \items n getKey ->
397398 let newHeap = union acc.heap [a] in
398399 let newSize = count newHeap in
399400 if newSize > acc.size
400− then {size: newSize, heap: newHeap, heapMax: case acc.heapMax of
401− Nothing {} -> Just {value: k}
402− Just {value: v} -> if k > v then Just {value: k} else acc.heapMax}
401+ then {size: newSize, heap: newHeap, heapMax: Just {value: case acc.heapMax of
402+ Nothing {} -> k
403+ Just {value: v} -> max k v}}
403404 else acc
404405 else case acc.heapMax of
405406 Nothing {} -> acc
@@ -411,9 +412,9 @@ boundedSmallest = \items n getKey ->
411412 then {done: True {}, items: r.items}
412413 else {done: r.done, items: union r.items [x]}) {done: False {}, items: []} acc.heap in
413414 let newHeap = union removed.items [a] in
414− let newMax = fold (\best x -> case best of
415− Nothing {} -> Just {value: getKey x}
416− Just {value: v} -> if getKey x > v then Just {value: getKey x} else best) Nothing {} newHeap in
415+ let newMax = fold (\best x -> Just {value: case best of
416+ Nothing {} -> getKey x
417+ Just {value: v} -> max (getKey x) v}) Nothing {} newHeap in
417418 {size: acc.size, heap: newHeap, heapMax: newMax}
418419 else acc) {size: 0, heap: [], heapMax: Nothing {}} items).heap
419420
@@ -430,9 +431,9 @@ safeCompare = \a b ->
430431 safeCompareLoop a b maxTokenLen 0 + (if a == "" then 1 else 0) + (if b == "" then 1 else 0) == 0
431432
432433evictOneOldest = \items getKey ->
433− let oldest = fold (\acc x -> let k = getKey x in case acc of
434− Nothing {} -> Just {value: k}
435− Just {value: v} -> if k < v then Just {value: k} else acc) Nothing {} items in
434+ let oldest = fold (\acc x -> let k = getKey x in Just {value: case acc of
435+ Nothing {} -> k
436+ Just {value: v} -> min k v}) Nothing {} items in
436437 case oldest of
437438 Nothing {} -> items
438439 Just {value: oldestKey} ->
@@ -706,8 +707,7 @@ pollOnce = \token cursor -> do
706707 Authorized {pubkey} -> do
707708 seqCounter <- *seqCounter
708709 messages <- *messages
709− let cursorCeil = if seqCounter > t then seqCounter else t
710− let clampedCursor = clamp 0 cursorCeil cursor
710+ let clampedCursor = clamp 0 (max seqCounter t) cursor
711711 let matchingMsgs = do
712712 m <- messages
713713 where m.toKey == pubkey && m.receivedAt >= t - messageMaxAge && m.receivedAt > clampedCursor
@@ -719,7 +719,7 @@ pollOnce = \token cursor -> do
719719 payload: {toKey: m.toKey, fromKey: m.fromKey,
720720 encryptedBlob: m.encryptedBlob,
721721 timestamp: m.timestamp}}
722− let lastReceivedAt = fold (\acc m -> if m.receivedAt > acc then m.receivedAt else acc) clampedCursor limitedMsgs
722+ let lastReceivedAt = fold (\acc m -> max acc m.receivedAt) clampedCursor limitedMsgs
723723 yield {events: evts, cursor: lastReceivedAt, authorized: True {}}
724724
725725pollLoop = \token cursor deadline -> do
@@ -761,7 +761,7 @@ serve = \req -> case req of
761761 sessions <- *sessions
762762 presence <- *presence
763763 let hasMatch = any challenges (\c -> c.pubkey == pubkey && c.challenge == challenge && c.expiresAt > t)
764− let sigValid = if hasMatch then verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature) else False {}
764+ let sigValid = hasMatch && verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
765765 if not sigValid
766766 then yield {pr: pr, verified: False {}, peerServers: []}
767767 else do