docs: rename macos-contacts.md to apple-contacts.md
It covers the iPhone too, so macOS came off the name;
five letters out, five letters in, and every wrap stayed the same.

Assisted-by: Claude Opus 5 via Claude Code
change wxxnprllwmoprpzozuxsruxzrymwozmr
commit bd0cff56f724610919d5744fb978584943de15fe
author Alpha Chen <alpha@kejadlen.dev>
date
parent vqzxwksr
diff --git a/README.md b/README.md
index 42d0ebb..8703ee6 100644
--- a/README.md
+++ b/README.md
@@ -109,7 +109,7 @@ carry:
   is its test.
 
 Three properties are load-bearing in non-obvious ways, documented in
-`docs/macos-contacts.md`: the collection's `resourcetype` (without
+`docs/apple-contacts.md`: the collection's `resourcetype` (without
 `card:addressbook` the client drops the account data), the
 `sync-collection` advertisement (without it the warm sync never runs), and
 `getctag` (without it no vCard is ever requested). Everything else the
@@ -118,7 +118,7 @@ me-card, principal-URL, the multiget/query advertisements — is optional.
 
 sabre/dav's [notes on the macOS Address Book client][sabre-osx] are the best
 single source of client quirks beyond this list, and they explain several
-failures that look like protocol bugs but are not. See `docs/macos-contacts.md`
+failures that look like protocol bugs but are not. See `docs/apple-contacts.md`
 for the details worth keeping close, including how to turn on the client's own
 debug logging.
 
diff --git a/docs/macos-contacts.md b/docs/apple-contacts.md
similarity index 98%
rename from docs/macos-contacts.md
rename to docs/apple-contacts.md
index 3b38266..6c71c19 100644
--- a/docs/macos-contacts.md
+++ b/docs/apple-contacts.md
@@ -1,9 +1,10 @@
-# macOS Contacts as a CardDAV client
+# Apple Contacts as a CardDAV client
 
-What the macOS Contacts client does, where it deviates from the RFCs, and
-which failures look like protocol bugs but are not. Most of this is
-distilled from sabre/dav's [client notes][sabre-osx] and from traffic
-captures against the servers in `servers/`.
+What the Contacts clients on macOS and iOS do, where they deviate from the
+RFCs, and which failures look like protocol bugs but are not. Most of this
+is macOS, distilled from sabre/dav's [client notes][sabre-osx] and from
+traffic captures against the servers in `servers/`; sections that cover
+iOS say so.
 
 [sabre-osx]: https://sabre.io/dav/clients/osx-addressbook/
 
diff --git a/docs/plans/2026-08-12-one-card-on-macos.md b/docs/plans/2026-08-12-one-card-on-macos.md
index 1f37672..646e28d 100644
--- a/docs/plans/2026-08-12-one-card-on-macos.md
+++ b/docs/plans/2026-08-12-one-card-on-macos.md
@@ -17,7 +17,7 @@ questions into experiments: change one response, resync, observe.
 It also fixes the network path early. Tailscale serve terminates TLS and
 proxies to the app, which is exactly the arrangement that breaks
 `/.well-known/carddav` redirects for everyone else (see
-`../macos-contacts.md`).
+`../apple-contacts.md`).
 
 ## Capabilities
 
@@ -48,7 +48,7 @@ Explicitly out of scope, to be built only after this milestone lands:
 
 ## Constraints
 
-- vCard 3.0, with an Apple-shaped `PRODID`. See `../macos-contacts.md`.
+- vCard 3.0, with an Apple-shaped `PRODID`. See `../apple-contacts.md`.
 - The `.well-known` redirect must be an absolute `https://` URL. A
   scheme-relative or `http://` redirect is dropped by the client without
   reaching the server.
diff --git a/docs/plans/2026-08-24-corrections-from-the-first-write.md b/docs/plans/2026-08-24-corrections-from-the-first-write.md
index 57b6ae8..916210f 100644
--- a/docs/plans/2026-08-24-corrections-from-the-first-write.md
+++ b/docs/plans/2026-08-24-corrections-from-the-first-write.md
@@ -5,7 +5,7 @@ the first time, and the captures in `log/unhandled` contradict three
 things in `2026-08-24-vcard-storage-and-groups.md`. That record stands
 as written; this one says what the first real card changed.
 
-The client behavior itself is in `../macos-contacts.md`. This covers
+The client behavior itself is in `../apple-contacts.md`. This covers
 only what it means for the storage design.
 
 ## Diffing a submitted card has to be semantic
diff --git a/docs/plans/2026-09-10-the-address-book-stays-writable.md b/docs/plans/2026-09-10-the-address-book-stays-writable.md
index d8ed847..107e086 100644
--- a/docs/plans/2026-09-10-the-address-book-stays-writable.md
+++ b/docs/plans/2026-09-10-the-address-book-stays-writable.md
@@ -9,7 +9,7 @@ showing nothing, so the idea is dropped.
 ## Every lever fails on macOS
 
 A client learns what it may do from `DAV:current-user-privilege-set`
-(RFC 3744 section 5.4) on the collection. `docs/macos-contacts.md`
+(RFC 3744 section 5.4) on the collection. `docs/apple-contacts.md`
 records what Contacts does with each form the property can take:
 
 | Server advertises | Contacts does | Recorded under |
diff --git a/lib/pro_tacts/birthday.rb b/lib/pro_tacts/birthday.rb
index f34a153..959e545 100644
--- a/lib/pro_tacts/birthday.rb
+++ b/lib/pro_tacts/birthday.rb
@@ -38,7 +38,7 @@ module ProTacts
     FULL_DATE = /\A(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])(?:T\d{2}:?\d{2}(:?\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?\z/ #: Regexp
 
     # The no-year shape macOS writes, with 1604 standing in for the year
-    # in both the parameter and the value (docs/macos-contacts.md,
+    # in both the parameter and the value (docs/apple-contacts.md,
     # "Birthdays without a year"). 1604 because it is the first year of
     # the Gregorian calendar; the parameter names the year to omit.
     # Components in range, like FULL_DATE.
@@ -54,7 +54,7 @@ module ProTacts
     # across a rewrite, by the same probe: the served shapes in any
     # spelling were visible, so their absence is a deletion, and an
     # unrecognized or out-of-range value is cleaner dying with the
-    # rewrite than living forever (docs/macos-contacts.md, "A birthday
+    # rewrite than living forever (docs/apple-contacts.md, "A birthday
     # the client cannot render is dropped from the card").
     YEAR_AND_MONTH = /\A(\d{4})-(0[1-9]|1[0-2])\z/ #: Regexp
     YEAR_ALONE = /\A(\d{4})\z/ #: Regexp
@@ -70,7 +70,7 @@ module ProTacts
 
     # The reduced no-year values macOS reads on a card it did not write
     # and converts to the sentinel on its own
-    # (docs/macos-contacts.md, "Birthdays without a year"). Together
+    # (docs/apple-contacts.md, "Birthdays without a year"). Together
     # with #from_property's two spellings this is the rendered set: a
     # BDAY a client could see, and so could have deleted. Capturing
     # for #from_value, like the unrendered patterns above.
diff --git a/lib/pro_tacts/card_diff.rb b/lib/pro_tacts/card_diff.rb
index 524f496..e4580b6 100644
--- a/lib/pro_tacts/card_diff.rb
+++ b/lib/pro_tacts/card_diff.rb
@@ -11,7 +11,7 @@ module ProTacts
   #
   # A multiset difference over the card's lines, not an edit script:
   # macOS Contacts reorders properties on every card it touches
-  # (docs/macos-contacts.md), so a positional diff would report a
+  # (docs/apple-contacts.md), so a positional diff would report a
   # shuffle as a change to every line below the first move. Lines that
   # both cards carry cancel, however far apart they sit, and what is
   # left is the write. A card carrying the same line twice is two
diff --git a/lib/pro_tacts/contact.rb b/lib/pro_tacts/contact.rb
index d730bc5..af9d2b9 100644
--- a/lib/pro_tacts/contact.rb
+++ b/lib/pro_tacts/contact.rb
@@ -398,7 +398,7 @@ module ProTacts
 
     # Every TYPE value (RFC 2426 section 3.3.1) that names a kind of
     # line. `pref` ranks a line instead, and the client adds it on its
-    # own (docs/macos-contacts.md, "The client rewrites every card it
+    # own (docs/apple-contacts.md, "The client rewrites every card it
     # touches"); `except` holds a property's own values that name no
     # kind either. Downcased: the spelling is the card's, and a screen
     # shows one.
diff --git a/lib/pro_tacts/store.rb b/lib/pro_tacts/store.rb
index 5dd7669..eaa4b01 100644
--- a/lib/pro_tacts/store.rb
+++ b/lib/pro_tacts/store.rb
@@ -377,7 +377,7 @@ module ProTacts
       # model emptied so nothing composes a second BDAY beside it
       # (RFC 6352 section 6.3.2.2). No BDAY at all is a client's
       # rewrite, and macOS Contacts drops the BDAY lines it cannot
-      # render from every card it writes (docs/macos-contacts.md):
+      # render from every card it writes (docs/apple-contacts.md):
       # what the stored card held in a spelling no client renders
       # rides across the rewrite, what it held that a client could
       # see was the user's deletion, an unseen model row survives as
@@ -1035,7 +1035,7 @@ module ProTacts
     # is a question about what it says and not about its bytes: macOS
     # re-serializes every card it touches, so `ADR;TYPE=home` comes
     # back `ADR;type=HOME;type=pref` on an address nobody edited
-    # (docs/macos-contacts.md, "The client rewrites every card it
+    # (docs/apple-contacts.md, "The client rewrites every card it
     # touches"). Comparing bytes reads every such line as an edit.
     #
     # What a line says is its value and its types: a member who
@@ -1070,7 +1070,7 @@ module ProTacts
     # The third: a type Contacts has no field for comes back moved,
     # not kept — into a property group, `TYPE` parameter gone, the type
     # the value of an `X-ABLabel` beside it — on an address nobody
-    # touched (docs/macos-contacts.md, "An address type the client
+    # touched (docs/apple-contacts.md, "An address type the client
     # cannot model becomes a custom label"). So that label counts as
     # one of the line's types, and `ADR;TYPE=dom` coming back as
     # `item1.ADR` with `item1.X-ABLabel:dom` is unedited.
diff --git a/lib/pro_tacts/web.rb b/lib/pro_tacts/web.rb
index d0f7773..ef506ef 100644
--- a/lib/pro_tacts/web.rb
+++ b/lib/pro_tacts/web.rb
@@ -585,7 +585,7 @@ module ProTacts
           # creates with If-None-Match: * and a UUID it mints into both
           # the URI and the card's UID, and updates with If-Match
           # carrying the strong etag it was last served — see
-          # docs/macos-contacts.md, "What a write looks like on the wire".
+          # docs/apple-contacts.md, "What a write looks like on the wire".
           r.put String do |filename|
             id = filename.delete_suffix(".vcf")
             # A last segment that is not this server's <id>.vcf shape
diff --git a/tasks/probe.rake b/tasks/probe.rake
index cf82ed6..1a4e425 100644
--- a/tasks/probe.rake
+++ b/tasks/probe.rake
@@ -2,7 +2,7 @@
 # probe is a card built to be edited by a real client and read back off
 # the wire, because what Apple does with a property is empirical and
 # every guess about it has been wrong at least once
-# (docs/macos-contacts.md).
+# (docs/apple-contacts.md).
 #
 # Seeded over HTTP rather than into the database, so the card travels
 # the same route a client's would and the dev server needs no
@@ -173,7 +173,7 @@ namespace :probe do
            cannot carry into a propagating write.
          - 3 Untyped Way: which types did the client write where the
            card carried none?
-      5. Record what happened in docs/macos-contacts.md, under "The
+      5. Record what happened in docs/apple-contacts.md, under "The
          client rewrites every card it touches".
       6. Then relabel one address to a custom label and read it again:
          a custom label is an itemN.ADR with an X-ABLabel beside it,
@@ -192,7 +192,7 @@ namespace :probe do
       4. Compare it with the card in tasks/probe.rake: which of the five
          forms came back, whether itemN was renumbered, and whether a
          companion stayed with the line it was grouped with.
-      5. Record what happened in docs/macos-contacts.md. Repeat on iOS,
+      5. Record what happened in docs/apple-contacts.md. Repeat on iOS,
          which numbers item groups differently (see
          test/fixtures/ios-exchange/05-put-contact-edit/request).
     NEXT
@@ -213,7 +213,7 @@ namespace :probe do
            item3.X-PT-GROUP follow them there, or stay at item3?
          The second answers whether a companion is a binding the client
          maintains or two numbers that happened to agree.
-      5. Record what happened in docs/macos-contacts.md, under "An
+      5. Record what happened in docs/apple-contacts.md, under "An
          annotation survives only on its own line".
     NEXT
   end
@@ -263,7 +263,7 @@ namespace :probe do
       #{reading_the_result(NOTES_ID, "Notes Boole", edit: "any field other than the note").chomp}
       4. Read the NOTE lines that come back: both as sent, one joined
          line, or one dropped — and which.
-      5. Record what happened in docs/macos-contacts.md.
+      5. Record what happened in docs/apple-contacts.md.
     NEXT
   end
 
@@ -280,7 +280,7 @@ namespace :probe do
       #{reading_the_result(CATEGORIES_ID, "Categories Boole").chomp}
       4. Read the CATEGORIES lines that come back: both as sent, merged
          into one, split one value per line, or gone.
-      5. Record what happened in docs/macos-contacts.md.
+      5. Record what happened in docs/apple-contacts.md.
     NEXT
   end
 end
diff --git a/test/fixtures/macos-exchange/README.md b/test/fixtures/macos-exchange/README.md
index 6dec261..b639728 100644
--- a/test/fixtures/macos-exchange/README.md
+++ b/test/fixtures/macos-exchange/README.md
@@ -54,4 +54,4 @@ at 76 octets a continuation — are built synthetically in
 `test/photo_card.rb`, and a later session that set pictures on the
 synthetic seed contacts deliberately (2026-09-04) is seeded directly
 as `test/fixtures/cards/{photo,emoji,memoji}.vcf`; see
-docs/macos-contacts.md, "Profile pictures".
+docs/apple-contacts.md, "Profile pictures".
diff --git a/test/pro_tacts/admin/test_contacts_pages.rb b/test/pro_tacts/admin/test_contacts_pages.rb
index 8347db5..36f3eb0 100644
--- a/test/pro_tacts/admin/test_contacts_pages.rb
+++ b/test/pro_tacts/admin/test_contacts_pages.rb
@@ -659,7 +659,7 @@ class AdminContactsPagesTest < Minitest::Test
 
   # The seed book carries one fixture card per picture kind macOS
   # sends — photo, emoji, memoji, promoted from real client sessions
-  # (see test/fixtures/cards and docs/macos-contacts.md) — and these
+  # (see test/fixtures/cards and docs/apple-contacts.md) — and these
   # tests are what keeps them exercising the path: a seed that stops
   # parsing, or stops sniffing to an image, fails here rather than
   # passing silently as an initials avatar. Runs against the suite's
diff --git a/test/pro_tacts/test_birthday.rb b/test/pro_tacts/test_birthday.rb
index 1b0f046..88014d8 100644
--- a/test/pro_tacts/test_birthday.rb
+++ b/test/pro_tacts/test_birthday.rb
@@ -93,7 +93,7 @@ class BirthdayTest < Minitest::Test
       ProTacts::Birthday.from_property(property("1985-04-12"))
   end
 
-  # docs/macos-contacts.md, "Birthdays without a year": the verified
+  # docs/apple-contacts.md, "Birthdays without a year": the verified
   # Apple form, with 1604 standing in for the year in both halves.
   def test_the_apple_no_year_form_reads_into_the_model
     sentinel = property("1604-04-12", parameters: [["X-APPLE-OMIT-YEAR", "1604"]])
diff --git a/test/pro_tacts/test_card_diff.rb b/test/pro_tacts/test_card_diff.rb
index d2e62b8..18983ec 100644
--- a/test/pro_tacts/test_card_diff.rb
+++ b/test/pro_tacts/test_card_diff.rb
@@ -36,7 +36,7 @@ class CardDiffTest < Minitest::Test
   end
 
   # macOS reorders properties on every card it touches
-  # (docs/macos-contacts.md), so a diff that read position would call a
+  # (docs/apple-contacts.md), so a diff that read position would call a
   # shuffle a rewrite of everything below the first move.
   def test_reordering_a_card_is_no_change
     shuffled = AIDEN.sub("FN:Aiden\r\nUID:aiden\r\n", "UID:aiden\r\nFN:Aiden\r\n")
diff --git a/test/pro_tacts/test_store.rb b/test/pro_tacts/test_store.rb
index 7d3cbdd..cfee6bd 100644
--- a/test/pro_tacts/test_store.rb
+++ b/test/pro_tacts/test_store.rb
@@ -772,7 +772,7 @@ class StoreTest < Minitest::Test
 
   # macOS Contacts drops the lines it cannot render from every card it
   # writes, so a rewrite that omits the BDAY carries them across rather
-  # than reading the absence as a deletion (docs/macos-contacts.md, "A
+  # than reading the absence as a deletion (docs/apple-contacts.md, "A
   # birthday the client cannot render is dropped from the card").
   def test_a_birthday_no_client_renders_survives_a_rewrite_that_drops_it
     ["BDAY:1985-04", "BDAY:1985", "BDAY:--04", "BDAY:---12"].each do |line|
@@ -1304,7 +1304,7 @@ class StoreTest < Minitest::Test
   # The group's lines as macOS returns them on a card whose address and
   # note nobody edited: the parameter name lowercased, its value
   # uppercased, `pref` filled in, an unmodeled parameter dropped, and
-  # every value byte-identical (docs/macos-contacts.md, "The client
+  # every value byte-identical (docs/apple-contacts.md, "The client
   # rewrites every card it touches" and "An annotation survives only on
   # its own line").
   RESERIALIZED_ADDRESS = HOUSEHOLD_ADDRESS.sub("ADR;TYPE=home:", "ADR;type=HOME;type=pref:") #: String
@@ -1439,7 +1439,7 @@ class StoreTest < Minitest::Test
   end
 
   # An untyped line comes back untyped: the client invents no type to
-  # fill the gap (docs/macos-contacts.md, "An address type the client
+  # fill the gap (docs/apple-contacts.md, "An address type the client
   # cannot model becomes a custom label").
   def test_an_untyped_lent_line_comes_back_untyped
     with_store({"aiden" => AIDEN}) do |store|