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

Merge remote-tracking branch 'origin/main' into claude/copy-profiles-sleek-aee53c

nandi committed 2026-08-30T18:54:17-07:00 Browse files
ac59bf2 parents: b79ebc4 297564c
modified android/build-jolt-boot.sh +6 -1
@@ -9,7 +9,12 @@ VIDYA="${VIDYA:-$(cd "$ROOT/../vidya" && pwd)}"
99 # has to be named here.
1010 GLIMMER="${GLIMMER:-$HOME/.jolt/gitlibs/https___github.com_jolt-lang_glimmer/5581c331c51aff989259b9e8e92ec920fe5e6741/src}"
1111 OUT="${1:?usage: build-jolt-boot.sh OUTPUT_DIRECTORY}"
12-JOLT="${JOLT:-$(command -v jolt)}"
12+# The DotSlash-pinned jolt beside this script, not whatever is on PATH: an
13+# upstream jolt cannot open a TLS connection on Android — it reads the socket
14+# address out of `struct addrinfo` at glibc's offset, which is Bionic's
15+# `ai_canonname` — so a build made with one produces an APK that cannot sign in
16+# or send a picture. Override with JOLT= to use another.
17+JOLT="${JOLT:-$ROOT/scripts/jolt}"
1318 MODULE="${MODULE:-frq.app}"
1419 CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}"
1520 HOST_SCHEME="$CHEZ_ANDROID/ta6le/bin/ta6le/scheme"
@@ -9,7 +9,12 @@ VIDYA="${VIDYA:-$(cd "$ROOT/../vidya" && pwd)}"
9 # has to be named here.9 # has to be named here.
10 GLIMMER="${GLIMMER:-$HOME/.jolt/gitlibs/https___github.com_jolt-lang_glimmer/5581c331c51aff989259b9e8e92ec920fe5e6741/src}"10 GLIMMER="${GLIMMER:-$HOME/.jolt/gitlibs/https___github.com_jolt-lang_glimmer/5581c331c51aff989259b9e8e92ec920fe5e6741/src}"
11 OUT="${1:?usage: build-jolt-boot.sh OUTPUT_DIRECTORY}"11 OUT="${1:?usage: build-jolt-boot.sh OUTPUT_DIRECTORY}"
12-JOLT="${JOLT:-$(command -v jolt)}"12+# The DotSlash-pinned jolt beside this script, not whatever is on PATH: an
13+# upstream jolt cannot open a TLS connection on Android — it reads the socket
14+# address out of `struct addrinfo` at glibc's offset, which is Bionic's
15+# `ai_canonname` — so a build made with one produces an APK that cannot sign in
16+# or send a picture. Override with JOLT= to use another.
17+JOLT="${JOLT:-$ROOT/scripts/jolt}"
13 MODULE="${MODULE:-frq.app}"18 MODULE="${MODULE:-frq.app}"
14 CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}"19 CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}"
15 HOST_SCHEME="$CHEZ_ANDROID/ta6le/bin/ta6le/scheme"20 HOST_SCHEME="$CHEZ_ANDROID/ta6le/bin/ta6le/scheme"
added scripts/jolt +28 -0
new file mode 100755
@@ -0,0 +1,28 @@
1+#!/usr/bin/env dotslash
2+
3+// jolt with the Bionic `struct addrinfo` fix, from the fork at
4+// gitlab.com/nandithebull/jolt (commit 2b80d68d). Upstream's own release reads
5+// `ai_addr` at glibc's offset, which on Android is `ai_canonname` a null
6+// pointer so every TLS connect fails there and reports a refusal the network
7+// never made. Nothing else differs from v0.7.28.
8+//
9+// Linux x86_64 only: it is what the APK is built on. Other platforms fall back
10+// to whatever `jolt` is on PATH, which is upstream's, which is fine for
11+// everything but an Android build.
12+{
13+ "name": "jolt",
14+ "platforms": {
15+ "linux-x86_64": {
16+ "size": 15782379,
17+ "hash": "sha256",
18+ "digest": "aabb71f809aebd9d607b7a5036229f933c733ae90785cb29df899f116be9589f",
19+ "format": "tar.gz",
20+ "path": "jolt-v0.7.28-1-g2b80d68d-x86_64-linux/jolt",
21+ "providers": [
22+ {
23+ "url": "https://gitlab.com/nandithebull/jolt/-/releases/v0.7.28-android/downloads/jolt-v0.7.28-1-g2b80d68d-x86_64-linux.tar.gz"
24+ }
25+ ]
26+ }
27+ }
28+}
new file mode 100755
@@ -0,0 +1,28 @@
1+#!/usr/bin/env dotslash
2+
3+// jolt with the Bionic `struct addrinfo` fix, from the fork at
4+// gitlab.com/nandithebull/jolt (commit 2b80d68d). Upstream's own release reads
5+// `ai_addr` at glibc's offset, which on Android is `ai_canonname` a null
6+// pointer so every TLS connect fails there and reports a refusal the network
7+// never made. Nothing else differs from v0.7.28.
8+//
9+// Linux x86_64 only: it is what the APK is built on. Other platforms fall back
10+// to whatever `jolt` is on PATH, which is upstream's, which is fine for
11+// everything but an Android build.
12+{
13+ "name": "jolt",
14+ "platforms": {
15+ "linux-x86_64": {
16+ "size": 15782379,
17+ "hash": "sha256",
18+ "digest": "aabb71f809aebd9d607b7a5036229f933c733ae90785cb29df899f116be9589f",
19+ "format": "tar.gz",
20+ "path": "jolt-v0.7.28-1-g2b80d68d-x86_64-linux/jolt",
21+ "providers": [
22+ {
23+ "url": "https://gitlab.com/nandithebull/jolt/-/releases/v0.7.28-android/downloads/jolt-v0.7.28-1-g2b80d68d-x86_64-linux.tar.gz"
24+ }
25+ ]
26+ }
27+ }
28+}
modified src/frq/app.jolt +47 -21
@@ -158,29 +158,37 @@
158158 (defn chats-screen []
159159 (let [buffers (s/channel-list)]
160160 [:page {:max-width 620}
161- [:title {:label "Chats"}]
161+ ;; Your nick in the title, where "Chats" alone said nothing you did not
162+ ;; already know. It is what every channel calls you and what your own
163+ ;; lines are signed with, and the only other place it showed was Settings.
164+ [:title {:label (if (s/connected?) (str "Chats as " @s/form-nick) "Chats")}]
162165 [error-note]
166+ ;; Join and search are the same shape a channel box with a button
167+ ;; beside it so they read as one control block rather than a titled
168+ ;; card and a stray row: same card, same widths, same gap. The screen
169+ ;; title already says what the box is for, which is what the "Join
170+ ;; channel" header was doing.
163171 [:card {}
164- [:title-2 {:label "Join channel"}]
165- [:hbox {:spacing 8}
166- [:entry {:text @s/join-input
167- :width-request 380
168- :placeholder "#channel"
169- :on-change #(reset! s/join-input %)
170- :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}]
171- [:button {:label "Join"
172- :kind :primary
173- :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]]]
174- ;; The clear button only shows while there is something to clear: an
175- ;; empty box has nothing to undo, and a dead button beside it reads as
176- ;; one that stopped working.
177- [:hbox {:spacing 6}
178- [:entry {:text @s/search
179- :width-request 380
180- :placeholder "Search channels"
181- :on-change #(reset! s/search %)}]
182- (when (seq @s/search)
183- [:button {:label "" :on-click #(reset! s/search "")}])]
172+ [:vbox {:spacing 8}
173+ [:hbox {:spacing 8}
174+ [:entry {:text @s/join-input
175+ :width-request 380
176+ :placeholder "#channel"
177+ :on-change #(reset! s/join-input %)
178+ :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}]
179+ [:button {:label "Join"
180+ :kind :primary
181+ :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]]
182+ ;; The clear button only shows while there is something to clear: an
183+ ;; empty box has nothing to undo, and a dead button beside it reads as
184+ ;; one that stopped working.
185+ [:hbox {:spacing 8}
186+ [:entry {:text @s/search
187+ :width-request 380
188+ :placeholder "Search channels"
189+ :on-change #(reset! s/search %)}]
190+ (when (seq @s/search)
191+ [:button {:label "" :on-click #(reset! s/search "")}])]]]
184192 (if (seq buffers)
185193 (for [b buffers] [conversation-row b])
186194 [:card {} [:dim-label {:label "No conversations yet — join a channel."}]])
@@ -1024,4 +1032,22 @@
10241032 (vidya/every! 200 #(let [w (vidya/window-width)]
10251033 (when (not= w @s/window-width)
10261034 (reset! s/window-width w))))
1035+ ;; The nick in the window title, so a second window of this client is told
1036+ ;; apart from the first by the one thing that differs and so the answer to
1037+ ;; "who am I here?" is on screen without opening Settings.
1038+ ;;
1039+ ;; Polled, like the width above and for the same reason: the title belongs to
1040+ ;; the window rather than to the tree, so no render puts it there, and the
1041+ ;; nick is only settled once a connection has been made. Twice a second is
1042+ ;; far more often than a nick changes, and a string compare is what a tick
1043+ ;; costs when it has not.
1044+ ;; Seeded with the title `run` opens the window under, so the first tick of
1045+ ;; a launch that has nobody signed in yet sets nothing.
1046+ (let [shown (atom "frq")]
1047+ (vidya/every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick))
1048+ (str "frq — " @s/form-nick)
1049+ "frq")]
1050+ (when (not= title @shown)
1051+ (reset! shown title)
1052+ (vidya/set-title! title)))))
10271053 (ui/run app :title "frq" :width 520 :height 860))
@@ -158,29 +158,37 @@
158 (defn chats-screen []158 (defn chats-screen []
159 (let [buffers (s/channel-list)]159 (let [buffers (s/channel-list)]
160 [:page {:max-width 620}160 [:page {:max-width 620}
161- [:title {:label "Chats"}]161+ ;; Your nick in the title, where "Chats" alone said nothing you did not
162+ ;; already know. It is what every channel calls you and what your own
163+ ;; lines are signed with, and the only other place it showed was Settings.
164+ [:title {:label (if (s/connected?) (str "Chats as " @s/form-nick) "Chats")}]
162 [error-note]165 [error-note]
166+ ;; Join and search are the same shape a channel box with a button
167+ ;; beside it so they read as one control block rather than a titled
168+ ;; card and a stray row: same card, same widths, same gap. The screen
169+ ;; title already says what the box is for, which is what the "Join
170+ ;; channel" header was doing.
163 [:card {}171 [:card {}
164- [:title-2 {:label "Join channel"}]172+ [:vbox {:spacing 8}
165- [:hbox {:spacing 8}173+ [:hbox {:spacing 8}
166- [:entry {:text @s/join-input174+ [:entry {:text @s/join-input
167- :width-request 380175+ :width-request 380
168- :placeholder "#channel"176+ :placeholder "#channel"
169- :on-change #(reset! s/join-input %)177+ :on-change #(reset! s/join-input %)
170- :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}]178+ :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}]
171- [:button {:label "Join"179+ [:button {:label "Join"
172- :kind :primary180+ :kind :primary
173- :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]]]181+ :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]]
174- ;; The clear button only shows while there is something to clear: an182+ ;; The clear button only shows while there is something to clear: an
175- ;; empty box has nothing to undo, and a dead button beside it reads as183+ ;; empty box has nothing to undo, and a dead button beside it reads as
176- ;; one that stopped working.184+ ;; one that stopped working.
177- [:hbox {:spacing 6}185+ [:hbox {:spacing 8}
178- [:entry {:text @s/search186+ [:entry {:text @s/search
179- :width-request 380187+ :width-request 380
180- :placeholder "Search channels"188+ :placeholder "Search channels"
181- :on-change #(reset! s/search %)}]189+ :on-change #(reset! s/search %)}]
182- (when (seq @s/search)190+ (when (seq @s/search)
183- [:button {:label "" :on-click #(reset! s/search "")}])]191+ [:button {:label "" :on-click #(reset! s/search "")}])]]]
184 (if (seq buffers)192 (if (seq buffers)
185 (for [b buffers] [conversation-row b])193 (for [b buffers] [conversation-row b])
186 [:card {} [:dim-label {:label "No conversations yet — join a channel."}]])194 [:card {} [:dim-label {:label "No conversations yet — join a channel."}]])
@@ -1024,4 +1032,22 @@
1024 (vidya/every! 200 #(let [w (vidya/window-width)]1032 (vidya/every! 200 #(let [w (vidya/window-width)]
1025 (when (not= w @s/window-width)1033 (when (not= w @s/window-width)
1026 (reset! s/window-width w))))1034 (reset! s/window-width w))))
1035+ ;; The nick in the window title, so a second window of this client is told
1036+ ;; apart from the first by the one thing that differs and so the answer to
1037+ ;; "who am I here?" is on screen without opening Settings.
1038+ ;;
1039+ ;; Polled, like the width above and for the same reason: the title belongs to
1040+ ;; the window rather than to the tree, so no render puts it there, and the
1041+ ;; nick is only settled once a connection has been made. Twice a second is
1042+ ;; far more often than a nick changes, and a string compare is what a tick
1043+ ;; costs when it has not.
1044+ ;; Seeded with the title `run` opens the window under, so the first tick of
1045+ ;; a launch that has nobody signed in yet sets nothing.
1046+ (let [shown (atom "frq")]
1047+ (vidya/every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick))
1048+ (str "frq — " @s/form-nick)
1049+ "frq")]
1050+ (when (not= title @shown)
1051+ (reset! shown title)
1052+ (vidya/set-title! title)))))
1027 (ui/run app :title "frq" :width 520 :height 860))1053 (ui/run app :title "frq" :width 520 :height 860))