Cupertino 1.6.0
Version 1.6.0 ·
Added
The Activity log can be kept, not just watched. Settings ▸ Activity turns on a durable log: append-only files under Application Support, readable only by you, with retention by age and by size. Off by default, and with it off nothing about the app changes — the log stays a ring in memory that is cleared when Cupertino quits.
Message contents take three separate switches to reach disk. One to record them at all, one to keep a log, and one more to let contents into the file, with a warning on the last. A mail body, a message and a note's text all arrive as tool arguments, so "record arguments" and "write my mail to a file" are two different decisions and are asked as two different questions.
Each record carries a hash of the one before it, so an edited field, a removed record or a truncated file can be detected — and the pane says exactly that much. It catches tampering by something that does not know it is a chain. It is not proof against anyone who can write the file, because they can recompute it, and claiming otherwise would be the kind of security sentence nobody can check. Retention drops whole files at a time for the same reason: a chain cannot lose a record from the middle and still verify.
The log can be exported, and signed if you want it. Export writes the log alongside a manifest naming each file, its record count and its digest. The count is the part that matters: a chain cannot detect its own truncation, because cutting off the end leaves a shorter chain that still verifies.
Signing is optional and off unless asked for. It proves an export came from this Mac and was not altered afterwards; it does not prove the log was not curated before it was signed, and it means nothing to someone who has not been given the key some other way — so the pane shows a fingerprint to send them once. A new Mac makes a new key, and exports already signed keep verifying against the old one.
The Activity log has a search. It matches the tool name, the surface and the arguments and results a call carried, so "which call touched that note" is answerable without reading the feed. A row whose match falls past the truncated preview opens itself, rather than appearing in the results with no visible reason for being there.
Logs and Settings are one click from the menu bar. Two glyphs beside Quit, with ⌘L and ⌘, while the panel is open. The log is the destination the panel argues for: every line in it is a count of calls, and "what were those calls" is the question the summary raises and cannot answer.
Safari can open a page and save one for later.
apple_safari_open_urlopens a URL in a new tab or in the tab the user is looking at, andapple_safari_add_reading_list_itemsaves one to the Reading List. Both are Apple Events behindAPPLE_SAFARI_ALLOW_WRITES, so Safari is the first surface whose write lane needs no Full Disk Access at all — and the last surface that had no write lane.supportsWritesinsurfaces.jsonflips with them.The reason it was false gets a correction rather than a reversal. It read "opening a URL or adding to the Reading List is an Apple Event that navigates a real, visible browser", which is true of the first and false of the second:
add reading list itemopens nothing and loads nothing. That is why it was the verb to build first.No
do JavaScript, and a navigation verb is where that decision gets tested. Navigating a tab to ajavascript:URL is that verb through the front door — same capability, without the developer-menu toggle, through a tool whose description says it opens web pages. Measured on macOS 26.6: Safari accepts such a URL, with no refusal and no toggle involved, so this is the boundary rather than a precaution. Both writes take an http/https allowlist, enforced before any Apple Event is sent and again inside the JXA, andtest/writes.test.tsasserts a refusal dispatches nothing at all. Clicking and filling belong to the extension lane, where Safari grants one website at a time; they are not built.Three disclosures the caller cannot infer, all carried in the tool output or its description: these verbs LAUNCH Safari when it is not running; a Reading List add FETCHES the page in the background, so saving a link contacts that host from the user's browser; and an add is
verified: trueornull, neverfalse— Safari writesBookmarks.pliston its own schedule, so an add that worked is routinely invisible a moment later, and a caller that read that as failure would retry into a duplicate that no verb can remove.The JXA was written from the dictionary and measured afterwards, which is the reverse of how every other lane here landed —
scripts/probe-safari-write.mjsis the instrument, and it found the uncertain idiom sound:tab-pushin 166 ms,current-tabin 108 ms, the Reading List add in 148 ms, and theopen locationfallback never fired. It also found the shipped tool advertising atitleparameter Safari overrules: a custom title survives only for a URL that does not resolve, which is both why the description now calls it best-effort and how the background fetch was discovered. Safari commits an add toBookmarks.plistafter a measured 2 s, which is why the confirmation is two looks a beat apart rather than the immediate re-read it started as — that one could never have found anything.
Changed
The Safari pane reports the extension. Safari is the only surface with three independent lanes, and its pane rendered two of them: Access is the Apple Events lane, Store is the file lane, and the extension — the only route to page content, and the only thing
apple_safari_read_pageruns on — was not shown at all. A Page content card now sits between Store and Capabilities, saying whether the extension is enabled, how many pages it has captured and how fresh the newest one is, with the two setup steps behind a disclosure.It belongs in the app because it cannot live anywhere else: a disabled extension leaves its last captures on disk and stops adding more, so
apple_safari_diagnosticsgoes on reporting a healthy store that answers with an ever-older page. Only Safari knows the switch is off, and only the containing app may ask it. The count is what makes the row worth more than the switch — Safari grants the extension one website at a time, so "enabled" and "allowed nowhere" look identical until something counts the captures. Verified against the running extension: 4 captures and a 261-second newest age, the same two numbersapple_safari_diagnosticsreported off the same directory.
Fixed
A composer shrunk to hide the citation-stripping flash stayed shrunk. Mail persists the compose window's frame, so the [1, 1] window pushed off-screen while quotes were stripped was inherited by the next composer opened by hand — and on a send that only saves a draft, that was the very window the caller had just been told to review in Mail. The frame is read before the window moves (once off-screen the window server answers with what it clamped to, not what you chose) and restored, position first, before the caller sees the window again — on the failure path too, so a strip that throws does not cost you your composer. A window whose geometry cannot be read back is left alone rather than hidden with no way to put it back.
The Safari extension row no longer offers a button that cannot work. It showed "Open Safari…" for every state that was not enabled, including
notInstalled— which is the normal answer on a locally built app, where the appex is stripped because Safari will not list an extension whose container is not notarized. The button is now absent in the two states nothing in Safari can resolve, the same way the Automation row stopped offering "Allow…" for states that cannot be re-prompted."Open Safari…" opens Safari's Extensions settings, with Cupertino selected, rather than launching the browser and leaving someone to find a three-level menu. There is still no
x-apple.systempreferences:pane for it, which is why this was a plain launch; there is aSFSafariApplicationcall that does exactly this, and the launch is kept as its fallback.The extension's state was only read when a window opened. It is the one status here that someone changes in another app and comes straight back to check, and it is re-read on every visit to the Safari pane now.
A Safari error that was not "no such extension" reported as "not installed".
SFErrorCodehas three values and only the first means that; the other two are "we could not ask", which is a state the enum already had. Reporting them as a missing extension is the one answer that sends someone to reinstall the app.notInstalledclaimed a cause that was only true of a local build. It said "not in this build — a locally built app ships no extension" to everyone. In a release build the likely cause is different and so is the fix: Safari will not list an extension whose container is translocated or outside/Applications, whichInstallLocationalready knew and no row was asking it.