vcard: keep the reading a card was split from
Import::Vcf.cards parsed the whole staged file and then made each card
from the joined bytes, so the envelope check and every later read
parsed each card again; a file with photos is megabytes, read on every
screen of the walk. VCard.of keeps the lines it was given, and
#extract and Vcf.read hand theirs on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNVLyiMrZJ7sZctEYq6WCM
change
commit 4db9213801f21552e85131239322d54d4e43b52e
author Claude <noreply@anthropic.com>
date
parent 0b8d29bd
diff --git a/lib/pro_tacts/import/vcf.rb b/lib/pro_tacts/import/vcf.rb
index 957efd5..02d73e6 100644
--- a/lib/pro_tacts/import/vcf.rb
+++ b/lib/pro_tacts/import/vcf.rb
@@ -87,7 +87,7 @@ module ProTacts
             open << line
             next unless name&.casecmp?("END")
 
-            cards << VCard.new(open.map(&:verbatim).join)
+            cards << VCard.of(open)
             open = nil
           end
         end
@@ -176,7 +176,7 @@ module ProTacts
           property = line.property
           property.nil? || (known?(property.name) && !orphaned.include?(property.group))
         }
-        Reading.new(card: VCard.new(kept.map(&:verbatim).join), dropped:)
+        Reading.new(card: VCard.of(kept), dropped:)
       end
 
       # The group prefixes (`item1.`) with nothing left in them but a
diff --git a/lib/pro_tacts/vcard.rb b/lib/pro_tacts/vcard.rb
index b74691c..d8ef090 100644
--- a/lib/pro_tacts/vcard.rb
+++ b/lib/pro_tacts/vcard.rb
@@ -104,11 +104,21 @@ module ProTacts
     # anything here sees them (ProTacts::Web's write_card). This is the
     # assertion under those callers, not a check any of them makes; the
     # store's bind holds the same line a third time below the card.
-    #: (String bytes) -> void
-    def initialize(bytes)
+    #: (String bytes, ?lines: Array[Parser::Line]?) -> void
+    def initialize(bytes, lines: nil)
       raise ArgumentError, "not valid UTF-8: a card is text" unless bytes.valid_encoding?
 
       @bytes = bytes
+      @lines = lines if lines
+    end
+
+    # A card made of lines already read, keeping that reading: a file
+    # split into cards (Import::Vcf.cards) or a card split by #extract
+    # has walked every one of them, and the bytes they join back into
+    # read as the same lines, so a second walk would find nothing new.
+    #: (Array[Parser::Line] lines) -> VCard
+    def self.of(lines)
+      new(lines.map(&:verbatim).join, lines:)
     end
 
     # The card's properties, folded off the same single walk #lines is
@@ -164,7 +174,7 @@ module ProTacts
     #: (String name) -> [Array[Parser::Line], VCard]
     def extract(name)
       taken, rest = lines.partition { it.names?(name) }
-      [taken, VCard.new(rest.map(&:verbatim).join)]
+      [taken, VCard.of(rest)]
     end
 
     #: () -> String