Render no name field for a book's group
A book's group keeps its name, and the way back above the form already
names it, so the editor shows no field for it rather than a read-only
one. A save that carries no name keeps the group's, the members'
posture; a blank name is still a request to go nameless.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VNdx5qiRSWADBoVfDRcrd
change
commit 7d837a8fe4ddbdc358e1bfb149d3291b32849787
author Claude <noreply@anthropic.com>
date
parent bfd0f017
diff --git a/lib/pro_tacts/admin/groups_edit.rb b/lib/pro_tacts/admin/groups_edit.rb
index 50e1342..c9e6e3f 100644
--- a/lib/pro_tacts/admin/groups_edit.rb
+++ b/lib/pro_tacts/admin/groups_edit.rb
@@ -59,12 +59,14 @@ module ProTacts
                 # The id as the blank's placeholder, because a group
                 # with no name is shown as its id everywhere else. A
                 # book's group keeps its name (Store#rename_group), so
-                # its field is read-only — still submitted, so the
-                # save carries the name it found.
-                label(class: "field") do
-                  span { "Name" }
-                  input(type: "text", name: "name", value: @group.name,
-                        placeholder: @group.id, autofocus: !@group.sync?, readonly: @group.sync?)
+                # it gets no field at all: the way back above already
+                # names it, and a save without one keeps the name
+                # (Web#apply_group_edit).
+                unless @group.sync?
+                  label(class: "field") do
+                    span { "Name" }
+                    input(type: "text", name: "name", value: @group.name, placeholder: @group.id, autofocus: true)
+                  end
                 end
                 @reading.addresses.each { |address| address_row(address) }
                 added_address_row
diff --git a/lib/pro_tacts/web/groups.rb b/lib/pro_tacts/web/groups.rb
index 8fd9249..f9472fe 100644
--- a/lib/pro_tacts/web/groups.rb
+++ b/lib/pro_tacts/web/groups.rb
@@ -98,15 +98,20 @@ module ProTacts
           group.members
         end #: Array[String]
 
+      # No name at all keeps the one it has, the members' posture
+      # above: a book's group renders no name field (Admin::GroupsEdit),
+      # where a blank one is a request to go nameless.
+      name = r.params.key?("name") ? r.params["name"].to_s : group.name
+
       begin
-        store.edit_group(id, name: r.params["name"].to_s, lines: Admin::CardForm.group_lines(group, r.params), members:)
+        store.edit_group(id, name:, lines: Admin::CardForm.group_lines(group, r.params), members:)
       rescue Sequel::UniqueConstraintViolation
         # A taken name (db/migrations/008_group_names.rb). The save is
         # one transaction, so nothing of it landed.
         return group_edit_screen(group, notice: "Another group is already named #{r.params['name']}; nothing was saved.")
       rescue Store::SyncGroupRename
         # A book's group keeps its name (Store#rename_group); the
-        # editor renders it read-only, so this is a doctored save.
+        # editor renders no field for it, so this is a doctored save.
         return group_edit_screen(group, notice: "The #{group.name} group names a book and keeps its name; nothing was saved.")
       end
       r.redirect "/groups/#{id}", 303
diff --git a/test/pro_tacts/admin/test_groups_pages.rb b/test/pro_tacts/admin/test_groups_pages.rb
index a508a7d..7b4924d 100644
--- a/test/pro_tacts/admin/test_groups_pages.rb
+++ b/test/pro_tacts/admin/test_groups_pages.rb
@@ -302,13 +302,27 @@ class AdminGroupsPagesTest < Minitest::Test
     end
   end
 
-  def test_a_books_group_renders_its_name_read_only
+  def test_a_books_group_renders_no_name_field
     with_contacts(BOOLES) do |store|
       id = FixtureData.seed_group(store, name: ProTacts::Group::EVERYONE, members: %w[george])
 
       get "/groups/#{id}/edit"
 
-      assert_includes last_response.body, "readonly"
+      refute_includes last_response.body, %(name="name")
+    end
+  end
+
+  # The form a book's group renders carries no name, and its save
+  # keeps the name the group has.
+  def test_a_save_without_a_name_keeps_it
+    with_contacts(BOOLES) do |store|
+      id = FixtureData.seed_group(store, name: ProTacts::Group::EVERYONE, members: %w[george])
+
+      post "/groups/#{id}", version: store.group(id).version, note: "Gate code 1854.", members: ["", "george"]
+
+      assert_equal 303, last_response.status
+      assert_equal ProTacts::Group::EVERYONE, store.group(id).name
+      assert_equal ["NOTE:Gate code 1854."], store.group(id).lines
     end
   end