Cupertino 1.18.0
Version 1.18.0 ·
Added
What changed, readable after you have already updated. Release notes existed in exactly one place a user could reach: the sheet Sparkle puts up while it asks permission to install. That sheet is gone the moment you press Install, which left the one person most likely to want them — somebody who has just replaced an app holding Full Disk Access — with nowhere to look but
CHANGELOG.mdon GitHub. Settings has a What's New pane now, beside Updates, because the two are halves of one question: what did this build change, and is there a newer one. General keeps the version and the build number, which answer which build this is — the question you ask with a bug report open, not the one you ask after updating.It is generated, not bundled.
make changelogcompiles the last five releases ofCHANGELOG.mdintoChangelog.swiftthe same waymake surfacescompilessurfaces.jsonintoSurfaceCatalog.swift, andchangelog-checkfails CI if the two drift — so the notes in the app are the notes in the repository, or the build goes red.### Internalsections are dropped at generation time rather than hidden at render time, so repo-facing prose never reaches the binary.[Unreleased]is emitted separately and shown only in a Debug build, which matters here because CI asserts only that a## [<version>]section exists, not that it is the top one.The parse behind it is shared with
changelog-notes.mjs, which renders the appcast, so the two cannot disagree about what a bullet is — and the appcast's own guards, on a missing section and an empty one, stay where they were.scripts/lib/changelog.test.mjsis written against the shapes this file actually contains rather than tidy examples: a bullet with no bold headline, a headline with a code span inside it, prose between a###heading and its first bullet, and a freely named section like### Note for 1.0.0 users. One assertion is deliberately about code spans rather than the word "undefined", because 1.3.0's prose is about a field that read back asundefinedand the blunt check fails on a correct render.Entries whose bold lead is a whole sentence get it pulled onto its own line; entries that bold only the subject and run on — "…filled the page, because the text column pass ran to the limit" — are left as one flowing paragraph, because splitting those puts a line break before a comma.
Anything that shipped since the version you last read is marked, and says so from the menu bar panel and the sidebar footer as well as in Settings — once, until you look. A fresh install is treated as caught up rather than greeted with five unread releases.
Fixed
The Simulator's tab bar was there all along; the walker could not see it.
docs/simulator.mdrecorded the tab bar as "genuinely childless, not merely unwalked": every children attribute on theAXGroupanswers 0. That was measured correctly and concluded wrongly. Hit-testing the group's frame returns fourAXRadioButtons, each naming that group as itsAXParentand each takingAXPress— the edge is one-way, the children know the parent and the parent does not list them. A walk that descends onlyAXChildrenstopped there and reported the walk complete, which is worse than a truncated one because nothing said so.AccessibilityDriver.walknow sweeps the frame of a container whose children link is empty withAXUIElementCopyElementAtPosition, keeps every distinct hit whose parent chain leads back to the container, and walks those as its children;apple_desktop_expandandapple_simulator_ui_treeon such a handle do the same. The answer carriesrecoveredwhen it happened. On the screen that found it the walk went from 8 elements to 17: the tab bar's four items and, from a navigation bar with the same broken link, a heading, a search field and three toolbar buttons. Measured across seven Mac apps first: a hit-test costs 0.3–3 ms and a whole tree holds at most five containers that qualify, so the sweep runs on every walk rather than behind a switch.The part worth knowing as a caller: the recovered tab items carry their SF Symbol name as
id(leaf,checkmark.circle,calendar,cross.case), which does not change with the device's language. WebDriverAgent gives the same tabs no identifier at all, only the translated label — so on this one point the Accessibility lane addresses more stably than the runner does. The tool descriptions say so, and the sentence that called the tab bar an empty container is gone.Turning on "Reach any application" could remove Screen's capture tool rather than widen it. The
surfaceargument'senumIS the scope, so lifting the gate has to lift the constraint — but it did that by setting the key tonil, and"enum": nullis not valid JSON Schema. A client that validates the tool list drops the whole tool instead of reporting the fault, so the switch meant to broaden capture silently took it away. The key is omitted now when the gate is off.screen-checkasserts on the serialized bytes, because a[String: Any]lookup reads an absent key and a null one identically — which is exactly why the check it already had passed.
Security
find_codessays which service delivered a code, becauseconfidencecannot. iMessage is authenticated against an Apple ID; SMS and RCS sender IDs are not, and can be forged — theFromon a text is a routing hint the sending network fills in, not a credential.confidencemeasures how cleanly a code was extracted from the message text, and all of that text is attacker-controlled, so a spoofed SMS naming a domain scoreshighexactly like a real one;ageSecondsdoes not help either, since a planted code is fresh. Every match now carriesservice, and the tool description says what it does and does not mean. Deliberately not done: filtering or down-ranking SMS matches. Genuine codes arrive overwhelmingly that way — this store holds 796 SMS handles against 265 iMessage and 15 RCS — so the point is not to discard them, it is not to treat a code as proof of anything.