admin: put the nickname under the name in the detail header
The grid row from the last commit read as one attribute among phones
and birthdays; a nickname is closer to a name, and the header is who
the person is. Muted type-body-sm keeps the formal name the heading.

Assisted-by: GLM-5.3 via pi
change spqqsnltmwvxwkkzsrttkkwwqzpqopmt
commit 35d9709b1d37e9abede338f8b477d4791c153be9
author Alpha Chen <alpha@kejadlen.dev>
date
parent vtzrqzoq
diff --git a/lib/pro_tacts/admin/contacts_show.rb b/lib/pro_tacts/admin/contacts_show.rb
index 540a325..a6c8fe0 100644
--- a/lib/pro_tacts/admin/contacts_show.rb
+++ b/lib/pro_tacts/admin/contacts_show.rb
@@ -29,7 +29,16 @@ module ProTacts
           div(class: "card") do
             div(class: "card-body") do
               div(class: "detail-header") do
-                h1(class: "type-h2", style: "margin: 0;") { @contact.name || @contact.id }
+                div do
+                  h1(class: "type-h2", style: "margin: 0;") { @contact.name || @contact.id }
+                  # The nickname rides under the name, muted so the formal
+                  # name stays the heading. In the header rather than the
+                  # grid: the grid is what reaches the person, the header
+                  # is who they are.
+                  if @contact.nickname
+                    div(class: "type-body-sm gl-muted", style: "margin-top: var(--gl-space-2xs);") { @contact.nickname }
+                  end
+                end
                 # Only a picture earns this slot: an initials circle
                 # beside the name in type-h2 would repeat what the name
                 # already says. The dashboard rows keep theirs — there
@@ -50,17 +59,13 @@ module ProTacts
 
       #: () -> bool
       def has_data?
-        !@contact.nickname.nil? ||
-          @contact.phones.any? || @contact.emails.any? || @contact.addresses.any? ||
+        @contact.phones.any? || @contact.emails.any? || @contact.addresses.any? ||
           !@birthday.nil? || !@contact.notes.nil?
       end
 
       # A missing TYPE parameter still gets a key: the fallback names
       # the kind of value, so no row renders unlabeled in the grid.
       def rows
-        # The nickname leads the grid: closest to the name the header
-        # already shows, ahead of the channels that reach the person.
-        row("nickname", @contact.nickname) if @contact.nickname
         @contact.phones.each { |phone| row(phone.type || "phone", phone.value) }
         @contact.emails.each { |email| row(email.type || "email", email.value) }
         @contact.addresses.each { |address| row(address.type || "address", address_lines(address)) }
diff --git a/test/pro_tacts/admin/test_contacts_pages.rb b/test/pro_tacts/admin/test_contacts_pages.rb
index a08e0ea..38294ce 100644
--- a/test/pro_tacts/admin/test_contacts_pages.rb
+++ b/test/pro_tacts/admin/test_contacts_pages.rb
@@ -283,20 +283,20 @@ class AdminContactsPagesTest < Minitest::Test
     end
   end
 
-  # The nickname is an attribute like the others here — a labeled
-  # grid row, leading the channels — not a second name in the header,
-  # which stays the name and the avatar alone.
-  def test_show_renders_the_nickname_as_a_row
+  # The nickname is a name-like fact, so it rides under the name in
+  # the header — muted, so the formal name stays the heading — and
+  # the grid keeps only what reaches the person.
+  def test_show_puts_the_nickname_under_the_name_in_the_header
     red = ADA.sub("FN:Ada Lovelace", "FN:Sarah\r\nNICKNAME:Red").sub("UID:ada", "UID:red")
 
     with_contacts({"red" => red}) do
       get "/contacts/red"
 
-      assert_includes last_response.body, '<dt class="type-label">nickname</dt>'
-      assert_includes last_response.body, "Red"
-      # The row leads the grid, ahead of the first phone.
       body = last_response.body
-      assert body.index('">nickname</dt>') < body.index('">mobile</dt>')
+      header = body.split("<dl").first
+      assert_includes header, "Sarah</h1>"
+      assert_includes header, ">Red<"
+      refute_includes body, '">nickname</dt>'
     end
   end