edited contact: raise on a lent note that will not read
EditedContact kept its own lent_text, which read an unreadable stored
lent NOTE as nil. A nil compares unequal to every section a client
sends, so the section read as deleted. Contact.lent_text already
raises on that line; the split now calls it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kqo9sKAi4DnKCQkrtYoAwM
change
commit 583dd52c5273c7102db192d03280ab4db9697445
author Claude <noreply@anthropic.com>
date
parent 582d9205
diff --git a/lib/pro_tacts/edited_contact.rb b/lib/pro_tacts/edited_contact.rb
index 633803f..b14d14d 100644
--- a/lib/pro_tacts/edited_contact.rb
+++ b/lib/pro_tacts/edited_contact.rb
@@ -288,7 +288,7 @@ module ProTacts
       split = NoteSections.split(value, labels)
       if split.sections.empty?
         # The two-line shape's truncation (see the method comment).
-        kept = rows.find { |row| lent_text(row) == value }
+        kept = rows.find { |row| Contact.lent_text(row) == value }
         unless kept.nil?
           return [
             vcard.replace("NOTE", []),
@@ -315,7 +315,7 @@ module ProTacts
         case text
         when nil
           GroupEdit.new(group_id: row.group.id, position: row.position, line: nil)
-        when lent_text(row)
+        when Contact.lent_text(row)
           nil
         else
           GroupEdit.new(
@@ -368,14 +368,6 @@ module ProTacts
       joined_notes(vcard, notes)
     end
 
-    # What a lent NOTE row says, as text: the unescaped value of its
-    # line, or nil for one that will not read and so cannot be compared
-    # with anything a client sent.
-    #: (Contact::Inherited row) -> String?
-    def lent_text(row)
-      parsed_line(row.line).property&.text
-    end
-
     # The submission's NOTE lines joined into the one the invariant
     # keeps, at the first one's position: a client's two-NOTE card is
     # foreign input, joined rather than refused. A line that will not
diff --git a/test/pro_tacts/test_edited_contact.rb b/test/pro_tacts/test_edited_contact.rb
index 847a309..e1e3409 100644
--- a/test/pro_tacts/test_edited_contact.rb
+++ b/test/pro_tacts/test_edited_contact.rb
@@ -203,6 +203,19 @@ class EditedContactTest < Minitest::Test
     )
   end
 
+  # A stored lent NOTE was read on its way in, so one that will not
+  # read now is a broken assumption to raise on (Contact.lent_text)
+  # rather than a section compared unequal to everything a client sent.
+  def test_a_lent_note_that_will_not_read_raises
+    stored = note_card("Own note.")
+    served = note_card("Own note.\n\nShared · Neighbours:\n#{NOTE_TEXT}")
+    lent = [ProTacts::Contact::Inherited.new(group: NEIGHBOURS, position: 1, line: "NOTE;=;:")]
+
+    error = assert_raises(RuntimeError) { decompose(served, before: before(stored, inherited: lent)) }
+
+    assert_equal "group nnnn lends a line that will not read", error.message
+  end
+
   # One of two sections deleted, the other and the member's own kept:
   # the present section is attributed and the absent one is the
   # removal of its row.