fixtures: thirty-five groups in the seed book
Four groups met a cap of eight, and nothing ever hid.
Thirty clubs and a zoo now show the dialog what it did.
George is in Zoo volunteers, the last name on the list,
so the row a cap may never take is one that can't be missed.

Assisted-by: Opus 5 via Claude Code
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
change uomtowprtrqkovyuptzzttnyuxzyuozs
commit 116002b93bbf0957035b1d6ed4a1e6903df02128
author Alpha Chen <alpha@kejadlen.dev>
date
parent 971a982b
diff --git a/test/fixture_data.rb b/test/fixture_data.rb
index 67c3c3f..aa9da9b 100644
--- a/test/fixture_data.rb
+++ b/test/fixture_data.rb
@@ -97,17 +97,62 @@ module FixtureData
   # `sync:` prefix, so joining them to a book is one rename in the admin UI.
   CURIES = %w[unsynced-marie unsynced-pierre].freeze #: Array[String]
 
+  # The clubs a family accumulates, and the reason the seed holds
+  # thirty of them: the groups dialog opens on eight rows and hides the
+  # rest (Admin::GroupDialog::LIMIT,
+  # docs/plans/2026-09-22-a-few-groups-at-a-time.md), which is nothing
+  # to look at in a book with four groups. Named rather than numbered,
+  # because what the filter is given is a name somebody is typing and
+  # "Group 12" exercises a search box against a string no one would
+  # type. Several share an opening — Book club and Boulderers and
+  # Brass band, Cheese share and Chess club and Choir — so a filter can
+  # be narrowed twice and still have somewhere to go.
+  #
+  # None of them carries a line. A group with properties composes them
+  # into its members' served bytes, and the recorded exchanges are a
+  # session that saw those cards without these groups in the book, so
+  # anything lent here is a replay diff (see MEMBERS).
+  CLUBS = [
+    "Allotment", "Book club", "Boulderers", "Brass band", "Cheese share", "Chess club",
+    "Choir", "Cycling", "Dentists", "Dog walkers", "Film night", "Fishing", "Gardeners",
+    "Hiking", "Knitting", "Locksmiths", "Neighbours", "Pub quiz", "Rowing", "Running club",
+    "Sailing", "School run", "Swimming", "Tennis", "Ukulele", "Vets", "Walkers",
+    "Wine club", "Yoga", "Zebrafish lab"
+  ].freeze #: Array[String]
+
+  # The one club seeded with a household in it, and last in the book by
+  # name on purpose: a dialog opened on a Boole then has a group it is
+  # already in sorting well past the cap, which is the row the cap is
+  # not allowed to take (Admin::GroupDialog#initialize).
+  ZOO = "Zoo volunteers" #: String
+
   # The third is `sync:*`, holding every card but the unsynced ones, so
   # every user's book is the rest of the seed — the replay's included,
   # whose recorded sessions saw every card they asked for — and the
   # admin UI still shows a contact that is in nobody's
   # (docs/plans/2026-09-12-per-user-books.md). The fourth is CURIES'.
+  # The rest are CLUBS and ZOO.
   #: (ProTacts::Store store) -> void
   def self.seed_groups(store)
     seed_group(store, name: "Booles", lines: HOUSEHOLD, members: MEMBERS)
     seed_group(store, members: birthday_cards)
     seed_group(store, name: ProTacts::Store::EVERYONE, members: cards.keys - unsynced_cards)
     seed_group(store, name: "Curies", members: CURIES)
+    seed_clubs(store)
+  end
+
+  # CLUBS, each holding a different few cards, so the list a group
+  # screen shows and the chips a contact's row carries both vary. Rolled
+  # off the card order rather than tabulated per club, for the reason
+  # ago_seconds is computed: a table of memberships is a second list to
+  # keep in step with test/fixtures/cards.
+  #: (ProTacts::Store store) -> void
+  def self.seed_clubs(store)
+    ids = cards.keys
+    CLUBS.each_with_index do |name, index|
+      seed_group(store, name:, members: ids.rotate(index).take(index % 4))
+    end
+    seed_group(store, name: ZOO, members: MEMBERS)
   end
 
   # A group, seeded without a change-log entry for any member it moves