nandi/frqpublic Fork 0
19a2ec6
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Go somewhere when the connect succeeds

It was not spinning and stopping. It was working, and then looking exactly
like it had not: `frq.state` does `(reset! screen :chats)` on 001 and the
phone's connect! did not, so a successful registration put the Connect button
back on the same screen and said "Connected as frq-guest" in the small grey
text beside it. There was nothing to tell it from a failure.

So 001 moves off the connect screen here too, and there is somewhere to
land — the status with its live dot, what the server said, and the way back.
Deliberately small: the desktop goes to :chats, and that screen reads rooms,
messages, avatars and the media plane, which is most of what is not ported.

Two things fixed on the way in and out.

A close we asked for came back as "closed by the server", which is untrue and
alarming; a flag tells a Disconnect from a drop, and only a drop reports.

And an unexpected close now says what the last line was and how many came
before it — a connection that registers and then drops is a different bug
from one that never registers, and only that tells them apart.

Verified on the device: Connect lands on the connected screen with the MOTD,
PING/PONG keeps it up, Disconnect returns to the connect screen with no
banner, and a drop would name itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-11T21:04:38-07:00 Browse files
19a2ec6 parent: ba9e7a9
modified flutter/src/frq/main.cljd +64 -10
@@ -34,7 +34,10 @@
3434 (defonce ^:private lines (atom []))
3535 (defonce ^:private conn (atom nil))
3636
37+(defonce ^:private last-line (atom nil))
38+
3739 (defn- note! [m]
40+ (reset! last-line (str (:command m) " " (last (:params m))))
3841 (swap! lines (fn [v] (vec (take-last 8 (conj v m))))))
3942
4043 (def ^:private registration-failed
@@ -54,9 +57,16 @@
5457
5558 (defonce ^:private attempt (atom 0))
5659
60+(defonce ^:private closing?
61+ ;; Whether the close about to arrive is one we asked for. Without it a tap
62+ ;; on Disconnect comes back as "closed by the server", which is both untrue
63+ ;; and alarming.
64+ (atom false))
65+
5766 (defn- fail! [why]
5867 (reset! cells/connecting? false)
5968 (reset! cells/status "Not connected")
69+ (reset! cells/screen :connect)
6070 (reset! cells/error why))
6171
6272 (defn ^:async connect!
@@ -71,7 +81,10 @@
7181 []
7282 ;; Close whatever was open first. Connecting twice left the old socket
7383 ;; holding the nick, so the second attempt got 433 from its own predecessor.
74- (when-let [c @conn] (net/close! c) (reset! conn nil))
84+ (when-let [c @conn]
85+ (reset! closing? true)
86+ (net/close! c)
87+ (reset! conn nil))
7588 (let [n (swap! attempt inc)]
7689 (reset! cells/connecting? true)
7790 (reset! cells/error nil)
@@ -100,7 +113,14 @@
100113 (do (reset! cells/connecting? false)
101114 (reset! cells/error nil)
102115 (reset! cells/status
103- (str "Connected as " @cells/form-nick)))
116+ (str "Connected as " @cells/form-nick))
117+ ;; Off the connect screen, as
118+ ;; frq.state does on 001. Without
119+ ;; this a successful connect just
120+ ;; puts the Connect button back
121+ ;; and looks exactly like a
122+ ;; failure.
123+ (reset! cells/screen :chats))
104124
105125 (contains? registration-failed cmd)
106126 (fail! (if (= "433" cmd)
@@ -114,10 +134,16 @@
114134 (reset! cells/status (str "… " cmd)))))
115135 :on-close (fn [why]
116136 (reset! conn nil)
117- (if why
118- (fail! why)
119- (do (reset! cells/connecting? false)
120- (reset! cells/status "Not connected"))))}))]
137+ ;; Say what the last thing seen was. A
138+ ;; connection that registers and then
139+ ;; drops is a different bug from one
140+ ;; that never registers, and only the
141+ ;; last line apart tells them.
142+ (if @closing?
143+ (reset! closing? false)
144+ (fail! (str (or why "closed by the server")
145+ " (after " (count @lines)
146+ " lines, last: " @last-line ")"))))}))]
121147 (reset! conn sock)
122148 (net/send-line! sock (str "NICK " @cells/form-nick))
123149 (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq")))
@@ -128,10 +154,12 @@
128154 (fail! (str e))))))
129155
130156 (defn- disconnect! []
157+ (reset! closing? true)
131158 (when-let [c @conn] (net/close! c))
132159 (reset! conn nil)
133160 (reset! cells/connecting? false)
134- (reset! cells/status "Not connected"))
161+ (reset! cells/status "Not connected")
162+ (reset! cells/screen :connect))
135163
136164 (defonce ^:private handle (atom "nandi-test.bsky.social"))
137165 (defonce ^:private identity-out (atom nil))
@@ -146,6 +174,29 @@
146174 (catch Exception e
147175 (reset! identity-out [(str "failed: " e)]))))
148176
177+(defn- connected-screen
178+ "Where a connect lands, until `frq.app`'s chats screen is portable.
179+
180+ Not much, and honest about it: the status, what the server said, and the way
181+ back. The desktop goes to :chats here — that screen reads rooms, messages,
182+ avatars and the media plane, which is most of what is not ported yet."
183+ []
184+ [:page {:max-width 520}
185+ [:title {:label "frq"}]
186+ [:status {:live true :label @cells/status}]
187+ [:card {}
188+ [:title-2 {:label "What the server said"}]
189+ (if (empty? @lines)
190+ [:dim-label {:label "nothing yet"}]
191+ (for [[i m] (map-indexed vector @lines)]
192+ [:label {:key i
193+ :label (str (:command m)
194+ (when-let [p (seq (:params m))]
195+ (str " " (last p))))}]))]
196+ [:hbox {:spacing 8}
197+ [:button {:label "Disconnect" :destructive true :on-click #(disconnect!)}]]
198+ [:dim-label {:label "The chats screen is next: it wants rooms, messages and avatars, none of which are ported yet."}]])
199+
149200 (defn ^:async main []
150201 (m/WidgetsFlutterBinding.ensureInitialized)
151202 (let [dir (.-path (await (pp/getApplicationSupportDirectory)))]
@@ -174,11 +225,14 @@
174225 c-tls cells/form-tls?
175226 c-apppw cells/form-app-password
176227 c-broker cells/broker-token
177- c-login cells/login-url]
228+ c-login cells/login-url
229+ c-screen cells/screen]
178230 :watch [st c-status er c-error cn c-connecting am c-mode
179231 fh c-handle nk c-nick hs c-host pt c-port tl c-tls
180- ap c-apppw bt c-broker lu c-login ls lines]
232+ ap c-apppw bt c-broker lu c-login ls lines sc c-screen]
181233 (m/SingleChildScrollView
182234 ;; frq.app's own connect screen, out of common/ — the same file the
183235 ;; desktop renders, painted by frq.hiccup in COSMIC's theme.
184- .child (h/render [connect/connect-screen]))))))
236+ .child (h/render (if (= :connect @cells/screen)
237+ [connect/connect-screen]
238+ [connected-screen])))))))
@@ -34,7 +34,10 @@
34 (defonce ^:private lines (atom []))34 (defonce ^:private lines (atom []))
35 (defonce ^:private conn (atom nil))35 (defonce ^:private conn (atom nil))
36 36
37+(defonce ^:private last-line (atom nil))
38+
37 (defn- note! [m]39 (defn- note! [m]
40+ (reset! last-line (str (:command m) " " (last (:params m))))
38 (swap! lines (fn [v] (vec (take-last 8 (conj v m))))))41 (swap! lines (fn [v] (vec (take-last 8 (conj v m))))))
39 42
40 (def ^:private registration-failed43 (def ^:private registration-failed
@@ -54,9 +57,16 @@
54 57
55 (defonce ^:private attempt (atom 0))58 (defonce ^:private attempt (atom 0))
56 59
60+(defonce ^:private closing?
61+ ;; Whether the close about to arrive is one we asked for. Without it a tap
62+ ;; on Disconnect comes back as "closed by the server", which is both untrue
63+ ;; and alarming.
64+ (atom false))
65+
57 (defn- fail! [why]66 (defn- fail! [why]
58 (reset! cells/connecting? false)67 (reset! cells/connecting? false)
59 (reset! cells/status "Not connected")68 (reset! cells/status "Not connected")
69+ (reset! cells/screen :connect)
60 (reset! cells/error why))70 (reset! cells/error why))
61 71
62 (defn ^:async connect!72 (defn ^:async connect!
@@ -71,7 +81,10 @@
71 []81 []
72 ;; Close whatever was open first. Connecting twice left the old socket82 ;; Close whatever was open first. Connecting twice left the old socket
73 ;; holding the nick, so the second attempt got 433 from its own predecessor.83 ;; holding the nick, so the second attempt got 433 from its own predecessor.
74- (when-let [c @conn] (net/close! c) (reset! conn nil))84+ (when-let [c @conn]
85+ (reset! closing? true)
86+ (net/close! c)
87+ (reset! conn nil))
75 (let [n (swap! attempt inc)]88 (let [n (swap! attempt inc)]
76 (reset! cells/connecting? true)89 (reset! cells/connecting? true)
77 (reset! cells/error nil)90 (reset! cells/error nil)
@@ -100,7 +113,14 @@
100 (do (reset! cells/connecting? false)113 (do (reset! cells/connecting? false)
101 (reset! cells/error nil)114 (reset! cells/error nil)
102 (reset! cells/status115 (reset! cells/status
103- (str "Connected as " @cells/form-nick)))116+ (str "Connected as " @cells/form-nick))
117+ ;; Off the connect screen, as
118+ ;; frq.state does on 001. Without
119+ ;; this a successful connect just
120+ ;; puts the Connect button back
121+ ;; and looks exactly like a
122+ ;; failure.
123+ (reset! cells/screen :chats))
104 124
105 (contains? registration-failed cmd)125 (contains? registration-failed cmd)
106 (fail! (if (= "433" cmd)126 (fail! (if (= "433" cmd)
@@ -114,10 +134,16 @@
114 (reset! cells/status (str "… " cmd)))))134 (reset! cells/status (str "… " cmd)))))
115 :on-close (fn [why]135 :on-close (fn [why]
116 (reset! conn nil)136 (reset! conn nil)
117- (if why137+ ;; Say what the last thing seen was. A
118- (fail! why)138+ ;; connection that registers and then
119- (do (reset! cells/connecting? false)139+ ;; drops is a different bug from one
120- (reset! cells/status "Not connected"))))}))]140+ ;; that never registers, and only the
141+ ;; last line apart tells them.
142+ (if @closing?
143+ (reset! closing? false)
144+ (fail! (str (or why "closed by the server")
145+ " (after " (count @lines)
146+ " lines, last: " @last-line ")"))))}))]
121 (reset! conn sock)147 (reset! conn sock)
122 (net/send-line! sock (str "NICK " @cells/form-nick))148 (net/send-line! sock (str "NICK " @cells/form-nick))
123 (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq")))149 (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq")))
@@ -128,10 +154,12 @@
128 (fail! (str e))))))154 (fail! (str e))))))
129 155
130 (defn- disconnect! []156 (defn- disconnect! []
157+ (reset! closing? true)
131 (when-let [c @conn] (net/close! c))158 (when-let [c @conn] (net/close! c))
132 (reset! conn nil)159 (reset! conn nil)
133 (reset! cells/connecting? false)160 (reset! cells/connecting? false)
134- (reset! cells/status "Not connected"))161+ (reset! cells/status "Not connected")
162+ (reset! cells/screen :connect))
135 163
136 (defonce ^:private handle (atom "nandi-test.bsky.social"))164 (defonce ^:private handle (atom "nandi-test.bsky.social"))
137 (defonce ^:private identity-out (atom nil))165 (defonce ^:private identity-out (atom nil))
@@ -146,6 +174,29 @@
146 (catch Exception e174 (catch Exception e
147 (reset! identity-out [(str "failed: " e)]))))175 (reset! identity-out [(str "failed: " e)]))))
148 176
177+(defn- connected-screen
178+ "Where a connect lands, until `frq.app`'s chats screen is portable.
179+
180+ Not much, and honest about it: the status, what the server said, and the way
181+ back. The desktop goes to :chats here — that screen reads rooms, messages,
182+ avatars and the media plane, which is most of what is not ported yet."
183+ []
184+ [:page {:max-width 520}
185+ [:title {:label "frq"}]
186+ [:status {:live true :label @cells/status}]
187+ [:card {}
188+ [:title-2 {:label "What the server said"}]
189+ (if (empty? @lines)
190+ [:dim-label {:label "nothing yet"}]
191+ (for [[i m] (map-indexed vector @lines)]
192+ [:label {:key i
193+ :label (str (:command m)
194+ (when-let [p (seq (:params m))]
195+ (str " " (last p))))}]))]
196+ [:hbox {:spacing 8}
197+ [:button {:label "Disconnect" :destructive true :on-click #(disconnect!)}]]
198+ [:dim-label {:label "The chats screen is next: it wants rooms, messages and avatars, none of which are ported yet."}]])
199+
149 (defn ^:async main []200 (defn ^:async main []
150 (m/WidgetsFlutterBinding.ensureInitialized)201 (m/WidgetsFlutterBinding.ensureInitialized)
151 (let [dir (.-path (await (pp/getApplicationSupportDirectory)))]202 (let [dir (.-path (await (pp/getApplicationSupportDirectory)))]
@@ -174,11 +225,14 @@
174 c-tls cells/form-tls?225 c-tls cells/form-tls?
175 c-apppw cells/form-app-password226 c-apppw cells/form-app-password
176 c-broker cells/broker-token227 c-broker cells/broker-token
177- c-login cells/login-url]228+ c-login cells/login-url
229+ c-screen cells/screen]
178 :watch [st c-status er c-error cn c-connecting am c-mode230 :watch [st c-status er c-error cn c-connecting am c-mode
179 fh c-handle nk c-nick hs c-host pt c-port tl c-tls231 fh c-handle nk c-nick hs c-host pt c-port tl c-tls
180- ap c-apppw bt c-broker lu c-login ls lines]232+ ap c-apppw bt c-broker lu c-login ls lines sc c-screen]
181 (m/SingleChildScrollView233 (m/SingleChildScrollView
182 ;; frq.app's own connect screen, out of common/ — the same file the234 ;; frq.app's own connect screen, out of common/ — the same file the
183 ;; desktop renders, painted by frq.hiccup in COSMIC's theme.235 ;; desktop renders, painted by frq.hiccup in COSMIC's theme.
184- .child (h/render [connect/connect-screen]))))))236+ .child (h/render (if (= :connect @cells/screen)
237+ [connect/connect-screen]
238+ [connected-screen])))))))