An ask reads at a glance on the phone: a headline, a summary and its points, the rest in a note (issues 383 and 639)
The operator would not read the Telegram ask for ADR 0343: thirty plain lines, every clause on one, no bold, no summary. A settings proposal was the same wall of text, its values masked so the approval was blind. - messenger: an ask's message is the asker's headline and explanation alone; who asked and when, what each answer does, the expiry, where else it is answered and the asker's Details travel as the message's note (asks.Message.Note). Answered, the message is one line, "You chose Approve at 16:06 on Telegram · <headline>". An explanation that does not read at a glance (asks.Glance) is shown and logged. On a kind that proves who answers, an ask's values are shown whole (asks.Ask.Whole), held to the secret rule alone (from the earlier work on issue 383, #178). - telegram: every message in HTML, never reworded: the title bold, a key line monospace, the note apart in a collapsed blockquote; markup escaped. A tap is compared by its words, whitespace aside, and a message shown before is still compared by the digest it was kept under. - desk-channel: the note follows the body. - mesh-knowledge: a record that needs the operator carries a brief (headline, summary, at most three points), the 30th per-record rule; its ask shows the brief and never the clauses. An ask made before is composed again as it was asked (form), so its warrant still holds. - claude-code: an instructions proposal's explanation is one summary line (the proposer's, or the module's), its text in the Details; a question's context is at most 300 characters and its recommendation one point. Older proposals and questions keep their form. The SDK is pinned at mesh-sdk 9588871 (fix/639-an-ask-reads-at-a-glance), which adds Message.Note and asks.Glance.
This commit is contained in:
@@ -202,7 +202,7 @@ func TestAQuestionIsShownWithItsTicketsAndRingsAndAWarningIsSilent(t *testing.T)
|
||||
}
|
||||
b := sent[0].Body
|
||||
kb, _ := json.Marshal(b["reply_markup"])
|
||||
if b["chat_id"] != float64(42) || b["text"] != "Delivery of hq held\nNeeds you: release it." || b["disable_notification"] != false ||
|
||||
if b["chat_id"] != float64(42) || b["text"] != "<b>Delivery of hq held</b>\nNeeds you: release it." || b["disable_notification"] != false ||
|
||||
string(kb) != `{"inline_keyboard":[[{"callback_data":"0123456789abcdef0123456789abcdef","text":"Release"}]]}` {
|
||||
t.Errorf("sent %v %s", b, kb)
|
||||
}
|
||||
@@ -230,12 +230,83 @@ func TestALongMessageIsCutAndTheCutSaid(t *testing.T) {
|
||||
if err := c.Take(subject, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := f.of("sendMessage")[0].Body["text"].(string)
|
||||
// The bound is on the words, as Telegram's is on the text once its markup is read: the tags do not count.
|
||||
got := strings.NewReplacer("<b>", "", "</b>", "").Replace(f.of("sendMessage")[0].Body["text"].(string))
|
||||
got = strings.ReplaceAll(got, ">", ">")
|
||||
if n := len([]rune(got)); n != MaxLength || !strings.HasSuffix(got, cutNote) {
|
||||
t.Errorf("%d characters, ends %q (D1)", n, got[len(got)-40:])
|
||||
}
|
||||
}
|
||||
|
||||
// A message is shown formatted, never reworded (issues 383 and 639): the title bold, a key line in monospace, the
|
||||
// note apart in a collapsed blockquote, and every '<', '>' and '&' escaped, so a message carrying one neither breaks
|
||||
// nor injects. The tap on it is still compared by the plain words, which is what Telegram gives back.
|
||||
func TestAMessageIsRenderedInHTMLWithItsMarkupEscaped(t *testing.T) {
|
||||
c, f, s, _ := newChannel(t, goodToken)
|
||||
subject, body := work(asks.Show, asks.Message{Handle: "q1", Title: "Set mounts on <shanks> & co?",
|
||||
Body: "Changes two settings.\n+ shares: media=/storage/media\n~ sources: a=b (was: c)",
|
||||
Note: "Asked by mesh-controller at 15:11.\nFingerprint 1234 5678 9abc.",
|
||||
To: "42", Buttons: []asks.Button{{Label: "Approve", Ticket: "tkt"}}})
|
||||
if err := c.Take(subject, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sent := f.of("sendMessage")
|
||||
want := "<b>Set mounts on <shanks> & co?</b>\nChanges two settings.\n<code>+ shares: media=/storage/media</code>\n" +
|
||||
"<code>~ sources: a=b (was: c)</code>\n\n<blockquote expandable>Asked by mesh-controller at 15:11.\nFingerprint 1234 5678 9abc.</blockquote>"
|
||||
if len(sent) != 1 || sent[0].Body["text"] != want || sent[0].Body["parse_mode"] != "HTML" {
|
||||
t.Fatalf("sent %v", sent)
|
||||
}
|
||||
f.mu.Lock()
|
||||
shown := f.next
|
||||
f.mu.Unlock()
|
||||
// Telegram gives the words back plain, the space around the blockquote as it pleases: the tap on them is taken.
|
||||
c.Handle(Update{UpdateID: 1, Callback: &Callback{ID: "cb1", Data: "tkt", From: User{ID: 42, FirstName: "Jo"},
|
||||
Message: &Msg{MessageID: shown, Chat: Chat{ID: 42, Type: "private"},
|
||||
Text: "Set mounts on <shanks> & co?\nChanges two settings.\n+ shares: media=/storage/media\n~ sources: a=b (was: c)\n" +
|
||||
"Asked by mesh-controller at 15:11.\nFingerprint 1234 5678 9abc."}}})
|
||||
if got := s.of(asks.IntakeSubject(asks.Chosen, Kind)); len(got) != 1 {
|
||||
t.Fatalf("the tap on the rendered message was not taken: %s", got)
|
||||
}
|
||||
// Words changed by another holder of the token are still told apart.
|
||||
c.Handle(Update{UpdateID: 2, Callback: &Callback{ID: "cb2", Data: "tkt", From: User{ID: 42, FirstName: "Jo"},
|
||||
Message: &Msg{MessageID: shown, Chat: Chat{ID: 42, Type: "private"},
|
||||
Text: "Set mounts on <shanks> & co?\nChanges three settings.\n+ shares: media=/storage/media\n~ sources: a=b (was: c)\n" +
|
||||
"Asked by mesh-controller at 15:11.\nFingerprint 1234 5678 9abc."}}})
|
||||
if got := s.of(asks.IntakeSubject(asks.Chosen, Kind)); len(got) != 1 {
|
||||
t.Fatalf("a tap on changed words was taken: %s", got)
|
||||
}
|
||||
// The edit says what was chosen, one line with the headline, and is in the same mode.
|
||||
subject, body = work(asks.Edit, asks.Message{Handle: "q1", Title: "You chose Approve at 15:06 on Telegram · Set mounts on <shanks> & co?",
|
||||
Note: "Told to mesh-controller, which acts on it itself.", To: "42"})
|
||||
if err := c.Take(subject, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ed := f.of("editMessageText")
|
||||
if len(ed) != 1 || ed[0].Body["parse_mode"] != "HTML" ||
|
||||
ed[0].Body["text"] != "<b>You chose Approve at 15:06 on Telegram · Set mounts on <shanks> & co?</b>\n\n<blockquote expandable>Told to mesh-controller, which acts on it itself.</blockquote>" {
|
||||
t.Errorf("edited %v", ed)
|
||||
}
|
||||
// A plain answer in the chat is escaped too.
|
||||
c.reply(42, "a <b>tag</b> & more")
|
||||
if last := f.of("sendMessage"); last[len(last)-1].Body["text"] != "a <b>tag</b> & more" {
|
||||
t.Errorf("a reply was not escaped: %v", last[len(last)-1].Body["text"])
|
||||
}
|
||||
}
|
||||
|
||||
// A message shown before issue 639 was kept under the digest of its lines: a tap on it is still taken.
|
||||
func TestATapOnAMessageShownBeforeIsStillTaken(t *testing.T) {
|
||||
c, _, s, st := newChannel(t, goodToken)
|
||||
words := "Delivery held\nRelease or Stop."
|
||||
if err := st.Show(42, 9, linesDigest(words)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Handle(Update{UpdateID: 1, Callback: &Callback{ID: "cb1", Data: "tkt", From: User{ID: 42, FirstName: "Jo"},
|
||||
Message: &Msg{MessageID: 9, Chat: Chat{ID: 42, Type: "private"}, Text: words}}})
|
||||
if got := s.of(asks.IntakeSubject(asks.Chosen, Kind)); len(got) != 1 {
|
||||
t.Fatalf("the tap on a message shown before was not taken: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnEditToTheSameTextIsASuccessAndAGoneMessageIsSentAgain(t *testing.T) {
|
||||
c, f, s, st := newChannel(t, goodToken)
|
||||
st.Place("m1", Placed{Chat: 42, ID: 7})
|
||||
@@ -528,7 +599,7 @@ func TestATapTheRouterIsSlowOnClaimsNothingAndItsWordComesInTheChat(t *testing.T
|
||||
if err := c.Take(subject, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sent := f.of("sendMessage"); len(sent) != sentBefore+1 || sent[len(sent)-1].Body["text"] != "You chose Release: recorded." {
|
||||
if sent := f.of("sendMessage"); len(sent) != sentBefore+1 || sent[len(sent)-1].Body["text"] != "<b>You chose Release: recorded.</b>" {
|
||||
t.Errorf("the late word was not said in the chat: %v", sent)
|
||||
}
|
||||
// Telegram refuses an acknowledgement that came too late: the word goes to the chat instead.
|
||||
|
||||
Reference in New Issue
Block a user