nandi/frqpublic Fork
⑂ 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.

Download ZIP

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])))))))