Cupertino 1.17.0
Version 1.17.0 ·
Added
A
simulatorsurface. Simulator.app bridges a simulated device's accessibility tree into the Mac's, so an iOS app's own controls are readable and pressable with no WebDriverAgent and no runner process — and until now reaching them cost switching Desktop to "Reach any application", which opens every window on the Mac to touch a developer's own app. The new surface is served in-process, pinned tocom.apple.iphonesimulatorwith no switch that widens it, and answers in iOS points: the device screen is found as theAXGroupwhose size is the device's point size times the window scale, the scale is measured against CoreSimulator's ownprofile.plistrather than assumed to be 1, and everyrectandpointcan be handed toios_simulator_tapunchanged. Four read tools and, behind writes,press,tap,swipe,type,keyandpress_button. Deliberately absent: boot, install, launch, screenshots, push and staging, which need no grant and belong to@mgcrea/mcp-ios-simulator. Measured in docs/simulator.md: a click lands only once the Simulator is frontmost, a drag arrives as a touch pan, and the Simulator forwards key codes rather than characters, so typing goes one key at a time on the Mac's layout.
Fixed
A depth-capped walk silently dropped every sibling after the first branch that hit the cap. The depth bound set the same "stopped" flag the node and time bounds do, and the walk returns on that flag before visiting the next node — so a depth-1 listing of a window with eleven children answered one and named
depth(1)as the reason, which is true and useless. Found by the first simulator probe; the cap is reported and no longer stops anything.A message addressed to a group could be delivered privately to one person. The send lane passed a group chat's first participant as its handle, and the JXA ladder guesses a one-to-one chat id from a handle whenever the chat lookup above it throws. Reconciliation then polled the group and found nothing, so it reported
pending— "do not send again" — for a message that had been sent, to the wrong recipient. A group ref now carries no handle at all.Every Maps favourite carried a wrong Apple place id, and synced it. The donor's
ZMUIDis a 64-bit integer read deliberately as text because a real one is pastMAX_SAFE_INTEGER; the write put it back through a JavaScript number, which SQLite binds as a double. The id landed rounded — measured 233 away — in a CloudKit-mirrored store, so it reached every device on the account.A pruned audit log reported itself as tampered with. Retention drops whole segments, and the verifier started every run from genesis, so the oldest surviving record was always a broken link and the Activity pane went red saying a record had been removed. It now seeds from the surviving segment's own link and reports "verifies from segment N". A mislabelled first segment and a gap in the middle are still failures. Retention's 30-day bound also never ran on a Mac that logs a few hundred calls a day, because pruning only happened on a 4 MB rotation.
A mailbox filter that matched nothing searched everything instead. An empty set of index rowids read the same as "no filter asked for", so a scoped Mail search could return other accounts' messages and a per-mailbox count could report the whole archive. It refuses now, naming the mailboxes the index actually holds.
Notes ignored the account allowlist and the folder argument whenever it could read the store. Both failed open, and only on machines with Full Disk Access — which is exactly where they matter. Both now take the slower Apple Events lane rather than answering with what was not asked for.
Date arguments were parsed six different ways, and three of them were wrong. An unreadable date became
NaN, which SQLite binds asNULL, so Mail answered a date written in words with zero results and no error. A bare day meant UTC midnight in some places and local midnight in others. An upper bound naming a day excluded that whole day.2026-02-30T09:00silently became 2 March. There is one grammar now, in core, and it refuses what it cannot read.Mail's composer waited on the wrong signal, and asked the wrong object who had focus. Two fixes from before this release: the readable state is not ready when the window appears, so the focus is polled; and focus is a property of the application, not of the element being addressed.
find_codescould miss a code that was right there. Its limit capped the messages scanned before the user's own replies were filtered out, so an ordinary few minutes of conversation pushed the code off the page. Direction is now a database predicate, and the limit applies to the codes returned.Messages search returned nothing recent once older results filled the page, because the text column pass ran to the limit before the archived-blob pass got a look — and every message since March 2026 lives only in a blob. The two passes are merged by date now.
update_eventignoredendanddurationMinutesunlessstartcame with them, reporting success and changing nothing.Contacts' diagnostics said the surface registers no mutating tool, while two have been registered behind its write flag.
A search could invalidate the element handles it had just issued, because the handle store emptied itself at capacity while a search was still filling it.
An attachment whose name is not ASCII could be neither listed nor saved. A filename is the one MIME parameter that routinely is not ASCII, and it has two encodings; neither was handled. One returned nothing, so the attachment was reported as having no filename at all; the other came back verbatim, so saving it wrote a file literally named
=?utf-8?Q?...?=.Safari reported when a page was first visited using only the visits inside the window asked for, while the visit count beside it stayed the page's lifetime total. A search of last week said a page had been first visited last Tuesday when it had been open since 2019.
A full page of calendar events was indistinguishable from a quiet week. The existing truncation flag is about how far the store's expansion reaches, so a dense day answered with the default fifty read as the whole day. There is a
hasMorebeside it now.Counting messages by a phone number only worked if you spelled it the way the store does, while sending to that same number resolved it properly — in the same server, against a description promising both spellings work.
Mail grouped by UTC days while filtering by local ones, so a message that arrived at 23:30 counted against the next day.
A wedged server was never cleaned up. Closing its input is a request to exit, and one that ignored it held a thread, three pipes and a process for as long as the app ran.
A malformed request got no answer at all from the three surfaces the app serves itself, so a client that sent broken JSON waited forever instead of being told.
Changed
The driving notice is an on-screen panel, not a menu bar badge. See the correction under 1.16.0: the badge could never render. The panel does not take focus and does not steal a keystroke from the sequence it is reporting on.
Simulator.app is a brokered application. The
simulatorsurface's entry in the manifest puts it into the set Desktop reaches without "Reach any application" and Screen can capture, by design; the Desktop guide's paragraph about it is no longer gated on the reach and sends a caller to thesimulatorsurface for iOS-point coordinates. Every "the eight applications Cupertino brokers" string now says nine.The Desktop guide's writes-on line names all eight driving verbs. It listed six;
focusandactivatewere registered and unmentioned.Walk bounds have a ceiling, so a budget of an hour cannot hold a session thread for one.
find_elementsalso declares thewindowargument it has always honoured.The handshake has a deadline on the app's side too. A process that connected and sent nothing used to hold a thread and a descriptor until it exited.
Notes and Reminders listings say when they were cut short.
apple_notes_list_notes,apple_notes_search_notes,apple_reminders_list_remindersandapple_reminders_search_remindersnow answer with an object —notesorreminders, plussourceandhasMore— rather than a bare array. This changes the output shape of four shipped tools. A caller that indexed the result directly has to read the named field instead.The reason is that all four truncated silently, and two of the three bounds involved are nothing the caller asked for. The Apple Events lane stops at its own cap (200 notes, 500 reminders) regardless of
limit, because a bulk read costs about 700ms per property whatever the library size — so asking for 500 and receiving 200 was indistinguishable from asking for 500 and there being 200. The Reminders index lane over-fetches from SQL and applies the date, flag and priority filters afterwards in JS; when those reject nearly everything, the window runs out before the page fills and the answer comes back short.hasMorereports that more matched than the page carries; anotefield appears only when a lane bound rather thanlimitis what ended the list, so it names something the reader can act on. Both diagnostics tools now report the cap.
Security
click,typeandkeynow light the driving notice. They are the most invasive verbs here — landing at a screen point, or in whatever is frontmost — and they were the only ones that did it without saying so.The application support directory is 0700, and an existing one is tightened rather than left as it was found. The socket is created under a private umask rather than narrowed a moment later.
Saving an attachment no longer checks and then writes, which was two steps with a window between them, and it refuses a bad destination before doing the work rather than after.
A failed audit write is reported instead of swallowed, so a full disk stops looking like tampering after the fact.