admin: elide PHOTO payloads in the raw vCard display
A photo card's raw section was thousands of folded base64 lines —
the seed carries ~200KB — drowning every other line once expanded.
The property renders with name and parameters whole and its value as
an octet count; everything else stays byte for byte.

Assisted-by: GLM-5.3 via pi
change zptomzzzsutvtptnutxzmvyolkxtkwtn
commit 43dadef029de5f3b216692ea2a379f2910c3f549
author Alpha Chen <alpha@kejadlen.dev>
date
parent zmxwpzst
diff --git a/docs/DESIGN.md b/docs/DESIGN.md
index 3ff142f..1116af8 100644
--- a/docs/DESIGN.md
+++ b/docs/DESIGN.md
@@ -93,8 +93,9 @@ Consequences worth keeping:
   row, not a scaffold of blank fields. The card's height is the record's real
   weight — an unfilled record should look unfilled.
 - **The stored card is reachable.** In its own card beneath the record, the
-  raw vCard sits collapsed by default — the interpretation is the interface,
-  but the bytes a client downloads are never invisible from it.
+  raw vCard sits collapsed by default, its inline photo payloads elided to
+  their size — the interpretation is the interface, but nothing a card
+  carries is invisible from it.
 - **Adding is explicit.** A quiet add affordance opens a native `dialog` that
   names the attribute types available. Multi-part additions (picking a group,
   then a role) are steps inside that one dialog, never a chain of popovers —
diff --git a/lib/pro_tacts/admin/contacts_show.rb b/lib/pro_tacts/admin/contacts_show.rb
index f8f605c..189bfdc 100644
--- a/lib/pro_tacts/admin/contacts_show.rb
+++ b/lib/pro_tacts/admin/contacts_show.rb
@@ -17,6 +17,13 @@ module ProTacts
     # keeps its mono type label for now. Revisit once Gloss or this
     # project settles on how icons ship.
     class ContactsShow < Phlex::HTML
+      # A PHOTO value at or under this many octets renders whole — a
+      # URI reference does; an inline payload never does, and a photo
+      # card's raw section would otherwise be a wall of folded base64
+      # (the seed photo card carries ~200KB of it).
+      ELIDE_PHOTO_ABOVE = 120 #: Integer
+      private_constant :ELIDE_PHOTO_ABOVE
+
       #: (Contact contact) -> void
       def initialize(contact:)
         @contact = contact
@@ -63,7 +70,7 @@ module ProTacts
             div(class: "card-body") do
               details do
                 summary(class: "type-label") { "raw vCard" }
-                pre(class: "type-mono") { @contact.vcard.to_s }
+                pre(class: "type-mono") { raw_card }
               end
             end
           end
@@ -114,6 +121,33 @@ module ProTacts
           div(style: "white-space: pre-wrap;") { value }
         end
       end
+
+      # The stored card for display: byte for byte, except a PHOTO
+      # value long enough to be nothing but a wall of folded base64,
+      # which renders as its octet count. The property stays visible —
+      # name and parameters whole, so what the card carries is still
+      # seeable — because the payload is not readable text and never
+      # was; the count says exactly what is there.
+      #: () -> String
+      def raw_card
+        @contact.vcard.lines.map { elide_photo(it) }.join
+      end
+
+      # One line of the display card: verbatim, unless it is a PHOTO
+      # whose value runs past ELIDE_PHOTO_ABOVE — then the value's
+      # octets stand in for it, under the line's own terminator so the
+      # card's line-break convention survives the elision.
+      #: (VCard::Parser::Line line) -> String
+      def elide_photo(line)
+        return line.verbatim unless line.names?("PHOTO")
+
+        prefix, value = line.verbatim.split(":", 2)
+        value = value.to_s
+        terminator = value.slice!(/\r?\n\z/) || "\r\n"
+        return line.verbatim if value.bytesize <= ELIDE_PHOTO_ABOVE
+
+        "#{prefix}:[#{value.bytesize} octets of base64 elided]#{terminator}"
+      end
     end
   end
 end
diff --git a/test/pro_tacts/admin/test_contacts_pages.rb b/test/pro_tacts/admin/test_contacts_pages.rb
index d8d4b70..615abbd 100644
--- a/test/pro_tacts/admin/test_contacts_pages.rb
+++ b/test/pro_tacts/admin/test_contacts_pages.rb
@@ -287,6 +287,23 @@ class AdminContactsPagesTest < Minitest::Test
     end
   end
 
+  # A photo card's raw section elides the base64 wall to its octet
+  # count; the property's name and parameters, and every other line,
+  # stay byte for byte. The refuted chunk is a continuation line of
+  # the stored card itself — the one string guaranteed contiguous in
+  # an unelided render.
+  def test_show_elides_photo_payloads_in_the_raw_card
+    with_contacts({"pic" => PhotoCard.photo("pic", bytes: 256)}) do |store|
+      get "/contacts/pic"
+
+      body = last_response.body
+      assert_includes body, " octets of base64 elided]"
+      chunk = store.contact("pic").vcard.to_s.lines.grep(/\A /).first
+      refute_includes body, chunk
+      assert_includes body, "X-IMAGETYPE:PHOTO"
+    end
+  end
+
   # A card that will not parse is served from its bytes regardless —
   # the raw section is the one thing such a card has to show, so it
   # renders with no grid under the header, not instead of the page.