import: annotate the empty collections the groups slot builds
Steep's three: an empty hash is a type until something says which,
and a two-element literal is an Array until something says it is a
pair -- once for the empty groups file an import opens with, once for
the hash #groups reads back, once for the id-to-label pairs the
review screen looks names up in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KrREeoPLXx1qsYYq7qkL8h
change
commit 0e0c2b4c5d04f2cf66c2a464fc8d1199514f2bff
author Claude <noreply@anthropic.com>
date
parent e82b4555
diff --git a/lib/pro_tacts/import/staged.rb b/lib/pro_tacts/import/staged.rb
index 978ef39..033f12c 100644
--- a/lib/pro_tacts/import/staged.rb
+++ b/lib/pro_tacts/import/staged.rb
@@ -60,7 +60,8 @@ module ProTacts
         # Empty rather than absent, so every slot of an import that
         # exists is a file that exists and a write can say which of
         # the two it found (#write).
-        File.binwrite(directory / file_name(GROUPS), JSON.generate({}))
+        nothing = {} #: Hash[String, Array[String]]
+        File.binwrite(directory / file_name(GROUPS), JSON.generate(nothing))
         id
       end
 
@@ -106,13 +107,19 @@ module ProTacts
       # will not parse is a broken assumption JSON says so about.
       #: (String id) -> Hash[String, Array[String]]
       def self.groups(id)
+        chosen = {} #: Hash[String, Array[String]]
         raw = read(id, GROUPS)
-        return {} if raw.nil?
+        return chosen if raw.nil?
 
         parsed = JSON.parse(raw)
-        return {} unless parsed.is_a?(Hash)
+        return chosen unless parsed.is_a?(Hash)
 
-        parsed.to_h { |index, ids| [index.to_s, (ids.is_a?(Array) ? ids : []).map(&:to_s)] }
+        parsed.each do |index, ids|
+          next unless ids.is_a?(Array)
+
+          chosen[index.to_s] = ids.map(&:to_s)
+        end
+        chosen
       end
 
       # The import, gone: the last step of a landing, and what keeps
diff --git a/lib/pro_tacts/web/import.rb b/lib/pro_tacts/web/import.rb
index 62d10d3..c8f92ad 100644
--- a/lib/pro_tacts/web/import.rb
+++ b/lib/pro_tacts/web/import.rb
@@ -118,7 +118,11 @@ module ProTacts
       # By name, because the row is read rather than submitted: a
       # group the walk ticked and someone then deleted is simply not
       # among these.
-      names = store.all_groups.to_h { |group| [group.id, group.label] }
+      names = store.all_groups.to_h { |group|
+        # A pair is a two-element array until something says
+        # otherwise, and an inline annotation needs its own line.
+        [group.id, group.label] #: [String, String]
+      }
       rows = landing.each_with_index.map { |card, index|
         # A literal of several elements is an Array until something
         # says otherwise, and an inline annotation needs its own line.