"An extension must not be able to fail silently in any direction."
Four Places a Share Can Vanish
A share extension is how a photo, a PDF or a link from another app becomes a capture. It has four stages, and the family has lost shares at every one of them, each time with no visible error: the system must offer the extension for that content, the extension must decide what each attachment is, it must load it, and it must write it somewhere the app will import.
Offered: The Activation Rule
The training app's extension never appeared for a check-up PDF shared from Mail. It was installed and registered: simctl spawn … pluginkit -m -i <extension id> listed it. It simply was not offered, because its Info.plist used the dictionary form of NSExtensionActivationRule (supports text, one web URL, eight images, four files), and "file" in that form did not cover a PDF handed over as com.adobe.pdf data. The predicate form Apple's extension guide documents, a SUBQUERY over the attachments with UTI-CONFORMS-TO, fixed it. Registered but absent from the sheet means the rule, not the code. And never ship TRUEPREDICATE: Apple's guide says an app whose extension contains it will be rejected.
What: Every File Is Also a URL
Measured with NSItemProvider(contentsOf:), a photo advertises public.jpeg, public.file-url and public.url. Every file-borne share conforms to public.url, because a file URL is a URL. So an extension that asks "is it a link?" first, in an else if chain, calls the photo a link, drops it, and leaves Post disabled. The discriminator is public.file-url: a web link is the only thing that is a URL without it. The shared code makes the decision a ranking rather than a check order, so no caller can reintroduce the bug by reordering its own conditions. And no ranked kind is not the same as not shareable: a zip is still a file the app may have promised to accept.
Loaded: A Deadline on Every Load
From the proof track: the same extension, once offered, sat alive for an hour because loadItem for Mail's PDF never called its completion on the device. Files go through loadFileRepresentation, reading the bytes inside its completion because the temporary URL is valid only there; files are copied, not decoded, to stay inside an extension's memory budget; and every load has a deadline that turns into a visible failure.
Written: No Success on Failure
An extension that swallows its save errors with try? and then calls completeRequest reports success for every failure. Throw, show the reason, and end with cancelRequest(withError:). The opposite silence exists too: the prose editor's shares had been landing for four builds while the owner believed they were dead, because the sheet simply closed. The fix there was an acknowledgment card. A share that worked says so.