fuzzyworld

fuzzyworld-tools / commit

Issues: accept a run record on the line after its label; never show that line as text

859a0fd4a88d590a24a1b8b5fc1298d57ba42145 · Pavel Safronov · 2026-11-03 22:35:44 -0800 · parents 55e5fc3e0cf7

Issues: accept a run record on the line after its label; never show that line as text

Seen in the first real IMAP mail: the client broke the line after "Run record: ", so the JSON sat on the
next line. find_record now allows that, and report_text drops a "{...}" line that follows a bare label.

FW-Tokens-In: 518 132082 27014761
FW-Tokens-Out: 4521468 13719
FW-Tokens-Cached:
FW-Session: 38928110-1cf6-4bd6-90d8-678798481a94
FW-Rtk-Saved:
FW-Rtk-Cmds:

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
 server/issues.py     |  8 ++++++--
 tests/test_issues.py | 15 +++++++++++++++
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/server/issues.py b/server/issues.py
index 97ee4be..baa97be 100644
--- a/server/issues.py
+++ b/server/issues.py
@@ -86,7 +86,7 @@ Error = comments.CommentError
 REPO_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
 BUGS_RE = re.compile(r"^bugs-k7q4\+([A-Za-z0-9][A-Za-z0-9._-]*)@fuzzyworld\.net$", re.I)
 ADDRESS_RE = re.compile(r"[^\s@<>]+@[^\s@<>]+")
-RECORD_RE = re.compile(r"^Run record: (\{.*\})\s*$", re.M)
+RECORD_RE = re.compile(r"^Run record:[ \t]*\n?[ \t]*(\{.*\})\s*$", re.M)   # the JSON may sit on the next line: some mail clients break the line after the label
 RECORD_MAX = 64 * 1024
 RECORD_DEPTH = 32
 RECORD_KEYS = ("simVersion", "seed", "startLevel", "startCharge", "options", "actions")   # every key of the real records; the rest is dropped
@@ -603,9 +603,13 @@ def report_text(content, flowed=False, delsp=False):
     if flowed:
         lines = unflow(lines, delsp)
     cut = find_cut(lines)
-    kept, in_device = [], False
+    kept, in_device, after_label = [], False, False
     for line in lines[:cut]:
         s = line.lstrip()
+        if after_label and s.startswith("{"):   # the record sat on the line after a bare "Run record:" label
+            after_label = False
+            continue
+        after_label = s.startswith("Run record:") and not s[len("Run record:"):].strip()
         if s.startswith("Device:"):
             in_device = True
             continue
diff --git a/tests/test_issues.py b/tests/test_issues.py
index fc730b1..ea8434e 100644
--- a/tests/test_issues.py
+++ b/tests/test_issues.py
@@ -322,6 +322,21 @@ class Records(Base):
         raw = rd(os.path.join(self.state, "repos", "explodorama", "1", "record.json"))
         self.assertEqual(raw, json.dumps(self.fixture(), separators=(",", ":")))   # re-serialized, compact
 
+    def test_find_record_accepts_the_json_on_the_next_line(self):
+        # seen in the first real IMAP mail: the mail client broke the line after "Run record: "
+        for text in ("x\nRun record: \n" + record() + " \n\n", "x\r\nRun record:\r\n" + record() + "\r\n"):
+            rec, why = issues.find_record(text)
+            self.assertEqual(rec["seed"], 7, why)
+        # but not across a blank line, and not when the next line is prose
+        self.assertEqual(issues.find_record("Run record:\n\n" + record() + "\n"), (None, ""))
+        self.assertEqual(issues.find_record("Run record:\nit crashed {sorry}\n"), (None, ""))
+
+    def test_record_json_on_the_next_line_is_not_shown_as_text(self):
+        text = issues.report_text("Hi\n\nSeed 1\nRun record: \n" + record() + " \n\nbye\n")
+        self.assertNotIn("simVersion", text)
+        self.assertIn("Seed 1", text)
+        self.assertIn("bye", text)
+
     def test_find_record_skips_the_too_long_footer(self):
         text = "x\nRun record: " + record() + "\n\n---\nRun record: too long for an email link. Paste it.\n"
         rec, why = issues.find_record(text)