Files
mesh-catalog/modules/telegram/cmd/telegram/render_test.go
T
jochen eadf301f9f 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.
2026-10-13 17:16:50 +02:00

77 lines
3.8 KiB
Go

package main
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// adr0343 is the ask for ADR 0343 as the router now sends it to this channel (novox/hq issue 639): the knowledge
// base's brief as its title and body, the rest in its note. On 2026-10-13 the same ask arrived as about thirty plain
// lines — every one of its thirteen clauses on a line of its own — and the operator would not read it.
var adr0343 = asks.Message{Handle: "h-343", To: "42",
Title: "Your agents' memory follows you to every machine",
Body: "What an agent learns in a project's memory is copied to every machine that runs the agent, so a session " +
"anywhere starts with it. A file changed on two machines keeps both versions and warns you; secrets in memory files " +
"are screened, though not perfectly.\n" +
"• Only memory folders travel: links and files over 128 KiB stay where they are\n" +
"• A conflict reaches you as a warning that needs you\n" +
"• A deleted file is marked, and a newer change elsewhere outlives the deletion",
Note: "Asked by mesh-knowledge at 16:05. Unanswered by tomorrow 15:55: the record stays proposed; nothing of it is in force.\n" +
"Approve: the record is accepted and its clauses are in force. Decline: the record stays proposed; nothing of it is in force.\n" +
"ADR 0343, proposed by fix-367@g14; the knowledge base's own review approved it. Fingerprint a59f 361b f60e cb5e d237 a837.\n" +
"Read its 13 clauses with the knowledge base's get, number 343.",
Buttons: []asks.Button{{Label: "Approve", Ticket: "t-approve"}, {Label: "Decline", Ticket: "t-decline"}}}
// The ADR 0343 ask fits a phone's glance: a bold headline, the summary and its points, then the rest apart and
// collapsed — at most twelve lines in all, and never a clause.
func TestTheADR0343AskFitsAGlance(t *testing.T) {
c, f, _, _ := newChannel(t, goodToken)
subject, body := work(asks.Show, adr0343)
if err := c.Take(subject, body); err != nil {
t.Fatal(err)
}
sent := f.of("sendMessage")
if len(sent) != 1 {
t.Fatalf("sent %d", len(sent))
}
html := sent[0].Body["text"].(string)
t.Logf("as sent:\n%s", html)
lines := strings.Split(html, "\n")
if len(lines) > 12 {
t.Errorf("the ask is %d lines, more than 12:\n%s", len(lines), html)
}
if lines[0] != "<b>Your agents&#39; memory follows you to every machine</b>" && lines[0] != "<b>Your agents' memory follows you to every machine</b>" {
t.Errorf("the headline is not first and bold: %q", lines[0])
}
if !strings.HasPrefix(lines[1], "What an agent learns") || strings.Count(html, "\n• ") != 3 {
t.Errorf("the summary and its three points do not follow the headline:\n%s", html)
}
if !strings.Contains(html, "\n\n<blockquote expandable>Asked by mesh-knowledge at 16:05.") || !strings.HasSuffix(html, "number 343.</blockquote>") {
t.Errorf("the note is not apart and collapsed:\n%s", html)
}
for _, lead := range []string{"Only the memory folders are carried", "How it is checked", "13 clause(s)", "**"} {
if strings.Contains(html, lead) {
t.Errorf("the ask carries %q", lead)
}
}
if sent[0].Body["parse_mode"] != "HTML" {
t.Errorf("not sent as HTML: %v", sent[0].Body["parse_mode"])
}
}
// A long note is cut before the body is, and the cut said; a body too long loses the note and is cut itself.
func TestALongNoteIsCutBeforeTheBody(t *testing.T) {
m := asks.Message{Title: "t", Body: "the summary", Note: strings.Repeat("n", 5000)}
s := partsOf(m)
if s.body != "the summary" || !strings.HasSuffix(s.note, cutNote) || len([]rune(s.plain())) != MaxLength {
t.Errorf("body %q, note ends %q, %d characters", s.body, s.note[len(s.note)-20:], len([]rune(s.plain())))
}
m.Body = strings.Repeat("b", 5000)
s = partsOf(m)
if s.note != "" || !strings.HasSuffix(s.body, cutNote) || len([]rune(s.plain())) != MaxLength {
t.Errorf("note %d, %d characters", len(s.note), len([]rune(s.plain())))
}
}