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

Rewrite a line from the phone

The pencil, the banner and the wire form were already here; what was not was
the half that makes a rewrite show. Without `echo-message` the server sends
nothing back, so the edit went out and the line on screen stayed as it was
said — and with the cap, an edit that added or dropped a picture link left the
row drawing `:images` read off the old text. Both are `edit-message!` now, the
fold the inbound side was already doing inline, with the links read again the
way `frq.state/edit-message!` reads them.

The three things Send could do wrong with an edit in the box, said out loud:
an attachment is refused rather than dropped on the floor, an empty box is not
a way to unsay a line, and no connection keeps the draft instead of falling
through to PRIVMSG and posting the rewrite as a new message. And opening
another room cancels the edit, because the next Send would otherwise rewrite a
message nobody in that room can see.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-12T07:36:04-07:00 Browse files
479ec83 parent: f50db56
modified flutter/src/frq/main.cljd +67 -16
@@ -115,6 +115,20 @@
115115 (doseq [url urls] (media/fetch! url bump!))
116116 urls))
117117
118+(defn- edit-message!
119+ "Rewrite a message in place, and say so.
120+
121+ `frq.edits` is the fold and what it answers; the cell and the picture links
122+ are this half's. The links are read again off the new text because a
123+ revision can add one or take one away, and a row drawing the picture its
124+ line no longer mentions is the shape that gave it away. The desktop's
125+ `frq.state/edit-message!` is the same function over its own atom."
126+ [room msgid from text]
127+ (let [out (edits/apply-edit @cells/channels room msgid from text
128+ #(assoc % :images (images-in text)))]
129+ (reset! cells/channels (:channels out))
130+ (:result out)))
131+
118132 (def ^:private history-limit
119133 "How many lines of backlog to ask a room for, as `frq.state` asks for them."
120134 100)
@@ -205,17 +219,15 @@
205219 ;; that line's own id and never the revision's own wire msgid, which
206220 ;; nothing else refers to. One older than the backlog we hold has
207221 ;; nothing here to replace, and is shown as itself rather than lost.
208- (let [out (edits/apply-edit @cells/channels name edit-of who text)]
209- (reset! cells/channels (:channels out))
210- (when (= :absent (:result out))
211- (swap! cells/channels update name
212- #(-> (merge {:name name :messages [] :unread 0} %)
213- (update :messages conj {:from who
214- :text text
215- :images (images-in text)
216- :did (:account m)
217- :id edit-of
218- :edited? true})))))
222+ (when (= :absent (edit-message! name edit-of who text))
223+ (swap! cells/channels update name
224+ #(-> (merge {:name name :messages [] :unread 0} %)
225+ (update :messages conj {:from who
226+ :text text
227+ :images (images-in text)
228+ :did (:account m)
229+ :id edit-of
230+ :edited? true}))))
219231 (swap! cells/channels update name
220232 #(-> (merge {:name name :messages [] :unread 0} %)
221233 (update :messages conj
@@ -636,17 +648,47 @@
636648 room (str @cells/current)
637649 edit @cells/editing]
638650 (cond
651+ (empty? room) nil
652+
653+ ;; A rewrite replaces what was said, and what was said is a line of
654+ ;; text: there is no wire form for adding a picture to a message already
655+ ;; sent, so the attachment is held back rather than silently dropped.
656+ ;; `frq.state/send-draft!` refuses the same pair for the same reason.
657+ (and edit @cells/attachment)
658+ (reset! cells/error "Finish the edit before sending a picture.")
659+
660+ ;; An edit of nothing is not a way to unsay a line: there is no delete
661+ ;; on the wire here, and blanking the message is not what emptying the
662+ ;; box asks for. Send waits for something to say instead.
663+ (and edit (empty? (.trim text))) nil
664+
639665 ;; A rewrite replaces what was said. It carries no new msgid of its own
640666 ;; the server files it under the original's id — so nothing is added
641- ;; here and the echo folds it in where the line already is.
642- (and edit (seq text) (seq room) @conn)
643- (do (net/send-line!
644- @conn
667+ ;; here and the echo folds it in where the line already is. The room is
668+ ;; the edit's own and not whichever one is open: they are the same room
669+ ;; while the banner is up, because leaving cancels the edit, and naming
670+ ;; the one being rewritten is what makes that true rather than assumed.
671+ edit
672+ (if-let [c @conn]
673+ (let [room (str (:channel edit))
674+ text (.trim text)]
675+ (net/send-line!
676+ c
645677 (mutate/edit-line room (:id edit) text
646678 (reactions/peer-did @cells/channels room
647679 (str @cells/form-nick))))
680+ ;; Same reason as a new message: the server's echo is the copy every
681+ ;; other client sees, and folding this one in as well would rewrite
682+ ;; the line twice. Without `echo-message` nothing comes back, so the
683+ ;; rewrite has to be applied here or it never shows.
684+ (when-not (handshake/acked? @caps "echo-message")
685+ (edit-message! room (:id edit) (str @cells/form-nick) text))
648686 (reset! cells/editing nil)
649687 (reset! cells/draft ""))
688+ ;; The draft stays in the box, with the banner still over it: the
689+ ;; rewrite is still what the reader wants to send once there is
690+ ;; somewhere to send it.
691+ (reset! cells/error "Not connected."))
650692
651693 ;; A picture still on its way up holds the send rather than losing it:
652694 ;; the line stays in the box, said so, and the reader presses send again
@@ -655,7 +697,7 @@
655697 (= :uploading (:status @cells/attachment))
656698 (reset! cells/error "The picture is still uploading.")
657699
658- (and (or (seq text) (:url @cells/attachment)) (seq room) @conn)
700+ (and (or (seq text) (:url @cells/attachment)) @conn)
659701 (let [url (:url @cells/attachment)
660702 ;; The picture becomes its link, at the end of the line: what goes
661703 ;; on the wire is the text the reader wrote and a URL after it,
@@ -913,6 +955,15 @@
913955 :jump-to-present! (fn [] (reset! cells/at-present? true))
914956 :open-channel! (fn [name]
915957 (reset! cells/current name)
958+ ;; An edit belongs to a line in the buffer being left:
959+ ;; carried across, the next Send would rewrite a
960+ ;; message nobody in this room can see. The box empties
961+ ;; with it, because what is in it is a copy of that
962+ ;; line `frq.state/open-channel!` does this too.
963+ (when (and @cells/editing
964+ (not= name (:channel @cells/editing)))
965+ (reset! cells/editing nil)
966+ (reset! cells/draft ""))
916967 ;; `frq.state/open-channel!` does this and more: it
917968 ;; marks the buffer read, remembers the room list and
918969 ;; asks for NAMES. What it also does, and what made
@@ -115,6 +115,20 @@
115 (doseq [url urls] (media/fetch! url bump!))115 (doseq [url urls] (media/fetch! url bump!))
116 urls))116 urls))
117 117
118+(defn- edit-message!
119+ "Rewrite a message in place, and say so.
120+
121+ `frq.edits` is the fold and what it answers; the cell and the picture links
122+ are this half's. The links are read again off the new text because a
123+ revision can add one or take one away, and a row drawing the picture its
124+ line no longer mentions is the shape that gave it away. The desktop's
125+ `frq.state/edit-message!` is the same function over its own atom."
126+ [room msgid from text]
127+ (let [out (edits/apply-edit @cells/channels room msgid from text
128+ #(assoc % :images (images-in text)))]
129+ (reset! cells/channels (:channels out))
130+ (:result out)))
131+
118 (def ^:private history-limit132 (def ^:private history-limit
119 "How many lines of backlog to ask a room for, as `frq.state` asks for them."133 "How many lines of backlog to ask a room for, as `frq.state` asks for them."
120 100)134 100)
@@ -205,17 +219,15 @@
205 ;; that line's own id and never the revision's own wire msgid, which219 ;; that line's own id and never the revision's own wire msgid, which
206 ;; nothing else refers to. One older than the backlog we hold has220 ;; nothing else refers to. One older than the backlog we hold has
207 ;; nothing here to replace, and is shown as itself rather than lost.221 ;; nothing here to replace, and is shown as itself rather than lost.
208- (let [out (edits/apply-edit @cells/channels name edit-of who text)]222+ (when (= :absent (edit-message! name edit-of who text))
209- (reset! cells/channels (:channels out))223+ (swap! cells/channels update name
210- (when (= :absent (:result out))224+ #(-> (merge {:name name :messages [] :unread 0} %)
211- (swap! cells/channels update name225+ (update :messages conj {:from who
212- #(-> (merge {:name name :messages [] :unread 0} %)226+ :text text
213- (update :messages conj {:from who227+ :images (images-in text)
214- :text text228+ :did (:account m)
215- :images (images-in text)229+ :id edit-of
216- :did (:account m)230+ :edited? true}))))
217- :id edit-of
218- :edited? true})))))
219 (swap! cells/channels update name231 (swap! cells/channels update name
220 #(-> (merge {:name name :messages [] :unread 0} %)232 #(-> (merge {:name name :messages [] :unread 0} %)
221 (update :messages conj233 (update :messages conj
@@ -636,17 +648,47 @@
636 room (str @cells/current)648 room (str @cells/current)
637 edit @cells/editing]649 edit @cells/editing]
638 (cond650 (cond
651+ (empty? room) nil
652+
653+ ;; A rewrite replaces what was said, and what was said is a line of
654+ ;; text: there is no wire form for adding a picture to a message already
655+ ;; sent, so the attachment is held back rather than silently dropped.
656+ ;; `frq.state/send-draft!` refuses the same pair for the same reason.
657+ (and edit @cells/attachment)
658+ (reset! cells/error "Finish the edit before sending a picture.")
659+
660+ ;; An edit of nothing is not a way to unsay a line: there is no delete
661+ ;; on the wire here, and blanking the message is not what emptying the
662+ ;; box asks for. Send waits for something to say instead.
663+ (and edit (empty? (.trim text))) nil
664+
639 ;; A rewrite replaces what was said. It carries no new msgid of its own665 ;; A rewrite replaces what was said. It carries no new msgid of its own
640 ;; the server files it under the original's id — so nothing is added666 ;; the server files it under the original's id — so nothing is added
641- ;; here and the echo folds it in where the line already is.667+ ;; here and the echo folds it in where the line already is. The room is
642- (and edit (seq text) (seq room) @conn)668+ ;; the edit's own and not whichever one is open: they are the same room
643- (do (net/send-line!669+ ;; while the banner is up, because leaving cancels the edit, and naming
644- @conn670+ ;; the one being rewritten is what makes that true rather than assumed.
671+ edit
672+ (if-let [c @conn]
673+ (let [room (str (:channel edit))
674+ text (.trim text)]
675+ (net/send-line!
676+ c
645 (mutate/edit-line room (:id edit) text677 (mutate/edit-line room (:id edit) text
646 (reactions/peer-did @cells/channels room678 (reactions/peer-did @cells/channels room
647 (str @cells/form-nick))))679 (str @cells/form-nick))))
680+ ;; Same reason as a new message: the server's echo is the copy every
681+ ;; other client sees, and folding this one in as well would rewrite
682+ ;; the line twice. Without `echo-message` nothing comes back, so the
683+ ;; rewrite has to be applied here or it never shows.
684+ (when-not (handshake/acked? @caps "echo-message")
685+ (edit-message! room (:id edit) (str @cells/form-nick) text))
648 (reset! cells/editing nil)686 (reset! cells/editing nil)
649 (reset! cells/draft ""))687 (reset! cells/draft ""))
688+ ;; The draft stays in the box, with the banner still over it: the
689+ ;; rewrite is still what the reader wants to send once there is
690+ ;; somewhere to send it.
691+ (reset! cells/error "Not connected."))
650 692
651 ;; A picture still on its way up holds the send rather than losing it:693 ;; A picture still on its way up holds the send rather than losing it:
652 ;; the line stays in the box, said so, and the reader presses send again694 ;; the line stays in the box, said so, and the reader presses send again
@@ -655,7 +697,7 @@
655 (= :uploading (:status @cells/attachment))697 (= :uploading (:status @cells/attachment))
656 (reset! cells/error "The picture is still uploading.")698 (reset! cells/error "The picture is still uploading.")
657 699
658- (and (or (seq text) (:url @cells/attachment)) (seq room) @conn)700+ (and (or (seq text) (:url @cells/attachment)) @conn)
659 (let [url (:url @cells/attachment)701 (let [url (:url @cells/attachment)
660 ;; The picture becomes its link, at the end of the line: what goes702 ;; The picture becomes its link, at the end of the line: what goes
661 ;; on the wire is the text the reader wrote and a URL after it,703 ;; on the wire is the text the reader wrote and a URL after it,
@@ -913,6 +955,15 @@
913 :jump-to-present! (fn [] (reset! cells/at-present? true))955 :jump-to-present! (fn [] (reset! cells/at-present? true))
914 :open-channel! (fn [name]956 :open-channel! (fn [name]
915 (reset! cells/current name)957 (reset! cells/current name)
958+ ;; An edit belongs to a line in the buffer being left:
959+ ;; carried across, the next Send would rewrite a
960+ ;; message nobody in this room can see. The box empties
961+ ;; with it, because what is in it is a copy of that
962+ ;; line `frq.state/open-channel!` does this too.
963+ (when (and @cells/editing
964+ (not= name (:channel @cells/editing)))
965+ (reset! cells/editing nil)
966+ (reset! cells/draft ""))
916 ;; `frq.state/open-channel!` does this and more: it967 ;; `frq.state/open-channel!` does this and more: it
917 ;; marks the buffer read, remembers the room list and968 ;; marks the buffer read, remembers the room list and
918 ;; asks for NAMES. What it also does, and what made969 ;; asks for NAMES. What it also does, and what made