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

Set an emoji in a message as a character, not as a reaction

The emoji came back from tofu wearing a pill: `:reaction` was the only
node that drew from the Twemoji pack, so a glyph in a sentence arrived
with a fill, a border and a hover card, and a line that ended in one read
as a line somebody had reacted to. Two 🤔 in a message and a reader
counts two reactions.

jolt-native grew `:emoji` for this — the same picture with none of the
tally around it — so ask for that instead. The row keeps a 2px spacing it
did not need before: runs alternate text and picture, so the gap only
ever falls either side of an emoji, and a bare glyph has no inner margin
of its own the way the pill did.

The pin moves with the call site, because a lock that named the old
jolt-native would make `:emoji` an unknown tag and drop the emoji from
the line altogether — worse than the pill it replaces. It also catches
frq up on the terminal backend, which landed while the lock sat still.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-02T18:52:31-07:00 Browse files
00b5f93 parent: 9afdfa4
modified flake.lock +5 -5
@@ -110,16 +110,16 @@
110110 "jolt-native": {
111111 "flake": false,
112112 "locked": {
113- "lastModified": 1788324929,
114- "narHash": "sha256-EqvQn/ZUxrWJPXVlMvJkCUeHWemElG8wzPveGyRmaX8=",
113+ "lastModified": 1788399042,
114+ "narHash": "sha256-8fqp7OGifbyaS8cKMlBou/NtH8/o5laGomF9Pmptxck=",
115115 "ref": "refs/heads/main",
116- "rev": "a9f3673367a8f0d3dd7d6ab1d380484d1df795c1",
117- "revCount": 30,
116+ "rev": "00d32dd6bf0cde9cd73efa265c3057741d82e524",
117+ "revCount": 32,
118118 "type": "git",
119119 "url": "https://gitlab.com/nandithebull/jolt-native"
120120 },
121121 "original": {
122- "rev": "a9f3673367a8f0d3dd7d6ab1d380484d1df795c1",
122+ "rev": "00d32dd6bf0cde9cd73efa265c3057741d82e524",
123123 "type": "git",
124124 "url": "https://gitlab.com/nandithebull/jolt-native"
125125 }
@@ -110,16 +110,16 @@
110 "jolt-native": {110 "jolt-native": {
111 "flake": false,111 "flake": false,
112 "locked": {112 "locked": {
113- "lastModified": 1788324929,113+ "lastModified": 1788399042,
114- "narHash": "sha256-EqvQn/ZUxrWJPXVlMvJkCUeHWemElG8wzPveGyRmaX8=",114+ "narHash": "sha256-8fqp7OGifbyaS8cKMlBou/NtH8/o5laGomF9Pmptxck=",
115 "ref": "refs/heads/main",115 "ref": "refs/heads/main",
116- "rev": "a9f3673367a8f0d3dd7d6ab1d380484d1df795c1",116+ "rev": "00d32dd6bf0cde9cd73efa265c3057741d82e524",
117- "revCount": 30,117+ "revCount": 32,
118 "type": "git",118 "type": "git",
119 "url": "https://gitlab.com/nandithebull/jolt-native"119 "url": "https://gitlab.com/nandithebull/jolt-native"
120 },120 },
121 "original": {121 "original": {
122- "rev": "a9f3673367a8f0d3dd7d6ab1d380484d1df795c1",122+ "rev": "00d32dd6bf0cde9cd73efa265c3057741d82e524",
123 "type": "git",123 "type": "git",
124 "url": "https://gitlab.com/nandithebull/jolt-native"124 "url": "https://gitlab.com/nandithebull/jolt-native"
125 }125 }
modified flake.nix +1 -1
@@ -41,7 +41,7 @@
4141 # drift cost was silence: the Jolt half sent a reaction pill's hover card
4242 # to a libvidya with no handler for one, and the pill said nothing.
4343 jolt-native = {
44- url = "git+https://gitlab.com/nandithebull/jolt-native?rev=a9f3673367a8f0d3dd7d6ab1d380484d1df795c1";
44+ url = "git+https://gitlab.com/nandithebull/jolt-native?rev=00d32dd6bf0cde9cd73efa265c3057741d82e524";
4545 flake = false;
4646 };
4747
@@ -41,7 +41,7 @@
41 # drift cost was silence: the Jolt half sent a reaction pill's hover card41 # drift cost was silence: the Jolt half sent a reaction pill's hover card
42 # to a libvidya with no handler for one, and the pill said nothing.42 # to a libvidya with no handler for one, and the pill said nothing.
43 jolt-native = {43 jolt-native = {
44- url = "git+https://gitlab.com/nandithebull/jolt-native?rev=a9f3673367a8f0d3dd7d6ab1d380484d1df795c1";44+ url = "git+https://gitlab.com/nandithebull/jolt-native?rev=00d32dd6bf0cde9cd73efa265c3057741d82e524";
45 flake = false;45 flake = false;
46 };46 };
47 47
modified src/frq/app.jolt +16 -8
@@ -293,10 +293,15 @@
293293
294294 Except for the emoji in it. A label is set in the text font, and that font —
295295 Ubuntu with a DejaVu subset behind it — has no emoji in it at all, so every
296- one anybody typed was drawn as tofu. A `:reaction` is the one node that
297- draws from the Twemoji pack, which is why a reaction pill has always shown
298- the same glyph a message could not: so the emoji come out of the text and go
299- in as chips, and the words either side stay text.
296+ one anybody typed was drawn as tofu. The Twemoji pack has the picture, so
297+ the emoji come out of the text and are drawn from the pack, and the words
298+ either side stay text.
299+
300+ `:emoji` and not `:reaction`, though both draw the same picture. A reaction
301+ is a tally: it wears a pill, it answers the pointer, and it names the people
302+ in it on hover. An emoji in a sentence is a character — so it is drawn bare,
303+ and a message that ends in one no longer reads as a message someone has
304+ reacted to.
300305
301306 The pieces go in a wrapping row rather than the column the runs themselves
302307 are stacked in: a row is what puts an emoji *in* a sentence instead of
@@ -308,12 +313,15 @@
308313 [:link {:key j :label value :on-click #(platform/open-url! value)}]
309314 (let [pieces (glyphs/runs (str/trim value))]
310315 (if (glyphs/emoji? pieces)
311- ;; No spacing of its own: the gaps between the words are the spaces
312- ;; the message was typed with, and a chip carries its own margin.
313- [:hbox {:key j :spacing 0}
316+ ;; Runs alternate text and picture, so this gap only ever falls either
317+ ;; side of an emoji — never between two words, whose spacing is the
318+ ;; spaces the message was typed with. A bare glyph carries no margin of
319+ ;; its own the way the pill did, and a picture set flush against a word
320+ ;; reads as part of it.
321+ [:hbox {:key j :spacing 2}
314322 (map-indexed (fn [k [pkind pvalue]]
315323 (if (= :emoji pkind)
316- [:reaction {:key k :emoji pvalue :size text-emoji-size}]
324+ [:emoji {:key k :emoji pvalue :size text-emoji-size}]
317325 (word-node k pvalue system?)))
318326 pieces)]
319327 (word-node j (str/trim value) system?)))))
@@ -293,10 +293,15 @@
293 293
294 Except for the emoji in it. A label is set in the text font, and that font —294 Except for the emoji in it. A label is set in the text font, and that font —
295 Ubuntu with a DejaVu subset behind it — has no emoji in it at all, so every295 Ubuntu with a DejaVu subset behind it — has no emoji in it at all, so every
296- one anybody typed was drawn as tofu. A `:reaction` is the one node that296+ one anybody typed was drawn as tofu. The Twemoji pack has the picture, so
297- draws from the Twemoji pack, which is why a reaction pill has always shown297+ the emoji come out of the text and are drawn from the pack, and the words
298- the same glyph a message could not: so the emoji come out of the text and go298+ either side stay text.
299- in as chips, and the words either side stay text.299+
300+ `:emoji` and not `:reaction`, though both draw the same picture. A reaction
301+ is a tally: it wears a pill, it answers the pointer, and it names the people
302+ in it on hover. An emoji in a sentence is a character — so it is drawn bare,
303+ and a message that ends in one no longer reads as a message someone has
304+ reacted to.
300 305
301 The pieces go in a wrapping row rather than the column the runs themselves306 The pieces go in a wrapping row rather than the column the runs themselves
302 are stacked in: a row is what puts an emoji *in* a sentence instead of307 are stacked in: a row is what puts an emoji *in* a sentence instead of
@@ -308,12 +313,15 @@
308 [:link {:key j :label value :on-click #(platform/open-url! value)}]313 [:link {:key j :label value :on-click #(platform/open-url! value)}]
309 (let [pieces (glyphs/runs (str/trim value))]314 (let [pieces (glyphs/runs (str/trim value))]
310 (if (glyphs/emoji? pieces)315 (if (glyphs/emoji? pieces)
311- ;; No spacing of its own: the gaps between the words are the spaces316+ ;; Runs alternate text and picture, so this gap only ever falls either
312- ;; the message was typed with, and a chip carries its own margin.317+ ;; side of an emoji — never between two words, whose spacing is the
313- [:hbox {:key j :spacing 0}318+ ;; spaces the message was typed with. A bare glyph carries no margin of
319+ ;; its own the way the pill did, and a picture set flush against a word
320+ ;; reads as part of it.
321+ [:hbox {:key j :spacing 2}
314 (map-indexed (fn [k [pkind pvalue]]322 (map-indexed (fn [k [pkind pvalue]]
315 (if (= :emoji pkind)323 (if (= :emoji pkind)
316- [:reaction {:key k :emoji pvalue :size text-emoji-size}]324+ [:emoji {:key k :emoji pvalue :size text-emoji-size}]
317 (word-node k pvalue system?)))325 (word-node k pvalue system?)))
318 pieces)]326 pieces)]
319 (word-node j (str/trim value) system?)))))327 (word-node j (str/trim value) system?)))))
modified src/frq/glyphs.jolt +4 -3
@@ -5,8 +5,9 @@
55 UI punctuation, maths-in-prose, box drawing and no emoji at all. So an
66 emoji anybody typed into a message was drawn as tofu: the hollow box a font
77 puts up for a codepoint it has no glyph for. The same emoji in a reaction
8- pill drew properly all along, because `:reaction` is the one node that looks
9- a glyph up in the Twemoji pack instead of setting it as text.
8+ pill drew properly all along, because a pill looks its glyph up in the
9+ Twemoji pack instead of setting it as text which is what `:emoji` does
10+ too, without the pill.
1011
1112 The split therefore has to happen before the text reaches a label, and this
1213 is where: the message cut into the parts a font can draw and the parts only
@@ -63,7 +64,7 @@
6364 (defn runs
6465 "`text` as alternating `[:text s]` and `[:emoji s]` runs, in order.
6566
66- An `:emoji` run is one glyph: a chip draws one picture, so two emoji side by
67+ An `:emoji` run is one glyph: a node draws one picture, so two emoji side by
6768 side are two runs and not one."
6869 [text]
6970 (let [cps (vec (seq (or text "")))
@@ -5,8 +5,9 @@
5 UI punctuation, maths-in-prose, box drawing and no emoji at all. So an5 UI punctuation, maths-in-prose, box drawing and no emoji at all. So an
6 emoji anybody typed into a message was drawn as tofu: the hollow box a font6 emoji anybody typed into a message was drawn as tofu: the hollow box a font
7 puts up for a codepoint it has no glyph for. The same emoji in a reaction7 puts up for a codepoint it has no glyph for. The same emoji in a reaction
8- pill drew properly all along, because `:reaction` is the one node that looks8+ pill drew properly all along, because a pill looks its glyph up in the
9- a glyph up in the Twemoji pack instead of setting it as text.9+ Twemoji pack instead of setting it as text which is what `:emoji` does
10+ too, without the pill.
10 11
11 The split therefore has to happen before the text reaches a label, and this12 The split therefore has to happen before the text reaches a label, and this
12 is where: the message cut into the parts a font can draw and the parts only13 is where: the message cut into the parts a font can draw and the parts only
@@ -63,7 +64,7 @@
63 (defn runs64 (defn runs
64 "`text` as alternating `[:text s]` and `[:emoji s]` runs, in order.65 "`text` as alternating `[:text s]` and `[:emoji s]` runs, in order.
65 66
66- An `:emoji` run is one glyph: a chip draws one picture, so two emoji side by67+ An `:emoji` run is one glyph: a node draws one picture, so two emoji side by
67 side are two runs and not one."68 side are two runs and not one."
68 [text]69 [text]
69 (let [cps (vec (seq (or text "")))70 (let [cps (vec (seq (or text "")))