Merge remote-tracking branch 'origin/main' into claude/nick-in-title-cd90e2
modified
android/build-jolt-boot.sh +6 -1 | @@ -9,7 +9,12 @@ VIDYA="${VIDYA:-$(cd "$ROOT/../vidya" && pwd)}" | ||
| 9 | 9 | # has to be named here. |
| 10 | 10 | GLIMMER="${GLIMMER:-$HOME/.jolt/gitlibs/https___github.com_jolt-lang_glimmer/5581c331c51aff989259b9e8e92ec920fe5e6741/src}" |
| 11 | 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 | 18 | MODULE="${MODULE:-frq.app}" |
| 14 | 19 | CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" |
| 15 | 20 | 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 +25 -20 | @@ -162,27 +162,32 @@ | ||
| 162 | 162 | ;; lines are signed with, and the only other place it showed was Settings. |
| 163 | 163 | [:title {:label (if (s/connected?) (str "Chats as " @s/form-nick) "Chats")}] |
| 164 | 164 | [error-note] |
| 165 | + ;; Join and search are the same shape — a channel box with a button | |
| 166 | + ;; beside it — so they read as one control block rather than a titled | |
| 167 | + ;; card and a stray row: same card, same widths, same gap. The screen | |
| 168 | + ;; title already says what the box is for, which is what the "Join | |
| 169 | + ;; channel" header was doing. | |
| 165 | 170 | [:card {} |
| 166 | - [:title-2 {:label "Join channel"}] | |
| 167 | - [:hbox {:spacing 8} | |
| 168 | - [:entry {:text @s/join-input | |
| 169 | - :width-request 380 | |
| 170 | - :placeholder "#channel" | |
| 171 | - :on-change #(reset! s/join-input %) | |
| 172 | - :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | |
| 173 | - [:button {:label "Join" | |
| 174 | - :kind :primary | |
| 175 | - :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]]] | |
| 176 | - ;; The clear button only shows while there is something to clear: an | |
| 177 | - ;; empty box has nothing to undo, and a dead button beside it reads as | |
| 178 | - ;; one that stopped working. | |
| 179 | - [:hbox {:spacing 6} | |
| 180 | - [:entry {:text @s/search | |
| 181 | - :width-request 380 | |
| 182 | - :placeholder "Search channels" | |
| 183 | - :on-change #(reset! s/search %)}] | |
| 184 | - (when (seq @s/search) | |
| 185 | - [:button {:label "✕" :on-click #(reset! s/search "")}])] | |
| 171 | + [:vbox {:spacing 8} | |
| 172 | + [:hbox {:spacing 8} | |
| 173 | + [:entry {:text @s/join-input | |
| 174 | + :width-request 380 | |
| 175 | + :placeholder "#channel" | |
| 176 | + :on-change #(reset! s/join-input %) | |
| 177 | + :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | |
| 178 | + [:button {:label "Join" | |
| 179 | + :kind :primary | |
| 180 | + :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]] | |
| 181 | + ;; The clear button only shows while there is something to clear: an | |
| 182 | + ;; empty box has nothing to undo, and a dead button beside it reads as | |
| 183 | + ;; one that stopped working. | |
| 184 | + [:hbox {:spacing 8} | |
| 185 | + [:entry {:text @s/search | |
| 186 | + :width-request 380 | |
| 187 | + :placeholder "Search channels" | |
| 188 | + :on-change #(reset! s/search %)}] | |
| 189 | + (when (seq @s/search) | |
| 190 | + [:button {:label "✕" :on-click #(reset! s/search "")}])]]] | |
| 186 | 191 | (if (seq buffers) |
| 187 | 192 | (for [b buffers] [conversation-row b]) |
| 188 | 193 | [:card {} [:dim-label {:label "No conversations yet — join a channel."}]]) |
| @@ -162,27 +162,32 @@ | |||
| 162 | ;; lines are signed with, and the only other place it showed was Settings. | 162 | ;; lines are signed with, and the only other place it showed was Settings. |
| 163 | [:title {:label (if (s/connected?) (str "Chats as " @s/form-nick) "Chats")}] | 163 | [:title {:label (if (s/connected?) (str "Chats as " @s/form-nick) "Chats")}] |
| 164 | [error-note] | 164 | [error-note] |
| 165 | + ;; Join and search are the same shape — a channel box with a button | ||
| 166 | + ;; beside it — so they read as one control block rather than a titled | ||
| 167 | + ;; card and a stray row: same card, same widths, same gap. The screen | ||
| 168 | + ;; title already says what the box is for, which is what the "Join | ||
| 169 | + ;; channel" header was doing. | ||
| 165 | [:card {} | 170 | [:card {} |
| 166 | - [:title-2 {:label "Join channel"}] | 171 | + [:vbox {:spacing 8} |
| 167 | - [:hbox {:spacing 8} | 172 | + [:hbox {:spacing 8} |
| 168 | - [:entry {:text @s/join-input | 173 | + [:entry {:text @s/join-input |
| 169 | - :width-request 380 | 174 | + :width-request 380 |
| 170 | - :placeholder "#channel" | 175 | + :placeholder "#channel" |
| 171 | - :on-change #(reset! s/join-input %) | 176 | + :on-change #(reset! s/join-input %) |
| 172 | - :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | 177 | + :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}] |
| 173 | - [:button {:label "Join" | 178 | + [:button {:label "Join" |
| 174 | - :kind :primary | 179 | + :kind :primary |
| 175 | - :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]]] | 180 | + :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]] |
| 176 | - ;; The clear button only shows while there is something to clear: an | 181 | + ;; The clear button only shows while there is something to clear: an |
| 177 | - ;; empty box has nothing to undo, and a dead button beside it reads as | 182 | + ;; empty box has nothing to undo, and a dead button beside it reads as |
| 178 | - ;; one that stopped working. | 183 | + ;; one that stopped working. |
| 179 | - [:hbox {:spacing 6} | 184 | + [:hbox {:spacing 8} |
| 180 | - [:entry {:text @s/search | 185 | + [:entry {:text @s/search |
| 181 | - :width-request 380 | 186 | + :width-request 380 |
| 182 | - :placeholder "Search channels" | 187 | + :placeholder "Search channels" |
| 183 | - :on-change #(reset! s/search %)}] | 188 | + :on-change #(reset! s/search %)}] |
| 184 | - (when (seq @s/search) | 189 | + (when (seq @s/search) |
| 185 | - [:button {:label "✕" :on-click #(reset! s/search "")}])] | 190 | + [:button {:label "✕" :on-click #(reset! s/search "")}])]]] |
| 186 | (if (seq buffers) | 191 | (if (seq buffers) |
| 187 | (for [b buffers] [conversation-row b]) | 192 | (for [b buffers] [conversation-row b]) |
| 188 | [:card {} [:dim-label {:label "No conversations yet — join a channel."}]]) | 193 | [:card {} [:dim-label {:label "No conversations yet — join a channel."}]]) |