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>
19a2ec6 parent: ba9e7a9 modified
flutter/src/frq/main.cljd +64 -10 | @@ -34,7 +34,10 @@ | ||
| 34 | 34 | (defonce ^:private lines (atom [])) |
| 35 | 35 | (defonce ^:private conn (atom nil)) |
| 36 | 36 | |
| 37 | +(defonce ^:private last-line (atom nil)) | |
| 38 | + | |
| 37 | 39 | (defn- note! [m] |
| 40 | + (reset! last-line (str (:command m) " " (last (:params m)))) | |
| 38 | 41 | (swap! lines (fn [v] (vec (take-last 8 (conj v m)))))) |
| 39 | 42 | |
| 40 | 43 | (def ^:private registration-failed |
| @@ -54,9 +57,16 @@ | ||
| 54 | 57 | |
| 55 | 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 | 66 | (defn- fail! [why] |
| 58 | 67 | (reset! cells/connecting? false) |
| 59 | 68 | (reset! cells/status "Not connected") |
| 69 | + (reset! cells/screen :connect) | |
| 60 | 70 | (reset! cells/error why)) |
| 61 | 71 | |
| 62 | 72 | (defn ^:async connect! |
| @@ -71,7 +81,10 @@ | ||
| 71 | 81 | [] |
| 72 | 82 | ;; Close whatever was open first. Connecting twice left the old socket |
| 73 | 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 | 88 | (let [n (swap! attempt inc)] |
| 76 | 89 | (reset! cells/connecting? true) |
| 77 | 90 | (reset! cells/error nil) |
| @@ -100,7 +113,14 @@ | ||
| 100 | 113 | (do (reset! cells/connecting? false) |
| 101 | 114 | (reset! cells/error nil) |
| 102 | 115 | (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 | 125 | (contains? registration-failed cmd) |
| 106 | 126 | (fail! (if (= "433" cmd) |
| @@ -114,10 +134,16 @@ | ||
| 114 | 134 | (reset! cells/status (str "… " cmd))))) |
| 115 | 135 | :on-close (fn [why] |
| 116 | 136 | (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 ")"))))}))] | |
| 121 | 147 | (reset! conn sock) |
| 122 | 148 | (net/send-line! sock (str "NICK " @cells/form-nick)) |
| 123 | 149 | (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq"))) |
| @@ -128,10 +154,12 @@ | ||
| 128 | 154 | (fail! (str e)))))) |
| 129 | 155 | |
| 130 | 156 | (defn- disconnect! [] |
| 157 | + (reset! closing? true) | |
| 131 | 158 | (when-let [c @conn] (net/close! c)) |
| 132 | 159 | (reset! conn nil) |
| 133 | 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 | 164 | (defonce ^:private handle (atom "nandi-test.bsky.social")) |
| 137 | 165 | (defonce ^:private identity-out (atom nil)) |
| @@ -146,6 +174,29 @@ | ||
| 146 | 174 | (catch Exception e |
| 147 | 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 | 200 | (defn ^:async main [] |
| 150 | 201 | (m/WidgetsFlutterBinding.ensureInitialized) |
| 151 | 202 | (let [dir (.-path (await (pp/getApplicationSupportDirectory)))] |
| @@ -174,11 +225,14 @@ | ||
| 174 | 225 | c-tls cells/form-tls? |
| 175 | 226 | c-apppw cells/form-app-password |
| 176 | 227 | c-broker cells/broker-token |
| 177 | - c-login cells/login-url] | |
| 228 | + c-login cells/login-url | |
| 229 | + c-screen cells/screen] | |
| 178 | 230 | :watch [st c-status er c-error cn c-connecting am c-mode |
| 179 | 231 | 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 | 233 | (m/SingleChildScrollView |
| 182 | 234 | ;; frq.app's own connect screen, out of common/ — the same file the |
| 183 | 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]))))))) | |
| @@ -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-failed | 43 | (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 socket | 82 | ;; 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/status | 115 | (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 why | 137 | + ;; 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 e | 174 | (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-password | 226 | c-apppw cells/form-app-password |
| 176 | c-broker cells/broker-token | 227 | 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-mode | 230 | :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-tls | 231 | 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/SingleChildScrollView | 233 | (m/SingleChildScrollView |
| 182 | ;; frq.app's own connect screen, out of common/ — the same file the | 234 | ;; 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]))))))) | ||