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
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.