Refine email-proposal review UX (v0.1.0:91)
Three post-smoke refinements to the Matrix email-proposal review:
1. Dash separators (bot): every card/reply is framed with a dash rule top and
bottom so threads stop bleeding together vertically on mobile.
2. Remove decided threads (bot): on a conclusive approve/dismiss from either
surface, the bot redacts the card (client.room_redact) so the room clears
down to only undecided items. Redacting the bot's own card needs no power;
the web->Matrix path now redacts instead of posting a closure note.
3. Clearer note wording (server v91 + bot): the proposed grid note now names who
emailed whom -- "{teammate} emailed {investor}" (outbound) / "{sender} emailed
the team" (inbound) -- instead of an ambiguous "Sent"/"Received". Outbound
detection also matches our corporate domain (public providers excluded), so a
teammate's mail from a non-enrolled @ten31.xyz address no longer reads as
"Received". Going-forward only; no schema change. The card drops its bare
direction label since the note now carries the relationship.
Tests updated; 30/30 green, render-smoke green.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""Offline tests for the email-proposal review logic (card render, reply grammar, note revision).
|
||||
The network/Matrix wiring lives in bot.py (live-smoke only); this covers the pure functions."""
|
||||
"""Offline tests for the email-proposal review logic (card render, framing, reply grammar, note
|
||||
revision). The network/Matrix wiring lives in bot.py (live-smoke only); this covers pure functions."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -11,7 +11,8 @@ ITEM = {
|
||||
"id": "p1", "investor_name": "Acme Capital", "direction": "received",
|
||||
"from_name": "Jane Doe", "from_email": "jane@acme.com",
|
||||
"email_subject": "Re: Fund III", "email_date": "2026-06-02",
|
||||
"snippet": "thanks for the deck — one question on terms", "proposed_note": "✉ Received: asked about terms",
|
||||
"snippet": "thanks for the deck — one question on terms",
|
||||
"proposed_note": "✉ Jane Doe emailed the team: asked about terms",
|
||||
}
|
||||
|
||||
|
||||
@@ -25,18 +26,28 @@ def test_interpret_yes_no_else():
|
||||
assert email_proposals.interpret("say we discussed the Q3 raise") == "revise"
|
||||
|
||||
|
||||
def test_frame_wraps_with_rules():
|
||||
out = email_proposals.frame("hello")
|
||||
lines = out.split("\n")
|
||||
assert lines[0] == email_proposals.RULE and lines[-1] == email_proposals.RULE
|
||||
assert "hello" in out
|
||||
|
||||
|
||||
def test_render_card_has_context_note_and_actions():
|
||||
card = email_proposals.render_card(ITEM)
|
||||
assert "Acme Capital" in card and "Received" in card
|
||||
assert "Acme Capital" in card
|
||||
assert "Jane Doe" in card
|
||||
assert "Re: Fund III" in card and "2026-06-02" in card
|
||||
assert "thanks for the deck" in card
|
||||
assert "✉ Received: asked about terms" in card
|
||||
assert "Jane Doe emailed the team: asked about terms" in card # the clear, named note
|
||||
assert "yes" in card.lower() and "no" in card.lower()
|
||||
|
||||
|
||||
def test_render_card_sent_direction():
|
||||
assert "(Sent)" in email_proposals.render_card(dict(ITEM, direction="sent"))
|
||||
def test_render_card_is_framed_and_dropless_direction():
|
||||
card = email_proposals.render_card(ITEM)
|
||||
assert card.startswith(email_proposals.RULE) and card.rstrip().endswith(email_proposals.RULE)
|
||||
# the bare Sent/Received label is gone — the note itself names who emailed whom
|
||||
assert "(Received)" not in card and "(Sent)" not in card
|
||||
|
||||
|
||||
def test_render_card_truncates_long_snippet():
|
||||
@@ -59,11 +70,6 @@ def test_revise_note_noop_or_empty_returns_none():
|
||||
assert email_proposals.revise_note("n", "y", parse_fn=lambda *a, **k: None) is None
|
||||
|
||||
|
||||
def test_closure_line_reflects_status():
|
||||
assert "approved" in email_proposals.closure_line("approved").lower()
|
||||
assert "dismiss" in email_proposals.closure_line("dismissed").lower()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
|
||||
for fn in fns:
|
||||
|
||||
Reference in New Issue
Block a user