Say "frq" once, and let the guest sign-in finish
Two bugs on the phone's side of the shared screen, both from the scaffolding
that was there before it.
The title twice: connect-screen opens with [:title {:label "frq"}] and the
Scaffold had an AppBar saying the same. The desktop has no bar above that
title — a window's title belongs to the window — so the bar is gone and
SafeArea keeps the screen out from under the status bar.
And the sign-in that spun: `connect!` was writing its progress to a
frq.main-local `status` atom left over from when this file drew its own
panels, while the screen reads `cells/status`. So the label could never leave
"Not connected" however well the connection went. It writes to the cell now.
Its catch was `Exception`, which in Dart does not include Error — the two are
separate hierarchies — so anything that threw a TypeError or a
NoSuchMethodError left `connecting?` true for ever: a spinner with nothing
behind it and no message saying why. `catch Object` now, the same lesson the
blank screen taught.
Both were fixed together, so which of them the spin came from is not settled;
the status cell is certainly why nothing said it had worked.
Verified on the device: one "frq", and Connect reaches irc.freeq.at:6697 over
TLS and reports "Connected as frq-guest".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>232a455 parent: 0bc64b5 modified
flutter/src/frq/main.cljd +15 -59 | @@ -32,7 +32,6 @@ | ||
| 32 | 32 | |
| 33 | 33 | (defonce ^:private lines (atom [])) |
| 34 | 34 | (defonce ^:private conn (atom nil)) |
| 35 | -(defonce ^:private status (atom "Not connected")) | |
| 36 | 35 | |
| 37 | 36 | (defn- note! [m] |
| 38 | 37 | (swap! lines (fn [v] (vec (take-last 8 (conj v m)))))) |
| @@ -52,7 +51,7 @@ | ||
| 52 | 51 | [] |
| 53 | 52 | (reset! cells/connecting? true) |
| 54 | 53 | (reset! cells/error nil) |
| 55 | - (reset! status (str "Connecting to " @cells/form-host "…")) | |
| 54 | + (reset! cells/status (str "Connecting to " @cells/form-host "…")) | |
| 56 | 55 | (reset! lines []) |
| 57 | 56 | (try |
| 58 | 57 | (let [sock (await (net/connect! |
| @@ -64,25 +63,29 @@ | ||
| 64 | 63 | (note! m) |
| 65 | 64 | (when (= "001" (:command m)) |
| 66 | 65 | (reset! cells/connecting? false) |
| 67 | - (reset! status | |
| 66 | + (reset! cells/status | |
| 68 | 67 | (str "Connected as " @cells/form-nick)))) |
| 69 | 68 | :on-close (fn [why] |
| 70 | 69 | (reset! cells/connecting? false) |
| 71 | - (reset! status "Not connected") | |
| 70 | + (reset! cells/status "Not connected") | |
| 72 | 71 | (when why (reset! cells/error why)))}))] |
| 73 | 72 | (reset! conn sock) |
| 74 | 73 | (net/send-line! sock (str "NICK " @cells/form-nick)) |
| 75 | 74 | (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq"))) |
| 76 | - (catch Exception e | |
| 75 | + ;; Object, not Exception. Dart keeps Error and Exception in separate | |
| 76 | + ;; hierarchies, so a TypeError or a NoSuchMethodError is not an Exception | |
| 77 | + ;; and would leave `connecting?` true for ever — a spinner that never | |
| 78 | + ;; stops and no message saying why, which is exactly what this did. | |
| 79 | + (catch Object e | |
| 77 | 80 | (reset! cells/connecting? false) |
| 78 | - (reset! status "Not connected") | |
| 81 | + (reset! cells/status "Not connected") | |
| 79 | 82 | (reset! cells/error (str e))))) |
| 80 | 83 | |
| 81 | 84 | (defn- disconnect! [] |
| 82 | 85 | (when-let [c @conn] (net/close! c)) |
| 83 | 86 | (reset! conn nil) |
| 84 | 87 | (reset! cells/connecting? false) |
| 85 | - (reset! status "Not connected")) | |
| 88 | + (reset! cells/status "Not connected")) | |
| 86 | 89 | |
| 87 | 90 | (defonce ^:private handle (atom "nandi-test.bsky.social")) |
| 88 | 91 | (defonce ^:private identity-out (atom nil)) |
| @@ -97,57 +100,6 @@ | ||
| 97 | 100 | (catch Exception e |
| 98 | 101 | (reset! identity-out [(str "failed: " e)])))) |
| 99 | 102 | |
| 100 | -(defn- screen | |
| 101 | - "The phone's panels, in glimmer's tags. | |
| 102 | - | |
| 103 | - NOT `frq.screens.connect` yet, and the reason is worth writing down rather | |
| 104 | - than leaving as a TODO: the extraction is done and the desktop renders the | |
| 105 | - shared file, but rendering it here paints nothing at all — no exception, | |
| 106 | - nothing in logcat, and the sibling widgets in the same `children` vector | |
| 107 | - disappear with it, which is what a Dart `Error` thrown while that vector is | |
| 108 | - being built looks like. `(catch Object ...)` around it did not surface one | |
| 109 | - either, so the failure is not where it appears to be. | |
| 110 | - | |
| 111 | - What is already proven: `frq.cells` compiles under both compilers and holds | |
| 112 | - its values here — a probe read `cells/status` as \"Not connected\" and | |
| 113 | - `cells/form-nick` as \"frq-guest\" on the device. So the seam is sound and | |
| 114 | - what is left is one cljd-shaped bug between it and the screen." | |
| 115 | - [] | |
| 116 | - [:page {:max-width 520} | |
| 117 | - [:title {:label "frq"}] | |
| 118 | - [:dim-label {:label "Painted by frq.hiccup in COSMIC's own theme."}] | |
| 119 | - [:card {} | |
| 120 | - [:title-2 {:label "Connection"}] | |
| 121 | - [:label {:label @status}] | |
| 122 | - [:hbox {:spacing 8} | |
| 123 | - [:button {:label "Connect" :kind :primary :on-click #(connect!)}] | |
| 124 | - [:button {:label "Disconnect" :destructive true :on-click #(disconnect!)}]]] | |
| 125 | - [:card {} | |
| 126 | - [:title-2 {:label "What the server said"}] | |
| 127 | - (if (empty? @lines) | |
| 128 | - [:dim-label {:label "nothing yet"}] | |
| 129 | - (for [[i m] (map-indexed vector @lines)] | |
| 130 | - [:label {:key i | |
| 131 | - :label (str (:command m) | |
| 132 | - (when-let [p (seq (:params m))] | |
| 133 | - (str " " (last p))))}]))] | |
| 134 | - [:card {} | |
| 135 | - [:title-2 {:label "Identity"}] | |
| 136 | - [:entry {:key :handle | |
| 137 | - :text @handle | |
| 138 | - :placeholder "alice.bsky.social" | |
| 139 | - :on-change #(reset! handle %)}] | |
| 140 | - [:button {:label "Resolve" :kind :primary :on-click #(resolve-identity!)}] | |
| 141 | - (when-let [out @identity-out] | |
| 142 | - (for [[i line] (map-indexed vector out)] | |
| 143 | - [:label {:key i :label line}]))] | |
| 144 | - [:card {} | |
| 145 | - [:title-2 {:label "Shared with the desktop"}] | |
| 146 | - [:label {:label (str "clock " (clock/clock-time (clock/now-ms)))}] | |
| 147 | - [:label {:label (str "cells " @cells/form-nick " @ " @cells/form-host | |
| 148 | - ":" @cells/form-port)}] | |
| 149 | - [:dim-label {:label "frq.cells, compiled by both."}]]]) | |
| 150 | - | |
| 151 | 103 | (defn ^:async main [] |
| 152 | 104 | (m/WidgetsFlutterBinding.ensureInitialized) |
| 153 | 105 | (let [dir (.-path (await (pp/getApplicationSupportDirectory)))] |
| @@ -158,8 +110,12 @@ | ||
| 158 | 110 | (f/run |
| 159 | 111 | (m/MaterialApp .title "frq" .theme (t/app-theme)) |
| 160 | 112 | .home |
| 161 | - (m/Scaffold .appBar (m/AppBar .title (m/Text "frq"))) | |
| 113 | + ;; No AppBar. `connect-screen` opens with [:title {:label "frq"}] — the | |
| 114 | + ;; desktop has no bar above it either, the window's title is the | |
| 115 | + ;; window's — so a bar here says "frq" twice. | |
| 116 | + (m/Scaffold) | |
| 162 | 117 | .body |
| 118 | + m/SafeArea | |
| 163 | 119 | (f/widget |
| 164 | 120 | :let [c-status cells/status |
| 165 | 121 | c-error cells/error |
| @@ -32,7 +32,6 @@ | |||
| 32 | 32 | ||
| 33 | (defonce ^:private lines (atom [])) | 33 | (defonce ^:private lines (atom [])) |
| 34 | (defonce ^:private conn (atom nil)) | 34 | (defonce ^:private conn (atom nil)) |
| 35 | -(defonce ^:private status (atom "Not connected")) | ||
| 36 | 35 | ||
| 37 | (defn- note! [m] | 36 | (defn- note! [m] |
| 38 | (swap! lines (fn [v] (vec (take-last 8 (conj v m)))))) | 37 | (swap! lines (fn [v] (vec (take-last 8 (conj v m)))))) |
| @@ -52,7 +51,7 @@ | |||
| 52 | [] | 51 | [] |
| 53 | (reset! cells/connecting? true) | 52 | (reset! cells/connecting? true) |
| 54 | (reset! cells/error nil) | 53 | (reset! cells/error nil) |
| 55 | - (reset! status (str "Connecting to " @cells/form-host "…")) | 54 | + (reset! cells/status (str "Connecting to " @cells/form-host "…")) |
| 56 | (reset! lines []) | 55 | (reset! lines []) |
| 57 | (try | 56 | (try |
| 58 | (let [sock (await (net/connect! | 57 | (let [sock (await (net/connect! |
| @@ -64,25 +63,29 @@ | |||
| 64 | (note! m) | 63 | (note! m) |
| 65 | (when (= "001" (:command m)) | 64 | (when (= "001" (:command m)) |
| 66 | (reset! cells/connecting? false) | 65 | (reset! cells/connecting? false) |
| 67 | - (reset! status | 66 | + (reset! cells/status |
| 68 | (str "Connected as " @cells/form-nick)))) | 67 | (str "Connected as " @cells/form-nick)))) |
| 69 | :on-close (fn [why] | 68 | :on-close (fn [why] |
| 70 | (reset! cells/connecting? false) | 69 | (reset! cells/connecting? false) |
| 71 | - (reset! status "Not connected") | 70 | + (reset! cells/status "Not connected") |
| 72 | (when why (reset! cells/error why)))}))] | 71 | (when why (reset! cells/error why)))}))] |
| 73 | (reset! conn sock) | 72 | (reset! conn sock) |
| 74 | (net/send-line! sock (str "NICK " @cells/form-nick)) | 73 | (net/send-line! sock (str "NICK " @cells/form-nick)) |
| 75 | (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq"))) | 74 | (net/send-line! sock (str "USER " @cells/form-nick " 0 * :frq"))) |
| 76 | - (catch Exception e | 75 | + ;; Object, not Exception. Dart keeps Error and Exception in separate |
| 76 | + ;; hierarchies, so a TypeError or a NoSuchMethodError is not an Exception | ||
| 77 | + ;; and would leave `connecting?` true for ever — a spinner that never | ||
| 78 | + ;; stops and no message saying why, which is exactly what this did. | ||
| 79 | + (catch Object e | ||
| 77 | (reset! cells/connecting? false) | 80 | (reset! cells/connecting? false) |
| 78 | - (reset! status "Not connected") | 81 | + (reset! cells/status "Not connected") |
| 79 | (reset! cells/error (str e))))) | 82 | (reset! cells/error (str e))))) |
| 80 | 83 | ||
| 81 | (defn- disconnect! [] | 84 | (defn- disconnect! [] |
| 82 | (when-let [c @conn] (net/close! c)) | 85 | (when-let [c @conn] (net/close! c)) |
| 83 | (reset! conn nil) | 86 | (reset! conn nil) |
| 84 | (reset! cells/connecting? false) | 87 | (reset! cells/connecting? false) |
| 85 | - (reset! status "Not connected")) | 88 | + (reset! cells/status "Not connected")) |
| 86 | 89 | ||
| 87 | (defonce ^:private handle (atom "nandi-test.bsky.social")) | 90 | (defonce ^:private handle (atom "nandi-test.bsky.social")) |
| 88 | (defonce ^:private identity-out (atom nil)) | 91 | (defonce ^:private identity-out (atom nil)) |
| @@ -97,57 +100,6 @@ | |||
| 97 | (catch Exception e | 100 | (catch Exception e |
| 98 | (reset! identity-out [(str "failed: " e)])))) | 101 | (reset! identity-out [(str "failed: " e)])))) |
| 99 | 102 | ||
| 100 | -(defn- screen | ||
| 101 | - "The phone's panels, in glimmer's tags. | ||
| 102 | - | ||
| 103 | - NOT `frq.screens.connect` yet, and the reason is worth writing down rather | ||
| 104 | - than leaving as a TODO: the extraction is done and the desktop renders the | ||
| 105 | - shared file, but rendering it here paints nothing at all — no exception, | ||
| 106 | - nothing in logcat, and the sibling widgets in the same `children` vector | ||
| 107 | - disappear with it, which is what a Dart `Error` thrown while that vector is | ||
| 108 | - being built looks like. `(catch Object ...)` around it did not surface one | ||
| 109 | - either, so the failure is not where it appears to be. | ||
| 110 | - | ||
| 111 | - What is already proven: `frq.cells` compiles under both compilers and holds | ||
| 112 | - its values here — a probe read `cells/status` as \"Not connected\" and | ||
| 113 | - `cells/form-nick` as \"frq-guest\" on the device. So the seam is sound and | ||
| 114 | - what is left is one cljd-shaped bug between it and the screen." | ||
| 115 | - [] | ||
| 116 | - [:page {:max-width 520} | ||
| 117 | - [:title {:label "frq"}] | ||
| 118 | - [:dim-label {:label "Painted by frq.hiccup in COSMIC's own theme."}] | ||
| 119 | - [:card {} | ||
| 120 | - [:title-2 {:label "Connection"}] | ||
| 121 | - [:label {:label @status}] | ||
| 122 | - [:hbox {:spacing 8} | ||
| 123 | - [:button {:label "Connect" :kind :primary :on-click #(connect!)}] | ||
| 124 | - [:button {:label "Disconnect" :destructive true :on-click #(disconnect!)}]]] | ||
| 125 | - [:card {} | ||
| 126 | - [:title-2 {:label "What the server said"}] | ||
| 127 | - (if (empty? @lines) | ||
| 128 | - [:dim-label {:label "nothing yet"}] | ||
| 129 | - (for [[i m] (map-indexed vector @lines)] | ||
| 130 | - [:label {:key i | ||
| 131 | - :label (str (:command m) | ||
| 132 | - (when-let [p (seq (:params m))] | ||
| 133 | - (str " " (last p))))}]))] | ||
| 134 | - [:card {} | ||
| 135 | - [:title-2 {:label "Identity"}] | ||
| 136 | - [:entry {:key :handle | ||
| 137 | - :text @handle | ||
| 138 | - :placeholder "alice.bsky.social" | ||
| 139 | - :on-change #(reset! handle %)}] | ||
| 140 | - [:button {:label "Resolve" :kind :primary :on-click #(resolve-identity!)}] | ||
| 141 | - (when-let [out @identity-out] | ||
| 142 | - (for [[i line] (map-indexed vector out)] | ||
| 143 | - [:label {:key i :label line}]))] | ||
| 144 | - [:card {} | ||
| 145 | - [:title-2 {:label "Shared with the desktop"}] | ||
| 146 | - [:label {:label (str "clock " (clock/clock-time (clock/now-ms)))}] | ||
| 147 | - [:label {:label (str "cells " @cells/form-nick " @ " @cells/form-host | ||
| 148 | - ":" @cells/form-port)}] | ||
| 149 | - [:dim-label {:label "frq.cells, compiled by both."}]]]) | ||
| 150 | - | ||
| 151 | (defn ^:async main [] | 103 | (defn ^:async main [] |
| 152 | (m/WidgetsFlutterBinding.ensureInitialized) | 104 | (m/WidgetsFlutterBinding.ensureInitialized) |
| 153 | (let [dir (.-path (await (pp/getApplicationSupportDirectory)))] | 105 | (let [dir (.-path (await (pp/getApplicationSupportDirectory)))] |
| @@ -158,8 +110,12 @@ | |||
| 158 | (f/run | 110 | (f/run |
| 159 | (m/MaterialApp .title "frq" .theme (t/app-theme)) | 111 | (m/MaterialApp .title "frq" .theme (t/app-theme)) |
| 160 | .home | 112 | .home |
| 161 | - (m/Scaffold .appBar (m/AppBar .title (m/Text "frq"))) | 113 | + ;; No AppBar. `connect-screen` opens with [:title {:label "frq"}] — the |
| 114 | + ;; desktop has no bar above it either, the window's title is the | ||
| 115 | + ;; window's — so a bar here says "frq" twice. | ||
| 116 | + (m/Scaffold) | ||
| 162 | .body | 117 | .body |
| 118 | + m/SafeArea | ||
| 163 | (f/widget | 119 | (f/widget |
| 164 | :let [c-status cells/status | 120 | :let [c-status cells/status |
| 165 | c-error cells/error | 121 | c-error cells/error |